emberjs pre-4 and ember-data: no data on browser-refresh - ember.js

If I browse from the index to a game, the data is shown, because of {{#linkTo}}s context, but if I'm refreshing the site, every time the game name doesn't show up.
EDIT: Here is a fiddle, but unfortunately the fiddle-version with the fixture adapter works properly, even with ken's suggestion to remove the model from the game-template.
the returned data from /api/games is as follows:
{
"games":[
{
"id":1,
"name":"First Game"
}
]
}
and the returned data from /api/games/1
{
"game": [
{
"id": 1,
"name": "First Game"
}
]
}
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="games">
<ul>{{#each game in controller}}
<li>{{#linkTo 'game' game}}{{game.name}}{{/linkTo}}</li>
{{/each}}</ul>
</script>
<script type="text/x-handlebars" data-template-name="game">
<h1>Name:{{model.name}}</h1>
</script>
<script type="text/javascript">
App = Ember.Application.create();
App.Game = DS.Model.extend({
name: DS.attr('string')
});
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.create({
namespace: 'api'
})
});
App.GamesController = Ember.ArrayController.extend({
content: []
});
App.GameController = Ember.ObjectController.extend({
content: null
});
App.Router.map(function(match) {
this.route("games", { path : "/" });
this.route("game", { path : "/games/:game_id" });
});
App.GameRoute = Ember.Route.extend({
model: function(params) {
return App.Game.find(params.game_id);
}
});
App.GamesRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Game.find());
}
});
</script>
Has anyone an idea?

Remove model from your game template, try this instead
<script type="text/x-handlebars" data-template-name="game">
<h1>Name:{{name}}</h1>
</script>
JSFiddle your code to see where the problem is

The response from my server for a single game was wrong. I had an array with a single object in it, but I used the Object controller, so I fixed it with just responding with
{
"game": {
"id": 1,
"name": "First Game"
}
}

Related

Ember.js link-to inside #each block does not render

