Ordem de classificação reversa com Backbone.js

89

Com Backbone.js , tenho uma coleção configurada com uma função de comparação. É uma boa classificação dos modelos, mas gostaria de inverter a ordem.

Como posso classificar os modelos em ordem decrescente em vez de crescente?

Drew Dara-Abrams
fonte
2
Para classificação reversa em Strings (incluindo os campos created_at), veja esta resposta em outra pergunta
Eric Hu
2
Há suporte para os comparadores de estilo tipo desde o início de 2012. Apenas aceite 2 argumentos e retorno -1, 0 ou 1. github.com/documentcloud/backbone/commit/...
Geon
Isso é o que funcionou para mim (Strings and Numbers): stackoverflow.com/questions/5013819/…
FMD

Respostas:

133

Bem, você pode retornar valores negativos do comparador. Se pegarmos, por exemplo, o exemplo do site do Backbone e quisermos inverter a ordem, ficará assim:

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'));
Infeligo
fonte
3
hi5 por usar o mesmo código de exemplo que o meu!, e dentro de 15 segundos de diferença. Como eu devo dizer.
DhruvPathak de
Oh, claro, apenas um sinal de menos. Obrigado a ambos por suas respostas rápidas!
Drew Dara-Abrams em
Observe também que (como mencionado no site do Backbone) isso quebra se você alterar qualquer um dos atributos do modelo (se você adicionar um atributo "comprimento" ao capítulo, por exemplo). Nesse caso, você terá que chamar manualmente "sort ()" na coleção.
cmcculloh
9
Há suporte para os comparadores de estilo tipo desde o início de 2012. Apenas aceite 2 argumentos e retorno -1, 0 ou 1. github.com/documentcloud/backbone/commit/...
Geon
A resposta @Fasani funciona para tipos diferentes de números, por favor, dê uma olhada: stackoverflow.com/questions/5013819/…
JayC
56

Pessoalmente, não estou muito satisfeito com nenhuma das soluções fornecidas aqui:

  • A solução de multiplicação por -1 não funciona quando o tipo de classificação não é numérico. Embora existam maneiras de contornar isso (ligando, Date.getTime()por exemplo), esses casos são específicos. Gostaria de uma maneira geral de inverter a direção de qualquer tipo, sem ter que me preocupar com o tipo específico de campo que está sendo classificado.

  • Para a solução de string, construir uma string com um caractere por vez parece um gargalo de desempenho, especialmente ao usar grandes coleções (ou mesmo, grandes strings de campo de classificação)

Esta é uma solução que funciona bem para campos String, Date e Numérico:

Em primeiro lugar, declare um comparador que reverterá o resultado de um resultado de uma função sortBy, assim:

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

Agora, se você quiser inverter a direção da classificação, tudo o que precisamos fazer é:

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

O motivo pelo qual isso funciona é que Backbone.Collection.sortse comporta de maneira diferente se a função de classificação tiver dois argumentos. Nesse caso, ele se comporta da mesma maneira que o comparador passou para Array.sort. Essa solução funciona adaptando a função sortBy de argumento único em um comparador de dois argumentos e revertendo o resultado.

Andrew Newdigate
fonte
7
Como a pessoa com a resposta mais votada para esta pergunta, eu realmente gosto dessa abordagem e acho que deveria ter mais votos positivos.
Andrew De Andrade
Muito obrigado Andrew
Andrew Newdigate
Muito bom, acabei ajustando um pouco e abstraindo no protótipo do Backbone.Collection. Muitíssimo obrigado!
Kendrick Ledet
46

O comparador de coleção do Backbone.js conta com o método _.sortBy de Underscore.js. A forma como o sortBy é implementado acaba "envolvendo" o javascript .sort () de uma forma que torna difícil a classificação reversa das strings. A simples negação da string acaba retornando NaN e quebra a classificação.

Se você precisar realizar uma classificação reversa com Strings, como uma classificação alfabética reversa, aqui está uma maneira realmente hack de fazer isso:

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

Não é nada bonito, mas é um "negador de strings". Se você não tiver nenhum escrúpulo em modificar os tipos de objeto nativos em javascript, poderá tornar seu código mais claro extraindo o negador de string e adicionando-o como um método em String.Prototype. No entanto, você provavelmente só deseja fazer isso se souber o que está fazendo, porque modificar os tipos de objetos nativos em javascript pode ter consequências imprevistas.

Andrew de andrade
fonte
Isso é muito útil. Obrigado Andrew!
Abel de
1
Sem problemas. Eu apenas acrescentaria que você deve se perguntar por que está implementando uma classificação alfabética reversa e se talvez haja uma solução mais elegante do ponto de vista da experiência do usuário.
André De Andrade
1
@AndrewDeAndrade Por quê: quero classificar uma coleção pelo atributo 'created_at', servindo as mais novas primeiro. Este atributo é um dos padrões do Backbone e é armazenado como String. Sua resposta chega mais perto do que eu quero. Obrigado!
Eric Hu,
Isso é incrível, estou trabalhando em uma solução para isso há uma hora.
28

Minha solução foi reverter os resultados após a classificação.

