Ember.Binding.transform has been removed or replaced in the latest ember. I have a number of bindings that take this form:
valueBinding: Ember.Binding.transform({
to: function(value, binding) {
//return something
},
from: function(value, binding) {
//return something
}
}).from('parentView.content.finishBy'),
Can anyone advise me how I should update my code to the new regime?
You can add your computed property definition to Ember.computed, see
http://jsbin.com/awufuv/edit#source:
Ember.computed.finishBy = function() {
return Ember.computed('parentView.content.finishBy', function(key) {
var finishBy = Ember.getPath(this, 'parentView.content.finishBy');
return finishBy === 'now';
}).cacheable();
};
You can then use it like this:
App.obj = Ember.Object.create({
parentView: {
content: {
finishBy: 'now'
}
},
finishProp: Ember.computed.finishBy()
});
See more examples in https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/computed.js#L417-434
Just to be clear: you're not forced to add your custom computed property to Ember.computed. You can basically declare the function anywhere in your code. Now that I think about it, you might want to add your helper to your applications namespace instead of adding / poluting it to Ember. But that's just an issue of personal preference.
There's a pretty good discussion on the original commit on how to update your code.
However, in your case, I think you could just use a computed property:
value: function(key, value){
if(arguments.length===1){
//return something
} else{
//set and return something
}
}).property('parentView.content.finishBy')
Related
I have code for generate checkboxes list:
accountsCheckboxes: Ember.computed('accountsCheckboxes.#each', function(){
return this.model.accounts.map(row => {
return {
label: row.get('name'),
value: row.get('id')
};
})
}),
but after modify accounts collection, add or remove, this computed property doesnt refresh. I tried find how to do it with events, or how to observe store collection, but without success.
I modyfy this model collection in others controllers.
Its a little confusing what you're trying to do by observing the same property you're defining:
// accountsCheckboxes observes accountsCheckboxes?
accountsCheckboxes: Ember.computed('accountsCheckboxes.#each', ...)
This won't work and will probably result in an infinite chain of lookups.
Did you mean to observe model.accounts instead? If so, this is what you could've done:
accountsCheckboxes: Ember.computed('model.accounts.#each.name', function() {
return this.get('model.accounts').map(row => {
return {
label: row.get('name'),
value: row.get('id')
};
})
});
Note that you must call this.get('model'), not this.model to make sure you always get the proper data.
Alternatively, you might use Ember.computed.map:
accountsCheckboxes: Ember.computed.map('model.accounts.#each.name', function(row) {
return {
label: row.get('name'),
value: row.get('id')
};
});
I wanna create a property that depends on a global attribute:
App.Test= Em.Object.extend();
App.Test.reopenClass({ all: Em.A() });
App.Other = Em.object.extend({
stuff: function() {
return "calculated stuff from this.get('foo') and App.Test.all";
}.property('foo', 'App.Test.all.#each.bar')
});
As a workarround I could create a observer and always set a dummy property with a new random value to trigger the property change, but is there a better way to do this?
I need this for some caching. I've a really crazy, and single threaded backend. So I write my own Model classes. So I try to reimplement a bit of the logic in the client for a better caching.
Ive an Item class (App.Item) and another class where each instance has a calculated reduced list of Items.
App.Model = Em.Object.extend({
});
App.Model.reopenClass({
all: Em.A(),
load: function(hash) {
return this.get('all').pushObject(this.create(hash));
}
});
App.Item = App.Model.extend({
});
App.List = App.Model.extend({
loadedInitItems: false,
items: function() {
if(!this.get('loadedInitItems')) { this.set('loadedInitItems', true); Backend.call('thelist', function(item) { App.Item.load(this); }); }
return App.Item.all.filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
});
}.property('someprops', 'App.Item.all.#each.foo')
});
Backend.call represents some AJAX stuff
the point is, that now any item could change so that the filter will return something diffrent. And there are other places om the application, where the user can add Items. I dont want to call the backend again, because its very slow! And I know that the backend will not modify the list! So I wanna cache it.
This is just a reduced example of my use case, but I think've described the point. In reallity I have this dozend of times, with over 25000 objects.
have you tried adding 'Binding' to your property and then the value you want to bind to ?, something like this:
App.PostsController = Em.ArrayController.extend({
nameOfYourVariableBinding: "App.SomeObject.propertyYouWantToBindTo"
})
It looks like the problem is the double uppercase letter. So App.test ist working, but not App.Foo.test.
But I was able to find a Solution with the ArrayProxy.
Its about this:
App.Model = Em.Object.extend({
});
App.Model.reopenClass({
all: Em.A(),
load: function(hash) {
return this.get('all').pushObject(this.create(hash));
}
});
App.Item = App.Model.extend({
});
App.List = App.Model.extend({
loadedInitItems: false,
items: function() {
var self = this;
if(!this.get('loadedInitItems')) {
this.set('loadedInitItems', true);
Backend.call('thelist', function(item) {
App.Item.load(this);
});
}
return Em.ArrayProxy.extend({
content: App.Item.all,
arrangedContent: function() {
return this.get('content').filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
// use self.get('someprops')
})
}.property('content.#each.foo')
});
}.property('someprops')
items: function() {
if(!this.get('loadedInitItems')) { this.set('loadedInitItems', true); Backend.call('thelist', function(item) { App.Item.load(this); }); }
return App.Item.all.filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
});
}.property('someprops', 'App.Item.all.#each.foo')
});
So I have what I think is a simple Ember Object.
App.Playlist = Ember.Model.extend({
_clips: [],
clips: function() {
var self = this;
if(this.get('clipIds')) {
this.get('clipIds').forEach(function(id) {
self.get('_clips').addObject({});
}
}
}.property('clipIds')
});
The problem is that the clips computed property gets called infinitely until it raises an exception Uncaught RangeError: Maximum call stack size exceeded
Ray, this should be defined differently. Computed properties are defined like functions and Ember will handle calling your function when it observes a change to whatever dependencies you define.
App.Playlist = Ember.Model.extend({
myComputed: function () {
return this.get('clipIds').map(function (id) {
return Ember.Clip.create({id: id});
});
}.property('clipIds.#each'),
});
this code would watch some property called "clipIds" (whatever that is) and would return a list of Ember.Clip objects based on that array of clipIds.
So here is how I ended up fixing this for now. Still not sure why the computed property gets called repeatedly.
App.Playlist = Ember.Model.extend({
clips: [],
loadClips: function() {
var self = this;
if(this.get('clipIds')) {
this.get('clipIds').forEach(function(id) {
self.get('clips').addObject({});
}
}
}.observes('clipIds.#each')
});
I have this test application which should print "filtered" and "changed" each time the applications view is clicked, because a computed property is called. However the property binding is only triggered when updating the property with an empty array:
window.App = Ember.Application.create({
Settings: Ember.Object.create({
filter: []
}),
ApplicationView: Ember.View.extend({
click: function(event) {
filter = App.get('Settings.filter');
console.dir(filter);
if (App.get('Room.filtered')) {
filter = filter.filter(function(item) {
return item != App.get('Room.name');
});
} else {
filter.push(App.get('Room.name'));
}
App.set('Settings.filter', filter);
}
})
});
Room = Ember.Object.extend({
name: "test",
_filterer: function() {
console.dir("changed");
}.observes('filtered'),
filtered: function() {
console.dir("filtered");
filter = App.get('Settings.filter');
for (var i = 0; i < filter.length; i++) {
if (filter[i] == this.get('name')) return true;
}
return false;
}.property('App.Settings.filter', 'name').volatile()
});
App.Room = Room.create();
setTimeout(function() {App.set('Settings.filter', ['test']); }, 3000);
Here is the jsbin: http://jsbin.com/edolol/2/edit
Why is the property binding only triggered when setting the Setting to an empty array? And why does it trigger when doing it manually in the timeout?
Update
Here is a working jsbin: http://jsbin.com/edolol/11/edit
When you're going to add/remove items from an array, and not change the entire array, you need to inform the computed property to observe the items inside the array, not only the array itself:
The syntax is:
function() {
}.property('App.settings.filter.#each')
The reason it was working with setTimeout is because you were replacing the entire array instead of the items inside it.
I fixed your jsbin: http://jsbin.com/edolol/10/edit
I fixed some minor other stuff such as filter.push is now filter.pushObject (Using Ember's MutableArray).
And after changing the filter array (filter = filter.filter()) you need to set the new filter variable as the property: App.set('Settings.filter', filter);
The Problem is that I have used .push() to add to App.Settings.filter and .filter() to remove from it. The first approach does not create a new array, the latter does. Thats why removing from that array has worked, but not adding.
I assume that using Ember.ArrayProxy and an observer for .#each would have worked. But thats out of my knowledge. This little problem is solved by just creating a new array though.
filter = App.get('Settings.filter').slice(0);
With Ember.js, I have the following scenario:
My Class has a property that require some manipulation at instanciation.
To achieve that, I'm calling a function that does the work from init. I also want to observe this propery change, so that if consumer will set new value at run time I will run my manipulation logic over the new value.
The problem is, that as part of the init flow, I'm setting myself the new value to the property after manipulation, and this invokes the trigger (as expected). I do not want this code to run twice.
Consider the following code. 'here' will be printed twice to the console.
var MyObj = Ember.Object.extend({
prop: null,
init: function init() {
this._super.apply(this, arguments);
this._applyProp();
},
_applyProp: function prop() {
console.log('here');
var prop = this.get('prop');
if (prop === 'Dan') {
prop = 'Hi' + prop;
}
this.set('prop', prop);
}.observes('prop')
});
MyObj.create({prop: 'Dan'});
Any advice will be really appreciated.
Having an observer that sets the property it is observing seems like a bad idea. Your particular example could be achieved using a computed property getter/setter:
var MyObj = Ember.Object.extend({
prop: function(key, value) {
if (value !== undefined) {
if (value == 'Dan') {
return 'Hi ' + value;
}
return value;
}
}.property(),
});
MyObj.create({prop: 'Dan'});
Would this be sufficient to cover your use cases?