How to get model properties - ember.js

I have model:
App.Item = DS.Model.extend({
itemId: DS.attr('string'),
itemName: DS.attr('string'),
itemType: DS.attr('string'),
});
I successfully create some items from JSON. I can put them to page by {{#each items}}{{ itemName}}{{/each}}. But I don't know, how to get itemName in javascript.
I tried this:
var item = App.Item.find(1);
console.log(item.itemName);
--> undefined
I can't find anything useful from emberjs and ember-data docs. Can anyone help me?
Thanks

I tried this:
var item = App.Item.find(1);
console.log(item.itemName);
--> undefined
This is normal because the call to .find(1); is asyncronous and returns a promise and not the item you are expecting.
Therefore you should try:
App.Item.find(1).then(function(result) {
console.log(record.get('itemName'));
});
It also depends from where you are doing App.Item.find() if it's from inside a route you should wait until the afterModel hook is called to access your items:
App.FooRoute = Ember.Route.extend({
model: function() {
return App.Item.find(1);
},
afterModel: function(record) {
console.log(record.get('itemName'));
}
});
Also be aware that if you where calling find() without parameter then you will receive a RecordArray which you need to loop over to get access to your items. Also worth mentioning is that in ember you should always use .get() and .set() instead of the vanilla dot-notation otherwise you hijack the binding mecanism resulting in no updates in your view etc.
Note, if you are using the latest ember.js release (1.0.0) then the call to .find() should be made somewhat different. But that's not clear from your question.
Hope it helps.

Related

Upgrading to Ember-Data 2.14 broke computed filter on hasMany

I have tried reproducing this problem in a twiddle but unfortunately the twiddle always succeeds, but I'm hoping the error might sound familiar to someone and they can point me in the right direction.
Here's the twiddle:
https://ember-twiddle.com/9ee6f28479449b7b1859e4c090490775?openFiles=routes.parent.js%2C
All the names have been changed to make our data security folks feel better:
The basics:
I have a model 'parent' with an async 'hasMany' relation to 'child'.
children: hasMany('child', {async: true}),
I then have a computed property bound to the hasMany:
sons: Ember.computed.filterBy('children', 'type', 'son'),
I then alias firstObject to get the first item in the 'sons' array:
firstSon: Ember.computed.alias('sons.firstObject')
In the parent route.setupController, I create a new 'son' object:
setupController: function(controller, model) {
this._super(...arguments);
var toy = this.get('store').createRecord('toy', {
type: 'latte'
});
this.get('store').createRecord('child',
{parent: model, type: 'son', toy: toy,
name: 'Tyler'});
}
With Ember 2.14 and Ember-Data 2.13, or any lower combination of matching versions, this works and my template can refer to parent.firstSon with no problems.
As soon as I upgrade Ember-Data to 2.14, when I reach my desired route, parent.children has the correct child but the sons: filterBy is empty and therefore the alias to sons.firstObject is also empty. (as validated through Ember inspector)
Reminder: the twiddle I linked does actually work, it's the more complicated version in my app that fails.
The simple act of upgrading breaks it - no other code changes occur.
I've seen some similar issues in ember-data github:
This issue: https://github.com/emberjs/data/issues/4974 is similar but it seems to start at 2.12 and seems tied to the newish 'hasMany' call that avoids loading data. I'm simply binding to the actual async hasMany
https://github.com/emberjs/ember.js/issues/16258 references a similar problem, but one that only starts with Ember 3.0.0 and isn't present in 2.18 - My problem happens from 2.14.1 through ~2.18
Any thoughts on what might have broken?
Looks like this was another issue with lazy hasMany creation. I called in the afterModel I did:
afterModel: function(model) {
var parentPromise = this._super(...arguments);
var loadChildren = () => {
return model.get('children').then(children => {
this.set('children', children);
});
};
return (parentPromise && parentPromise.then) ? parentPromise.then(loadChildren) : loadChildren();
},
And changed the setupController to be:
setupController: function(controller, model) {
this._super(controller, model);
var hasSons = model.get('sons').length > 0;
if(!hasSons) {
var toy = this.get('store').createRecord('toy', {...etc});
var source = this.get('store').createRecord('child',
{parent: model, type: 'son', toy: toy});
this.get('children').removeObject(son).pushObject(son);
}
},
And now I have the old functionality back
Note that:
this.get('children').removeObject(son).pushObject(son);
was recommended from https://github.com/emberjs/data/issues/4974
and I did need both removeObject and pushObject to get it to work

ember.js cannot set property 'store' of undefined

I have been trying to set up an Ember.js application together with a RESTful API i have created in Laravel.
I have encountered a problem trying to get the data trough the store, and depending on my implementation, I get different errors, but never any working implementations.
The ember.js guide have one example, other places have other examples, and most information I find is outdated.
Here's my current code:
App = Ember.Application.create();
App.Router.map(function() {
this.resource("world", function() {
this.resource("planets");
});
});
App.PlanetsRoute = Ember.Route.extend({
model: function() {
return this.store.find('planet');
}
});
App.Planet = DS.Model.extend({
id: DS.attr('number'),
name: DS.attr('string'),
subjectId: DS.attr('number')
});
And when I try to click the link for planets, thats when the error occurs, and I get the following error right now:
Error while loading route: TypeError {} ember-1.0.0.js:394
Uncaught TypeError: Cannot set property 'store' of undefined emberdata.js:15
No request is sent for /planets at all. I had it working with a $.getJSON, but I wanted to try to implement the default ember-data RESTAdapter.
For reference, these are some of the implementations i've tried:
var store = this.get('store'); // or just this.get('store').find('planet')
return store.find('planet', 1) // (or findAl()) of store.findAll('planet');
App.store = DS.Store.create();
I also tried DS.Store.all('planet') as I found it in the ember.js api, but seemed like I ended up even further away from a solution.
Most other implementations give me an error telling me there is no such method find or findAll.
EDIT (Solution)
After alot of back and forward, I managed to make it work.
I'm not sure exactly which step fixed it, but I included the newest versions available from the web (Instead of locally), and the sourcecode now looks like this:
window.App = Ember.Application.create();
App.Router.map(function() {
this.resource("world", function() {
this.resource("planets");
});
});
App.PlanetsRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('planet');
}
});
App.Planet = DS.Model.extend({
name: DS.attr(),
subjectId: DS.attr()
});
The error you had is probably due to the fact that you added a "s" plural of your objects.
i.e. if you use
App.Planets = DS.Model.extend({
})
you would get that error.

