Advertisement

Getting into Ember.js: The Next Steps

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

In my introductory article, I went over the basics of the Ember.js framework, and the foundational concepts for building an Ember application. In this follow-up article, we'll dive deeper into specific areas of the framework to understand how many of the features work together to abstract the complexities of single-page application development.


A Basic App

I noted previously that the easiest way to get the files you need is to go to the Ember.js Github repo and pull down the start kit, and that still holds true. This boilerplate kit includes all the files that you'll need to kickstart your Ember experience, so be sure to download it from this article.

The interesting thing is that the starter kit is also a great example of a very basic Ember app. Let's walk through it to gain an understanding of what's going on. Note that I'll be digging deeper into specific areas later, so don't worry if something doesn't make immediate sense in this section. It's more to give you a high-level understanding of the functionality before diving into the details.

Open index.html in your browser, and you'll see the following:

Welcome to Ember.js

  • red
  • yellow
  • blue

This is not very exciting, I know, but if you look at the code that rendered this, you'll see that it was done with very little effort. If we look at "js/app.js", we see the following code:

App = Ember.Application.create({});

App.IndexRoute = Ember.Route.extend({
  setupController: function(controller) {
    controller.set('content', ['red', 'yellow', 'blue']);
  }
});

At its most basic level, an Ember app only needs this one line to technically be considered an "app":

App = Ember.Application.create({});

This code sets up an instance of the Ember application object, along with a default application template, event listeners and application router. Take a second and try to think of the code you would normally have to write to create a global namespace, a client-side template, bind event handlers for global user interaction and include history & state management in your code. Yes, that one line does all of that. Let's be clear, though: I'm not saying that it's doing all of the work for you, but it is creating the foundation you'll build upon, via one method call.

The next set of code sets up the behavior of a route, in this case, for the main index.html page:

App.IndexRoute = Ember.Route.extend({
  setupController: function(controller) {
    controller.set('content', ['red', 'yellow', 'blue']);
  }
});

Remember that routes are used to manage the resources associated with a specific URL within the application, and allows Ember to track the various states of individual pages. The URL is the key identifier that Ember uses to understand which application state needs to be presented to the user.

In this case, the root route is created by default in Ember. I could've also explicitly defined the route this way:

App.Router.map( function() {
    this.resource( 'index', { path: '/' } ); // Takes us to "/"
});

But Ember takes care of that for me for the "root" of my application. We'll tackle routes in more detail later.

Going back to the following code:

App.IndexRoute = Ember.Route.extend({
  setupController: function(controller) {
    controller.set('content', ['red', 'yellow', 'blue']);
  }
});

In this case, when a user hits the site's root, Ember will setup a controller that will load a sample set of data with a semantic name, called content. This data can later be used in the app, via this controller using that name. And that's specifically what happens in index.html. Open the file and you'll find the following:

