Loading state for ApplicationRoute - ember.js
Using: ember 1.7.0
I have some server-side data that I want to load up in my ember app before any routes are transitioned to. Multiple (but not all) of my other routes / controllers need this data. I was thinking that I could just load up this data in the ApplicationRoute model method. It works fine, but does not display a loading state.
Is it possible to have the ApplicationRoute display a loading state until it's model promise gets resolved.
Here's a jsbin illustrating the problem: http://jsbin.com/soqivo/1/
Thanks for the help!
Update:
As of 1.11.0 release, it should be possible to define a loading substate for the application route.
TL;DR;
I think this is by design, but not a design flaw. This particular issue is happening because that long model request is taking place in ApplicationRoute#model when it should be in IndexRoute#model. Move that promise/request into the index route and it should be fine. If you must add stuff to the application controller, consider this, combined with something that says "loading" in your index.html file while the app is waiting.
Why
Ember.Route has a number of hooks that we often override so it does what we want instead of a default implementation. The most obvious hook being model and setupController. But sometimes we simply don't write the setuptController method because it does what we want it to do already (given one just wants to set the model into the controller). But regardless of these methods being overridden, they will run as part of an internal workflow anyway. This workflow has a number of steps that are not often discussed because they already do what we want and we tend to forget about them and their importance, and, as for this particular issue, the order in which these methods get called in the route life cycle.
App = Ember.Application.create();
App.logs = Ember.ArrayProxy.create({
content: []
});
App.Router.map(function() {
this.resource('posts', function() {});
});
function loggingAlias(property) {
return function() {
App.logs.pushObject(this._debugContainerKey + ' ' + property);
return this._super.apply(this, arguments);
};
}
App.LoggingRoute = Ember.Route.extend({
enter: loggingAlias('enter (private)'),
exit: loggingAlias('exit (private)'),
activate: loggingAlias('activate'),
deactivate: loggingAlias('deactivate'),
serialize: loggingAlias('serialize'),
deserialize: loggingAlias('deserialize (private)'),
model: loggingAlias('model'),
setupController: loggingAlias('setupController'),
afterModel: loggingAlias('afterModel'),
beforeModel: loggingAlias('beforeModel'),
renderTemplate: loggingAlias('renderTemplate'),
redirect: loggingAlias('redirect')
});
App.LogsController = Ember.ArrayController.extend({
content: App.logs,
actions: {
clearLogs: function() {
App.logs.clear();
}
}
});
App.ApplicationRoute = App.LoggingRoute.extend();
App.PostsRoute = App.LoggingRoute.extend();
App.PostsIndexRoute = App.LoggingRoute.extend();
/* Put your CSS here */
html,
body {
margin: 20px;
}
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Ember Route Hook Order" />
<meta charset="utf-8">
<title>Ember Route Hook Order</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/2.1.0/normalize.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.2.1.js"></script>
<script src="http://builds.emberjs.com/beta/ember.js"></script>
</head>
<body>
<script type="text/x-handlebars">
<strong>Note:</strong> <em>
MilkyWayJoe says: I didn't write this. I have found in jsbin at: http://jsbin.com/rolo/2/edit?output
<br />
Added here in case the link goes kaput just to point out the order of Ember.Route internal workflow steps
</em>
<br />
<br />{{link-to 'Index' 'index'}} {{link-to 'Posts' 'posts'}} {{outlet}} {{render 'logs'}}
</script>
<script type="text/x-handlebars" id='logs'>
<h3>Logged Method Calls</h3>
<a href="#" {{action 'clearLogs'}}>Clear Logs</a>
<ul>
{{#each}}
<li>{{this}}</li>
{{/each}}
</ul>
</script>
</body>
</html>
Since renderTemplate is the last to get called so it only makes sense that it doesn't render anything until the promise(s) within a given route get resolved.
For child routes, that's totally fine because their loading substate will have some type of canvas to draw onto, since a parent-already-loaded-living-and-breathing-route has been loaded prior this route even being instantiated. But that's not true for the ApplicationRoute since it has no parent route or template to rely upon, thus rendering a blank page until all promises get resolved.
Working snippet
The next best thing is moving any long running requests to a child route. As the proposed solution, I've moved your 3 sec promise to IndexRoute#model since this route will run anyway and is the direct child of ApplicationRoute by default. I would say reserve the application route or controller for handling error events instead.
App = Em.Application.create({
displayName: 'Test.App'
});
App.Router.map(function() {
this.resource('files', function() {
this.route('index', {path: '/'});
this.resource('file', { path: ':file_id' }, function() {
this.route('index', {path: '/'});
this.route('detail');
});
});
});
App.FilesController = Em.ArrayController.extend();
App.FilesFileController = Em.ObjectController.extend();
App.Person = Ember.Object.extend({});
App.IndexRoute = Ember.Route.extend({
model: function(params, transition){
return new Ember.RSVP.Promise(function(resolve){
Ember.run.later(function(){
var model = App.Person.create();
resolve(model);
}, 3000);
});
}
});
App.LoadingRoute = Em.Route.extend({
renderTemplate: function() {
this.render('loading', {
into: 'application',
outlet: 'loading'
});
}
});
App.FileLoadingRoute = App.LoadingRoute.extend();
App.FilesRoute = Em.Route.extend({
model: function() {
var selfie = this;
return new Ember.RSVP.Promise(function(resolve){
Ember.run.later(function() {
var model = selfie.store.find('file');
resolve(model);
}, 800);
});
}
});
App.FilesIndexRoute = Em.Route.extend({
model: function(){
return this.store.all('file');
}
});
App.FileRoute = Em.Route.extend({
model: function(params) {
return this.store.find('file', params.file_id);
}
});
App.FileIndexRoute = Em.Route.extend({
model: function() {
return this.modelFor('file');
},
renderTemplate: function() {
this.render('files/index', {
into: 'application'
});
this.render('file/index', {
into: 'files/index',
outlet: 'file'
});
}
});
App.FileDetailRoute = Em.Route.extend({
model: function() {
var selfie = this;
return new Ember.RSVP.Promise(function(resolve){
Ember.run.later(function(){
var file = selfie.modelFor('file');
var model = selfie.store.find('fileDetail', file.id);
resolve(model);
}, 800);
});
},
renderTemplate: function() {
this.render('files/index', {
into: 'application'
});
this.render('file/index', {
into: 'files/index',
outlet: 'file'
});
this.render('file/detail', {
into: 'file/index',
outlet: 'detail'
});
},
actions: {
loading: function() {
return true;
}
}
});
App.RlLoadIndicatorComponent = Em.Component.extend({
classNames: ['rl-load-indicator'],
classNameBindings: ['isLoading:rl-overlay:rl-silent'],
overlay: true,
spinner: true,
message: 'Loading...',
loading: false,
isLoading: function() {
return this.get('loading');
}.property('loading'),
spinnerClass: function() {
if (this.get('loading')) {
if (this.get('spinner')) {
return 'rl-spinner';
}
}
return "";
}.property(),
actions: {
setLoading: function() {
this.set('loading', true);
},
setDone: function() {
this.set('loading', false);
}
}
});
App.ApplicationAdapter = DS.FixtureAdapter.extend();
App.File = DS.Model.extend({
name: DS.attr('string'),
text: DS.attr('string'),
detail: DS.belongsTo('fileDetail', {async: true})
});
App.FileDetail = DS.Model.extend({
owner: DS.attr('string'),
canEdit: DS.attr('bool'),
file: DS.belongsTo('file'),
property1: DS.attr('string'),
property2: DS.attr('string'),
property3: DS.attr('string'),
property4: DS.attr('string'),
property5: DS.attr('string')
});
App.File.FIXTURES = [
{id: 1, name: 'File 1', text: 'Blah 1', detail: 1},
{id: 2, name: 'File 2', text: 'Blah 2', detail: 2},
{id: 3, name: 'File 3', text: 'Blah 3', detail: 3},
{id: 4, name: 'File 4', text: 'Blah 4', detail: 4},
{id: 5, name: 'File 5', text: 'Blah 5', detail: 5},
{id: 6, name: 'File 6', text: 'Blah 6', detail: 6},
{id: 7, name: 'File 7', text: 'Blah 7', detail: 7},
{id: 8, name: 'File 8', text: 'Blah 8', detail: 8},
{id: 9, name: 'File 9', text: 'Blah 9', detail: 9},
{id: 10, name: 'File 10', text: 'Blah 10', detail: 10}
];
App.FileDetail.FIXTURES = [
{
id: 1,
owner: 'Spiderman',
canEdit: true,
file_id: 1,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'With great values, comes great bindings'
},
{
id: 2,
owner: 'Iron Man',
canEdit: true,
file_id: 2,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Another Value'
},
{
id: 3,
owner: 'Thor',
canEdit: false,
file_id: 3,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Another Value'
},
{
id: 4,
owner: 'Captain America',
canEdit: false,
file_id: 4,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Another Value'
},
{
id: 5,
owner: 'Neil DeGrasse Tyson',
canEdit: true,
file_id: 5,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Another Value'
},
{
id: 6,
owner: 'Dr. Doom',
canEdit: false,
file_id: 6,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Another Value'
},
{
id: 7,
owner: 'Reed Richards',
canEdit: true,
file_id: 7,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Another Value'
},
{
id: 8,
owner: 'Walter White',
canEdit: true,
file_id: 8,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Say My Name!'
},
{
id: 9,
owner: 'Jesse Pinkmann',
canEdit: true,
file_id: 9,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Bitch'
},
{
id: 10,
owner: 'Hawk Barton',
canEdit: false,
file_id: 10,
property1: 'Value 1',
property2: 'Value 2',
property3: 'Value 3',
property4: 'Value 4',
property5: 'Another Value'
}
];
/* Put your CSS here */
html, body {
margin: 20px;
}
.rl-load-indicator {
text-align: center;
}
.rl-overlay {
position:fixed;
top:0;
left:0;
right:0;
bottom:0;
background-color:rgba(0, 0, 0, 0.85);
background: url(data:;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABl0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuNUmK/OAAAAATSURBVBhXY2RgYNgHxGAAYuwDAA78AjwwRoQYAAAAAElFTkSuQmCC) repeat scroll transparent\9; /* ie fallback png background image */
z-index:9999;
color:white;
}
.rl-silent {
display: none;
visibility: hidden;
}
.rl-spinner {
width: 30px;
height: 30px;
background-color: #27ae60;
margin: 100px auto;
margin-bottom: 8px;
-webkit-animation: rotateplane 1.2s infinite ease-in-out;
animation: rotateplane 1.2s infinite ease-in-out;
}
.arrow-right {
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 5px solid green;
}
#-webkit-keyframes rotateplane {
0% { -webkit-transform: perspective(120px) }
50% { -webkit-transform: perspective(120px) rotateY(180deg) }
100% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) }
}
#keyframes rotateplane {
0% {
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
-webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)
} 50% {
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
-webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)
} 100% {
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
-webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
}
}
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Loading Thingy" />
<meta charset="utf-8">
<title>Ember Starter Kit</title>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.7.0/ember.js"></script>
<script src="http://builds.emberjs.com/beta/ember-data.js"></script>
</head>
<body>
<script type="text/x-handlebars">
<h1>{{unbound App.displayName}}</h1>
{{partial "menu"}}
<hr />
{{outlet}}
{{outlet "loading"}}
</script>
<script type="text/x-handlebars" data-template-name="loading">
{{rl-load-indicator loading=true}}
</script>
<script type="text/x-handlebars" data-template-name="_menu">
{{#link-to 'index'}}Home{{/link-to}} | {{#link-to 'files.index'}}Files{{/link-to}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h3>Index</h3>
Content goes here
</script>
<script type="text/x-handlebars" data-template-name="files/index">
<h3>Files</h3>
<table class="table table-hover">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th> </th>
</tr>
</thead>
<tbody>
{{#each file in model}}
<tr>
<td>{{file.id}}</td>
<td>{{file.name}}</td>
<td>
{{#link-to 'file.index' file}}
<p class="arrow-right"></p>
{{/link-to}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{outlet "file"}}
</script>
<script type="text/x-handlebars" data-template-name="file/index">
<h3>{{name}}</h3>
{{text}}
<hr />{{#link-to 'file.detail'}}Detail{{/link-to}}
{{outlet "detail"}}
</script>
<script type="text/x-handlebars" data-template-name="file/detail">
<h5>Details</h5>
<hr />
<ul>
<li>owner: {{owner}}</li>
<li>can edit: {{canEdit}}</li>
<li>property 1: {{property1}}</li>
<li>property 2: {{property3}}</li>
<li>property 3: {{property3}}</li>
<li>property 4: {{property4}}</li>
<li>property 5: {{property5}}</li>
</script>
<script type="text/x-handlebars" data-template-name="components/rl-load-indicator">
<div {{bind-attr class=spinnerClass}}></div>
{{unbound message}}
</script>
</body>
</html>
Just create a different resource below the application route, and do all of your loading there. Don't use the index route though, it is only hit when you hit the root of a particular resource (App.IndexRoute would be when you just hit the root of your application).
App = Ember.Application.create();
App.Router.map(function() {
this.resource("top",function(){ // a place you can grab things for the app and block
this.resource('home'); // a place you want to get when everything is ready
});
});
App.ApplicationRoute = Ember.Route.extend();
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo("home");
}
});
App.TopRoute = Ember.Route.extend({
// This does trigger a loading state
model: function(params){
return new Ember.RSVP.Promise(function(resolve){
setTimeout(function(){
resolve();
}, 3000); // 3 second delay, wooh, your server is slow!!!
});
}
});
http://jsbin.com/mivul/edit?html,js,output
Related
Get data from ember data bind it to a widget in view
I have a router that returns data from the data model. I want to use this data to bind it to a widget in a view. Model: myApp.Unit = DS.Model.extend({ active: DS.attr('boolean'), allowdecimals: DS.attr('boolean'), name: DS.attr('string'), count: DS.attr('number'), }); Router: myApp.EunitsRoute = Ember.Route.extend({ model: function() { return this.store.find('unit'); }, setupController: function(controller, model) { this._super(controller, model); controller.set('units', model); }, actions: { ... In the view I expect an object formated as follows: [ {"id": 1,"active": true,"allowdecimals": true,"name": "Pint","count": 8}, {"id": 2,"active": true,"allowdecimals": true,"name": "Each","count": 8}, ...] What I am getting now in the view is an object:<DS.RecordArray:ember400> View: var source10 = { datatype: "array", datafields: [ { name: 'id' }, { name: 'name' }, { name: 'allowdecimals' }, { name: 'active' }, { name: 'count' } ], localdata: controller.get('units') }; var dataAdapter10 = new $.jqx.dataAdapter(source10); $("#eunits_grid").jqxGrid({ pageable: true, theme: 'energyblue', autorowheight : true, rowsheight : 50, pagesize: 10, source: dataAdapter10, .... Template: <script type="text/x-handlebars" data-template-name="eunits"> <div class="col-md-12"> <h3 class="page-header">Edit Units</h3> {{#if adding}} {{view AddnewunitView}} {{/if}} {{view UnitsView id='eunits_grid'}} </div> </script>
Handling multiple checkbox array in Ember.js
Here is the thing I have a form which have multiple checkboxes and they have the same name attribute "classifications[]" with the code: <input type="checkbox" name="classification[]" value="Value 1" /> <input type="checkbox" name="classification[]" value="Value 2" /> <input type="checkbox" name="classification[]" value="Value 3" /> this works properly by default it posts "classification[]" like this [ 0 => "Value 1", 1 => "Value 2"] but I want this to work in an ember app so I did this (shortened version) // index.html {{input type="checkbox" name="classification[]" value="Cell Member" checked=classification}} App.ApplicationController = Ember.Controller.extend({ var user = this.store.createRecord('user',{ classification: this.get("classification") }) user.save(); }) App.User = DS.Model.extend({ classification: DS.attr("string") }); but the only posted classification value is True..
try this // index.html {{#for possibleValues}} <label>{{this.label}}</label> {{input type="checkbox" value=this.isChecked}} {{/for}} <a {{action save}}>save</a> var possibleValue = [{ label: 'Label for value 1', isChecked: false, value: 'Value 1' },{ label: 'Label for value 2', isChecked: false, value: 'Value 2' },{ label: 'Label for value 3', isChecked: false, value: 'Value 3' }]; App = Ember.Application.create(); App.ApplicationAdapter = DS.RESTAdapter.extend(); App.ApplicationController = Ember.ObjectController.extend({ init: function () { this._super(); var user = this.store.createRecord('user'); this.set('content', user); }, actions:{ save: function () { var user = this.get('content'), collected = user.get('classifications'); this.set('collected', collected); user.save(); } }, //executes when isChecked is changed in one item of possibleValues collectChecked: function () { var classifications; classifications = this.get('possibleValues').filterBy('isChecked', true); //filter checked only classifications = classifications.mapBy('value'); //map values this.set('classifications', classifications); console.log(classifications); }.observes('possibleValues.#each.isChecked'), possibleValues: possibleValues }); App.RawTransform = DS.Transform.extend({ deserialize: function(serialized) { return serialized; }, serialize: function(deserialized) { return deserialized; } }); App.User = DS.Model.extend({ classifications: DS.attr('raw') }); http://jsbin.com/dokiwati/6/edit
template syntax for related data
This seems like it should be simple. Suppose I have the following model: App.Folder = DS.Model.extend({ folder_id: DS.attr('number'), folder_name: DS.attr('string'), items: DS.hasMany('App.Item') }); App.Item = DS.Model.extend({ item_id: DS.attr('number'), folder_id: DS.attr('number'), item_name: DS.attr('string'), folder: DS.belongsTo('App.Folder') }); ...and the following data: App.Folder.FIXTURES = [ { id: 1, folder_name: 'My First Folder', items: [100] }, { id: 2, folder_name: 'Another Folder', items: [101] }, { id: 3, folder_name: 'Folder 3', items: [102] } ]; App.Item.FIXTURES = [ { id: 100, folder_id: 1, item_name: 'This is an item' }, { id: 101, folder_id: 2, item_name: 'A cleverly named item' }, { id: 102, folder_id: 3, item_name: 'This one is also an item in a folder' } ]; Now, in my "item" template, shouldn't I be able to do something like this to show the folder related to an item: <script type="text/x-handlebars" data-template-name="snapshot"> <div class="row-fluid property-row-alt"> <div class="span4 property-label">item name:</div> <div class="span8">{{ item_name }}</div> </div> <div class="row-fluid property-row"> <div class="span4 property-label">folder:</div> <div class="span8">{{ folder.folder_name }}</div> </div> </script> "folder.folder_name" is the key syntax I'm trying to nail down here. Maybe this isn't possible, but I'm hoping to get the related folder and drill down into it's folder_name inside my item template. Can I do this in Ember? Using the latest versions.
attributeBindings in a View aren't updating based on model change
I wonder if I'm just doing something fundamentally wrong here but I'm trying to have a model define the style attribute in a view. So for example the ember view uses the card template and starts out with <div style="color: green">...</div> that is backed by the model property color. When I change it somewhere else via App.Card.find(2).set("color", "color: red").save() I expect the template to update the value, but it does nothing. Using {{ bindAttr style model.color }} in the template directly does keep the value in sync, but then I have an extra ember-view div element. http://jsfiddle.net/dbhWg/3/ javascript: App = Ember.Application.create(); App.Store = DS.Store.extend({ adapter: 'DS.FixtureAdapter' }); App.Router.map(function () { // put your routes here }); App.IndexRoute = Ember.Route.extend({ model: function () { return App.Card.find() } }); App.Card = DS.Model.extend({ color: DS.attr('string'), }); App.Card.FIXTURES = [{ id: 1, color: "color: green" }, { id: 2, color: "color: blue" }]; App.CardView = Ember.View.extend({ templateName: "card", attributeBindings: ['style'], style: function () { return this.get('controller.model.color') }.property('controller.model'), didInsertElement: function () { App.Card.find(2).set('color', "color: red").save() console.log(App.Card.find(2).get('color')) } }); templates: <script type="text/x-handlebars" data-template-name="card"> <h1> HELLO THERE </h1> </script> <script type="text/x-handlebars"> <h2> Welcome to Ember.js </h2> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="index"> {{#each item in model}} {{render "card" item}} {{/each}} </script>
You forgot to add the dependecency for the color in computed property style: function () { return this.get('controller.model.color') }.property('controller.model.color'), Working Fiddle As far as I know, you can't update CSS using bindAttr what I'd suggest you to use class instead, define classes as follows: .red{ color: red; } .green{ color: green; } .blue: { color: blue; } The update fixtures as: App.Card.FIXTURES = [{ id: 1, color: "green" }, { id: 2, color: "blue" }]; Bind the color to class as follows App.CardView = Ember.View.extend({ templateName: "card", classNameBindings: ['color'], color: function () { return this.get('controller.model.color'); }.property('controller.model.color'), didInsertElement: function () { App.Card.find(2).set('color', "red").save(); console.log(App.Card.find(2).get('color')); } });
how to create a right structure for nested routes in ember.js?
I need to create an application with routes: /users - list of users /users/123 - user info /users/123/items - list of user items /users/123/items/456 - items info I wrote this code here $(function() { var App = window.App = Ember.Application.create({LOG_TRANSITIONS: true}); App.Router.map(function() { this.resource("users", {path: '/users'}, function() { this.resource("user", {path: '/:user_id'}, function() { this.resource("items", {path: '/items'}, function() { this.route("item", {path: '/:item_id'}); }); }); }); }); App.Store = DS.Store.extend({ revision: 11, adapter: "DS.FixtureAdapter" }); //ROUTES App.UsersIndexRoute = Ember.Route.extend({ model: function() { return App.User.find(); } }); App.UsersUserIndexRoute = Ember.Route.extend({ model: function(params) { return App.User.find(params.user_id); }, setupController: function(controller, model) { controller.set('content', model); } }); //DATA App.User = DS.Model.extend({ name: DS.attr('string'), items: DS.hasMany('App.Item') }); App.Item = DS.Model.extend({ name: DS.attr('string') }); App.User.FIXTURES = [ { id: 1, name: 'Joe', items: [1, 2] }, { id: 2, name: 'John', items: [2, 3] } ]; App.Item.FIXTURES = [ { id: 1, name: 'Item 1', }, { id: 2, name: 'Item 2', }, { id: 3, name: 'Item 3', } ]; return true; }); And templates: <script type="text/x-handlebars" data-template-name="application"> <div> <h1>ember routes</h1> {{outlet}} </div> </script> <script type="text/x-handlebars" data-template-name="index"> <h2>Hello from index</h2> {{#linkTo 'users'}}users area{{/linkTo}} </script> <script type="text/x-handlebars" data-template-name="users"> <h2>users area</h2> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="users/index"> <h3>users list</h3> {{#each user in controller}} {{#linkTo "user" user}}{{user.name}}{{/linkTo}}<br/> {{/each}} </script> <script type="text/x-handlebars" data-template-name="users/user"> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="users/user/index"> <h4>user {{name}} index</h4> </script> <script type="text/x-handlebars" data-template-name="users/user/items"> <h4>user {{name}} items</h4> {{outlet}} </script> If I go to /users , I see a list of users - its ok. If I go to /users/1 I see message in browser console: "Transitioned into 'users.user.index'", but the content of the template 'users/user/index' is not showing. I do not understand why, because I have App.UsersUserIndexRoute Maybe I missed something?
if i'm not mistaken, using a UserIndexRoute (instead of a UsersUserIndexRoute - routes that are defined as resources usually don't have their parent routes' names prepended) with the model hook model: function(params) { return this.modelFor("user"); } (and a corresponding template) should do the trick.