Nested routes and outlets within outlets - ember.js

This could be a rather trivial question, but as an Ember beginner, I am struggling with it a bit.
The site I'm building has a parent navigation, but some of the templates inside its outlet may have their own navigation and their own outlet (at least that's what I'm intending). In other words, I might have something like (skipping a lot of HTML):
<script type="text/x-handlebars" data-template-name="application">
<nav>
<ul class="nav">
<li>{{#link-to 'index'}}Home{{/link-to}}</li>
<li>{{#link-to 'testing'}}Testing{{/link-to}}</li>
</ul>
</nav>
<div>{{outlet}}{/div>
</script>
<script type="text/x-handlebars" data-template-name="testing">
<ul class="nav">
<li>{{#link-to 'testing.encoding'}}Encoding{{/link-to}}</li>
<li>{{#link-to 'testing.user'}}User Admin{{/link-to}}</li>
</ul>
<div>{{outlet}}{/div>
</script>
<script type="text/x-handlebars" data-template-name="testing/encoding">
<h3>Encoding</h3>
</script>
<script type="text/x-handlebars" data-template-name="testing/user">
<h3>User Admin</h3>
</script>
The application template wires great. Click on "Home", you get the index template; click on "Testing", you get the testing template. Super. What I want is for when they click "Encoding", the route is then /testing/encoding and the testing/encoding template is rendered in the outlet inside of testing. My Router looks like this:
App.Router.map(function() {
this.route('testing', function() {
this.route('encoding');
this.route('users');
});
});
However, the page won't load at all, giving me: Uncaught Error: There is no route named encoding.index. I suspect I named my templates poorly, or configured my Router incorrectly, or perhaps need to name the outlet in the testing template... and I've attacked those potential issues, but have yet to come up with a resolution.
Ideas? This is probably a typical pattern and likely has a clean, easy solution with minimal javascript. I'm just not seeing it, I suppose.

You want your router to look like this.
App.Router.map(function() {
this.resource('testing', function() {
this.route('encoding');
this.route('users');
});
});
You can't nest a route under a route. What you get with the resource is the leaf routes of index, loading and error.
There is not route encoding.index in this router.
The routing guide here is really helpful.
Cheers

For one you didn't name your route correctly (it should be this.route('encoding'); instead of this.route('encode');. But the bigger issue is that Ember doesn't allow for nested routes, only nested resources (and routes nested in resources).
The Ember docs say: "You cannot nest routes, but you can nest resources."

Related

Ember.js: ArrayController undefined in template

Problem:
I am kind of struggling with the organization of my first ember app. The current issue is that the my Items ArrayController is not defined in my dashboard template:
<script type="text/x-handlebars" data-template-name="dashboard">
{{#if controllers.items}}
<p class="alert alert-error">Dashboard can access item's info - Nice!</p>
{{else}}
<p class="alert alert-error">Dashboard cannot access items... :-/</p>
{{/if}}
</script>
Likely cause: *
**EDIT: after talking with #conrad below, I'm kind of questioning this:*
I had a similar issue in an earlier post and kingpin2k suggested the cause was that I:
"never created anything that uses the options controller".
This is probably the case here as well. This quick screencast shows that a breakpoint on my ArrayController is not hit on page load - but it is hit when I inspect the Items controller in the Ember inspector tool (eg, Ember creates the ArrayController object right then for the first time).
Apparent non-solutions:
My Dashboard controller says it needs the Items controller. I guess that isn't enough to instantiate the ArrayController?
App.ItemsController = Ember.ArrayController.extend({
len: function(){
return this.get('length');
}.property('length'),
totalCost: function() {
return this.reduce( function(prevCost, item){
return parseInt(item.get('values').findBy('type', 'cost').price, 10) + prevCost;
}, 0);
}.property('#each.values')
[more computed properties...]
});
App.DashboardController = Em.Controller.extend({
needs: ['items'],
itemsLength: Ember.computed.alias('controllers.items.len'),
itemsTotalCost: Ember.computed.alias('controllers.items.totalCost'),
[more computed properties...]
});
Furthermore, each item in Items is being rendered in my items template. I guess that does not create the missing controllers.items either...
<script type="text/x-handlebars" data-template-name="items">
{{#each}}
[these render fine]
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="display">
<!-- DISPLAY TEMPLATE -->
{{!- DASHBOARD -}}
{{render dashboard}}
{{!- ITEMS -}}
{{render 'items' items}}
</script>
So then.. what?
I can imagine many possible avenues, but haven't gotten any of them to work yet:
Specify the Items ArrayController in {{render dashboard}}?
Some configuration in a Route?
Maybe my templates/routes are not correctly arranged?
You could make sure that the ItemController is instantiated in the dashboard template by calling it in the DashboardController's init function:
App.DashboardController = Em.Controller.extend({
needs: ['items'],
init: function() {
this._super();
this.get('controllers.items.length');
}
});
/edit:
removed the part that was not helpful

Understand routing in Ember.js

I'm really struggling to understand the routing concept in Ember but is more complicate than what it seem. From the doc. you can see you have different route whenever there is different url or path and if you have different path in the same url, easy you just need to create a nested template.
But what about when you have 3 different path in one url?
And what's the difference from this.resource and this.route?
Since live example are always better than pure theory, here my app.
In index or '/' I should render "list template", "new template" and when user click on a list link, the "note template" is render instead "new template".
My router:
Notes.Router.map(function () {
this.resource('index', { path: '/' }, function (){
this.resource('list', {path: ':note_title'});
this.resource('new', {path: '/'});
this.resource('note', { path: ':note_id' });
});
});
My template:
<script type="text/x-handlebars" data-template-name="index">
<div class="wrap">
<div class="bar">
{{input type="text" class="search" placeholder="Where is my bookmark??" value=search action="query"}}
<div class="bar-buttons">
<button {{action "addNote"}}> NEW </button>
</div>
</div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="list">
<aside>
<h4 class="all-notes">All Notes {{length}}</h4>
{{#each item in model}}
<li>
{{#link-to 'note' item}} {{item.title}} {{/link-to}}
</li>
{{/each}}
</aside>
</script>
<script type="text/x-handlebars" data-template-name="new">
<section>
<div class="note">
{{input type="text" placeholder="Title" value=newTitle action="createNote"}}
<div class="error" id="error" style="display:none"> Fill in the Title! </div>
{{input type="text" placeholder="What you need to remember?" value=newBody action="createNote"}}
{{input type="text" placeholder="Url:" value=newUrl action="createNote"}}
</div>
</section>
</script>
<script type="text/x-handlebars" data-template-name="note">
<section>
<div class="note">
{{#if isEditing}}
<h2 class="note-title input-title"> {{edit-input-note value=title focus-out="modified" insert-newline="modified"}} </h2>
<p class="input-body"> {{edit-area-note value=body focus-out="modified" insert-newline="modified"}} </p>
{{edit-input-note value=url focus-out="modified" insert-newline="modified"}}
{{else}}
<h2 {{action "editNote" on="doubleClick"}} class="note-title" > {{title}} </h2>
<button {{action "removeNote"}} class="delete"> Delete </button>
<p {{action "editNote" on="doubleClick"}}> {{body}} </p>
{{input type="text" placeholder="URL:" class="input" value=url }}
{{/if}}
</div>
</section>
</script>
Or here the Js Bin: http://jsbin.com/oWeLuvo/1/edit?html,js,output
If my controllers or model are needed I will add that code as well.
thanks in advance
Your example seems to be working.
You just miss dependencies. You haven't included Handlebars and Ember.data
If you'd have checked your javascript console, you'd have seen the error thrown.
working example: http://jsbin.com/oWeLuvo/2/
In Ember a resource and a route are both routes. They have two names in order for Ember to differentiate between what is a resource and a route. In all honesty to remember that they are both routes and to keep your sanity you could refer to them respectively as a 'resource route' and a 'route'. A resource can be nested and have children resources or routes nested within them. Routes on the other hand cannot have nested anything.
Install the Ember Inspector if you are not already using it. It is a chrome extension and will help you with routes, controllers, templates, data and alot of other things with Ember, that you install into the Chrome Web Browser. The last that I heard the Ember Inspector is available in the FireFox Dev Tools as well. https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi?hl=en
So if have a resource, you can nest a resource, and a route. The nested resources will preserve their name space, routes will get appended to nested name space. Remember you can not nest anything within a route.
App.Router.map(function() {
//creating a resource
this.resource('foo', {path: 'somePathYouPut'}, function() {
//nesting stuff inside of the resource above
//could open another nest level here in bar if you want
this.resource('bar');
//can not nest in route. Sad face. Sorry
this.route('zoo');
});
});
Since you can not nest anything into a route your {{outlet}} in the index template does not have any children to look for and by default and render into that {{outlet}}. I believe that is what you think is going to happen.
<script type="text/x-handlebars" data-template-name="index">
<div class="wrap">
<div class="bar">
{{input type="text" class="search"
placeholder="Where is my bookmark??" value=search action="query"}}
<div class="bar-buttons">
<button {{action "addNote"}}> NEW </button>
</div>
</div>
{{outlet}}
</div>
</script>
In your code you referred to the index as a resource, its a route. Since the index is a route, remember you can not nest elements within a route, your code should have looked more like this. Also your resource 'new' path / can be removed as well.
Notes.Router.map(function () {
//this is a route not a resource you had it as resource
//and tried to nest in your code
this.route('index', { path: '/' });
this.resource('list', {path: ':note_title'});
this.resource('new');
this.resource('note', { path: ':note_id' });
});
You get an index at each nested level starting with the top most level which comes from the application level but you don't have to explicitly define them in the Router they are just there. The index route that you get for free at each nested level is associated with its immediate parent by default and render into its parents 'outlet' by default. You could think of the Router looking something like this.
For Illustrative purposes only:
Notes.Router.map(function() {
//think of this as your application level stuff that Ember just does!!
//this is not written like this but to illustrate what is going on
//you would get application template, ApplicationRoute, ApplicationController
this.resource('application', function() {
//this is an index that you get for free cause its nested
//its that index template, IndexController, and IndexRoute you were using
this.route('index', { path: '/' });
this.resource('foo', {path: 'somePathYouPutHere' }, function() {
//since you started another nested level surprise you get another `index`
//but this one is `foo/index` template.
this.route('index', {path: '/'});
this.route('zoo');
});
});
});
The first part of the above exaggerated router example, Ember does automatically behind the scenes, its part of the 'magic' you hear about. It does two things it sets up an Application environment for its self and you get ApplicationRoute, ApplicationController, and a application template which are always there behind the scene. Second it makes that index and you get IndexController, IndexRoute, and a index template that you can use or ignore. So if you do nothing else, no other code that declaring and Ember App in a file like var App = Ember.Application.create(); and open the Ember Inspector and look into routes you will see the above mentioned assets.
Now, the resource foo in the above router is an example of a resource you would make and as you see you get an index in there because you started to nest. As mentioned above you do not have to define the index at each nest level, this.route('index', {path: '/'}); from inside foo could be totally omitted and Ember will still do the same thing. You will end up with foo/index template, FooIndexRoute, FooIndexController along with the expected foo template, FooRoute, and FooController. You can think of thefooindex as a place that says 'hey' before anything else gets rolled into my parentfoo` and gets rendered I can show something if you want, use me.
This is also a good time to highlight what happens with namespace when you nest route in a resource like this.route('zoo') in the above example. The namespace of the route zoo is now going to be appended to resource foo and you end up with foo/zoo template, FooZooRoute and a FooZooController.
If you were to change zoo to a resource nested in the foo resource this.resource('zoo'); the namespace will be keep for zoo. You will end up with 'zoo' template, ZooRoute and a ZooController. The namespace is kept. Ok, enough side tracking what about your App.
You said that you wanted / url of your app to render the list template. In order to accomplish that you have to override the default behavior that happens when Ember boots up. You override the top level / by adding the {path: '/'} to the first resource or route in the Router. From the fake router code above the first index route you get is associate with the application. By default if you do nothing Ember will push that index template into the application template. However that is not what you want you want your list template to be pushed into the application template when you hit the base url of /' for your App.
Notes.Router.map(function() {
this.resource('list', {path: '/'});
this.resource('new');
this.resource('note', { path: ':note_id' });
});
By adding the code {path: '/'} to the first resource like I did above, you are now telling Ember 'hey' when my app url reaches the base url of / or when you boot up use my list template not your default index template and render that into the application template. In addition since the other resources are not nested when your App transitions to those routes via the url they will blow out whats in the application template {{outlet}} and replace themselves in there.
You mentioned about defining a "path" in a route what that does is tell Ember how you want to represent that route in the url. In the above example if you leave the new resource as is, Ember by default will use the routes name as the path, the url would be /new. You can put any name in path for the new resource, this.resource(new, {path :'emberMakesMeKingOfWorld'}); and that name will be represented in the url as /emberMakesMeKingOfWorld and that long thing will still be associated with you new route. Your user might be confused what this url means but behind the scence you would know its tied to your new route. Its just an example but probably good practice to use descriptive names so your user knows what hitting a url in your App is meant to do.
After overriding the default index template that is associated with the application. I moved your code into the application template. The reason for that it seemed as though you wanted that bar with the 'newNote' action to be present all the time. If you want something present all the time in your App, like a navigation bar, footer, im sure you can think of better stuff, place it in the application template.
Here is a JSBIN and I adjusted you code a little
http://jsbin.com/oWeLuvo/8
I hope this helps Happy Coding.

EmberJS - adding some template in separated file

I'm a newbie yet on EmberJS, started to learn a week ago. Well, I have some .html file named contactsTemplate.html that is a template what I want to use. Something like this below:
<script type="text/x-handlebars" data-template-name="contacts">
<h2>My Contact List</h2>
{{#each contact in contactList}}
{{contact.name}}
{{/each}}
</script>
Great. I have a index.html file that contains an {{outlet}} expression. It means that every new content will be loaded into this space, right?
<script type="text/x-handlebars" data-template-name="application">
<h1>My Application</h1>
{{outlet}}
</script>
Ok... To manage this template named application, I've created a Controller object.
App.ApplicationController = Ember.ObjectController.extend({
...
});
Same to contacts template:
App.ContactsController = Ember.Controller.extend({
contactList: []
});
I've created two routes to manage this transition between pages:
App.Router.map(function() {
this.route("index", "/");
this.route("contacts", "/contacts");
});
My doubt is:
How can I associate this separated files to a route? I mean, when I call on browser http://mysite.com/ the index.html file should be loaded. When I call http://mysite.com/#/contacts the contactsTemplate.html should be loaded.
Sorry for the long text, and thanks for any help!
By default each declared this.route(routeName) in Router map, will have a [routeName]Controller, [routeName]Route, and expect a [routeName] template, following these conventions described in http://emberjs.com/guides/concepts/naming-conventions/.
To change from one route to other, you can use:
Inside of a controller this.transitionToRoute(routeName)
Inside of a route this.transitionTo(routeName)
Inside of a template {{#link-to "routeName" }}My route{{/link-to}}
You can find more information in the routing guide http://emberjs.com/guides/routing
I hope it helps

How to have an element load with the application in Ember.js

I am trying to create my first ember application but I can't have a template load with the application template:
Below is a simplified version of my layout:
<script type="text/x-handlebars">
{{outlet}}
</script>
How do I get the another template into the outlet section on page load?
Thanks in advance
For example if my second template is:
<script type="text/x-handlebars" id="yellow">
<p>Something</p>
</script>
#Marcio answer is correct, ember does create an implicit index route.
However the most important part here is that if you have, say a route like yellow that you want to be rendered inside the application's {{outlet}} (instead of the implicit index template) then you need to define a path for that route so the correspondent named template, (in your case yellow) will be rendered into the application's {{outlet}} instead of the implicit index:
App.Router.map(function() {
this.route('yellow', {path: '/'})
});
<script type="text/x-handlebars" data-template-name="application">
<h1>Hi</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="yellow">
<h2>Welcome from yellow</h2>
</script>
If you don't do this then ember will search for an index template to be rendered inside the outlet, and since you doesn't have one, nothing will be displayed. So again, by defining a path like "/" for your yellow route you instruct ember to use the yellow template instead of the index.
Of course, if you rename your yellow template to index then it will be picked up by ember and rendered inside the application outlet and you don't need to specify a path for the yellow route.
Example jsfiddle.
Hope it helps.
When you create a route like this:
App.Router.map(function() {
this.route('yellow')
});
and a transition is performed to yellow route.
For example, via {{#link-to 'yellow'}}Yellow{{/link-to}} or changing the url tohttp://www.yourhost.com#/yellow`
A handlebar template is appended in the dom, with the same name like the route:
<script type="text/x-handlebars" id="yellow">
<p>Something</p>
</script>
By default ember create a route('index') in your router mapping, like this:
App.Router.map(function() {
this.route('yellow')
// this.route('index', { path: '/' })
});
So creating a index template will work
<script type="text/x-handlebars" id="index">
Welcome
</script>
When the user navigate to the url http://www.yourhost.com, he will see Welcome.
See this demo http://jsfiddle.net/marciojunior/Db49u/

Use "subcontrollers" in EmberJS

i'm having some trouble designing an EmberJS layout. I have a view divided in two parts :
a content library on the left
a playlist editor on the right
Currently, those two elements share the same controller.
I can't figure how to use two different controllers for those two sides to be able to re-use the content library in other views or even having a view with two playlist editors.
My root view looks like this so far :
<script type="text/x-handlebars" data-template-name="playlists">
<div id="library">{{template library}}</div>
<div id="playlistEditor">{{template playlisteditor}}</div>
</script>
I saw docs about the {{control}} helper, but it is unstable and i'm not sure this is what i'm looking for.
Thanks !
Okay, i found the answer, i had to use the {{render}} helper, like that :
<script type="text/x-handlebars" data-template-name="playlists">
<div id="library">{{render "library" library}}</div>
<div id="playlistEditor">{{render "playlisteditor" playlist}}</div>
</script>
Then, in my route :
App.PlaylistsRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('playlist', playlist);
controller.set('library', library);
}
});
Then, this EmberJS will automatically wire the App.PlaylisteditorController, the App.LibraryController and the views playlisteditor and library. Awesome.