Ordinamento inverso con Backbone.js


89

Con Backbone.js ho una raccolta impostata con una funzione di confronto. Sta ordinando bene i modelli, ma vorrei invertire l'ordine.

Come posso ordinare i modelli in ordine decrescente anziché crescente?


2
Per l'ordinamento inverso sulle stringhe (inclusi i campi created_at), vedere questa risposta su un'altra domanda
Eric Hu

2
V'è il supporto per i comparatori sorta di stile a partire dall'inizio del 2012. Proprio accetta 2 argomenti e ritorno -1, 0 o 1. github.com/documentcloud/backbone/commit/...
Geon

Questo è ciò che ha funzionato per me (stringhe e numeri): stackoverflow.com/questions/5013819/…
FMD

Risposte:


133

Bene, puoi restituire valori negativi dal comparatore. Se prendiamo, ad esempio, l'esempio dal sito di Backbone e vogliamo invertire l'ordine, sarà simile a questo:

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'));

3
ciao5 per aver utilizzato lo stesso codice di esempio del mio! ed entro 15 secondi di differenza. Di mentalità simile devo dire.
DhruvPathak

Oh, ovviamente, solo un segno meno. Grazie ad entrambi per le vostre risposte rapide!
Drew Dara-Abrams,

Nota anche che (come menzionato sul sito di Backbone) questo si interrompe se cambi uno qualsiasi degli attributi del modello (se aggiungi un attributo "lunghezza" al capitolo per esempio). In questo caso dovrai chiamare manualmente "sort ()" sulla Collection.
cmcculloh

9
È disponibile il supporto per i comparatori in stile ordinamento dall'inizio del 2012. Accetta solo 2 argomenti e restituisci -1, 0 o 1. github.com/documentcloud/backbone/commit/…
geon

La risposta di @Fasani funziona per tipi diversi dai numeri, dai un'occhiata: stackoverflow.com/questions/5013819/…
JayC

56

Personalmente, non sono molto soddisfatto di nessuna delle soluzioni fornite qui:

  • La soluzione moltiplicando per -1 non funziona quando il tipo di ordinamento non è numerico. Sebbene ci siano modi per aggirare questo problema (chiamando Date.getTime()ad esempio) questi casi sono specifici. Vorrei un modo generale per invertire la direzione di qualsiasi tipo, senza doversi preoccupare del tipo specifico di campo da ordinare.

  • Per la soluzione stringa, la costruzione di una stringa un carattere alla volta sembra un collo di bottiglia delle prestazioni, specialmente quando si utilizzano raccolte di grandi dimensioni (o addirittura stringhe di campi di ordinamento di grandi dimensioni)

Ecco una soluzione che funziona bene per i campi String, Date e Numeric:

In primo luogo, dichiara un comparatore che invertirà il risultato di un risultato di una funzione sortBy, in questo modo:

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

Ora, se vuoi invertire la direzione dell'ordinamento, tutto ciò che dobbiamo fare è:

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'));

Il motivo per cui funziona è che Backbone.Collection.sortsi comporta in modo diverso se la funzione di ordinamento ha due argomenti. In questo caso si comporta come il comparatore passato a Array.sort. Questa soluzione funziona adattando la funzione sortBy a un argomento in un comparatore a due argomenti e invertendo il risultato.


7
Essendo la persona con la risposta più votata a questa domanda, mi piace molto questo approccio e penso che dovrebbe avere più voti positivi.
Andrew De Andrade

Grazie mille Andrew
Andrew Newdigate

Molto bello, ho finito per modificare un po 'questo aspetto e astrarlo nel prototipo di Backbone.Collection. Grazie mille!
Kendrick Ledet

46

Il comparatore di raccolte di Backbone.js si basa sul metodo Underscore.js _.sortBy. Il modo in cui viene implementato sortBy finisce per "avvolgere" javascript .sort () in un modo che rende difficile l'ordinamento delle stringhe al contrario. La semplice negazione della stringa finisce per restituire NaN e interrompe l'ordinamento.

Se devi eseguire un ordinamento inverso con le stringhe, come l'ordinamento alfabetico inverso, ecco un modo davvero hacker per farlo:

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

Non è affatto carino, ma è un "negatore di stringhe". Se non hai problemi con la modifica dei tipi di oggetti nativi in ​​javascript, potresti rendere il codice più chiaro estraendo il negatore di stringa e aggiungendolo come metodo su String.Prototype. Tuttavia, probabilmente vuoi farlo solo se sai cosa stai facendo, perché la modifica dei tipi di oggetti nativi in ​​javascript può avere conseguenze impreviste.


Questo è molto utile. Grazie Andrew!
Abel

1
Nessun problema. Vorrei solo aggiungere che dovresti chiederti perché stai implementando un ordinamento alfabetico inverso e se esiste forse una soluzione più elegante dal punto di vista dell'esperienza utente.
Andrew De Andrade

1
@AndrewDeAndrade Sul perché: voglio ordinare una raccolta in base all'attributo 'created_at', servendo prima quelle più recenti. Questo attributo è uno di quelli predefiniti di Backbone ed è memorizzato come String. La tua risposta si avvicina di più a quello che voglio. Grazie!
Eric Hu

È incredibile, ormai da un'ora sto lavorando a una soluzione.

28

La mia soluzione era invertire i risultati dopo l'ordinamento.

