Ordre de tri inversé avec Backbone.js

89

Avec Backbone.js, j'ai une collection configurée avec une fonction de comparaison. C'est bien le tri des modèles, mais j'aimerais inverser l'ordre.

Comment puis-je trier les modèles par ordre décroissant plutôt que par ordre croissant?

Drew Dara-Abrams
la source
2
Pour le tri inversé sur les chaînes (y compris les champs created_at), voir cette réponse sur une autre question
Eric Hu
2
Il existe un support pour les comparateurs de type tri depuis le début de 2012. Il suffit d'accepter 2 arguments et de renvoyer -1, 0 ou 1. github.com/documentcloud/backbone/commit/…
geon
C'est ce qui a fonctionné pour moi (Strings and Numbers): stackoverflow.com/questions/5013819/…
FMD

Réponses:

133

Eh bien, vous pouvez renvoyer des valeurs négatives à partir du comparateur. Si nous prenons, par exemple, l'exemple du site de Backbone et que nous voulons inverser l'ordre, cela ressemblera à ceci:

var Chapter  = Backbone.Model;
var chapters = new Backbone.Collection;

chapters.comparator = function(chapter) {
  return -chapter.get("page"); // Note the minus!
};

chapters.add(new Chapter({page: 9, title: "The End"}));
chapters.add(new Chapter({page: 5, title: "The Middle"}));
chapters.add(new Chapter({page: 1, title: "The Beginning"}));

alert(chapters.pluck('title'));
Inféligo
la source
3
hi5 pour avoir utilisé le même exemple de code que le mien!, et dans les 15 secondes de différence. Comme je dois dire.
DhruvPathak
Oh, bien sûr, juste un signe moins. Merci à tous les deux pour vos réponses rapides!
Drew Dara-Abrams le
Notez également que (comme mentionné sur le site de Backbone) cela casse si vous modifiez l'un des attributs du modèle (si vous ajoutez un attribut "longueur" au chapitre par exemple). Dans ce cas, vous devrez appeler manuellement "sort ()" sur la collection.
cmcculloh
9
Il existe un support pour les comparateurs de type tri depuis le début de 2012. Il suffit d'accepter 2 arguments et de renvoyer -1, 0 ou 1. github.com/documentcloud/backbone/commit
geon
La réponse @Fasani fonctionne pour des types autres que des nombres, veuillez jeter un œil: stackoverflow.com/questions/5013819
...
56

Personnellement, je ne suis pas très satisfait des solutions proposées ici:

  • La solution de multiplication par -1 ne fonctionne pas lorsque le type de tri n'est pas numérique. Bien qu'il existe des moyens de contourner cela (en appelant Date.getTime()par exemple), ces cas sont spécifiques. Je voudrais une manière générale d'inverser la direction de toute sorte, sans avoir à me soucier du type spécifique du champ à trier.

  • Pour la solution de chaîne, la construction d'une chaîne un caractère à la fois semble être un goulot d'étranglement des performances, en particulier lors de l'utilisation de grandes collections (ou même de grandes chaînes de champ de tri)

Voici une solution qui fonctionne bien pour les champs String, Date et Numeric:

Tout d'abord, déclarez un comparateur qui inversera le résultat d'un résultat d'une fonction sortBy, comme ceci:

function reverseSortBy(sortByFunction) {
  return function(left, right) {
    var l = sortByFunction(left);
    var r = sortByFunction(right);

    if (l === void 0) return -1;
    if (r === void 0) return 1;

    return l < r ? 1 : l > r ? -1 : 0;
  };
}

Maintenant, si vous voulez inverser le sens du tri, tout ce que nous devons faire est:

var Chapter  = Backbone.Model;
var chapters = new Backbone.Collection;

// Your normal sortBy function
chapters.comparator = function(chapter) {
  return chapter.get("title"); 
};


// If you want to reverse the direction of the sort, apply 
// the reverseSortBy function.
// Assuming the reverse flag is kept in a boolean var called reverseDirection 
if(reverseDirection) {
   chapters.comparator = reverseSortBy(chapters.comparator);
}

chapters.add(new Chapter({page: 9, title: "The End"}));
chapters.add(new Chapter({page: 5, title: "The Middle"}));
chapters.add(new Chapter({page: 1, title: "The Beginning"}));

alert(chapters.pluck('title'));

La raison pour laquelle cela fonctionne est qu'il Backbone.Collection.sortse comporte différemment si la fonction de tri a deux arguments. Dans ce cas, il se comporte de la même manière que le comparateur passé Array.sort. Cette solution fonctionne en adaptant la fonction sortBy à un seul argument dans un comparateur à deux arguments et en inversant le résultat.

