Defining Your Routes Edit Page


When your application starts, the router is responsible for displaying templates, loading data, and otherwise setting up application state. It does so by matching the current URL to the routes that you've defined.

The map method of your Ember application's router can be invoked to define URL mappings. When calling map, you should pass a function that will be invoked with the value this set to an object which you can use to create routes and resources.

1
2
3
4
App.Router.map(function() {
  this.route("about", { path: "/about" });
  this.route("favorites", { path: "/favs" });
});

Now, when the user visits /about, Ember.js will render the about template. Visiting /favs will render the favorites template.

Note that you can leave off the path if it is the same as the route name. In this case, the following is equivalent to the above example:

1
2
3
4
App.Router.map(function() {
  this.route("about");
  this.route("favorites", { path: "/favs" });
});

Inside your templates, you can use {{link-to}} to navigate between routes, using the name that you provided to the route method (or, in the case of /, the name index).

1
2
3
4
5
6
{{#link-to 'index'}}<img class="logo">{{/link-to}}

<nav>
  {{#link-to 'about'}}About{{/link-to}}
  {{#link-to 'favorites'}}Favorites{{/link-to}}
</nav>

The {{link-to}} helper will also add an active class to the link that points to the currently active route.

You can customize the behavior of a route by creating an Ember.Route subclass. For example, to customize what happens when your user visits /, create an App.IndexRoute:

1
2
3
4
5
6
App.IndexRoute = Ember.Route.extend({
  setupController: function(controller) {
    // Set the IndexController's `title`
    controller.set('title', "My App");
  }
});

The IndexController is the starting context for the index template. Now that you've set title, you can use it in the template:

1
2
<!-- get the title from the IndexController -->
<h1>{{title}}</h1>

(If you don't explicitly define an App.IndexController, Ember.js will automatically generate one for you.)

Ember.js automatically figures out the names of the routes and controllers based on the name you pass to this.route.

URL Route Name Controller Route Template
/ index IndexController IndexRoute index
/about about AboutController AboutRoute about
/favs favorites FavoritesController FavoritesRoute favorites

Resources

You can define groups of routes that work with a resource:

1
2
3
4
5
App.Router.map(function() {
  this.resource('posts', { path: '/posts' }, function() {
    this.route('new');
  });
});

As with this.route, you can leave off the path if it's the same as the name of the route, so the following router is equivalent:

1
2
3
4
5
App.Router.map(function() {
  this.resource('posts', function() {
    this.route('new');
  });
});

This router creates three routes:

URL Route Name Controller Route Template
/ index IndexController IndexRoute index
N/A posts1 PostsController PostsRoute posts
/posts posts.index PostsController
PostsIndexController
PostsRoute
PostsIndexRoute
posts
posts/index
/posts/new posts.new PostsController
PostsNewController
PostsRoute
PostsNewRoute
posts
posts/new

1 Transitioning to posts or creating a link to posts is equivalent to transitioning to posts.index or linking to posts.index

NOTE: If you define a resource using this.resource and do not supply a function, then the implicit resource.index route is not created. In that case, /resource will only use the ResourceRoute, ResourceController, and resource template.

Routes nested under a resource take the name of the resource plus their name as their route name. If you want to transition to a route (either via transitionTo or {{#link-to}}), make sure to use the full route name (posts.new, not new).

Visiting / renders the index template, as you would expect.

Visiting /posts is slightly different. It will first render the posts template. Then, it will render the posts/index template into the posts template's outlet.

Finally, visiting /posts/new will first render the posts template, then render the posts/new template into its outlet.

NOTE: You should use this.resource for URLs that represent a noun, and this.route for URLs that represent adjectives or verbs modifying those nouns. For example, in the code sample above, when specifying URLs for posts (a noun), the route was defined with this.resource('posts'). However, when defining the new action (a verb), the route was defined with this.route('new').

Dynamic Segments

One of the responsibilities of a resource's route handler is to convert a URL into a model.

For example, if we have the resource this.resource('posts');, our route handler might look like this:

1
2
3
4
5
App.PostsRoute = Ember.Route.extend({
  model: function() {
    return this.store.find('posts');
  }
});

The posts template will then receive a list of all available posts as its context.

Because /posts represents a fixed model, we don't need any additional information to know what to retrieve. However, if we want a route to represent a single post, we would not want to have to hardcode every possible post into the router.

Enter dynamic segments.

A dynamic segment is a portion of a URL that starts with a : and is followed by an identifier.

1
2
3
4
5
6
7
8
9
10
App.Router.map(function() {
  this.resource('posts');
  this.resource('post', { path: '/post/:post_id' });
});

App.PostRoute = Ember.Route.extend({
  model: function(params) {
    return this.store.find('post', params.post_id);
  }
});

Because this pattern is so common, the above model hook is the default behavior.

For example, if the dynamic segment is :post_id, Ember.js is smart enough to know that it should use the model App.Post (with the ID provided in the URL). Specifically, unless you override model, the route will return this.store.find('post', params.post_id) automatically.

Not coincidentally, this is exactly what Ember Data expects. So if you use the Ember router with Ember Data, your dynamic segments will work as expected out of the box.

If your model does not use the id property in the URL, you should define a serialize method on your route:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
App.Router.map(function() {
  this.resource('post', {path: '/posts/:post_slug'});
});

App.PostRoute = Ember.Route.extend({
  model: function(params) {
    // the server returns `{ slug: 'foo-post' }`
    return jQuery.getJSON("/posts/" + params.post_slug);
  },

  serialize: function(model) {
    // this will make the URL `/posts/foo-post`
    return { post_slug: model.get('slug') };
  }
});

The default serialize method inserts the model's id into the route's dynamic segment (in this case, :post_id).

Nested Resources

You cannot nest routes, but you can nest resources:

1
2
3
4
5
6
7
8
App.Router.map(function() {
  this.resource('post', { path: '/post/:post_id' }, function() {
    this.route('edit');
    this.resource('comments', function() {
      this.route('new');
    });
  });
});

This router creates five routes:

URL Route Name Controller Route Template
/ index App.IndexController App.IndexRoute index
N/A post App.PostController App.PostRoute post
/post/:post_id2 post.index App.PostIndexController App.PostIndexRoute post/index
/post/:post_id/edit post.edit App.PostEditController App.PostEditRoute post/edit
N/A comments App.CommentsController App.CommentsRoute comments
/post/:post_id/comments comments.index App.CommentsIndexController App.CommentsIndexRoute comments/index
/post/:post_id/comments/new comments.new App.CommentsNewController App.CommentsNewRoute comments/new

2 :post_id is the post's id. For a post with id = 1, the route will be: /post/1

The comments template will be rendered in the post outlet. All templates under comments (comments/index and comments/new) will be rendered in the comments outlet.

You are also able to create deeply nested resources in order to preserve the namespace on your routes:

1
2
3
4
5
6
7
App.Router.map(function() {
  this.resource('foo', function() {
    this.resource('foo.bar', { path: '/bar' }, function() {
      this.route('baz'); // This will be foo.bar.baz
    });
  });
});

This router creates the following routes:

URL Route Name Controller Route Template
/ index App.IndexController App.IndexRoute index
/foo foo.index App.FooIndexController App.FooIndexRoute foo/index
/foo/bar foo.bar.index App.FooBarIndexController App.FooBarIndexRoute foo/bar/index
/foo/bar/baz foo.bar.baz App.FooBarBazController App.FooBarBazRoute foo/bar/baz

Initial routes

A few routes are immediately available within your application:

  • App.ApplicationRoute is entered when your app first boots up. It renders the application template.

  • App.IndexRoute is the default route, and will render the index template when the user visits / (unless / has been overridden by your own custom route).

Remember, these routes are part of every application, so you don't need to specify them in App.Router.map.

Wildcard / globbing routes

You can define wildcard routes that will match mutliple routes. This could be used, for example, if you'd like a catchall route which is useful when the user enters an incorrect URL not managed by your app.

1
2
3
App.Router.map(function() {
  this.route('catchall', {path: '/*wildcard'});
});

Like all routes with a dynamic segment, you must provide a context when using a {{link-to}} or transitionTo to programatically enter this route.

1
2
3
4
5
6
7
App.ApplicationRoute = Ember.Route.extend({
  actions: {
    error: function () {
      this.transitionTo('catchall', "application-error");
    }
  }
});

With this code, if an error bubbles up to the Application route, your application will enter the catchall route and display /application-error in the URL.