Ember: ArrayController computed property based on array item properties - ember.js

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

Related

How to set default values for query params based on dynamic segment?

I am creating universal grid for some entities. For that I added this in routes:
this.route('record', { path: '/record' },function() {
this.route('index', {path: '/:entity'});
this.route('view', {path: '/:entity/:record_id'});
});
and created new "index" route:
export default Ember.Route.extend({
entity: '',
queryParams: {
sort: {
refreshModel: true
}
},
beforeModel: function(transition) {
var entity = transition.params[this.get('routeName')].entity;
this.set('entity', entity);
},
model: function(params) {
delete params.entity;
return this.store.findQuery(this.get('entity'), params);
},
}
my controller
export default Ember.ArrayController.extend({
queryParams: ['sort'],
sort: ''
}
how can I set default value for the "sort" based on dynamic segment?
For example in my settings I store sort values for all entites:
'settings.user.sort': 'email ASC',
'settings.company.sort': 'name ASC',
I tried to define "sort" as computed property, but its "get" method is called in time when I can't get a value of dynamic segment from currentHadlerInfo or from route.
Also defining of the property "sort" as computed property has strange effect, for example, when I define it as
sort: 'email ASC'
in my template it is displayed via {{sort}} as expected (email ASC).
but when I return a value from computed property, I see empty value in my template and this affects on a work of components (I can't get current sorted column)
What can I do?..
Here is a rough implementation of setting the sort properties based on dynamic segment values. The code will look like
<script type="text/x-handlebars" data-template-name="index">
{{#link-to 'sort' model.settings.user.sort}}Sort{{/link-to}}
</script>
<script type="text/x-handlebars" data-template-name="sort">
<ul>
{{#each arrangedContent as |item|}}
<li>{{item.val}}</li>
{{/each}}
</ul>
</script>
var settings = Em.Object.create({
settings: {
user: {
sort: 'val ASC'
}
}
});
App.Router.map(function() {
this.route('sort', {path: '/:sortParams'});
});
App.SortRoute = Ember.Route.extend({
params: null,
model: function(params) {
this.set('params', params);
return [{val:1}, {val:5}, {val:0}];
},
setupController: function(controller, model) {
this._super(controller, model);
var props = this.get('params').sortParams.split(' ');
var property = [props[0]];
var order = props[1] === 'ASC'? true : false;
controller.set('sortProperties', property);
controller.set('sortAscending', order);
}
});
The working demo can be found here..
As you can see I access the params object in the model hook and store it on the route. In the setupController hook I access the params and set the required values on the controller.

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

Set computed property from another controller

I am trying to set the value of a computed property from one controller to another.
var BusinessOwner = Ember.ObjectController.extend({
actions: {
save: function(){
var self = this;
return Ember.$.ajax({
}).then(function(){
var ownerShow = self.store.getById('application',100);
ownerShow.get('ownerGeneral');
ownerShow.set('ownerGeneral', 'complete')
Ember.set(self, 'controllers.collectinfo.ownerGeneral','completed');
//self.set('controllers.collectinfo.ownerGeneral', "completed");
});
}
}
I have tried several different attempts at setting this property but have proved unsuccessful. If I use the self set, errors that I must use Ember.set(). If I use Ember.set() I get error collectinfo must be global if no obj given.
Thanks for any help
EDIT:
Thanks for looking at this. Yes I am includeing needs: 'collectinfo' I am still getting the error that Ember.set() needs to be used to set the object
You need to provide needs array in the controller as well.
var BusinessOwner = Ember.ObjectController.extend({
needs: 'collectinfo'
actions: {
save: function(){
var self = this;
return Ember.$.ajax({
}).then(function(){
var ownerShow = self.store.getById('application',100);
ownerShow.get('ownerGeneral');
ownerShow.set('ownerGeneral', 'complete')
Ember.set(self, 'controllers.collectinfo.ownerGeneral','completed');
//self.set('controllers.collectinfo.ownerGeneral', "completed");
});
}
}
Coding wise i suggest you create a own computed property for the one you want to access from other controller. So code becomes like this.
var BusinessOwner = Ember.ObjectController.extend({
needs: 'collectinfo',
ownerGeneral: Ember.computed.alias('controllers.collectinfo.ownerGeneral')
actions: {
save: function(){
var self = this;
return Ember.$.ajax({
}).then(function(){
var ownerShow = self.store.getById('application',100);
ownerShow.get('ownerGeneral');
ownerShow.set('ownerGeneral', 'complete')
Ember.set(self, 'ownerGeneral','completed');
//self.set('controllers.collectinfo.ownerGeneral', "completed");
});
}
}
You can set dependencies between controller with the controller needs property, it's documented at Ember Guide.
App.IndexController = Em.Controller.extend({
needs: 'application',
message: 'hi!',
actions: {
changeApplicationMessage: function() {
this.set('controllers.application.message', 'good bye');
},
changeMessage: function(){
this.set('message', 'bye');
}
}
});
The dependent controller property will be accesible in the controller at {{controllers.controllerName.propertyName}}
Demo: http://emberjs.jsbin.com/vevet/1/edit
In addition to what others said about "needs," just declare a shortcut variable for set and get:
var get = Ember.get;
var set = Ember.set;
and then use them like so:
set(object, 'property', 'value-to-set-property-to');
I assume that your controller declares a needs property with "collectInfo" as value? Then it should work this way:
var BusinessOwner = Ember.ObjectController.extend({
needs : ['collectInfo'],
actions: {
save: function(){
var collectinfoController = this.get('controllers.collectinfo');
return Ember.$.ajax({
}).then(function(){
var ownerShow = self.store.getById('application',100);
ownerShow.get('ownerGeneral');
ownerShow.set('ownerGeneral', 'complete')
collectinfoController.set('ownerGeneral','completed');
});
}
}

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