Trouble accessing controller's computed property from view - ember.js

My controller has a computed property:
App.IndexController = Ember.ArrayController.extend({
grandTotal: function () {
return this.getEach('total').reduce(function(accum, item) {
return accum + item;
}, 0);
}.property('#each.total'),
});
but I'm having trouble accessing it with my view. Here's my view:
App.SummaryView = Ember.View.extend({
templateName: 'summary',
companiesChanged: function() {
Ember.run.once(this, 'logCompanies');
}.observes('controller.#each'),
logCompanies: function() {
console.log(this.get('controller').get('model').get('length'));
console.log(this.get('controller').get('grandTotal'));
}
});
.get('length') returns correctly, so I know when this is called the models are loaded. But grandTotal is coming back as NaN, even though I know it's coded correctly since it's being rendered in the template. I need to access it within my view for additional reasons.
Any ideas?

Even though the controller's computed property changes with #each.total, the view only cares about the controller's property. Thus, the view was wrongly observing #each model, when it should have just been observing controller.grandTotal:
App.SummaryView = Ember.View.extend({
templateName: 'summary',
companiesChanged: function() {
Ember.run.once(this, 'logCompanies');
}.observes('controller.grandTotal'),
logCompanies: function() {
console.log(this.get('controller').get('model').get('length'));
console.log(this.get('controller').get('grandTotal'));
}
});

Related

Ember toggle element in an array

What is the Ember way to do the following?
App.IndexController = Ember.Controller.extend({
actions: {
change: function(){
var model = this.get('model');
model[0] = true;
this.set('model', model);
}
}
});
I want to toggle an element (index 0 in this example) in model.
Here is the jsbin: http://emberjs.jsbin.com/doyejipagu/1/edit. The change to the model is not being reflected.
The solution is to use replace to modify the array:
change: function(){
this.get('model').replace(0, 1, [true]);
}
See http://emberjs.com/api/classes/Ember.MutableArray.html#method_replace. The above means "starting at position 0, replace 1 element, with the single element true". replace notifies Ember that the array contents have changed, so it is reflected everywhere.
It would be nice if there were a replaceAt API, allowing us to just say model.replaceAt(0, true), but there's not. Of course, you could write your own:
Ember.MutableArray.reopen({
replaceAt: function(pos, val) {
return this.replace(pos, 1, [val]);
}
});
The problem with your code is that nothing alerts Ember to the fact that the internal values of model have changed. model[0] = true triggers nothing. Your this.set('model', model) does not change the value of the model property itself; so neither does it trigger any observers or bindings.
You could also create a new array (here using slice), which would work:
var model = this.get('model').slice();
model[0] = true;
this.set('model', model);
Now, Ember sees that model has changed, and does all its magic.
What you try to do is not possible. A model either has to be an object or an array of objects otherwise you cannot set properties on it.
So you could do for example:
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
Ember.Object.create({value: false}),
Ember.Object.create({value: true}),
Ember.Object.create({value: false})
];
}
});
App.IndexController = Ember.Controller.extend({
actions: {
change: function(){
this.get('model')[0].toggleProperty('value');
}
}
});

Ember.js Controller with computed property not being recomputed