<script type="text/x-handlebars" data-template-name="application">
<ul>
{{#each person in controller}}
<li>{{#link-to 'foo' person}}{{person.firstName}}{{/link-to}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="foo">
<h5>Foo route</h5>
{{name}}: converted to {{fullName}}
</script>
javascript:
App = Ember.Application.create({});
App.ApplicationRoute = Ember.Route.extend({
model: function(){
return [
{firstName: 'Kris', lastName: 'Selden'},
{firstName: 'Luke', lastName: 'Melia'},
{firstName: 'Formerly Alex', lastName: 'Matchneer'}
];
}
});
App.Router.map(function() {
this.route('foo');
});
App.FooRoute = Ember.Route.extend({
model: function(params) {
// just ignore the params
return Ember.Object.create({
name: 'something'
});
}
});
App.FooController = Ember.ObjectController.extend({
fullName: function() {
return this.get('name') + ' Jr.';
}
});
I must be doing something wrong, because these {{#link-to}}'s are failing. Here's a JSBin. Hesitating to file an issue because this seems like such a simple thing:
http://jsbin.com/ucanam/4777/edit?html,js,output
Your route does not allow parameter. Change it to
this.resource('foo', { path: '/:person_id'});

Pass Iterating Object to Controller Method

When trying to pass an iterating object to the Controller method, the object is passed as a String.
Template:
<script type="text/x-handlebars" data-template-name="gradebooks">
{{#each}}
{{#link-to "gradebook" this}}{{title}}{{/link-to}}
{{/each}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="gradebook">
<h1>Student Grades</h1>
{{#each student in students}}
{{#each assignment in assignments}}
{{student.name}}, {{assignment.name}}: {{getGrade student assignment}}%
{{/each}}
{{/each}}
</script>
Routes:
App.Router.map(function() {
this.resource('gradebooks', function() {
this.resource('gradebook', { path: ':gradebook_id' });
})
});
App.GradebooksRoute = Ember.Route.extend({
model: function() {
return this.store.find(App.Gradebook);
}
});
App.GradebookRoute = Ember.Route.extend({
model: function(params) {
return this.store.find(App.Gradebook, params.gradebook_id);
}
});
Controller:
App.GradebookController = Ember.ObjectController.extend({
getGrade: function(student, assignment) {
console.log(student, assignment);
return 5;
}
});
Model:
App.Gradebook = DS.Model.extend({
title: DS.attr('string'),
students: DS.hasMany('student', { async: true}),
assignments: DS.hasMany('assignment', { async: true})
});
App.Student = DS.Model.extend({
name: DS.attr('string'),
gradebook: DS.belongsTo('gradebook'),
grades: DS.hasMany('grade', { async: true })
});
App.Assignment = DS.Model.extend({
name: DS.attr('string'),
gradebook: DS.belongsTo('gradebook'),
grades: DS.hasMany('grade', { async: true })
});
App.Grade = DS.Model.extend({
score: DS.attr('number'),
student: DS.belongsTo('student'),
assignment: DS.belongsTo('assignment')
});
Currently, the above outputs the string "student" for each student. If I were to have {{getGrade student.id}}, it would output the string "student.id". How could I get it to pass the student as an object?
Given your revision I've revised my answer below to better align:
App.AssignmentController = Ember.ObjectController.extend({
needs: ["student"],
getGrade: function() {
var student = this.get('controllers.student');
//logic to get grade
}.property() //thing to observe for changes
});
<script type="text/x-handlebars" data-template-name="gradebooks">
<h1>Student Grades</h1>
{{#each}}
{{#link-to "gradebook" this}}{{title}}{{/link-to}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="gradebook">
{{#each student in students}}
{{render 'student' student}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="student">
{{name}}
{{#each assignment in assignments}}
{{render 'assignment' assignment}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="assignment">
{{name}}: {{getGrade}}%
</script>
A useful tool is {{log model}} or {{log record}} or {{log ...whatver..}} to understand what data your view is receiving.
If you open the Chrome Ember Inspector you should also see what views, controllers, routes, models, etc. have been defined which will help with understanding.
In short you cannot pass parameters to controllers by using {{getGrade student.id}} - you can define a Handelbars helper to accept parameters but I don't think this is what you are after. The method above worked for me.
Hope this helps

What is the reference for this Ember.js guides setupController multiple models with arraycontroller

Presently I am going though every page and snippit of code in the Ember.js Guides and building a small sample app. Some I have gotten stuck on for a bit but solved. This one however befuddles me.
At http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/
It's also here but does not use the .get('songs") http://emberjs.com/guides/controllers/representing-a-single-model-with-objectcontroller/
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
I don't know what playlist.get('songs') is referencing. I assume it's a model object array inner object but I am wrong obviously. But since the example code at their site does not have mock stub data to work from I am just guessing from all of my tests.
The code provided here has some commented out bits to see what I was testing.
<script type="text/x-handlebars" data-template-name="songs">
<h1>Playlist</h1>
<ul>
{{#each}}
<li>{{name}} by {{artist}}</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="playlist">
<h3>Playlist: </h3>
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0-rc.3/handlebars.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/ember.js/1.0.0-rc.6/ember.min.js"></script>
<script type="text/javascript">
window.App = Ember.Application.create();
App.Router.map(function () {
this.resource('songs');
this.resource('playlist');
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('songs');
}
});
// App.SongsRoute = Ember.Route.extend({
// setupController: function(controller, model) {
// controller.set('model', model);
// },
// model: function () {
// // return songs;
// return playlist.songs;
// }
// });
App.SongsRoute = Ember.Route.extend({
playlist: function() {
var playlist = { songs: [{fish: "fish"}, {fish: "fish"}] };
return playlist;
}.property(),
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
App.PlaylistRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('model', model);
},
model: function () {
return playlist;
}
});
App.SongsController = Ember.ArrayController.extend();
var songs = {
duration: 777,
name: 'Ember',
artist: 'Jimmy Smith',
};
var playlist = {
songs: [
{
id: 1,
duration: 777,
name: 'Ember',
artist: 'Jimmy Smith',
},
{
id: 2,
duration: 888,
name: 'jQuery',
artist: 'Hyper Cat',
}
]
};
</script>
Unfortunately this link (http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/) is a little confusing. They are assuming that a model relationship is there, but they are not showing it.
They are assuming that there is something like this :
App.Playlist = DS.Model.extend({
name : DS.attr('string'),
songs : DS.hasMany('song',{async:true})
});
App.Song = DS.Model.extend({
name : DS.attr('string')
});
And then generally what you'd want to do is to pull the collection from the model in setupController, and then set that as content on a nested controller that has been needed by the main controller.
App.PlaylistRoute = Ember.Route.extend({
setupController : function(controller,model){
this._super(controller,model);
this.controllerFor('songs').set('content',model.get('songs'));
}
});
App.PlaylistController = Ember.ObjectController.extend({
needs : ['songs']
});
And then since you're using ArrayController for the collection, you have built in sorting if you define the sortProperties and sortAscending properties.
App.SongsController = Ember.ArrayController.extend({
sortProperties : ['name'],
sortAscending : true
});
Here's a JSBin showing the general idea, using the FixtureAdapter.
http://jsbin.com/ucanam/1073/edit

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.

{{action}} link with transitionTo using relationship id

Given a view with a context like { id: 1, form_id: 5}, I want to create an {{action}} link to the form using the form_id.
My view code looks like:
<script type="text/x-handlebars" data-template-name="group">
{{action showForm form_id href=true}}
</script>
And the action in my router looks like:
showForm: function(router, event) {
var form_id = event.context;
router.transitionTo('root.form', { id: form_id });
},
I get an error that reads:
Uncaught Error: assertion failed: You must specify a target state for event 'showForm' in order to link to it in the current state 'root.index'.
I'm guessing that the problem is with the way I'm setting up the context for transitionTo, but I haven't been able to figure out the correct solution.
Here is the full code to reproduce the problem:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="group">
{{action showForm form_id href=true}}
</script>
MyApp = Ember.Application.create({
autoinit: false
});
MyApp.router = Ember.Router.create({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
// Throws error:
// You must specify a target state for event 'showForm' in
// order to link to it in the current state 'root.index'
//
showForm: function(router, event) {
var form_id = event.context;
router.transitionTo('root.form', { id: form_id });
},
// Won't work because form deserialize finds id, not form_id
//showForm: Em.Route.transitionTo('root.form'),
// This won't work either
// showForm: Em.Route.transitionTo('root.form', { id: this.form_id }),
connectOutlets: function( router, context ){
var group = Em.Object.create({ id:1, form_id: 5 });
router.get( 'applicationController' ).connectOutlet( 'group', group );
}
}),
form: Ember.Route.extend({
route: '/form/:id',
serialize: function( router, context ){
return { id: context.id }
},
deserialize: function( router, context ){
var form = Em.Object.create({ id: 5, name: 'my form' });
return MyApp.Form.find( context.id );
},
connectOutlets: function( router, context ){
// left out for fiddle example
}
})
})
});
MyApp.ApplicationController = Ember.Controller.extend({});
MyApp.GroupController = Em.ObjectController.extend({});
MyApp.GroupView = Em.View.extend({ templateName: 'group' });
MyApp.initialize(MyApp.router);​
And the cooresponding fiddle:
http://jsfiddle.net/jefflab/LJGCz/
I was able to come up with an ugly solution to this problem using a computed property as the context of my action. The key snippets are:
<script type="text/x-handlebars" data-template-name="group">
<a {{action showForm view.formHash href=true}}>go to form</a>
</script>
MyApp.GroupView = Em.View.extend({
templateName: 'group',
formHash: function() {
return { id: this.get('controller').get('form_id') };
}.property('form_id')
});
And the working fiddle is here:
http://jsfiddle.net/jefflab/pGv8u/
However, after talking to Tom Dale, it is clear that the "right way" to solve to solve this problem is by using materialized objects instead of id values. If you are using Ember data, this is a great use case for the "sideloading" belongsTo feature.