In general most templating languages provide some way to wrap the target
content of a page into a main layout. This allows separation of the common page layout into another file and the smaller target template in a different file.
There have been a few iterations of this in Ember, currently this functionality is provided by the {{outlet}}
helper. Outlets are Ember's way to yield
into a layout.
The area where outlet
diverges significantly from yield
is nesting. Yielding on server-side is much simpler. You only need to mark areas of a template to yield into and then call to yield a block of content into that designated target.
However when the rendering of content is switched to client-side javascript, only parts of a page are updated on demand. You can no longer simply yield
directly into markers. You need a smarter yield
ie:- outlet
.
There are 2 sides to an {{outlet}}
.
- A marker that indicates where you want to yield. This is the
{{outlet}}
helper.
- Code that renders a template into this outlet. This is the
render
method used inside the renderTemplate
hook.
By default an {{outlet}}
does not need a name. This makes it the default outlet for that template. There can be many such outlets in a template and they can be specified by giving it a name. For eg:-
{{outlet 'sidebar'}}
{{outlet 'nav'}}
This declares 2 outlets named 'sidebar' and 'nav'. You can now render other templates into these outlets.
Default outlets are used when rendering without an explicit outlet name. For named outlets the rendering is done by calling render
in a renderTemplate
hook of a Route
.
You do this by specifying an outlet
option in a hash passed to the render
method as options.
renderTemplate() {
this.render('recentPosts', { outlet: 'sidebar' });
}
Here, the template recentPosts
will be rendered into an outlet named 'sidebar' inside its parent template.
When routes are nested inside other nested routes they will render into the nearest parent outlet. If the parent resource doesn't have a default outlet then it's parent is used, and so on until the application
template is reached.
When you declare a resource
with this.resource('posts');
in the Router
, you are indicating a few things based on convention.
- Render the
posts
route with the layout template posts
.
- Optionally, render the implicit
posts.index
route with the template posts/index
.
The posts
template contains layout common to all posts and it's sub resources. At the bare minimum it must contain at least a default outlet like, {{outlet}}
.
Without this {{outlet}}
child routes will not have an immediate parent outlet to render into. They will then render in that parent's parent or ultimately the application
template's outlet. When this happens you will see the "The immediate parent route did not render into the main outlet ..."
warning. Check the location of your outlets
when this happens.
The posts.index
is an implicit route given to all resources that have nested routes. In other words if your resource has nested routes, you don't need to explicitly declare a nested, this.route('index
)`.
This index
route can display the content of that resource. For instance, for posts.index
, You can display a list of all posts
. One secondary caveat with this implicit route is that the model is on the parent posts
route. You have to use the needs
api to get at this model in the PostsIndexController
.
needs: ['posts'],
contentBinding: 'controller.posts'
Further, this posts.index
route is optional. You can place the UI from posts/index
used to display a list of posts, directly into the posts
template itself. However this means any child resource will also render with the list of posts, along side the outlet in posts
. The decision whether to use an explicit index route or not depends on the UI that needs to be displayed.
Sitting above all other templates is the application
template. It must have an outlet
for nested resources to render in, and will typically house the layout common to the page. If you don't specify an application template a default template will be used. This generated template is equivalent to {{outlet}}
, ie:- a template with just a default outlet.
Consider the following routes.
App.Router.map(function() {
this.resource('posts', function() {
this.route('new')
this.resource('post', {path: ':post_id'}, function() {
this.resource('comments', function() {
this.route('new');
});
});
});
});
Here, posts.new
will be rendered into posts
which will be rendered inside posts
, which will be rendered into the application
template's default outlet. The rest of the templates used are listed below.
+---------------------------+--------------------------------------------------------+
| Route | Templates used (default outlets) |
+---------------------------+--------------------------------------------------------+
| posts.index | posts.index > posts > application |
+---------------------------+--------------------------------------------------------+
| posts.new | posts.new > posts > application |
+---------------------------+--------------------------------------------------------+
| posts.post.index | post.index > post > posts > application |
+---------------------------+--------------------------------------------------------+
| posts.post.new | post.new > post > posts > application |
+---------------------------+--------------------------------------------------------+
| posts.post.comments.index | comments.index > comments > post > posts > application |
+---------------------------+--------------------------------------------------------+
| posts.post.comments.new | comments.new > comments > post > posts > application |
+---------------------------+--------------------------------------------------------+
This default template hierarchy can be changed by specifying an into
option to the render
method.
renderTemplate: function() {
this.render('posts', { into: 'sidebar' })
}
Here the posts
template will render into the default outlet of the sidebar
template.
That's about it. Outlet
is another ember concept that uses a good deal of convention over configuration. The defaults are quite good, at the same time easy to customize.