I'm trying to add permissions to groups, and I have a drag and drop set up so that a user can pull the unselected permissions over to selected, or vice versa. Unselected permissions are computed via removing the selected permissions from all permissions. This code is all functioning properly. The first time a user brings up the page, only those permissions that are unselected appear in the unselected side, and the same for selected.
However, when the user chooses another group to look at, the selected side is correct, while the unselected side shows what was displayed for the last group. Here is the route and controller:
App.GroupsEditRoute = Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
controller.set('allPermissions', this.store.find('permission'));
},
actions: {
'update': function(group){
var route = this;
group.save().then(function(){
route.transitionTo('groups');
});
},
'cancel': function(group){
group.rollback();
this.transitionTo('groups');
},
'delete': function(group){
group.destroyRecord();
this.transitionTo('groups');
}
}
});
App.GroupsEditController = Ember.ObjectController.extend({
unselectedPermissions: function() {
console.log('UNSELECTED');
var allPermissions=this.get('allPermissions');
var permissions=this.get('permissions');
var self=this;
allPermissions.then( function() {
permissions.then( function() {
var unselected=allPermissions.filter(function(permission) {
return !permissions.contains(permission);
});
unselected=Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
sortProperties: ['name'],
content: unselected
});
self.set('unselectedPermissions',unselected);
});
});
}.property('model.unselectedPermissions'),
selectedPermissions: function() {
console.log('SELECTED');
return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
sortProperties: ['name'],
content: this.get('permissions')
});
}.property('model.selectedPermissions')
});
When I use unselectedPermissions in my view via {{#each}}, it only fires once. I never see UNSELECTED in my log after that. However, the SELECTED, which is used in the same fashion, fires every time. Of course, the data displayed on the page is not updated, either, unless I refresh.
The setupController is being called each time a page is displayed, as it should.
I'm not sure what I'm doing wrong.
Any ideas?
in general computed properties shouldn't be set. When you set them you destroy the computed property portion of the code. There are a couple of different ways to handle this, the easiest is using an observer instead of computed property and setting the property.
unselectedPermissionList: [],
unselectedWatcher: function() {
console.log('UNSELECTED');
var allPermissions=this.get('allPermissions');
var permissions=this.get('permissions');
var self=this;
allPermissions.then( function() {
permissions.then( function() {
var unselected=allPermissions.filter(function(permission) {
return !permissions.contains(permission);
});
unselected=Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
sortProperties: ['name'],
content: unselected
});
self.set('unselectedPermissionList',unselected);
});
});
}.observes('selectedPermissions')
The other way is to return an array reference, then push objects into that array after the fact.
unselectedWatcher: function() {
console.log('UNSELECTED');
var allPermissions=this.get('allPermissions'),
permissions=this.get('permissions'),
self=this,
ret = [];
allPermissions.then( function() {
permissions.then( function() {
var unselected=allPermissions.filter(function(permission) {
return !permissions.contains(permission);
});
unselected=Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
sortProperties: ['name'],
content: unselected
});
unselected.forEach(function(item){
ret.pushObject(item);
});
});
});
return ret;
}.property('selectedPermissions')
Additionally your two properties claim to be dependent on each other, which should fire an infinite loop of property updating (a changes, b is dirty, b updates, a is dirty etc).
I'm not sure why selectedPermissions is a computed property, it seems like it would just be a list that's added to or removed from, and unselectedPermissions would just be allPermisions not selectedPermissions

Ember: ArrayController computed property based on array item properties

I have an ArrayContoller on which I want to set a boolean property based on the properties of its contents.
Plain-language description of the logic:
If the array contains any items with a property of isRetired equal to true, set the retiredShoes property of the ArrayController to true, otherwise, set the ArrayController retiredShoes property to false.
It seems like this should be a simple matter, but I haven't found a solution anywhere, and I'm still pretty new at this.
I'll put together a jsfiddle if necessary.
Here are the controllers for the array and the object:
App.ApplicationController = Ember.ArrayController.extend({
sortProperties: ['title'],
itemController: 'shoe',
retiredShoes: function() {
//how do I compute this sucker?
}
});
App.ShoeController = Ember.ObjectController.extend({
needs: ['application'],
actions: {
delete: function() {
var shoe = this.get('model'),
runs = shoe.get('runs');
shoe.deleteRecord();
shoe.save();
},
toggleRetired: function() {
var shoe = this.get('model');
shoe.toggleProperty('isRetired');
shoe.save();
}
}
});
Off top of my head, without jsbin. If there's a problem/bug, drop me a comment and I'll look it over again.
App.ApplicationController = Ember.ArrayController.extend({
retiredShoes: function() {
return this.get("model").isAny("isRetired", true);
}.property("model.#each.isRetired")
});

Setting a belongsTo attribute via an action on controller not working

