Ember.js Routing and Rendering Simplified

Last week we quickly scaffolding an Ember.js application using Yeoman. That’s great, but it’s hardly useful to have an app that displays a list. I can write HTML that does that just fine, thanks very much. So, what’s useful here? Why bother with a client side framework at all? In this Ember.js tutorial, I want to do something that shows off WHY you’d do this in Ember, rather than with Turbolinks and a sprinkling of JavaScript. At the end of the tutorial, you’ll understand enough about Ember to do something useful with it.

As web developers, we all know that the internet is basically a series of text, pictures, buttons and text fields that we put stuff into and look at. Our job is to facilitate these types of interactions for our users; by making them quick and pleasing we will leave the people using our products feeling satisfied. That means that we must use JavaScript. The traditional approach is to augment static pages, but using a client side framework like Ember or Angular allows this work to happen quickly, while creating a maintainable code base. To demonstrate how ember.js handles routing and rendering views, we’re going to create a user profile editor.

If you’d like to follow along, make a new directory called users and initialize an ember.js app with yeoman in that directory. If you need more information on how to do this, get started with Ember.js in 5 minutes and then meet me back here.

With our initialization of the app, Users will be the global namespace that everything happens in.

View Templates and {{outlet}}

We need to set up a space for our sidebar navigation, and a main area where our logic will go. To do this, open up app/templates/application.hbs and change it to look like this:

<div>
  <div class="container-fluid" id="main">
    {{outlet}}
  </div>
</div>

Don’t worry, we’ll be adding back the navigation logic in the next step, but here its important to concentrate on {{outlet}} and what its meaning is. The default hierarchy of template rendering works by first rendering the overall application template which we’ve specified in application.hbs; the call to {{outlet}} specifies where the template that is specific to the current page will be rendered.

The default route - Index

Even if you haven’t done anything in your application yet, Ember provides you with a few basics without you even having to touch any code. By adding a resource to the application’s router, Ember will look for the associated route logic, the controller and the view template. If it doesn’t find the route logic or the controller, Ember.js will automatically generate these for you. Out of the box, without adding any code, visiting your root url goes through the following actions:

  • The ‘index’ route name is visited, this passes the request to
  • The IndexRoute namespace route logic, which is auto generated if it’s not present
  • The IndexController, (also auto generated) then handles the view template rendering of
  • A template with the associated name - in our case index.hbs

This is an outline of the four most simple steps of a request in an Ember.js application.

For this example, we want to set up our index template to render a list of users in a navigation panel on the left. Edit app/templates/index.hbs to look like this:

<div class="row-fluid">
  <div>
    <div class="span3">
      <div class="well sidebar-nav">
        <span class="nav-header">Users</span>
        <ul class="nav nav-list">
        </ul>
      </div>
    </div>
    <div class="span9">
      {{outlet}}
    </div>
  </div>
</div>

If you’ve scaffolded your application using Yeoman, then running grunt server will show you a page that looks like this:

Very basic user navigation
Boring, but it’s a start…

Great, okay, so, we have a basic place where we could render a list of users to click on and a nested {{outlet}} - this is where we’ll render a user profile.

Rendering Data

Adding data to Ember is simple using the ember-data library. Right now, the library is under HEAVY development, but for our purposes here, version 0.13 does the trick nicely. We’re going to add a User model and some user fixtures to render since we’re just demoing some front end features.

Our first step will be to add the user model.

// app/scripts/models/user.js
Users.User = DS.Model.extend({
  firstName: DS.attr('string'),
  lastName: DS.attr('string'),
  administrator: DS.attr('boolean'),
});

Fixtures are built in to Ember-data. To add them, we’ll just configure our data store.

// app/scripts/store.js
Users.Store = DS.Store.extend({
  revision: 1,
  adapter: 'DS.FixtureAdapter'
});

We’ll also have to tell yeoman to load this file by adding the following to app/scripts/app.js:

require('scripts/store');

