Orden de clasificación inverso con Backbone.js


89

Con Backbone.js tengo una colección configurada con una función de comparación. Está ordenando muy bien los modelos, pero me gustaría invertir el orden.

¿Cómo puedo ordenar los modelos en orden descendente en lugar de ascender?


2
Para la ordenación inversa en cadenas (incluidos los campos created_at), consulte esta respuesta a otra pregunta
Eric Hu

2
Hay soporte para comparadores de estilos de ordenación desde principios de 2012. Simplemente acepte 2 argumentos y devuelva -1, 0 o 1. github.com/documentcloud/backbone/commit/…
geon

Esto es lo que funcionó para mí (Strings and Numbers): stackoverflow.com/questions/5013819/…
FMD

Respuestas:


133

Bueno, puede devolver valores negativos del comparador. Si tomamos, por ejemplo, el ejemplo del sitio de Backbone y queremos invertir el orden, se verá así:

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
hi5 por usar el mismo código de ejemplo que el mío, y con una diferencia de 15 segundos. De ideas afines, debo decir.
DhruvPathak

Oh, por supuesto, solo un signo menos. ¡Gracias a ambos por sus rápidas respuestas!
Drew Dara-Abrams

También tenga en cuenta que (como se menciona en el sitio de Backbone) esto se rompe si cambia cualquiera de los atributos del modelo (si agrega un atributo de "longitud" al capítulo, por ejemplo). En ese caso, tendrá que llamar manualmente a "sort ()" en la Colección.
cmcculloh

9
Hay soporte para comparadores de estilos de ordenación desde principios de 2012. Simplemente acepte 2 argumentos y devuelva -1, 0 o 1. github.com/documentcloud/backbone/commit/…
geon

La respuesta de @Fasani funciona para tipos distintos de números, eche un vistazo: stackoverflow.com/questions/5013819/…
JayC

56

Personalmente, no estoy tan contento con ninguna de las soluciones que se dan aquí:

  • La solución de multiplicar por -1 no funciona cuando el tipo de ordenación no es numérico. Aunque hay formas de evitar esto (llamando, Date.getTime()por ejemplo), estos casos son específicos. Me gustaría una forma general de invertir la dirección de cualquier tipo, sin tener que preocuparme por el tipo específico de campo que se está ordenando.

  • Para la solución de cadenas, construir una cadena de un carácter a la vez parece un cuello de botella en el rendimiento, especialmente cuando se utilizan colecciones grandes (o de hecho, cadenas de campos de clasificación grandes)

Aquí hay una solución que funciona muy bien para los campos de cadena, fecha y numérico:

En primer lugar, declare un comparador que revertirá el resultado de un resultado de una función sortBy, como esta:

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

Ahora, si desea invertir la dirección del ordenamiento, todo lo que tenemos que hacer es:

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 razón por la que esto funciona es que se Backbone.Collection.sortcomporta de manera diferente si la función de clasificación tiene dos argumentos. En este caso, se comporta de la misma manera que el comparador al que se pasó Array.sort. Esta solución funciona adaptando la función sortBy de un solo argumento en un comparador de dos argumentos e invirtiendo el resultado.


7
Como la persona con la respuesta más votada a esta pregunta, realmente me gusta este enfoque y creo que debería tener más votos positivos.
Andrew De Andrade

Muchas gracias Andrew
Andrew Newdigate

Muy bien, terminé modificando esto un poco y abstrayéndolo en el prototipo de Backbone.Collection. ¡Gracias un montón!
Kendrick Ledet

46

El comparador de colecciones de Backbone.js se basa en el método Underscore.js _.sortBy. La forma en que se implementa sortBy termina "envolviendo" javascript .sort () de una manera que dificulta la clasificación de cadenas en reversa. La simple negación de la cadena termina devolviendo NaN y rompe la clasificación.

Si necesita realizar una ordenación inversa con cadenas, como una ordenación alfabética inversa, aquí hay una forma realmente pirata de hacerlo:

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

De ninguna manera es bonito, pero es un "negador de cuerdas". Si no tiene ningún reparo en modificar los tipos de objetos nativos en javascript, puede hacer que su código sea más claro extrayendo el negador de cadenas y agregándolo como método en String.Prototype. Sin embargo, probablemente solo desee hacer esto si sabe lo que está haciendo, porque la modificación de los tipos de objetos nativos en javascript puede tener consecuencias imprevistas.


Esto es muy útil. ¡Gracias Andrew!
Abel

1
No hay problema. Solo agregaría que debería preguntarse por qué está implementando un orden alfabético inverso y si tal vez existe una solución más elegante desde la perspectiva de la experiencia del usuario.
Andrew De Andrade

1
@AndrewDeAndrade Sobre por qué: Quiero ordenar una colección por el atributo 'created_at', sirviendo primero las más nuevas. Este atributo es uno de los predeterminados de Backbone y se almacena como String. Tu respuesta se acerca más a lo que quiero. ¡Gracias!
Eric Hu