Per evitare il doppio rendering, prima ordina con silent, quindi attiva 'reset'.

collections.sort({silent:true})
collections.models = collections.models.reverse();
collections.trigger('reset', collections, {});

2
Una soluzione migliore è negare il risultato del confronto.
Daniel X Moore

7
Daniel, non è sempre possibile, ad esempio quando si ordinano stringhe o date.
Gavin Schulz

1
Certo che lo e. Per Date puoi fare in modo che il comparatore ritorni -Date.getTime(), possibilmente con qualche hack in più se ritieni che le date nel tuo sistema attraverseranno l'epoca unix. Per alfabetico, guarda la risposta di Andrew sopra.
asparagino

@DanielXMoore Perché è necessariamente una soluzione migliore? È più veloce? Personalmente trovo molto facile memorizzare l'attributo in base al quale l'utente ha ordinato la raccolta per ultimo, quindi invertire l'ordinamento corrente se ordina nuovamente in base a quell'attributo; (per implementare il comportamento di ordinamento che vedi in una tabella di Wikipedia, dove la direzione di ordinamento può essere attivata facendo clic ripetutamente sull'attributo nell'intestazione della tabella) In questo modo mantengo pulita la mia logica di confronto e mi salvo un'operazione di ordinamento se passiamo da Asc a Desc ordinamento
Mansiemans

13

Modifica la tua funzione di confronto per restituire un valore proporzionale inverso invece di restituire i dati che sei attualmente. Un po 'di codice da: http://documentcloud.github.com/backbone/#Collection-comparator

Esempio:

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

9

Quanto segue ha funzionato bene per me:

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

8

Ho letto alcuni in cui la funzione di confronto Backbone utilizza o imita la funzione JavaScript Array.prototype.sort (). Ciò significa che utilizza 1, -1 o 0 per decidere l'ordinamento di ciascun modello nella raccolta. Questo si aggiorna costantemente e la raccolta rimane nell'ordine corretto in base al comparatore.

Informazioni su Array.prototype.sort () possono essere trovate qui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?redirectlocale=en-US&redirectslug=JavaScript% 2FReference% 2FGlobal_Objects% 2FArray% 2Fsort

Molte risposte a questa domanda invertono semplicemente l'ordine appena prima di renderlo alla pagina. Questo non è l'ideale.

Usando l'articolo precedente su Mozilla come guida, sono stato in grado di mantenere la mia raccolta ordinata in ordine inverso utilizzando il seguente codice, quindi eseguiamo semplicemente il rendering della raccolta sulla pagina nell'ordine corrente e possiamo utilizzare un flag (reverseSortDirection) per invertire l'ordine .

//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;
      }

},

Comparatore tranne i due modelli, in caso di modifiche alla raccolta o al modello la funzione compartor viene eseguita e cambia l'ordine della raccolta.

Ho testato questo approccio con Numbers e Strings e sembra funzionare perfettamente per me.

Spero davvero che questa risposta possa aiutare poiché ho lottato con questo problema molte volte e di solito finisco per usare un rimedio.


1
Mi piace questo perché la logica vive nella raccolta, ma si può facilmente impostare sortKeye reverseSortDirectione la chiamata .sort()da qualsiasi opinione che ha una maniglia sulla raccolta. Più semplice di altre risposte con voti alti, per me.
Noah Heldman

5

Questo può essere fatto elegantemente sovrascrivendo il metodo sortBy. Ecco un esempio

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

});

Quindi puoi usarlo in questo modo:

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();

Questa soluzione non dipende dal tipo di campo.


1
Buono! "void 0" può essere sostituito da "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;
};

2

Ecco una soluzione davvero semplice, per chi vuole semplicemente capovolgere l'ordine corrente. È utile se hai una tabella le cui colonne possono essere ordinate in due direzioni.

collection.models = collection.models.reverse()

Puoi combinarlo con qualcosa del genere se sei interessato al case da tavolo (coffeescript):

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

  collection.sort()

  if reverseOrder
    collection.models = collection.models.reverse()

2
In questo caso, quando un nuovo modello viene aggiunto alla raccolta, non verrà ordinato correttamente poiché il metodo .set mantiene in ordine i nuovi modelli aggiunti utilizzando il metodo di ordinamento, ottenendo il risultato da prima dell'applicazione di reverseOrder.
Fabio

1

Per ordine inverso su ID:

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

0

Avevo un requisito leggermente diverso, in quanto sto visualizzando i miei modelli in una tabella e volevo essere in grado di ordinarli in base a qualsiasi colonna (proprietà del modello) e ascendente o discendente.

Ci è voluto un bel po 'di confusione per far funzionare tutti i casi (dove i valori possono essere stringhe, stringhe vuote, date, numeri. Tuttavia penso che questo sia abbastanza vicino.

    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;
        }
    },

uso:

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

0

Ho appena sovrascritto la funzione di ordinamento per invertire l'array dei modelli al termine. Inoltre ho tenuto a bada l'attivazione dell'evento "sort" fino a quando il contrario non è stato completato.

    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;
    },

0

Recentemente ho riscontrato questo problema e ho deciso di aggiungere un metodo rsort.

    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;

            }
    });

Si spera che qualcuno lo trovi utile


0

Ecco un modo semplice e veloce per ordinare al contrario i modelli di una raccolta utilizzando UnderscoreJS

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.