Force ember route to request additional data before rendering - ember.js

I'm trying to get a nested route to make a request for additional data, update the record, and then render the view. See below:
// models
var attr = DS.attr,
hasMany = DS.hasMany,
belongsTo = DS.belongsTo
App.List = DS.Model.extend({
title: attr('string'),
links: hasMany('link')
})
App.Link = DS.Model.extend({
list: belongsTo('list'),
subtitle: attr('string'),
})
// JSON for index route
{
"list": [
{
"id": 532,
"title": "first list"
},
{
"id": 991,
"title": "second list"
},
{
"id": 382,
"title": "third list"
}
]
}
// JSON for list route - /user/532
{
"list":
{
"id": 532,
"title": "list numero uno",
"links" : [1, 2]
},
"link": [
{
"id": 1,
"subtitle": "this is a subtitle of the firsto listo!"
},
{
"id": 2,
"subtitle": "this is a subtitle of a second part in the list"
}
]
}
// routing
this.resource('user', function(){
this.route('list', {path: ':id'})
})
App.UserRoute = Ember.Route.extend({
model: function(){
return this.store.find('list')
}
})
App.UserListRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('list', params.id)
}
})
I want the index route to display a basic list with just the {{title}}, and clicking on one links it to UserListRoute which then makes another ajax call to update the record with the additional link data. I've tried adding:
afterModel: function(modal){
model.reload()
}
to the UserListRoute but it initially uses the index route model, and then it reloads with the new data. I'm trying to avoid this, and make it send an ajax request immediately and then render the content - so I don't want it to depend on the parent model/data.

create a different record type, and extend from the original, or just use a completely different model.
App.BasicList = DS.Model.extend({
foo: DS.attr()
});
App.FullList = App.BasicList.extend({
//foo
bar: DS.attr()
});
App.UserListRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('fullList', params.id)
}
})

