I need to sort a collection of Ember Models by multiple properties, and not necessary in the same direction/order. I.e. I need to sort by property a in ascending order and by property b in descending. Is there a way to achieve this?
Update
I tried setting the sortAscending
property to a array, but it is not working.
After looking into the source it seems that this functionality is not supported out of the box(yet).
I need to sort a collection of Ember Models by multiple properties, and not necessary in the same direction/order. I.e. I need to sort by property a in ascending order and by property b in descending. Is there a way to achieve this?
Update
I tried setting the sortAscending
property to a array, but it is not working.
After looking into the source it seems that this functionality is not supported out of the box(yet).
3 Answers
Reset to default 15In your ArrayController:
sortProperties: ["propA:asc", "propB:desc"]
sortedModel: Ember.computed.sort("model", "sortProperties");
Then reference sortedModel
in your template's #each
handler.
I decided to create a mixin which allows sorting in multiple orders(directions). It extends the SortableMixin
trying to as much backwards-compatible as possible. Actually it can be used as regular SortableMixin
. What it adds is the sortAscendingProperties
property which is array of sort property names(members of sortProperty
array) which should be sorted in ascending order. If the property is in sortAscendingProperties
it will be sorted in ascending order, otherwise it will be sorted according to sortAscending
, which serves as kind of default.
I called the mixin the MultiSortableMixin
although I think it is not the best name.
(function() {
var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
/**
* extends the SortableMixin by allowing sorting by multiple orders.
*
* sortProperties - array of strings
* sortAscending - default sort order
* sortAscendingProperties - properties which are listed here will be sorted in ascending order, those which are not listed - will be sorted according to sortAscending
*/
Ember.MultiSortableMixin = Ember.Mixin.create(Ember.SortableMixin, {
/**
Specifies the arrangedContent's sort direction
@property {Array} sortAscendingProperties
*/
sortAscendingProperties: null,
orderBy: function(item1, item2) {
var result = 0,
sortProperties = get(this, 'sortProperties'),
sortAscending = get(this, 'sortAscending'),
sortAscendingProperties = get(this, 'sortAscendingProperties');
Ember.assert("you need to define `sortProperties`", !!sortProperties);
forEach(sortProperties, function(propertyName) {
if (result === 0) {
result = Ember.compare(get(item1, propertyName), get(item2, propertyName));
//use default sortAscending if propertyName is not listed in sortAscendingProperties
var sa = (sortAscendingProperties && sortAscendingProperties.indexOf(propertyName) > -1) || sortAscending;
if ((result !== 0) && !sa) {
result = (-1) * result;
}
}
});
return result;
},
//overrided to add more watched props. TODO - the contents of this method is the same as parent's - find the way to just add watched stuff
arrangedContent: Ember.computed('content', 'sortProperties.@each', 'sortAscendingProperties.@each', 'sortAscending', function(key, value) {
var content = get(this, 'content'),
isSorted = get(this, 'isSorted'),
sortProperties = get(this, 'sortProperties'),
self = this;
if (content && isSorted) {
content = content.slice();
content.sort(function(item1, item2) {
return self.orderBy(item1, item2);
});
forEach(content, function(item) {
forEach(sortProperties, function(sortProperty) {
Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
}, this);
}, this);
return Ember.A(content);
}
return content;
}),
// unneeded in this mixin, overrided to disable functionality from SortableMixin. TODO - find a way to just remove it
sortAscendingDidChange: Ember.observer(function() {
//empty
}, 'sortAscending')
});
})();
Usage example:
App.ThingsController = Ember.ArrayController.extend(Ember.MultiSortableMixin, {
sortProperties: ['prop1', 'prop2', 'prop3'],
sortAscending: false,
sortAscendingProperties: ['prop2', 'prop3'],
//your stuff
});
In this example the content of ThingsController
will be sorted by prop1 - in descending order, then by prop2 and prop3 - both in ascending order.
This is no out of the box functionality of Ember. But looking at the code of SortableMixin one can see that it uses Ember.compare to compare two entities:
orderBy: function(item1, item2) {
var result = 0,
sortProperties = get(this, 'sortProperties'),
sortAscending = get(this, 'sortAscending');
Ember.assert("you need to define `sortProperties`", !!sortProperties);
forEach(sortProperties, function(propertyName) {
if (result === 0) {
result = Ember.compare(get(item1, propertyName), get(item2, propertyName));
if ((result !== 0) && !sortAscending) {
result = (-1) * result;
}
}
});
return result;
},
And Ember.compare contains a check on the Comparable Mixin:
var Comparable = Ember.Comparable;
if (Comparable) {
if (type1==='instance' && Comparable.detect(v.constructor)) {
return v.constructor.compare(v, w);
}
if (type2 === 'instance' && Comparable.detect(w.constructor)) {
return 1-w.constructor.compare(w, v);
}
}
Therefore my proposed solution is:
1 - Add an additional field to your models which contains a wrapper object of all your sortingProperties, e.g. "combinedAandB"
App.YourModel = Ember.Object.extend({
a : null,
b : null,
combinedAandB : function(){
// the following Object should implement SortableMixin
var comparator = App.AandBComparator.create(this.get("a"), this.get("b"));
return comparator;
}.property("a","b")
2 - Your ComparatorModel (App.AandBComparator) should implement the Comparable Mixin. Inside this comparation method respect your desired sorting behaviour (prop a ascending and prop b descending).
3 - Now you could instantiate an ArrayController and sort it based on your combined property:
var yourModelController = //whereever you may get that one from
yourModelController.set("sortProperties", "combinedAandB");
Note: This is just a spontaneous idea i got when reading your requirement. I have not yet implemented this. So this is likely not perfect :-)