I have the following models, customer:
App.Customer = DS.Model.extend({
//Other Attributes
area: DS.belongsTo('area', {async: true})
});
and area model -
App.Area = DS.Model.extend({
name: DS.attr('string'),
zip_code: DS.attr('number')
});
And I use an Ember.Select view to show a dropdown of the area a customer is in -
{{view Ember.Select viewName="select_area" content=possible_areas optionValuePath="content.id" optionLabelPath="content.name" value=area.id prompt="No area selected" selectionBinding="selected_area"}}
and the controller which wires up everything together -
App.CustomerController = Ember.ObjectController.extend({
possible_areas: function() {
return this.store.find('area');
}.property(),
selected_area: null,
actions: {
save: function() {
var selected_area_id = this.get('selected_area.id');
console.log(selected_area_id);
var selected_area = this.store.find('area', selected_area_id);
var self = this;
selected_area.then(function(area) {
console.log(area.get('id'));
var updated_customer = self.get('model');
updated_customer.set('area', area );
console.log(new_customer.get('area.id'));
updated_customer.save()
.then(function(customer) {
//success
},
function() {
//failure
});
});
}
}
});
Now here is the weird thing. Upon calling the 'save' action the first time, the line updated_customer.set('area', area ); fails with the error
Uncaught Error: Assertion Failed: Cannot delegate set('id', ) to the 'content' property of object proxy <DS.PromiseObject:ember551>: its 'content' is undefined.
Upon calling 'save' action immediately after that, the save goes through without any error and area of the customer is updated successfully. Although the dropdown shows selected_area to be null.
How do I prevent the first save from erroring out?
I am using ember-data 1.0.0-beta.6.
Since you have the association defined in your Customer model, I would remove the selected_area property from the controller and use ember-data's associations instead. Bind to the "area" association in the Ember.Select by using the selectionBinding property.
{{view Ember.Select viewName="select_area"
content=possible_areas
optionValuePath="content.id"
optionLabelPath="content.name"
prompt="No area selected"
selectionBinding="area"}}
This way, the area attribute will change when the user interacts with the select menu.
This has the added benefit of cleaning up your save action since we're binding directly to the area association for the Customer.
App.CustomerController = Ember.ObjectController.extend({
possible_areas: function() {
return this.store.find('area');
},
actions: {
save: function() {
this.get('model').save().then(this.onDidCreate.bind(this), this.onDidFail.bind(this))
}
onDidCreate: function() {
// Fullfilled callback
}
onDidFail: function() {
// Rejected callback
}
}
});
However, the possible_areas property won't be populated when the template first renders since this.get('area') returns a promise. I would wait to render the select until the promise settles. You can do this in the routes model hook since it waits until promises settle to render the template.
App.CustomerRoute = Ember.Route.extend({
route: function(params) {
return Ember.RSVP.hash({
customer: this.store.find('customer', params.customer_id),
possible_areas: this.store.find('area')
});
},
// Since the route hook returns a hash of (hopefully) settled promises, we
// have to set the model property here, as well as the possible_areas property.
setupController: function(controller, model) {
controller.set('model', model.customer);
controller.set('possible_areas', model.possible_areas);
}
});

Ember.js get controller in view

I feel like this should be pretty straight-forward, but I'm unable to get the contents of a controller in a different view. Here is my code:
App.MapView = Ember.View.extend({
elementId: ['map-canvas'],
didInsertElement: function() {
var self = this;
var controller = this.get('controllers.markers');
}
});
If I console.log(controller) I get undefined.
In a controller I would do something like:
App.MarkersController = Ember.ArrayController.extend({
needs: ['map']
});
App.MapController = Ember.ObjectController.extend({
plot: function() {
var markers = this.get('controllers.markers');
}
});
You place the needs on the controller that needs another controller, and where you'll be accessing the other controller.
And from a view, in order to grab the controller you do this.get('controller') and the controllers object lives on the controller, so controller.controllers.markers
Additionally, the view is only created with the controller by default if ember creates it, if you are doing something like {{view App.MapView}} it isn't creating the MapController and associating it with it, it's using the controller that was in scope when you created the view.
App.MapView = Ember.View.extend({
elementId: ['map-canvas'],
didInsertElement: function() {
var self = this;
var controller = this.get('controller.controllers.markers');
}
});
App.MarkersController = Ember.ArrayController.extend({
});
App.MapController = Ember.ObjectController.extend({
needs: ['markers'],
plot: function() {
var markers = this.get('controllers.markers');
}
});
Check out this implementation of it:
http://emberjs.jsbin.com/ODuZibod/1/edit