Ember todos: an Ember.CollectionView's content must implement Ember.Array

I'm trying to get my head around Ember and going through the todos tutorial. I get stuck on the displaying-model-data step here
http://emberjs.com/guides/getting-started/displaying-model-data/
here's the javascript i copied and pasted from the tutorial:
window.Todos = Ember.Application.create();
Todos.Router.map(function () {
this.resource('todos', { path: '/' });
});
Todos.TodosRoute = Ember.Route.extend({
model: function () {
return Todos.Todo.find();
}
});
Todos.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
});
Todos.Todo = DS.Model.extend({
title: DS.attr('string'),
isCompleted: DS.attr('boolean')
});
Todos.Todo.FIXTURES = [
{
id: 1,
title: 'Learn Ember.js',
isCompleted: true
},
{
id: 2,
title: '...',
isCompleted: false
},
{
id: 3,
title: 'Profit!',
isCompleted: false
}
];
Then here's my handlebars template:
...
{{#each controller}}
<li>
<input type="checkbox" class="toggle">
<label>{{title}}</label><button class="destroy"></button>
</li>
{{/each}}
And yet I get this error
Uncaught Error: assertion failed: an Ember.CollectionView's content must implement Ember.Array. You passed <(generated todos controller):ember257>
It looks to me like whatever default controller object Ember generates should be of type Ember.Array but it is not happening for some reason. I am wondering if it is a problem with ember-data?
I am using all the files from the starter kit which are
ember 1.0.0 rc5
handlebars 1.0.0 rc4
jquery 1.9.1
and ember-data, the only versioning indication i can tell is from a comment
// Last commit: 3981a7c (2013-05-28 05:00:14 -0700)
Is there a dependency problem someone knows about or did I do something wrong?
I wouldn't say its a problem with ember data, since that module is responsible only for talking to the api and giving you clever model objects.
You were right in saying ember is generating the wrong type of controller. By default Ember will probably generate a Controller, when what you need is an ArrayController. To get around the issue, simply create an empty controller like this
Todo.TodosController = Em.ArrayController.extend({});
The guide does say that ember creates an ArrayController, but perhaps it doesn't anymore!? let me know if it works by explicitly creating an arraycontroller. If it does we can let the ember team know.
I had this exact same issue today walking through the Getting Started Guide but it appeared to be due to a typo.
According to the documentation, the generated controller is supposed to be of type ArrayController. I dug into the Ember source and found the Ember.generateController method that generates the controller depending on the context. I set a break point and found that when Ember was trying to create a controller for the "Todos" route, the context was undefined, so the basic controller was generated.
Working backward from there, I set a breakpoint on the model function of my router to see what it was returning but found it was not being called at all. At this point, I began to get suspicious that I had done something wrong. And that is when I noticed that I had named the TodosRoute as TodosRouter (as you have in your original question). Changing the name to TodosRoute correctly called my model function and everything worked as expected. It was not necessary to include the line that explicitly created the TodosController as an ArrayController.
While it appears you had it correct in your question, I wanted to post this here in case someone else has the same issue.
Adding the line Gevious suggested corrected this issue for me. For clarification my router.js file now looks like this:
Todos.Router.map(function(){
this.resource('todos', {path: '/'});
});
Todos.TodosRoute = Ember.Route.extend({
model: function () {
return Todos.Todo.find();
}
});
Todos.TodosController = Em.ArrayController.extend({});

Ember.js sorting and filtering children of a hasMany relationship in parent route

Update #2
I found that when I refactored the filtering logic to take place in a compound computed property within the PostController instead of within individual routes, I was able to get it working. The solution was ultimately dependent upon a single dynamic variable set by the specific #linkTo route action that triggered filtering changes within a PostController computed property. I have a lot of work to catch up on so I can't post the solution to this specific question now, but when I can I will detail an explanation of the solution below. For now I have marked #twinturbo's answer as correct for the partial but incredibly helpful guidance he gave below. Thanks again man!! Much appreciated!!
Update #1
The latest fiddle is at http://jsfiddle.net/aZNRu/14/ with #twinturbo's help, sorting the "rank" attribute of Comments in its Post parent controller is working, along with basic filtering. Still having the problem of not getting auto updating views when in a filtered route and a new comment is created.
Original Question
I see that there is talk of combining the sortable mixin with filtering functionality, but for now, as you can see in my jsfiddle example, I'm having issues with both sorting and filtering:
1) I can't figure out how to sort by a specific child attribute in the controller of its parent. If we have:
App.Post = DS.Model.extend({
title: DS.attr('string'),
post: DS.attr('string'),
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.extend({
post: DS.belongsTo('App.Post'),
description: DS.attr('string'),
isActive: DS.attr('boolean'),
rank: DS.attr('number')
});
App.Router.map(function() {
this.resource("posts", { path: "/" }, function() {
this.resource('post', { path: ':post_id' }, function() {
this.route('active');
this.route('inactive');
});
});
});
I want to be able to sort each post's comments in ascending order by it's "rank" attribute. I want to do something like:
App.PostController = Ember.ObjectController.extend({
sortProperties: ['comments.rank']
but for one, I think sortProperties only works on arrayControllers, and I don't think it can work more than one level deep. How could I achieve this?
2) The second problem is not getting auto-updating views when in a filtered route. For example, if you view the jsfiddle and go into the active filtered route by clicking "Active Comments" you get a nice filtering effect on the current data. But if you remain in the active route and create a new record that is active by clicking "Add New Comment," the record does not automatically render under "Active," and only appears if you click on another route and then return to it.
Am I setting up the route filtering incorrectly in the route or referencing it wrong in the template?
App.PostActiveRoute = Ember.Route.extend({
setupController: function() {
var post = this.controllerFor('post').get('model'),
comments = post.get('comments');
var activeComments = comments.filter(function(comment) {
if (comment.get('isActive')) { return true; }
});
this.controllerFor('post').set('filteredComments', activeComments);
}
});
<ul>
{{#each comment in filteredComments}}
<li>{{comment.rank}} {{comment.description}} - isActive: {{comment.isActive}}</li>
{{/each}}
</ul>
Any insight you could give on these issues would be greatly appreciated!
but for one, I think sortProperties only works on arrayControllers, and I don't think it can work more than one level deep. How could I achieve this?
You are correct that sortProperties only works on Ember.ArrayController.
You really don't need to do anything fancy to achieve this. Simply wrap the comments array in a new ArrayProxy that includes the sortable mixin. Then you can sort the comments. Then you don't need a nest property because you're sorting an array of comments.
Please don't extend DS.ManyArray. There is no need for that.
As for sorting and filtering, you need to use composition here. That means creating something like filteredContent and sortedContent. Then you can have sortedContent use filteredContent.
Update:
PostController = Ember.ObjectController.extend({
comments: (function() {
return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
sortProperties: ['rank'],
content: this.get('content.comments')
});
}).property('content.comments')
This can be done with a computed property macro, too.
PostController = Ember.ObjectController.extend({
commentSortProperties: ['rank:desc', 'createdAt:asc'],
comments: Em.computed.sort('model.comments', 'commentSortProperties')
});
Take a look at the code here.
Perhaps you can make your many-arrays sortable by extending the store and add the sortable mixin in the createManyArray method? (I did not test if it works)
App.store = DS.Store.create({
createManyArray: function (type, clientIds) {
var array = DS.ManyArray.create(Ember.SortableMixin, { type: type, content: clientIds, store: this });
clientIds.forEach(function (clientId) {
var recordArrays = this.recordArraysForClientId(clientId);
recordArrays.add(array);
}, this);
return array;
}
});
then you can bind to the arrangedContent of the array
{#each comment in filteredComments.arrangedContent}}

How to change DS.Model url in ember-data?

I am new(to ember) and trying to build a search centric Ember App w/ Ember-data also. I wanted to change the url on the fly(based on search string) and the data should change automatically(on the fly). How to do it?
This is my not working code:
Emapp.Data = DS.Model.extend({
first_name: DS.attr('string')
}).reopenClass({
url: Emapp.MyURL.get('url')
});
Emapp.MyURL = Em.Object.create({
urlParam: 'John',
url: function()
{
return 'emb/data.php?id=%#'.fmt(this.get('urlParam'));
}.property('urlParam')
});
When I execute. emapp.MyURL.set('urlParam', 'Adams'). I can inspect and see the url changed to 'Adams'. But data is not fetched again.
Edit: emapp -> Emapp (pointed out by rudi-angela)
As you have made the 'url' property a computed property, Ember takes care of updating this value when the urlParam changes. That is all you have instructed Ember to do here (and apparently it is doing it properly).
But I reckon what you want here is any change in the 'urlParam' property to trigger a fetch action. In that case a solution would be to create a separate object that observes the urlParam and will take action when the 'urlParam' value changes. Something along these lines:
emapp.watcher = Ember.Object.create({
valueBinding: "emapp.MyURL.urlParam",
observer: function() {
console.log("urlParam has changed:");
// perform your fetch here
}.observes("value"),
});
Note: I thought there was a requirement for the namespace to be capitalised (rather Emapp instead of emapp).