Para evitar a renderização dupla, primeiro classifique com silencioso e, em seguida, acione 'reset'.

collections.sort({silent:true})
collections.models = collections.models.reverse();
collections.trigger('reset', collections, {});
Tomasz Trela
fonte
2
Uma solução melhor é negar o resultado do comparador.
Daniel X Moore
7
Daniel, isso nem sempre é possível, por exemplo ao ordenar strings ou datas.
Gavin Schulz
1
Claro que é. Para Date, você pode ter o retorno do comparador -Date.getTime(), possivelmente com algum hacking extra se você acredita que as datas em seu sistema irão cruzar a época unix. Para a ordem alfabética, veja a resposta de Andrew acima.
asparagino
@DanielXMoore Por que essa é necessariamente uma solução melhor? É mais rápido? Pessoalmente, acho muito fácil armazenar o atributo pelo qual o usuário classificou a coleção pela última vez e, em seguida, reverter a classificação atual se ele classificar novamente por esse atributo; (para implementar o comportamento de classificação que você vê em uma tabela da Wikipedia, onde a direção da classificação pode ser alternada clicando repetidamente no atributo no cabeçalho da tabela) Dessa forma, eu mantenho minha lógica de comparação limpa e economizo uma operação de classificação se mudarmos de Asc para ordem de classificação Desc
Mansiemans
13

Modifique sua função de comparador para retornar algum valor proporcional inverso em vez de retornar os dados que você está atualmente. Algum código de: http://documentcloud.github.com/backbone/#Collection-comparator

Exemplo:

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
fonte
9

O seguinte funcionou bem para mim:

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
fonte
8

Eu li alguns em que a função de comparador de Backbone usa ou imita a função JavaScript Array.prototype.sort (). Isso significa que ele usa 1, -1 ou 0 para decidir a ordem de classificação de cada modelo na coleção. Isso é atualizado constantemente e a coleção permanece na ordem correta com base no comparador.

Informações sobre Array.prototype.sort () podem ser encontradas aqui: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?redirectlocale=en-US&redirectslug=JavaScript% 2FReference% 2FGlobal_Objects% 2FArray% 2Fsort

Muitas respostas a essa pergunta simplesmente invertem a ordem antes de enviá-la para a página. Isso não é ideal.

Usando o artigo acima no Mozilla como guia, consegui manter minha coleção classificada em ordem reversa usando o código a seguir, então simplesmente renderizamos a coleção na página em sua ordem atual e podemos usar um sinalizador (reverseSortDirection) para inverter a ordem .

//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 exceto dois modelos, nas alterações na coleção ou no modelo, a função de compartimento executa e altera a ordem da coleção.

Eu testei essa abordagem com Numbers e Strings e parece estar funcionando perfeitamente para mim.

Eu realmente espero que esta resposta possa ajudar, já que lutei com esse problema muitas vezes e geralmente acabo usando uma solução alternativa.

FMD
fonte
1
I como este, porque a lógica vive na coleção, mas você pode facilmente configurar sortKeye reverseSortDirectione chamada .sort()a partir de qualquer ponto de vista que tem uma alça sobre a coleção. Mais simples do que outras respostas com maior votação, para mim.
Noah Heldman
5

Isso pode ser feito elegantemente substituindo o método sortBy. Aqui está um exemplo

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

});

Então você pode usá-lo assim:

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 solução não depende do tipo de campo.

Aman Mahajan
fonte
1
Um bom ! "void 0" pode ser substituído 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;
};
wprl
fonte
2

Aqui está uma solução realmente simples, para aqueles que simplesmente desejam inverter o pedido atual. É útil se você tiver uma tabela cujas colunas podem ser ordenadas em duas direções.

collection.models = collection.models.reverse()

Você pode combiná-lo com algo assim se estiver interessado no caso da mesa (coffeescript):

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

  collection.sort()

  if reverseOrder
    collection.models = collection.models.reverse()
dezman
fonte
2
Nesse caso, quando um novo modelo é adicionado à coleção, ele não será classificado corretamente, pois o método .set mantém os novos modelos adicionados em ordem usando o método de classificação, obtenha o resultado antes de o reverseOrder ser aplicado.
Fabio
1

Para ordem reversa no id:

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

Eu tinha um requisito um pouco diferente, no sentido de que estou exibindo meus modelos em uma tabela e queria poder classificá-los por qualquer coluna (propriedade do modelo) e ascendente ou descendente.

Demorou bastante para fazer todos os casos funcionarem (onde os valores podem ser strings, strings vazias, datas, números. No entanto, acho que isso está bem próximo.

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

usar:

    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
fonte
0

Acabei de substituir a função de classificação para reverter a matriz de modelos após a conclusão. Também evitei o acionamento do evento 'sort' até que o reverso fosse concluído.

    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
fonte
0

Recentemente tive esse problema e decidi adicionar um 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;

            }
    });

Espero que alguém ache isso útil

Diego alejos
fonte
0

Esta é uma maneira rápida e fácil de reverter a classificação dos modelos de uma coleção usando UnderscoreJS

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

John Ryan Cottam
fonte