This is probably a very common task, but I am not sure which is the right approach to tackle this.
I have a Route / Controller / Model setup that loads groups. In the header area before the main content I want to show notifications for the user. I have created a notificationcontroller that is an arraycontroller and as well as a route that loads all notifications.
I don't want this route to be used for anything else than within this area.
I have already tried in my groups.hbs to add an additional {{ outlet notification }} and try to render in it:
App.GroupsRoute = GambifyApp.BaseRoute.extend({
model: function() {
return this.get('store').find('group');
},
renderTemplate: function() {
this.render('notifications', { // the template to render
// into: 'notifications', // the route to render into
outlet: 'notification', // the name of the outlet in the route's template
controller: 'notifications', // the controller to use for the template
});
this.render('groups');
}
});
But somehow my notifications template is not used. For now there is not much in my NotificationsController (It's an ArrayController) , NotificationsRoute is only reading all Notifications as Model and the template is currently only text, which is not rendered in my groups.hbs
The template containing the outlet you want to render into must be rendered first in order to render into the outlet. Here is an example of rendering into a named outlet that is in the application template from the index route:
http://emberjs.jsbin.com/qoyi/1/edit?html,js,output
Leave a comment if this example doesn't clear it up for you.
Related
In my EmberJS applications I have two separates routes as follows,
Route A - "main/books/add"
Route B - "main/authors/add"
I have an "Add Authors" button In Route A template and when a user presses that button I want to load and render Route B in a modal to add new authors.
I know its possible to achieve somewhat smiler to this by using the route's render method to render the Route B template and respective controller.
But in that case, the "model" hook of Route B in "main/authors/add.js" file does not get invoked.
It would be really nice if someone can suggest me a method to render a separate route into a modal.
EDIT - Although this is entirely valid (the premise of rendering into using named outlets, views are now deprecated in Ember 1.1. The same can be achieved by using a Component
Yup, you can do this:
What you'd want to do is create a modal in a template and assign a named outlet into it (or create a view that is a modal with an outlet):
in modal.hbs:
<div class='modal'>
{{outlet "modalContent"}}
</div>
Then I would override your base button like so:
App.BasicButton = Em.View.extend({
context: null,
template: Em.Handlebars.compile('<button>Click Me!</button>');
click: function(evt) {
this.get('controller').send('reroute', this.get('context'));
}
});
And in your template set up your button to trigger your modal:
in trigger.hbs
<!-- content and buttons for doing stuff -->
{{View App.BasicButton context='modalContent'}}
Finally, you want to create a method in your route which handles rendering specific content into your outlet:
App.TriggerRoute = Em.Route.extend({
actions: {
reroute: function(route) {
this.render(route, {into: 'modal', outlet: route});
}
}
});
So in essence, you're rendering the template (called "modalContent") into a specific outlet (called "modalContent"), housed within the template/view (called "modal")
You would also want to write some logic to trigger the modal to open on element insertion. To do that, I would use the didInsertElement action in the modal view:
App.ModalView = Em.View.extend({
didInsertElement: function() {
this.$.css("display", "block");
//whatever other properties you need to set to get the modal to pop up
}
});
I have an application route that is rendering a template into an outlet named 'sidebar', this should be viewable across the whole of the app. I have set up a quick example here.
When I go into one of the routes (in my example, the color route) this outlet will render a different template and when you navigate to another route in the app it should show the sidebar that was there originally.
This doesn't happen automatically and I understand it is because once the ApplciationRoute has been entered which is when the app is first loaded the renderTemplate is called and not called again until page refresh. This makes sense to me, but I'm unsure how to get around this.
I have tried re-calling the Route#render method again under the willTransition action of the ColorRoute but it doesn't work.
...
actions: {
willTransition: function() {
this.render('color.sidebar', {
into: 'application',
outlet: 'sidebar'
});
}
}
...
I just came up with another "workaround" for this using a component instead of a named outlet.
Instead of {{ outlet "sidebar" }} in your application template just use {{ x-sidebar }}
Then, define the x-sidebar component template as follows:
<script type="text/x-handlebars" id="components/x-sidebar">
{{partial sidebar }}
</script>
So, now your newly created component is expecting a sidebar property to tell it which template to display.
You can pass that property when you use the component like so:
{{ x-sidebar sidebar=sidebar }}
Then, you can use activate/deactivate hooks in your routes to set the sidebar property on the application controller, for example:
App.ColorRoute = Ember.Route.extend({
model: function(params) {
return params.color;
},
activate: function(){
this.controllerFor('application').set('sidebar', 'color/sidebar');
},
deactivate: function(){
this.controllerFor('application').set('sidebar', 'sidebar');
}
});
Working solution here
Someone apparently wrote an ember-cli addon to address this
See the following SO answer Ember sidebar - returning from "admin" sidebar to "normal"
I have a template called new, which has some input helpers to submit a new Request (subject and body). After them I have a named outlet tags, which should display a list of possible services we can add to the request (tags: fix, purchase, etc).
The problem is that when I navigate to new, only static data is displayed from the tags template ("inside tags template" would be displayed), but the #each does not loop at all.
If I add tags as a new resource into new, and navigate to new/tags, then the tags template would be rendered into both outlets of the new template ({{outlet}} and {{outlet tags}}, so my code is not faulty when it comes to displaying data, its just faulty when it comes to displaying it where and when I want to (only inside the new route).
Also, both my routes' models have a console.log message saying which route is accessed, and when I go to new only the new route displays a message.
I believe new does not know that it is supposed to use the tags controller, but I am clueless when it comes to Ember... (I do not want to get the tags via the new route, I want to use the tag route)
export default Ember.Route.extend({
model: function(){
console.log("in new");
},
setupController : function(controller, model){
controller.set("model", model);
},
renderTemplate: function() {
this.render();
this.render('tags', {
outlet: 'tagO',
into: "new",
controller: 'tags'
});
}
});
It's easier to just type {{render 'tags' someModel}} from the template than to programmatically do it in the renderTemplate hook and named outlet. You need to make the someModel available on the controller that you are currently in.
You'll want to hook up multiple models on the controller, see: EmberJS: How to load multiple models on the same route?
Example: http://emberjs.jsbin.com/OxIDiVU/1051/edit
I am trying to programmatically render different templates into a named outlet based on a specific value in my model.
Here are two JSBin examples:
This one shows the basic structure but the specific render code is commented out.
http://jsbin.com/OhegexO/1/
But when I try to use the renderTemplate method in my route it doesn't work
http://jsbin.com/OhegexO/2/
I see the following errors in my console
Error while loading route: TypeError {}
Uncaught TypeError: Cannot call method 'connectOutlet' of undefined
I can seem to figure this out. The use case is that I want to use different templates based on some parameter that will be in my model.
Admittedly I'm not very familiar with rendering into named outlets, but it appears it won't render into an outlet that hasn't been rendered yet. That being said it's possible instead of doing it from renderTemplate, you could allow the product template to render normally (don't override renderTemplate), then render the edit form delayed, into the product template afterward (see 2nd jsbin). That's a little awkward, so if you wanna hijack the renderTemplate for the sake of it, see this first jsbin. They both involve waiting for the product template to render, then rendering it.
And I believe if you override the renderTemplate hook, it skips the default rendering, hence never rendering the product template. After further investigation, this is true, if you super it, it works as well. BTW this._super() says run the default implementation of this method as well.
http://jsbin.com/ujiKire/5/edit
Using setup controller:
http://jsbin.com/OvONejo/1/edit
setupController: function(controller, model){
var templateEditForm = model.get('editform');
var templateInto = 'product';
var templateOutlet = 'editform';
console.log("render the [%s] form into the [%s] template, using the [%s] outlet",templateEditForm, templateInto, templateOutlet);
// Why does this code not work
var self = this;
Ember.run.later(function(){
self.render(templateEditForm, { // the template to render
into: 'product', // the template to render into
outlet: templateOutlet, // the name of the outlet in that template
controller: controller // the controller to use for the template
});
},1);
}
Can someone explain in layman's terms the way nested outlets work in ember templates?
In particular trying understand this from the docs:
http://emberjs.com/guides/routing/rendering-a-template/
"The immediate parent route did not render into the main outlet ..."
This means that the current route tried to render into the parent
route's template, but the parent route didn't render a template, or,
if it did, that the template which the parent route provided did not
render into the main template (i.e., a default {{outlet}}).
More specifically I am trying understand how to create a nested view hierarchy in my app.
It is three layers deep of collections. I want to create a series of collapsible nested views, based on the contents of the collections. The data structure could be tree like.
Libraries -> each Library has many Books -> each Book has many Pages
Looking for an illustrative jsbin or code sample that demonstrates the nested template structure in practice.
Imagine a router that looks like this:
App.Router.map(function() {
this.resource('libraries', function() {
this.route('new');
this.resource('library', {path: ':library_id'}, function() {
this.resource('books', function() {
this.route('new');
this.resource('book', {path: ':book_id'}, function() {
this.resource('pages', function() {
this.route('new');
this.resource('page', {path: ':page_id'}, function() {
}); // Page
}); // Pages
}); // Book
}); // Books
}); // Library
}); // Libraries
}); // map
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.