Andrew Newdigate
la source
7
En tant que personne ayant la réponse la plus positive à cette question, j'aime vraiment cette approche et je pense qu'elle devrait avoir plus de votes positifs.
Andrew De Andrade
Merci beaucoup Andrew
Andrew Newdigate
Très bien, j'ai fini par peaufiner un peu cela et l'abstraction dans le prototype de Backbone.Collection. Merci beaucoup!
Kendrick Ledet
46

Le comparateur de collection de Backbone.js repose sur la méthode Underscore.js _.sortBy. La façon dont sortBy est implémenté finit par «envelopper» javascript .sort () d'une manière qui rend difficile le tri des chaînes en sens inverse. Une simple négation de la chaîne finit par renvoyer NaN et rompt le tri.

Si vous devez effectuer un tri inversé avec des chaînes, comme un tri alphabétique inversé, voici une façon vraiment hackish de le faire:

comparator: function (Model) {
  var str = Model.get("name");
  str = str.toLowerCase();
  str = str.split("");
  str = _.map(str, function(letter) { 
    return String.fromCharCode(-(letter.charCodeAt(0)));
  });
  return str;
}

Ce n'est en aucun cas joli, mais c'est un "négateur de chaîne". Si vous n'avez aucun scrupule à modifier les types d'objets natifs en javascript, vous pouvez clarifier votre code en extrayant le négateur de chaîne et en l'ajoutant en tant que méthode sur String.Prototype. Cependant, vous ne voudrez probablement le faire que si vous savez ce que vous faites, car la modification de types d'objets natifs en javascript peut avoir des conséquences imprévues.

Andrew De Andrade
la source
Ceci est très pratique. Merci Andrew!
Abel
1
Pas de problème. J'ajouterais simplement que vous devriez vous demander pourquoi vous implémentez un tri alphabétique inversé et s'il existe peut-être une solution plus élégante du point de vue de l'expérience utilisateur.
Andrew De Andrade
1
@AndrewDeAndrade Pourquoi: je veux trier une collection par l'attribut 'created_at', en servant les plus récents en premier. Cet attribut est l'un des attributs par défaut de Backbone et est stocké sous forme de chaîne. Votre réponse se rapproche le plus de ce que je veux. Merci!
Eric Hu
C'est incroyable, je travaille sur une solution à cela depuis une heure maintenant.
28

Ma solution était d'inverser les résultats après le tri.

Pour éviter le double rendu, effectuez d'abord le tri avec silencieux, puis déclenchez 'reset'.

collections.sort({silent:true})
collections.models = collections.models.reverse();
collections.trigger('reset', collections, {});
Tomasz Trela
la source
2
Une meilleure solution consiste à annuler le résultat du comparateur.
Daniel X Moore
7
Daniel, ce n'est pas toujours possible, par exemple lors du tri des chaînes ou des dates.
Gavin Schulz
1
Bien sûr que ça l'est. Pour Date, vous pouvez faire revenir le comparateur -Date.getTime(), éventuellement avec un piratage supplémentaire si vous pensez que les dates de votre système traverseront l'époque Unix. Pour l'ordre alphabétique, regardez la réponse d'Andrew ci-dessus.
asparagino
@DanielXMoore Pourquoi est-ce nécessairement une meilleure solution? Est-ce plus rapide? Personnellement, je trouve très facile de stocker l'attribut par lequel l'utilisateur a trié la collection en dernier, puis d'inverser le tri actuel s'il trie à nouveau par cet attribut; (pour implémenter le comportement de tri que vous voyez dans une table Wikipedia, où le sens de tri peut être basculé en cliquant à plusieurs reprises sur l'attribut dans l'en-tête du tableau) De cette façon, je garde ma logique de comparateur propre et me sauve une opération de tri si nous basculons d'Asc à l'ordre de tri Desc
Mansiemans
13

Modifiez votre fonction de comparaison pour renvoyer une valeur proportionnelle inversée au lieu de renvoyer les données que vous êtes actuellement. Un peu de code de: http://documentcloud.github.com/backbone/#Collection-comparator

Exemple:

var Chapter  = Backbone.Model;
var chapters = new Backbone.Collection;

/* Method 1: This sorts by page number */
chapters.comparator = function(chapter) {
  return chapter.get("page");
};

/* Method 2: This sorts by page number in reverse */
chapters.comparator = function(chapter) {
  return -chapter.get("page");
};

