9

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

0

3 Answers 3

15

In your ArrayController:

sortProperties: ["propA:asc", "propB:desc"]
sortedModel: Ember.computed.sort("model", "sortProperties");

Then reference sortedModel in your template's #each handler.

Sign up to request clarification or add additional context in comments.

Comments

5

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.

Comments

0

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

1 Comment

took some time to get to it, but even before trying this: this idea can be good when sort properties are known beforehand, but i want to be able to specify sort properties and directions at run-time. Still doable using your approach, but seems to be clumsy and not elegant. I'm thinking now about extending the SortableMixin with required functionality..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.