Arranging data in Ember: sorting an array based on several properties in different directions

Is it possible to sort a collection of Ember Models by multiple properties, where each property can be sorted in different directions? For example, sorting by property a in ascending order and by property b in descending order?

Update

I attempted to use the sortAscending property with an array, but unfortunately it did not work. Upon further investigation, it appears that this functionality is not currently supported out of the box.

Answer №1

Within your ArrayController:

sortProperties: ["propertyA:asc", "propertyB:desc"]
sortedResults: Ember.computed.sort("data", "sortProperties");

Afterward, utilize sortedResults in the #each loop within your template.

Answer №2

After careful consideration, I decided to develop a special mixin that enables sorting in multiple directions. This extension of the SortableMixin is designed to be as backwards-compatible as possible. Essentially, it functions like a typical SortableMixin, but with the addition of the sortAscendingProperties property. This property is an array of sort property names (members of the sortProperty array) that should be sorted in ascending order. If a property is included in sortAscendingProperties, it will be sorted in ascending order; otherwise, it will be sorted according to the sortAscending value, which serves as a default sorting direction.
I chose to name this mixin MultiSortableMixin, although I acknowledge that it may not be the most fitting name.

(function() {

    var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;

    /**
     * Extends the SortableMixin by allowing sorting in multiple orders.
     *
     * sortProperties - array of strings
     * sortAscending - default sort order
     * sortAscendingProperties - properties listed here will be sorted in ascending order, while others will follow the default sort order
     */
    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;
        },
        //Overridden to include additional watched properties. TODO - Since the code is identical to the parent method, explore ways to simply add watched properties
        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;
        }),
    //Not needed in this mixin, overridden to disable functionality from SortableMixin. TODO - Find a way to remove it without impacting other functionalities
        sortAscendingDidChange: Ember.observer(function() {
            //empty
        }, 'sortAscending')
    });

})();

Example of how to use:

App.ThingsController = Ember.ArrayController.extend(Ember.MultiSortableMixin, {
    sortProperties: ['prop1', 'prop2', 'prop3'], 
    sortAscending: false,
    sortAscendingProperties: ['prop2', 'prop3'], 
    //your stuff
});

In this demonstration, the content of ThingsController will be sorted first by prop1 in descending order, followed by prop2 and prop3 in ascending order.

Answer №3

Ember does not come with out-of-the-box sorting functionality, but by examining the code of SortableMixin found here, we can see that it utilizes 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;
  },

The Ember.compare function includes 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);
    }
  }

My suggested solution involves:

1 - Adding an extra field to your models that contains a wrapper object of all your sorting properties, for example, "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. Within this comparison method, follow your desired sorting behavior (prop a ascending and prop b descending).

3 - Now you can create an ArrayController and sort it based on your combined property:

var yourModelController = //wherever you may obtain that one from
yourModelController.set("sortProperties", "combinedAandB");

Note: This is just a spontaneous idea I had while reading your requirement. I have not yet implemented this, so it's likely not perfect :-)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Experiencing an issue where the canvas element fails to render on mobile Chrome browser,

I've encountered an issue with a script that draws a canvas based on the background color of an image. The image is loaded dynamically from a database using PHP. The responsive functionality works fine on mobile Safari, but not on Chrome. When the re ...

Encountering a Django Channels issue while loading the JavaScript wrapper

After successfully running the server, the following appeared in my cmd prompt: System check identified no issues (0 silenced). January 21, 2020 - 17:43:42 Django version 3.0.2, using settings 'crm.settings' Starting ASGI/Channels version 2.4.0 ...

What are some creative ways to reveal a concealed card through animation?

I have a collection of MUI cards where one card remains hidden until the others are expanded. My goal is to add animation to the hidden card so it doesn't abruptly appear. Below is the styling and logic for achieving this: ** Styling ** const useStyl ...

Angular directive to delete the last character when a change is made via ngModel

I have 2 input fields where I enter a value and concatenate them into a new one. Here is the HTML code: <div class="form-group"> <label>{{l("FirstName")}}</label> <input #firstNameInput="ngMode ...

Conceal the countdown clock and reveal the message box