chapters.add(new Chapter({page: 9, title: "The End"}));
chapters.add(new Chapter({page: 5, title: "The Middle"}));
chapters.add(new Chapter({page: 1, title: "The Beginning"}));
DhruvPathak
la source
9

Ce qui suit a bien fonctionné pour moi:

comparator: function(a, b) {
  // Optional call if you want case insensitive
  name1 = a.get('name').toLowerCase();
  name2 = b.get('name').toLowerCase();

  if name1 < name2
    ret = -1;
  else if name1 > name2
    ret = 1;
  else
    ret = 0;

  if this.sort_dir === "desc"
    ret = -ret
  return ret;
}

collection.sort_dir = "asc";
collection.sort(); // returns collection in ascending order
collection.sort_dir = "desc";
collection.sort(); // returns collection in descending order
rbhitchcock
la source
8

J'en ai lu où la fonction de comparaison Backbone utilise ou imite la fonction JavaScript Array.prototype.sort (). Cela signifie qu'il utilise 1, -1 ou 0 pour décider de l'ordre de tri de chaque modèle de la collection. Cela se met constamment à jour et la collection reste dans le bon ordre en fonction du comparateur.

Des informations sur Array.prototype.sort () peuvent être trouvées ici: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?redirectlocale=en-US&redirectslug=JavaScript% 2FReference% 2FGlobal_Objects% 2FArray% 2Fsort

De nombreuses réponses à cette question inversent simplement l'ordre juste avant de le rendre sur la page. Ce n'est pas idéal.

En utilisant l'article ci-dessus sur Mozilla comme guide, j'ai pu garder ma collection triée dans l'ordre inverse en utilisant le code suivant, puis nous rendons simplement la collection à la page dans son ordre actuel et nous pouvons utiliser un indicateur (reverseSortDirection) pour inverser l'ordre .

//Assuming this or similar will be in your Backbone.Collection.
sortKey: 'id', //The field name of the item in your collection

reverseSortDirection: false,

comparator: function(a, b) {

  var sampleDataA = a.get(this.sortKey),
      sampleDataB = b.get(this.sortKey);

      if (this.reverseSortDirection) {
        if (sampleDataA > sampleDataB) { return -1; }
        if (sampleDataB > sampleDataA) { return 1; }
        return 0;
      } else {
        if (sampleDataA < sampleDataB) { return -1; }
        if (sampleDataB < sampleDataA) { return 1; }
        return 0;
      }

},

Comparateur à l'exception des deux modèles, lors des modifications apportées à la collection ou au modèle, la fonction de compartiment s'exécute et change l'ordre de la collection.

J'ai testé cette approche avec Numbers and Strings et cela semble fonctionner parfaitement pour moi.

J'espère vraiment que cette réponse pourra vous aider car j'ai lutté avec ce problème à plusieurs reprises et finit généralement par utiliser un travail de contournement.

La fièvre aphteuse
la source
1
J'aime cela parce que la logique vit dans la collection, mais vous pouvez facilement définir sortKeyet reverseSortDirectionet appeler à .sort()partir de n'importe quelle vue qui a une poignée sur la collection. Plus simple que d'autres réponses aux votes plus élevés, pour moi.
Noah Heldman
5

Cela peut être fait avec élégance en remplaçant la méthode sortBy. Voici un exemple

var SortedCollection = Backbone.Collection.extend({

   initialize: function () {
       // Default sort field and direction
       this.sortField = "name";
       this.sortDirection = "ASC";
   },

   setSortField: function (field, direction) {
       this.sortField = field;
       this.sortDirection = direction;
   },

   comparator: function (m) {
       return m.get(this.sortField);
   },

   // Overriding sortBy (copied from underscore and just swapping left and right for reverse sort)
   sortBy: function (iterator, context) {
       var obj = this.models,
           direction = this.sortDirection;

       return _.pluck(_.map(obj, function (value, index, list) {
           return {
               value: value,
               index: index,
               criteria: iterator.call(context, value, index, list)
           };
       }).sort(function (left, right) {
           // swap a and b for reverse sort
           var a = direction === "ASC" ? left.criteria : right.criteria,
               b = direction === "ASC" ? right.criteria : left.criteria;

           if (a !== b) {
               if (a > b || a === void 0) return 1;
               if (a < b || b === void 0) return -1;
           }
           return left.index < right.index ? -1 : 1;
       }), 'value');
   }

});

Vous pouvez donc l'utiliser comme ceci:

