Ember.js uses a runtime resolver to wire up your objects without a lot of boilerplate. As a developer, this resolver will work automatically if you place code in conventional locations within your project.
You can usually guess the names and locations, but this guide outlines, in one place, all of the naming conventions.
The Application
When your application boots, Ember will look for the objects exported by these modules in your project:
app/app.js
app/controllers/application.js
app/templates/application.hbs
Ember.js will render the application
template as the main template.
If controller:application
is provided, Ember.js will set an
instance of controller:application
as the controller for the
template. This means that the template will get its properties from
the controller.
If your app provides an route at app/routes/application.js
Ember.js will invoke
the router's hooks first, before rendering the
application
template. Hooks are implemented as methods and provide
you access points within an Ember object's lifecycle to intercept and
execute code to modify the default behavior at these points to meet
your needs. Ember provides several hooks for you to utilize for various
purposes (e.g. model
, setupController
, etc). In the example below
route:application
Route, which is an Ember.Route
object, implements
the setupController
hook.
Here's a simple example that uses a route, controller, and template:
export default Ember.Route.extend({
setupController: function(controller) {
// `controller` is the instance of ApplicationController
controller.set('title', "Hello world!");
}
});
export default Ember.Controller.extend({
appName: 'My First Example'
});
In Ember.js applications, you will always provide your objects as classes, and the framework is responsible for instantiating them and providing them to your templates at runtime through the resolver.
Simple Routes
Each of your routes will have a controller, and a template with the same name as the route.
Let's start with a simple router:
export default Ember.Router.extend().map(function(){
this.route('favorites');
});
If your user navigates to /favorites
, Ember.js will look for these
classes in your project:
app/routes/favorites.js
app/controllers/favorites.js
app/templates/favorites.hbs
Ember.js will render the favorites
template into the {{outlet}}
in the application
template. It will set an instance of the
controller:favorites
as the controller for the template.
If your app provides an route:favorites
, the framework will
invoke it before rendering the template. Yes, this is a bit
repetitive.
For a route like route:favorites
, you will probably implement
the model
hook to specify what model your controller will present
to the template.
Here's an example:
export default Ember.Route.extend({
model: function() {
// the model is an Array of all of the posts
// fetched from this url
return $.ajax('/a/service/url/where/posts/live');
}
});
In this example, we didn't provide a controller:favorites
. Because
the model is an Array, Ember.js will automatically supply an instance
of Ember.ArrayController
, which will present the backing Array as
its model.
You can treat the Ember.ArrayController
as if it was the model itself.
This has two major benefits:
- You can replace the controller's model at any time without having to directly notify the view of the change.
- The controller can provide additional computed properties or view-specific state that do not belong in the model layer. This allows a clean separation of concerns between the view, the controller and the model.
The template can iterate over the elements of the controller:
<ul>
<li></li>
</ul>
Dynamic Segments
If a route uses a dynamic segment (a URL that includes a parameter), the route's model will be based on the value of that segment provided by the user.
Consider this router definition:
export default Ember.Router.extend().map(function(){
this.route('post', { path: '/posts/:post_id' });
});
In this case, the route's name is post
, so Ember.js will look for
these objects:
app/routes/post.js
app/controllers/post.js
app/templates/post.hbs
Your route handler's model
hook converts the dynamic :post_id
parameter into a model. The serialize
hook converts a model object
back into the URL parameters for this route (for example, when
generating a link for a model object).
export default Ember.Route.extend({
model: function(params) {
return $.ajax('/my-service/posts/' + params.post_id);
},
serialize: function(post) {
return { post_id: Ember.get(post, 'id') };
}
});
Because this pattern is so common, it is the default for route handlers.
- If your dynamic segment ends in
_id
, the defaultmodel
hook will convert the first part into a model class on the application's namespace (post
looks forapp/models/post.js
). It will then callfind
on that class with the value of the dynamic segment. - The default behaviour of the
serialize
hook is to replace the route's dynamic segment with the value of the model object'sid
property.
Route, Controller and Template Defaults
If you don't specify a route handler for the post
route
(app/routes/post.js
), Ember.js will still render the app/templates/post.hbs
template with the app's instance of app/controllers/post.js
.
If you don't specify the controller (app/controllers/post.js
),
Ember will automatically make one for you based on the return value
of the route's model
hook. If the model is an Array, you get an
ArrayController
. Otherwise, you get an ObjectController
.
If you don't specify a post
template, Ember.js won't render
anything!
Nesting
You can nest routes under a resource
.
export default Ember.Router.extend().map(function(){
this.route('posts', function() { // the `posts` route
this.route('favorites'); // the `posts.favorites` route
this.route('post'); // the `posts.post` route
});
});
Here are the naming conventions for each of the routes defined in this router:
Route Name | Controller | Route | Template |
---|---|---|---|
posts |
app/controllers/posts.js |
app/routes/posts.js |
app/templates/posts.hbs |
posts.favorites |
app/controllers/posts/favorites.js |
app/routes/posts/favorites.js |
app/templates/posts/favorites.hbs |
post |
app/controllers/post.js |
app/routes/post.js |
app/templates/post.hbs |
The Index Route
At every level of nesting (including the top level), Ember.js
automatically provides a route for the /
path named index
.
For example, if you write a simple router like this:
export default Ember.Router.extend().map(function(){
this.route('favorites');
});
It is the equivalent of:
export default Ember.Router.extend().map(function(){
this.route('index', { path: '/' });
this.route('favorites');
});
If the user visits /
, Ember.js will look for these objects:
app/routes/index.js
app/controllers/index.js
app/templates/index.hbs
The index
template will be rendered into the {{outlet}}
in the
application
template. If the user navigates to /favorites
,
Ember.js will replace the index
template with the favorites
template.
A nested router like this:
export default Ember.Router.extend().map(function(){
this.route('posts', function() {
this.route('favorites');
});
});
Is the equivalent of:
export default Ember.Router.extend().map(function(){
this.route('index', { path: '/' });
this.route('posts', function() {
this.route('index', { path: '/' });
this.route('favorites');
});
});
If the user navigates to /posts
, the current route will be
posts.index
. Ember.js will look for objects named:
app/routes/posts/index.js
app/controllers/posts/index.js
app/templates/posts/index.hbs
First, the posts
template will be rendered into the {{outlet}}
in the application
template. Then, the posts/index
template
will be rendered into the {{outlet}}
in the posts
template.
If the user then navigates to /posts/favorites
, Ember.js will
replace the {{outlet}}
in the posts
template with the
posts/favorites
template.