I am attempting to create a functionality where the text box will replace the timer when it reaches 0, and then the timer will be hidden. I am seeking a straightforward solution using either the 'v-show' or 'destroy' property in vue.js ...

Custom cellRenderer prevents Ag Grid's autoHeight and wrapText features from functioning properly

I've been attempting to adjust the formatting of a long cell value by wrapping the text. According to the documentation, setting autoHeight=true and wrapText=true works fine without any cellRenderer components. However, when using a cellRendererFramew ...

Understanding the Importance and Benefits of Using the Classnames Utility in React Components

Can you break down for me the purpose of utilizing the Classnames utility in React code? I've reviewed the Classnames documentation, but I'm still struggling to comprehend why it is used in code like this: import classnames from 'classnames ...

Utilizing v-if and splice on users to select items from v-model

After following a tutorial, I have the code snippet below that I would like to enhance: <div style="margin-top: 10px;"> v-for="task in taskItems" :key="task.id" <q-icon :name="task.icon"/> <div ...

Unable to locate and interact with a specific element within a dropdown menu while utilizing Protractor

I am currently facing an issue with selecting a checkbox from a dropdown in jq widgets. The code seems to work fine when the element is visible on the screen, but fails otherwise. I have tried various methods such as executeScript and scrollIntoView to bri ...

Tips for evaluating an array of objects in JavaScript

Welcome to the world of coding! Here's a scenario with an array: [ { "question1": "Apple", "question2": 5, "question3": "Item 1" }, { "question1": ...

Having trouble updating attribute through ajax requests

How can I set attribute values using ajax-jquery? Here are some snippets of code: HTML_CODE ========================================================================== <div class="col"> <ul class="nav nav-pills justify-content-end& ...

Issue with styled-components not being exported

Issue: ./src/card.js There was an import error: 'Bottom' is not exported from './styles/cards.style'. card.js import React from 'react' import { Bottom, Color, Text, Image } from "./styles/cards.style"; fu ...

What causes the issue of the 'MERGE' statement in Node.js resulting in the creation of duplicate nodes in Neo4j?

Currently tackling a Node.js project involving the creation of nodes in Neo4j using the 'MERGE' statement. Despite employing 'MERGE' to prevent duplicate nodes, duplicates are still being generated at times. Extensive searches through ...

The React component continuously refreshes whenever the screen is resized or a different tab is opened

I've encountered a bizarre issue on my portfolio site where a diagonal circle is generated every few seconds. The problem arises when I minimize the window or switch tabs, and upon returning, multiple circles populate the screen simultaneously. This b ...

What is the reason that when we assign `'initial'` as the value for `display` property, it does not function as intended for list elements?

To toggle the visibility of elements, I have created a unique function that accepts an object and a boolean. Depending on the boolean value, either 'none' or 'initial' is assigned to the 'display' property of the specified obj ...

Increasing the upward motion of the matrix raining HTML canvas animation

Recently, I've been experimenting with the Matrix raining canvas animation here and I was intrigued by the idea of making it rain upwards instead of downwards. However, my attempts to achieve this using the rotate() method resulted in skewing and stre ...

Is there a way to prevent SignalR from disconnecting when the settings window is moved?

I am currently developing a webpage that utilizes SignalR events to trigger ajax requests to our servers. These requests return a URL with a custom URI scheme that, when accessed, opens up a specific program on the client's device without leaving the ...

Adjusting the size of content tags depending on their popularity

Currently, I am working on setting up a basic tagging system by following the database structure provided in this Stack Overflow post. My goal is to create a single page that showcases all the tags, with each tag being visually scaled based on its popular ...

React HTML ignore line break variable is a feature that allows developers to

Can you help me with adding a line break between two variables that will be displayed properly in my HTML output? I'm trying to create an object with a single description attribute using two text variables, and I need them to be separated by a line b ...

What advantages can be gained by opting for more precise module imports?

As an illustration, consider a scenario where I have an Angular 6 application and need to bring in MatIconModule from the @angular/material library. Two options could be: import { MatIconModule } from '@angular/material/icon'; Or import { Mat ...