<script type="text/x-handlebars" data-template-name="index">
<h2>Welcome to Ember.js</h2>
<ul>
{{#each item in model}}
    <li>{{item}}</li>
{{/each}}
 </ul>
</script>

This is a Handlebars client-side template. Remember that Handlebars is the templating library for Ember, and is vital to creating data-driven user interfaces for your app. Ember uses data attributes to link these templates to the controllers that manage your data, whether they're specified via a route or as a standalone controller.

In my last article, I mentioned that naming conventions are important in Ember, and that they make connecting features easy. If you look at the template code, you'll see that the name of the template (specified via the data-template-name attribute) is "index". This is purposeful and is meant to make it easy to connect to the controller specified within the route of the same name. If we look at the route code once again, you'll see that it's called "IndexRoute" and inside of it is a controller with data being set:

App.IndexRoute = Ember.Route.extend({
  setupController: function(controller) {
    controller.set('content', ['red', 'yellow', 'blue']);
  }
});

The controller sets a datasource named "content" and loads it with an array of strings for the colors. Basically, the array is your model, and the controller is used to expose that attributes of the model.

The naming conventions allow Ember to link this route's resources (e.g.: the controller with data) to the template specified by the same name. This gives the template access to the data exposed by the controller so it can render it using Handlebars' directives. From there, the items in the array are looped over using Handlebars' each directive and specifying the alias model which points to the datasource:

{{#each item in model}}
    <li>{{item}}</li>
{{/each}}

To be more precise, the data is populated into dynamically created list items, thus generating the markup for you on the fly. That's the beauty of client-side templates.

I think this basic app highlights how Ember abstracts a lot of things for you. It is a bit of black magic though and it's not always easy to grasp how things work. That actually happened to me and things didn't quite click at first. Once you start understanding the relationships between the various components of the framework, it starts to make more sense. Let's start from the ground up to get a better understanding of this.


Starting from the Ground Up

I briefly touched on the Ember application object and the fact that it builds the foundation for your application. The Ember guides do an excellent job of outlining specifically what instantiating an Ember application object does:

  • It sets your application's namespace. All of the classes in your application will be defined as properties on this object (e.g. App.PostsView and App.PostsController). This helps to prevent polluting the global scope.
  • It adds event listeners to the document and is responsible for sending events to your views.

  • It automatically renders the application template, the root-most template, into which your other templates will be rendered.
  • It automatically creates a router and begins routing, based on the current URL.

So this simple statement:

App = Ember.Application.create({});

wires up a whole ton of foundational pieces that your application will depend on. It's important to note that App is not a keyword in Ember. It's a normal global variable that you're using to define the namespace and could be any valid variable name. From what I've seen, though, the variable name, App, is a commonly used convention in most Ember apps and is actually recommended to make it easier to copy and paste much of the sample code being created in the community.

Taking the list above, what Ember does, via that one line, is essentially create this code for you automatically behind the scenes:

// Create the application namespace
App = Ember.Application.create({});

// Create the global router to manage page state via URLs 
App.Router.map( function() {});

// Create the default application route to set application-level state properties
App.ApplicationRoute = Ember.Route.extend({});

// Create the default application template
<script type="text/x-handlebars" data-template-name="application">
    {{outlet}}
</script>

So, while the starter kit didn't explicitly define an application-scoped router, route or template, Ember ensured that they're created and available so that the foundation of your app is set and available to you. It's definitely okay to explicitly create the code. In fact, you may want to do so if you plan to pass data or set attributes for your instance of the application object.

Now you might be wondering about this "application template" getting automatically rendered and why you don't see it in index.html. That's because it's optional to explicitly create the application template. If it's in the markup, Ember will immediately render it. Otherwise, it carries on processing other parts of your application as normal. The typical use-case for the application template is defining global, application-wide user interface elements, such as header and footers.

Defining the application template uses the same style syntax as any other template except with one small difference: the template name doesn't need to be specified. So defining your template like this:

<script type="text/x-handlebars">
    <h1>Application Template</h1>
</script>

or this:

<script type="text/x-handlebars" data-template-name="application">
    <h1>Application Template</h1>
</script>

gives you the same exact results. Ember will interpret a template with no data-template-name as the application template and will render it automatically when the application starts.

If you update index.html by adding this code:

<script type="text/x-handlebars" data-template-name="application">
    <h1>Application Template</h1>
    {{outlet}}
</script>

You'll now see that the contents of the header tag appears on top of the content of the index template. The Handlebars {{outlet}} directive serves as a placeholder in the application template, allowing Ember to inject other templates into it (serving as a wrapper of sorts), and allowing you to have global UI features such as headers and footers that surround your content and functionality. By adding the application template to index.html, you've instructed Ember to:

  • Automatically render the application template
  • Inject the index template into the application template via the Handlebars {{outlet}} directive
  • Immediately process and render the index template

An important takeaway is that all we did was add one template (application), and Ember immediately took care of the rest. It's these feature bindings that make Ember.js such a powerful framework to work with.


Setting up Routes

Routing is arguably the most difficult concept to understand in Ember, so I'll do my best to break it down to manageable steps. As a user navigates your application, there needs to be a method for managing the state of the various parts the user visits. That's where the application's router and location-specific routes come in.

The Ember router object is what manages this through the use of routes that identify the resources needed for specification locations. I like to think of the router as a traffic cop that's directing cars (users) to different streets (URLs & routes). The routes, themselves, are tied to specific URLs and, when the URL is accessed, the routes resources are made available.

Looking at js/app.js again, you'll notice that a route has been created for the root page (index):

App.IndexRoute = Ember.Route.extend({
  setupController: function(controller) {
    controller.set('content', ['red', 'yellow', 'blue']);
  }
});

However, there's no router instance. Remember that Ember will create a router by default if you don't specify one. It will also create a default route entry for the root of the application similar to this:

App.Router.map( function() {
   this.resource( 'index', { path: '/' } ); 
});

This tells Ember that, when the root of the application is hit, it should load the resources of a route object instance called IndexRoute if it's available. This is why, despite no router instance being declared, the application still runs. Ember internally knows that the root route should be named IndexRoute, will look for it, and load its resources, accordingly. In this case, it's creating a controller that will contain data to be used in the index template.

Since URLs are the key identifiers that Ember uses to manage the state of your application, each one will generally have their own route handler specified if resources need to be loaded for that section of the app. Here's what I mean; suppose that you have an app with three sections:

  • Account: (URL: /account)
  • Profile (URL: /profile)
  • Gallery (URL: /gallery)

In most cases, each one of these sections will have its own unique resources that need to be loaded (e.g.: data or images). So you would create route handlers using the resource() method within Ember's application router object instance like this:

App.Router.map( function() {
   this.resource( 'accounts' ); 
   this.resource( 'profiles' ); 
   this.resource( 'gallery' ); 
});

This allows Ember to understand the structure of the application and manage resources, accordingly. The routes definitions will correlate to individual route object instances which actually do the heavy-lifting like setting up or interfacing controllers:

App.GalleryRoute = Ember.Route.extend({
  setupController: function(controller) {
    controller.set('content', ['pic-1.png', 'pic-2.png', 'pic-3.png']);
  }
});

So in the example above, when a user visits "/gallery", Ember.js instantiate the GalleryRoute route object, setup a controller with data and render the gallery template. Again, this is why naming conventions are so important in Ember.

Your application may also have nested URLs, like /account/new

For these instances, you can define Ember resources that allow you to group routes together, like so:

App.Router.map( function() {
   this.resource( 'accounts',  function() {
     this.route( 'new' ); 
   });
});

In this example, we used the resource() method to group the routes together and the route() method to define the routes within the group. The general rule of thumb is to use resource() for nouns (Accounts and Account would both be resources even when nested) and route() for modifiers: (verbs like new and edit or adjectives like favorites and starred).

Apart from grouping the routes, Ember builds internal references to the controllers, routes and templates for each of the group routes specified. This is what it would look like (and again it touches on Ember's naming conventions):

"/accounts":

  • Controller: AccountsController
  • Route: AccountsRoute
  • Template: accounts (yes it's lowercase)

"/accounts/new":

  • Controller: AccountsNewController
  • Route: AccountsNewRoute
  • Template: accounts/new

When a user visits "/accounts/new" there's a bit of a parent/child or master/detail scenario that occurs. Ember will first ensure that the resources for accounts are available and render the accounts template (this is the master part of it). Then, it will follow-up and do the same for "/accounts/new", setting up resources and rendering the accounts.new template.

Note that resources can also be nested for much deeper URL structures, like this:

App.Router.map( function() {
  this.resource( 'accounts', function() {
    this.route( 'new' ); 
    this.resource( 'pictures', function() {
      this.route( 'add' ); 
    });
  });
});

Next Steps

I've covered a lot of material in this post. Hopefully, it has helped to simplify some of the aspects of how an Ember application functions and how routes work.

We're still not finished, though. In the next entry, I'll dive into Ember's features for pulling back data and making it available with your app. This is where models and controllers come in, so we'll focus on understanding how the two work together.

Advertisement