ComputedProperty doesn't get updated - ember.js

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);

Related

Updated : FirstObject is undefined emberjs array

I need to get firstObject of my carousel and set it as active
this is how I am setting carousel property
JSBIN for code
App.ApplicationController = Ember.ArrayController.extend({
content: [],
carouselData: function () {
var categories = this.get('content');
var products = Em.A([]);
categories.forEach(function (category) {
category.get('items').then(function (data) {
data.forEach(function (product) {
products.addObject(product);
});
});
});
console.log(products);
console.log(products.get('firstObject'));
return products;
}.property('content')
});
Update
#ppcano Thanks for the explanation :). I got what you are asking me to do. return model only when hasMany has fulfilled. and then with computed property save them in carouselData property. but may be I am missing something in implementation the cdata.get('firstObject') returns a promise updated jsbin UPDATED JSBIN in App.Caroure
update 2
SOLVED enter link description here
Your problem is that the computed property does not work within async execution.
category.get('items').then(function (data)
The products variable is returned before any data can be pushed into products, because the items must be requested.
You could solve it when you ensure that items are loaded when the property is computed. You could do it in your route model as:
model: function(){
return this.store.find('facture').then(function(factures){
var productPromises = factures.getEach('items');
return Ember.RSVP.all(productPromises).then(function(products) {
return factures;
});
});
}
Then, you could define your CP as:
carouselData: function(){
var result = Em.A([]);
this.get('content').getEach('items').forEach(function(items){
result.pushObjects(items.toArray());
});
return result;
}.property('content.#each.items.#each')

Mutual Exclusion in an Ember ArrayController

I have a list of items in an array controller. Clicking on an item makes it "active", but I’d like only one item to be active at any a time (akin to radio buttons).
I have this working by storing the active item in a computed property, then toggling its active state in an action on the array controller, see: http://emberjs.jsbin.com/wavay/2/edit
However, this doesn’t handle the case where an item is made active by some other means i.e. not through the action.
I have experimented with observing the isActive change (using .observesBefore('#each.isActive')), and flipping the state of the activeItem, but of course, this approach causes an infinite loop.
Is there a better way?
This can be solved using a combination of Ember.reduceComputed and observers.
The removedItem and addedItem callbacks in Ember.reduceComputed are given access to the object which has changed, as well as instanceMeta, which can be used to store the “active” item:
App.IndexController = Ember.ArrayController.extend({
itemController: 'item',
activeItem: Ember.reduceComputed('#this.#each.isActive', {
initialValue: null,
removedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
if (item.get('isActive')) {
var previousActiveItem = instanceMeta.activeItem;
if (previousActiveItem) previousActiveItem.set('isActive', false);
return instanceMeta.activeItem = item;
}
return instanceMeta.activeItem = null;
},
addedItem: function (accumulatedValue, item, changeMeta, instanceMeta) {
return instanceMeta.activeItem;
}
})
…
However if activeItem is not accessed anywhere, removedItem and addedItem will never be called, and therefore items will remain active until they are manually toggled. To fix this, an observer can be set up to call this.get('activeItem') whenever an isActive property is changed:
setActiveItem: function () {
this.get('activeItem');
}.observes('#each.isActive')
See the updated jsbin: http://emberjs.jsbin.com/wavay/3/edit?js,output
Related: David Hamilton’s presentation on Array Computing Properties.
A possible solution based on your toggleActive implementation is available here.
This solution works if the "active" flag is updated only with the toggleActive controller method. So far the controller represents the state, it makes sense that provides the api to update its data correctly.
App.IndexController = Ember.ArrayController.extend({
itemController: 'item',
activeItem: function() {
return this.findBy('isActive');
}.property('#each.isActive'),
toggleActiveModel: function(model) {
var controller = this.findBy('model', model);
this._toggleActive(controller);
},
_toggleActive: function(controller) {
var previouslyActive = this.get('activeItem');
if(previouslyActive && previouslyActive !== controller) {
previouslyActive.set('isActive', false);
}
controller.set('isActive', !controller.get('isActive'));
},
actions: {
toggleActive: function(controller) {
this._toggleActive(controller);
},
toggle: function(modelValue) {
this.toggleActiveModel(modelValue);
}
}
});

Do something when Ember component is instantiated?

I call a component like this:
{{Gd-text-input label="Specify" name="Specify" key="entry.810220554" triggerKey="tada" hideIf="Client"}}
I would like to run some javascript-code that sets an additional property to this component.
What I'm trying to run is something like this
//Convert string ot array.
GdRadioInput = Em.Component.extend({
init: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}
});
But it doesn't run. If someone could just provide a sample that console.logs a the value of a property of a component whenever that component is created, that would be very helpful.
You can run this code in the init method
init:function(){
this._super();
hideIf = this.get('hideIf');
key = this.get('key')
if(hideIf === key){
this.set('class', 'hide');
}
}
Good luck
PD: now this method is private: http://emberjs.com/api/classes/Ember.Component.html#method_init
I know this is an old question, but I wanted to update it with the new way to do things. Instead of overriding the init function, you can now cause a function to run on initialization with .on('init'). Example:
GdRadioInput = Em.Component.extend({
setupFunc: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}.on('init')
});
A follow-up: Just in case you are depending on a fully loaded DOM tree, you can use .on('didInsertElement') instead of on('init'):
GdRadioInput = Em.Component.extend({
setupFunc: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}.on('didInsertElement')
});
That event is fired when the view's element has been inserted into the DOM: http://emberjs.com/api/classes/Ember.Component.html#event_didInsertElement

How to make a computed property that depends on a global class attribute?

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')
});

Is it possible to observe arrays within arrays using #each?

I have a controller that contains an array of "things". Within each of these things is an array of "subthings". I'd like to create a computed property that contains all subthings in all things (a flattened array of subthings).
My computed property depends on things.#each.subthings.length. I find that if I set the subthings property of a thing, my computed property is updated. However if I call pushObjects to add new data to my existing subthings array, my computed property does not update.
I've created a jsfiddle to demonstrate. The code is as follows:
App = Em.Application.create({});
App.controller = Em.Object.create({
things: [
Em.Object.create({subthings:[]}),
Em.Object.create({subthings:[]}),
Em.Object.create({subthings:[]})
],
allSubThings : function() {
var things = this.get('things');
var results = [];
things.forEach( function(thing) {
results.pushObjects( thing.get('subthings') );
});
return results;
}.property('things.#each.subthings.length').cacheable()
});
setTimeout(function() {
var things = App.controller.get('things');
// This works:
things.objectAt(0).set('subthings',[1,2,3]);
}, 1000);
setTimeout(function() {
var things = App.controller.get('things');
// This does not:
things.objectAt(1).get('subthings').pushObjects([1,2,3]);
}, 2000);
​
Thanks!
Add another #each to the property list .property('things.#each.subthings.#each.length')
http://jsfiddle.net/tomwhatmore/pDQeT/3/