I tried with kingpin2k's answer, and it worked on a page refresh but navigating from the user index to the nested still caused it to use the parent model. I figured out why it wasn't used the correct model. My links on the user index template were:
{{#link-to 'user.list' this}} {{title}} {{/link-to}}
So clicking on link correctly redirects me to /users/:id, but it passes on the data of the item selected. To remedy this I did:
{{#link-to `user.list` id}} {{title}} {{/link-to}}
So by changing this to id, when it transitions to the route, it uses the appropriate model specified in the route.

Related

Ember: accessing sideloaded data in setupController with ember-data

I'm using RESTAdapter and trying to figure out how to access sideloaded data.
A sample of the payload is:
{
"category": {
"categoryName": "test category",
"id": 6,
"products": [
4419,
502,
3992
]
},
"products": [{
"description": "Whatevs",
"id": 4419,
"name": "Product 1",
"price": 114.95,
"skuid": "S21046"
}, {
"description": "Whatevs",
"id": 502,
"name": "Product 2",
"price": 114.95,
"skuid": "SOLS2594"
}, {
"description": "Whatevs",
"id": 3992,
"name": "Product 3",
"price": 114.95,
"skuid": "S21015"
}]
}
I can see 'category' and 'product' data models (and data) in the ember inspector, so I know they are being loaded.
I can even access the products in the template model.products. BUT I can't access model.products in the route's setupController. The error I get is:
TypeError: Cannot read property '_relationships' of undefined
This is really perplexing me! My route model hook is:
model(params) {
return this.get('store').queryRecord('category', {
id: params.id
})
}
The setupController hook (that causes the error) is:
setupController(controller, model) {
controller.set('results', model.products);
}
The 'category' model:
export default DS.Model.extend({
products: hasMany('product'),
categoryName: attr('string')
});
The 'product' model:
export default DS.Model.extend({
name: attr('string'),
skuid: attr('string'),
price: attr('number'),
description: attr('string')
});
My template (which works, if I remove the 'setupController' hook from the route):
{{#each model.products as |product|}}
{{product.name}} {{product.skuid}}<br />
{{/each}}
I'd like to be able to access model.products from the route's setupController so I can call it something else. Any help appreciated.
Relationship returns Promises. so to get the result you need to use then. but accessing it in template will work because template is by default promise aware.
setupController(controller, model) {
//controller.set('results', model.products);
model.get('products').then((result) => {
controller.set('results',result);
});
}
Please give read on relationships as promises guide.

Why can't I display the second association in a nested each loop?

I have 2 models deliverHour and day. In an .hbs file, I have two nested each loops.
I am trying to display the delivery hours for each day. Apparently, once it reaches the second each loop, it doesnt render anything. Not even this line does not render!
Looking in Ember Inspector. I do not see any failed promises. The network tab doesn't even show Ember trying to attempt calling the delivery hour end point.
The Data tab shows delivery-hour (0), which means no delivery hours in the store, which is strange.
Since the relationship for the day model has DS.hasMany('deliveryHour', { async: true }) I would have expected Ember to do an asynchronous call to the delivery hour api resource. No?
What am I missing here?
// Route
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
var addressId = this.checkoutCart.get('addressId');
return Ember.RSVP.hash({
address: this.store.find('address', addressId),
delivery: this.store.createRecord('delivery'),
days: this.store.find('day')
});
},
// You can use model.delivery to get delivery, etc
// Since the model is a plain object you can just use setProperties
// http://stackoverflow.com/questions/16463958/how-to-use-multiple-models-with-a-single-route-in-emberjs-ember-data
setupController: function(controller, model) {
controller.setProperties(model);
},
});
// app/models/delivery-hour.js
import DS from 'ember-data';
export default DS.Model.extend({
day: DS.belongsTo('day', { async: true }),
from: DS.belongsTo('hour', { async: true }),
to: DS.belongsTo('hour', { async: true }),
status: DS.attr('string')
});
// app/models/day.js
import DS from 'ember-data';
export default DS.Model.extend({
deliveryHours: DS.hasMany('deliveryHour', { async: true }),
name: DS.attr('string'),
dayOfTheWeek: DS.attr('number')
});
// hbs
{{#each day in days}}
<li>
{{day.name}} // Returns something like 'Monday'
{{day.deliveryHours}} // Returns something like <(subclass of DS.PromiseArray):ember770>
{{day.deliveryHours.length}} // Returns 0
{{#each deliveryHour in day.deliveryHours}}
this line does not render!
{{deliveryHour.hour}} <----- ideally, this is what i want to see
{{/each}}
</li>
{{/each}}
{{day.deliveryHours.length}} returns 0. I wanted to verify this. I am using Rails as my API. In Rails console, when I do Day.first.delivery_hours.count, it returns 4. Strange that Ember returns 0. The Network tab, in Ember Inspector, does not show Ember trying to call the delivery hours API endpoint.
On the top right corner of the screenshot, I click on the > $E. Then I go to the console tab of Ember inspector and paste: $E.get('deliveryHours'). I get Object { content: Getter, toString: makeToString/<(), isFulfilled: true, 4 more… }.
In the console of Ember Inspector, I also tried:
>> $E.get('deliveryHours').then(function(hours) { console.log(hours.get('length'));});
Object { _id: 156, _label: undefined, _state: undefined, _result: undefined, _subscribers: Array[0] }
Update: My API response for days returns as:
{
"days":
[
{
"id": 1,
"name": "Monday",
"day_of_the_week": 1
},
{
"id": 2,
"name": "Tuesday",
"day_of_the_week": 2
},
{
"id": 3,
"name": "Wednesday",
"day_of_the_week": 3
},
{
"id": 4,
"name": "Thursday",
"day_of_the_week": 4
},
{
"id": 5,
"name": "Friday",
"day_of_the_week": 5
},
{
"id": 6,
"name": "Saturday",
"day_of_the_week": 6
},
{
"id": 7,
"name": "Sunday",
"day_of_the_week": 7
}
]
}
http://emberjs.com/guides/models/the-rest-adapter/#toc_relationships indicates that I should probably load the delivery_hours as an array of ID's. I've tried this too, same results..
That's because deliveryHours is an async property in your model, which is why {{day.deliverHours}} returns an unresolved promise, instead of the record array you were expecting.
You'll have to explicitly resolve it in an afterModel hook, like so:
// Route
import Ember from 'ember';
var RSVP = Ember.RSVP;
export default Ember.Route.extend({
// ...
afterModel: function(model) {
return RSVP.all(
model.days.map(function(day) {
return day.get('deliveryHours');
})
);
}
});

Not understanding fixtures in ember

I am trying to, for better lack of a term stub out my application's user model using fixtures. I have been following the docs on fixtures, as well as associated links for model development. The issue is that I keep running into cannot GET /user/Administrator I have used yeoman to generate a ember project, but lets walk through what I have:
Set up the app
var EW = window.EW = Ember.Application.create();
Setup the fixture (store.js)
EW.ApplicationAdapter = DS.FixtureAdapter;
/* I need a fixture. */
EW.User.FIXTURES = [
{
"user":{
"id":1,
"first_name":"Adam",
"email":"adamkylebalan#email.com",
"last_name":"Balan",
"user_name": "Administrator",
"role_names":[
"Administrator"
],
"blogs":[
{
"id":2,
"title":"Blog Title",
"posts":[
{
"id":1,
"title":"Something",
"content":"Some Content",
"tag_names":[
"Some Tag",
"Other Tag"
],
"category_names":[
"Some Category",
"Another Category"
],
"blog_id":2,
"comments":[
{
"id":1,
"post_id":1,
"author":"Adam",
"comment":"This is a valid long comment."
}
]
}
]
}
]
}
}
]
Model
var attr = DS.attr;
// Set up the user model and it's relationships.
EW.User = DS.Model.extend({
first_name: attr('string'),
last_name: attr('string'),
user_name: attr('string'),
blogs: DS.hasMany('blog'),
role: DS.hasMany('roles'),
// Create full names for every one.
fullName: function(){
return this.get('first_name') + ' ' + this.get('last_name');
}.property('first_name', 'last_name')
});
Router
EW.Router.map(function(){
this.resource('user', { path: '/user/:user_name' });
});
EW.UserRoute = Ember.Route.extend({
model: function(params){
return jQuery.getJSON("/user/" + params.user_name);
}
serialize: function(model){
return { user_name: model.get('user_name') }
}
});
As far as I can tell I have done everything correctly. throws the error:
Cannot GET /user/Administrator
and I am not sure if I failed to set something up properly. the console gives me no errors, the ember console wont load because of this error and the network tab is giving me a 404 for localhost:9000/user/Administrator
Ideas?
You’ll want to call return this.get('store').find('user', params.user_id) in your model hook:
EW.Router.map(function() {
this.resource('user', { path: '/users/:user_id' });
});
EW.UserRoute = Ember.Route.extend({
model: function(params) {
return this.get('store').find('user', params.user_id);
}
});
Note that it’s user_id rather than user_name. This is because the fixture adapter is hard coded to find records by their id rather than name. I’d suggest using the url-friendly form of the name as the ID.

Ember data relationship and fixtures

With ember data relationship can be {async: true} or {async: false}. How to create a model FIXTURES that mimic the behavior of an synced relashionship as described in the documentation :
var attr = DS.attr,
hasMany = DS.hasMany,
belongsTo = DS.belongsTo;
App.Post = DS.Model.extend({
title: attr(),
comments: hasMany('comment'),
user: belongsTo('user')
});
App.Comment = DS.Model.extend({
body: attr()
});
Ember Data expects that a GET request to /posts/1 would return the JSON in the following format:
{
"post": {
"id": 1,
"title": "Rails is omakase",
"comments": ["1", "2"],
"user" : "dhh"
},
"comments": [{
"id": "1",
"body": "Rails is unagi"
}, {
"id": "2",
"body": "Omakase O_o"
}]
}
You'll want to override the find method of the adapter. Here is the current implementation of the find method in the FixtureAdapter. You can see that it simply finds the record with the given ID, then returns it. You're going to want to modify that so that it side-loads the proper records. Something like this:
var json = {};
json[type.typeKey] = requestedRecord;
type.eachRelationship(function(name, meta) {
if (!meta.async) {
json[meta.type.typeKey.pluralize()] = [ /* put related records here */ ];
}
});
The syntax may not be perfect, but you should get the idea.

routes are not rendering results

I'm trying to figure out why my routes have stopped working. I recently upgraded to Ember JS RC 3 (from RC2) and after going through the changelog, I can't figure out why my routes stopped working. Maybe I changed something in trying to fix it; I don't know. But here's what I've got.
Here are my routes.
App.Router.map(function() {
this.route("site", { path: "/" });
this.route("about");
this.resource('posts', { path: '/posts' }, function() {
this.route('new');
this.route('show', { path: '/:post_id' });
this.route('edit', { path: '/:post_id/edit'})
});
this.route("map");
});
Here is my route handler...
App.PostsIndexRoute = Em.Route.extend({
model: function(){
return App.Post.find();
},
setupController: function(controller, model){
controller.set('content', model);
}
});
Here is my model
App.Post = DS.Model.extend({
body: DS.attr('string'),
headline: DS.attr('string'),
creator: DS.attr('string'),
created: DS.attr('date')
});
Here's the JSON being pulled over the network (I see it coming, just not being rendered -- the view is not rendered at all)
[
{
"creatorID": "512808071f7152f9b6000001",
"creator": "Aaron",
"headline": "second entry",
"body": "this is my second entry. ",
"updated": "2013-03-04T20:10:14.566Z",
"created": "2013-03-04T20:10:14.566Z",
"id": "5134ffa6095e930000000001"
},
{
"body": "this is my first entry. ",
"created": "2013-02-28T22:39:32.001Z",
"creator": "Aaron",
"creatorID": "512808071f7152f9b6000001",
"headline": "first entry",
"updated": "2013-03-01T18:13:35.934Z",
"id": "512fdca479d3420000000001"
}
]
Probably also important to note that I do NOT see any JS errors here, as I'm visiting localhost/posts
Thanks for your help!
EDIT: Here is the template I'm using to render the entries (jade based).
div {{content}}
{{#each post in content}}
div(class="post limit-width")
h3(class="headline") {{#linkTo 'posts.show' post}} {{ post.headline }} {{/linkTo}}
p {{{post.body}}}
h6
em Posted by {{post.creator}} on {{post.created}}
{{/each}}
note that the {{content}} tag is being used to determine if there's even an object present; there is, but its length seems to be zero. Which makes me think that no entries are being loaded via Ember Data, but I cannot figure out why.