I have a model in ember-data defined as:
App.Note = DS.Model.extend({
content: attribute('string'),
createdDate: attribute('string', {
defaultValue: function() {return new Date()}
}),
title: function() {
// do stuff
}.property('content', 'createdDate')
});
I notice that when I create a new object with:
this.store.createRecord('note');
The title property is not computed. I assumed that the default value would trigger the property to update, but it's not. How can I get a default value to also trigger a computed property to fire?
I believe the problem is that you are using 'content' as a property name. I would avoid using that word, as Ember tends to use it a lot itself and it can mess things up. Here is a jsbin of your code woriking: http://emberjs.jsbin.com/jebugofo/6/edit?html,css,js,output . Simply needed to get rid of that name for the property.
Related
I have 3 different fixture models, as shown below.
var Room = DS.Model.extend({
title: DS.attr('string'),
categories: DS.hasMany('Category', { async: true }),
isSelected: DS.attr('boolean')
});
var Category = DS.Model.extend({
title: DS.attr('string'),
room: DS.belongsTo('Room', {async: true }),
materials: DS.hasMany('Material', { async: true }),
isSelected: DS.attr('boolean')
});
var Material = DS.Model.extend({
title: DS.attr('string'),
category: DS.belongsTo('Category', {async: true} ),
isSelected: DS.attr('boolean')
});
I find when I try to view the contents inside the Materials model it is blank. In my controller I expose the materials by doing this:
currentMaterials: function() {
var room = this.filterBy('isSelected', true).get('firstObject');
var categories = room.get('categories');
var selectedCategory = categories.get('firstObject');
var material = selectedCategory.get('materials');
return material;
}.property('#each.isSelected')
However when I try to access currentMaterials the value is null. I am ONLY able to access its values if I first access the Rooms/Categories using a {{#each} loop. Oddly once I do the {{#each}} I am then able to access the values in currentMaterials.
Does anyone understand why?
It's due to fact of promises existance. Your categories relationship is async, which means that it's not present initially and ember-data should fetch it if needed. However, it takes time to fetch data, therefore ember-data returns a promise from this: var categories = room.get('categories'). After that promise, you first get firstObject from it, which does not exist for a promise (is null), and than you get materials relationship from that null. It simply is null.
However, ember templates are smart and if you put an each on them, they know that these relationships are needed and makes ember-data fetch these data.
What you can do? If you need this data to perform page-specific job, you should make sure that you have access to it before showing the page to the user - therefore in the model hook. You can use Ember.RSVP to make multiple fetch calls and set them up in the controller:
model: function() {
data =
room: store.find("room")
categories: store.find("category)
materials: store.find("material")
return Ember.RSVP.hash(data)
}
However, take notice that it will fetch all the materials, etc. If you need only the ones connected to your model, you should consider speeding up your data fetching using side loading. If you are using fixtures, it won't work.
Last that I can think of is making computed property a method that would fetch the data, but set them on other variable. You can use some kind of flag to inform the app when the data is ready:
currentMaterials: function() {
var room = this.filterBy('isSelected', true).get('firstObject');
room.get('categories').then(function(categories) {
return categories.get('firstObject').get('materials');
}).then(function(materials) {
// here you have your materials
// you can pass _this to that method and set these materials
// on some kind of controller property (e.g. materialsChosen)
// and use a flag like setting 'is Fetching' on the start of this
// computed property and setting down right here
});
}.property('#each.isSelected')
I have a computed property, which fetches an associated record and tries to print it. The first time I fetch the record, it's null. All subsequent accesses work correctly. It is set as 'async: true', but setting it as false doesn't change this behavior.
MyApp.ThingsController = Ember.ArrayController.extend({
myProperty: function() {
var content = this.get('content');
return content.filter(function(thing) {
console.log(thing.get('title')); // Since this is a direct attribute on the model, it prints fine.
var associatedThing = thing.get('associatedThing'), otherThings = [];
console.log(associatedThing.get('content')); // This is a hasMany attribute on the model, and is null the *first* time, but fine on subsequent accesses.
otherThings = associatedThing.get('content'); // Obviously doesn't work the first time either.
return thing.get('title') + otherThings[0].get('name'); // Or similar.
});
}.property('content.#each') // adding observers for content.associatedThing.#each does not seem to make any difference.
});
Models are like:
MyApp.Thing = DS.Model.extend({
title: DS.attr('string'),
associatedThings: DS.hasMany('associatedThing', { async: true })
});
MyApp.AssociatedThing = DS.Model.extend({
name: DS.attr('string')
});
Obviously, I cannot use promises here since I need to return a value from the function, so I cannot use a callback (since we're in a computed property.) How can I make this work the first time this associated record is accessed?
Edit: myProperty is a computed property on an ArrayController, and is used for showing or hiding Things
Actually, you can use a promise, just not in the way you're thinking. For hasMany relationships, Ember-Data returns a PromiseArray. That means that it returns a promise that will resolve to an array. But in the meantime, the proxy will actually respond to get requests that you make with undefined. Then, when the promise resolves, any observers are fired. So, if you have your property depend on the associatedThings property, it will update when the promise resolves. In other words, this will work as expected:
MyApp.Thing = DS.Model.extend({
title: DS.attr('string'),
associatedThings: DS.hasMany('associatedThing', { async: true }),
sum: function() {
var things = this.get('associatedThings');
return things.filter(function(thing) {
return shouldFilterThing(thing);
});
}.property('associatedThings.#each.size')
});
Also, please don't be bugged by the fact that this doesn't happen synchronously. Trying to change it from asynchronous to synchronous will just make your code that much more fragile. Let Ember do its job and handle all of the properties and bindings for you.
My solution to this was simply to access the associated data in the ArrayController's init method:
init: function() {
var content = this.get('content');
content.forEach(thing) {
// Prime the data.
var associatedThings = get('associatedThings');
});
}
This makes everything work as expected.
What is the best way to remember the property value of a controller? Take a look here:
jsbin example
I want to remember if an element was expanded, but only during the current session, I don't want to save this value into the datastore. The problem is, doing the following steps:
Expand "Artist"
Expand "Album"
Collapse "Artist"
Expand again "Artist"
"Album" is also collapsed! The value isExpanded from the "Album" was lost, seems that ember recreates the controller every time. Which would we a good solution to remember the value of isExpanded?
If you want the value to be persisted past the lifetime of the controller then you need to save it in a controller that will stay in scope as long as you want it to live (I'm not sure what your full intent is, but if it's really a lifetime of the page value you may want to store it on the application controller)
http://jsbin.com/osoFiZi/5/edit
App.AlbumController = Ember.ObjectController.extend({
needs: ['artist'],
actions: {
toggleExpanded: function() {
this.toggleProperty('controllers.artist.isAlbumsExpanded');
}
}
});
You can add property to model
App.Artist = DS.Model.extend({
isExpanded: false, // this property will not be included into PUT request when you will save record
name: DS.attr('string'),
albums: DS.hasMany('album', {async: true})
});
and toggle it in controller
actions: {
toggleExpanded: function() {
this.toggleProperty('model.isExpanded');
}
}
http://jsbin.com/OtEBoNO/2/edit
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).
I have an ember object and i'd like to know if it is in a dirty state.
var App.Post = Ember.Object.create({
title: "Test",
isDirty: false
});
App.Post.set("title", "Test2");
App.Post.get("isDirty") // Should === true
For the moment, I have tried overloading the set for the object
App.Post = Ember.Object.create({
set: function(path, value) {
this._super(path, value);
this._super("isDirty", true);
}
})
It works when I am calling directly myObject.set but it doesn't seem to use that set function when using embers binding. I added logs and this method isn't called by the regular emberjs bindings workflow.
Another thing I've tried is to add an observers to toggle the dirty flag.
App.Post = Ember.Object.create({
hasBeenModified: function() {
this.set("isDirty", true);
}.observes("title")
})
For a reason still unknown, when I use observes at the model level my bindings do not work anymore in the UI.
I believe you may also need to override setUnknownProperty. The UI is using Ember.set(object, key, value). If you look at the implementation
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/property_set.js#L60
It doesn't call your setter, but will call setUnknownProperty if it exists.
Actually, at
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/property_set.js#L52
It looks like they will call your setter if you have predefined the field in your App.Post class.