var collection = new SortedCollection([
  { name: "Ida", age: 26 },
  { name: "Tim", age: 5 },
  { name: "Rob", age: 55 }
]);

//sort by "age" asc
collection.setSortField("age", "ASC");
collection.sort();

//sort by "age" desc
collection.setSortField("age", "DESC");
collection.sort();

Cette solution ne dépend pas du type de champ.

Aman Mahajan
la source
1
Bon! "void 0" peut être remplacé par "undefined".
GMO
3
// For numbers, dates, and other number-like things
var things = new Backbone.Collection;
things.comparator = function (a, b) { return b - a };

// For strings
things.comparator = function (a, b) {
  if (a === b) return 0;
  return a < b ? -1 : 1;
};
wprl
la source
2

Voici une solution vraiment simple, pour ceux qui veulent simplement inverser l'ordre actuel. C'est utile si vous avez une table dont les colonnes peuvent être ordonnées dans deux directions.

collection.models = collection.models.reverse()

Vous pouvez le combiner avec quelque chose comme ceci si vous êtes intéressé par le cas de table (coffeescript):

  collection.comparator = (model) ->
    model.get(sortByType)

  collection.sort()

  if reverseOrder
    collection.models = collection.models.reverse()
Dezman
la source
2
Dans ce cas, lorsqu'un nouveau modèle est ajouté à la collection, il ne sera pas trié correctement car la méthode .set conserve les nouveaux modèles ajoutés dans l'ordre à l'aide de la méthode de tri, récupérez le résultat avant l'application de reverseOrder.
Fabio
1

Pour l'ordre inverse sur id:

comparator: function(object) { return (this.length - object.id) + 1; }
mzzl
la source
0

J'avais une exigence légèrement différente, en ce que j'affiche mes modèles dans un tableau, et je voulais pouvoir les trier par n'importe quelle colonne (propriété du modèle) et soit par ordre croissant soit par ordre décroissant.

Il a fallu beaucoup de temps pour que tous les cas fonctionnent (où les valeurs peuvent être des chaînes, des chaînes vides, des dates, des nombres. Cependant, je pense que c'est assez proche.

    inverseString: function(str)
    {
        return str.split("").map(function (letter) { return String.fromCharCode(-(letter.charCodeAt(0))); }).join("");
    }
    getComparisonValue: function (val, desc)
    {
        var v = 0;
        // Is a string
        if (typeof val === "string")
        {
            // Is an empty string, upgrade to a space to avoid strange ordering
            if(val.length === 0)
            {
                val = " ";
                return desc ? this.inverseString(val) : val;
            }                

            // Is a (string) representing a number
            v = Number(val);
            if (!isNaN(v))
            {
                return desc ? -1 * v : v;
            }
            // Is a (string) representing a date
            v = Date.parse(val);
            if (!isNaN(v))
            {
                return desc ? -1 * v : v;
            }
            // Is just a string
            return desc ? this.inverseString(val) : val;
        }
        // Not a string
        else
        {
            return desc ? -1 * val : val;
        }
    },

utilisation:

    comparator: function (item)
    {
        // Store the collumn to 'sortby' in my global model
        var property = app.collections.global.get("sortby");

        // Store the direction of the sorting also in the global model
        var desc = app.collections.global.get("direction") === "DESC";

        // perform a comparison based on property & direction
        return this.getComparisonValue(item.get(property), desc);
    },
Josh Mc
la source
0

Je viens de remplacer la fonction de tri pour inverser le tableau des modèles à la fin. J'ai également retardé le déclenchement de l'événement «sort» jusqu'à ce que l'inverse soit terminé.

    sort : function(options){

        options = options || {};

        Backbone.Collection.prototype.sort.call(this, {silent:true});
        this.models.reverse();

        if (!options.silent){
            this.trigger('sort', this, options);
        }

        return this;
    },
user2686693
la source
0

Récemment rencontré ce problème et vient de décider d'ajouter une méthode de tri.

    Collection = Backbone.Collection.extend({
            comparator:function(a, b){
                return a>b;
            },
            rsort:function(){
                var comparator = this.comparator;

                this.comparator = function(){
                    return -comparator.apply(this, arguments);
                }
                this.sort();

                this.comparator = comparator;

            }
    });

J'espère que quelqu'un trouvera cela utile

Diego Alejos
la source
0

Voici un moyen rapide et facile de trier à l'envers les modèles d'une collection à l'aide de UnderscoreJS

 var reverseCollection = _.sortBy(this.models, function(model) {
   return self.indexOf(model) * -1;
 });

John Ryan Cottam
la source