Esto es increíble, he estado trabajando en una solución para esto durante la última hora.

28

Mi solución fue revertir los resultados después de ordenarlos.

Para evitar el doble renderizado, primero ordene con silencioso, luego active 'reset'.

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

2
Una mejor solución es negar el resultado del comparador.
Daniel X Moore

7
Daniel, eso no siempre es posible, por ejemplo, al ordenar cadenas o fechas.
Gavin Schulz

1
Claro que lo es. Para Date, puede hacer que el comparador regrese -Date.getTime(), posiblemente con alguna piratería adicional si cree que las fechas en su sistema cruzarán la época de Unix. Por orden alfabético, mire la respuesta de Andrew anterior.
asparagino

@DanielXMoore ¿Por qué es necesariamente una mejor solución? Es mas rapido? Personalmente, me resulta muy fácil almacenar el atributo por el que el usuario ordenó la colección por última vez, y luego revertir el orden actual si vuelve a ordenar por ese atributo; (para implementar el comportamiento de clasificación que ve en una tabla de Wikipedia, donde la dirección de clasificación se puede alternar haciendo clic repetidamente en el atributo en el encabezado de la tabla) De esa manera, mantengo limpia la lógica de mi comparador y me ahorro una operación de clasificación si cambiamos de Asc al orden de clasificación Desc
Mansiemans

13

Modifique su función de comparación para devolver un valor proporcional inverso en lugar de devolver los datos que está actualmente. Algún código de: http://documentcloud.github.com/backbone/#Collection-comparator

Ejemplo:

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

Lo siguiente funcionó bien para mí:

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

Leí en algún lugar que la función del comparador Backbone utiliza o imita la función JavaScript Array.prototype.sort (). Esto significa que utiliza 1, -1 o 0 para decidir el orden de clasificación de cada modelo de la colección. Esto se actualiza constantemente y la colección se mantiene en el orden correcto según el comparador.

Puede encontrar información sobre Array.prototype.sort () aquí: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?redirectlocale=en-US&redirectslug=JavaScript% 2FReference% 2FGlobal_Objects% 2FArray% 2Fsort

Muchas respuestas a esta pregunta simplemente invierten el orden justo antes de volver a abrirla. Esto no es ideal.

Usando el artículo anterior sobre Mozilla como guía, pude mantener mi colección ordenada en orden inverso usando el siguiente código, luego simplemente representamos la colección en la página en su orden actual y podemos usar una bandera (reverseSortDirection) para invertir el orden .

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

},

Comparador excepto los dos modelos, al cambiar la colección o modelo, la función de compartimiento se ejecuta y cambia el orden de la colección.

Probé este enfoque con Numbers and Strings y parece funcionar perfectamente para mí.

Realmente espero que esta respuesta pueda ayudar, ya que he luchado con este problema muchas veces y, por lo general, termino usando una solución alternativa.


1
Me gusta esto porque la lógica vive en la colección, pero puede configurar fácilmente sortKeyy reverseSortDirectiony llamar .sort()desde cualquier vista que tenga un identificador en la colección. Para mí, es más simple que otras respuestas con más votos.
Noah Heldman

5

Esto se puede hacer con elegancia anulando el método sortBy. Aquí hay un ejemplo

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

});

Entonces puedes usarlo así:

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

Esta solución no depende del tipo de campo.


1
¡Buena! "void 0" se puede reemplazar por "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

Aquí hay una solución realmente simple, para aquellos que simplemente quieren cambiar el orden actual. Es útil si tiene una tabla cuyas columnas se pueden ordenar en dos direcciones.

collection.models = collection.models.reverse()

Puede combinarlo con algo como esto si está interesado en el caso de la tabla (coffeescript):

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

  collection.sort()

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

2
En este caso, cuando se agrega un nuevo modelo a la colección, no se ordenará correctamente ya que el método .set mantiene los nuevos modelos agregados en orden mediante el método de ordenación, obtenga el resultado antes de que se aplique reverseOrder.
Fabio

1

Para orden inverso en id:

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

0

Tenía un requisito ligeramente diferente, ya que estoy mostrando mis modelos en una tabla y quería poder ordenarlos por cualquier columna (propiedad del modelo) y ya sea ascendente o descendente.

Se ha necesitado una buena cantidad de trabajo para que todos los casos funcionen (donde los valores pueden ser cadenas, cadenas vacías, fechas, números. Sin embargo, creo que esto está bastante cerca.

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

utilizar:

    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

Simplemente anulé la función de clasificación para revertir la matriz de modelos al finalizar. También detuve la activación del evento 'sort' hasta que se haya completado el proceso inverso.

    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

Recientemente me encontré con este problema y simplemente decidí agregar un método 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;

            }
    });

Ojalá alguien lo encuentre útil.


0

Aquí hay una manera rápida y fácil de ordenar de forma inversa los modelos de una colección usando UnderscoreJS

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.