You might notice the revision attribute. This just represents the revision of the API that you’ll be communicating with, which, in our case is meaningless. Right now this is required, but I’m assuming that with development, Ember-data will just handle this automatically. Specifying DS.FixtureAdapter just means that we can add user fixtures and Ember.js will treat them like they’ve come from a web service. Let’s do that. Add the these fixtures to the bottom of app/scripts/user.js

Users.User.FIXTURES = [
  {
    id: 1,
    firstName: 'Tom',
    lastName: 'Dale'
  },
  {
    id: 2,
    firstName: 'Matthew',
    lastName: 'Lehner'
  }
];

Great - we’ve got some users. If you’d like to test it out, just type Users.User.find(1).get('firstName') into your browser console and you can see that the response is this:

Found Tom.
Finding records with ember-data.

Rendering our data

So, here comes the good stuff. Finally we get to make a view render something interesting, other than static HTML. How do we do this?

First, we have to find this data. The route for the specific view will specifies which data is rendered in the template. To do this, we add the following logic to app/scripts/router.js:

Users.IndexRoute = Ember.Route.extend({
  model: function () {
    return Users.User.find();
  }
});

This tells your Ember.js app that the index route (remember, that’s the default route for the ‘/’ url) is dealing with the data found in the Users.User model. Now we’ll tell the template in app/templates/index.hbs to render the users for us. Add the following handlebars logic inside the <ul class="nav nav-list"> tag:

{{#each controller}}
  <li>{{firstName}} {{lastName}}</li>
{{/each}}

Refresh your page, and suddenly we’re looking at a list of users!

List of users.
The users are rendering, just fine!

Doing something useful - Adding a UI for editing.

So now that we’ve done a lot of work to create a list, lets do something useful with it. We’ll make the side bar into a navigation piece. Clicking on a user will display a form for editing their profile. There are two steps to this, we need to add a nested resource to the index route and tell the view template what to render.

Add the following to app/scripts/router.js:

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

Users.UserRoute = Ember.Route.extend({
  model: function (params) {
    return Users.User.find(params.user_id);
  }
});

update the list items in app/templates/index.hbs to link to each user resource:

<li>
  {{#linkTo 'user' this}}
    {{firstName}} {{lastName}}
  {{/linkTo}}
</li>

and the following view template to app/templates/user.hbs:

<header>
  <h1>Editing {{firstName}} {{lastName}}</h1>
</header>

<form>
  <fieldset>
    <div>{{view Ember.TextField valueBinding='firstName'}}</div>
    <div>{{view Ember.TextField valueBinding='lastName'}}</div>
  </fieldset>
</form>

And now we have editable users and thanks to Ember.js and Handlebars data binding implementation, wherever an attribute is rendered on the page, it will automatically change when edited. Try it out!

User edit screen

Since this Ember.js tutorial is about routing and rendering views, lets examine what we’ve accomplished with this last step. You can see we’ve nested the user resource under the index route; because it’s nested, this will automatically render to the {{outlet}} in our index template.

Nested Views

Any web application that you’ll be creating will have different requirements for certain sections - perhaps a widget that gets rendered everywhere or a portion that changes based on state. With Ember’s handling of nested routes, you get these page areas automatically. Remember how our index.hbs template has another {{outlet}}? Well, that’s where any resources that are nested under the index action will be rendered. You can have nested routes for anything that’s associated with users such as activity, edit, show, profile, friends, etc. and they will automatically render where you specify the outlet on the main user template.

Data binding

Another benefit of Ember is that the data binding keeps all of the associated attributes updated everywhere they’ve been rendered. There’s no lag between editing a user name, and that user’s name showing up the way it’s been changed.

This guide to Ember.js routing and rendering is really just touching on the basics of routing and rendering within the framework. Each JavaScript MVC framework has its own unique paradigm to how development should be done and I find it helpful to start with a good foundation. From here, you can start building out a more complicated application that consumes an API, has animated elements and a user interface that responds instantly.

comments powered by Disqus