Odwróć kolejność sortowania za pomocą Backbone.js


89

W Backbone.js mam zestaw skonfigurowany z funkcją komparatora. Ładnie sortuje modele, ale chciałbym odwrócić kolejność.

Jak mogę sortować modele w porządku malejącym, a nie rosnącym?


2
Aby uzyskać informacje o odwrotnym sortowaniu według ciągów znaków (w tym pól created_at), zobacz tę odpowiedź na inne pytanie
Eric Hu

2
Jest wsparcie dla komparatorów rodzaj stylu od początku 2012. Wystarczy zaakceptować 2 argumenty i zwracają -1, 0 lub 1. github.com/documentcloud/backbone/commit/...
GEON

To właśnie zadziałało dla mnie (ciągi znaków i liczby): stackoverflow.com/questions/5013819/ ...
Fasani

Odpowiedzi:


133

Cóż, możesz zwrócić wartości ujemne z komparatora. Jeśli weźmiemy na przykład przykład ze strony Backbone i będziemy chcieli odwrócić kolejność, będzie to wyglądać tak:

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 za użycie tego samego przykładowego kodu co mój! i różnica 15 sekund. Muszę powiedzieć, że podobnie myślący.
DhruvPathak,

Och, oczywiście, tylko znak minus. Dziękuję obu za szybkie odpowiedzi!
Drew Dara-Abrams

Zauważ również, że (jak wspomniano na stronie Backbone) to zepsuje się, jeśli zmienisz którykolwiek z atrybutów modelu (na przykład jeśli dodasz atrybut „długość” do rozdziału). W takim przypadku będziesz musiał ręcznie wywołać „sort ()” w kolekcji.
cmcculloh

9
Jest wsparcie dla komparatorów rodzaj stylu od początku 2012. Wystarczy zaakceptować 2 argumenty i zwracają -1, 0 lub 1. github.com/documentcloud/backbone/commit/...
GEON

Odpowiedź @Fasani działa dla typów innych niż liczby, spójrz: stackoverflow.com/questions/5013819/ ...
JayC,

56

Osobiście nie jestem zadowolony z żadnego z podanych tutaj rozwiązań:

  • Rozwiązanie mnożenia przez -1 nie działa, gdy typ sortowania jest nieliczbowy. Chociaż istnieją sposoby obejścia tego ( Date.getTime()na przykład dzwoniąc ), te przypadki są specyficzne. Chciałbym mieć ogólny sposób na odwrócenie kierunku dowolnego rodzaju, bez martwienia się o konkretny typ sortowanego pola.

  • W przypadku rozwiązania w postaci ciągów tworzenie ciągu znaków po jednym znaku na raz wydaje się wąskim gardłem wydajności, szczególnie w przypadku korzystania z dużych kolekcji (lub rzeczywiście, dużych ciągów pól sortowania)

Oto rozwiązanie, które działa dobrze w przypadku pól typu String, Date i Numeric:

Najpierw zadeklaruj komparator, który odwróci wynik wyniku funkcji sortBy, na przykład:

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

Teraz, jeśli chcesz odwrócić kierunek tego rodzaju, wszystko, co musimy zrobić, to:

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

Powodem tego jest to, że Backbone.Collection.sortzachowuje się inaczej, jeśli funkcja sortowania ma dwa argumenty. W tym przypadku zachowuje się tak samo, jak przeszedł komparator Array.sort. To rozwiązanie działa poprzez dostosowanie funkcji sortBy z jednym argumentem do komparatora z dwoma argumentami i odwrócenie wyniku.


7
Jako osoba z największą pozytywną odpowiedzią na to pytanie, bardzo podoba mi się to podejście i uważam, że powinno mieć więcej głosów za.
Andrew De Andrade

Dziękuję bardzo Andrew
Andrew Newdigate

Bardzo fajnie, skończyło się na tym, że trochę to poprawiłem i wyabstrahowałem w prototypie Backbone.Collection. Wielkie dzięki!
Kendrick Ledet

46

Komparator kolekcji Backbone.js opiera się na metodzie Underscore.js _.sortBy. Sposób implementacji sortBy kończy się „zawijaniem” javascript .sort () w sposób, który utrudnia odwrotne sortowanie ciągów. Prosta negacja łańcucha kończy się zwróceniem NaN i zerwaniem sortowania.

Jeśli chcesz wykonać odwrotne sortowanie za pomocą ciągów, na przykład odwrotne sortowanie alfabetyczne, oto naprawdę hakerski sposób:

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

W żadnym wypadku nie jest ładny, ale jest to „negator stringów”. Jeśli nie masz żadnych problemów z modyfikowaniem natywnych typów obiektów w javascript, możesz uczynić kod bardziej przejrzystym, wyodrębniając negator ciągu i dodając go jako metodę w String.Prototype. Jednak prawdopodobnie chcesz to zrobić tylko wtedy, gdy wiesz, co robisz, ponieważ modyfikowanie natywnych typów obiektów w javascript może mieć nieprzewidziane konsekwencje.


To jest bardzo przydatne. Dzięki Andrew!
Abel

1
Nie ma problemu. Dodam tylko, że powinieneś zadać sobie pytanie, dlaczego wdrażasz sortowanie w odwrotnej kolejności alfabetycznej i czy istnieje być może bardziej eleganckie rozwiązanie z punktu widzenia doświadczenia użytkownika.
Andrew De Andrade

1
@AndrewDeAndrade Dlaczego: chcę posortować kolekcję według atrybutu „created_at”, obsługując najpierw najnowsze. Ten atrybut jest jednym z domyślnych atrybutów Backbone i jest przechowywany jako String. Twoja odpowiedź jest najbliższa temu, czego chcę. Dzięki!
Eric Hu

To niesamowite, od godziny pracuję nad rozwiązaniem tego problemu.

28

Moim rozwiązaniem było odwrócenie wyników po sortowaniu.

Aby zapobiec podwójnemu renderowaniu, najpierw posortuj z wyciszeniem, a następnie uruchom „reset”.

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

2
Lepszym rozwiązaniem jest zanegowanie wyniku komparatora.
Daniel X Moore

7
Daniel, nie zawsze jest to możliwe, na przykład podczas sortowania ciągów lub dat.
Gavin Schulz,

1
Jasne, że tak. W przypadku Date możesz zwrócić komparator -Date.getTime(), być może z dodatkowym hakowaniem, jeśli uważasz, że daty w twoim systemie przekroczą epokę uniksową. Jeśli chodzi o alfabet, spójrz na odpowiedź Andrzeja powyżej.
asparagino

@DanielXMoore Dlaczego to koniecznie lepsze rozwiązanie? Czy to jest szybsze? Osobiście uważam, że bardzo łatwo jest zapisać atrybut, według którego użytkownik ostatnio posortował kolekcję, a następnie odwrócić bieżące sortowanie, jeśli ponownie sortuje według tego atrybutu; (aby zaimplementować zachowanie sortowania, które widzisz w tabeli Wikipedii, gdzie kierunek sortowania można przełączać, kilkakrotnie klikając atrybut w nagłówku tabeli) W ten sposób utrzymuję logikę komparatora w czystości i zachowuję operację sortowania, jeśli przełączymy się z Asc to Desc sort order
Mansiemans

13

Zmodyfikuj funkcję komparatora, aby zamiast zwracać aktualne dane, zwracała pewną odwrotnie proporcjonalną wartość. Jakiś kod z: http://documentcloud.github.com/backbone/#Collection-comparator

Przykład:

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

Poniższe działały dobrze dla mnie:

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

Czytałem, że funkcja komparatora Backbone wykorzystuje lub naśladuje funkcję JavaScript Array.prototype.sort (). Oznacza to, że używa 1, -1 lub 0, aby zdecydować o kolejności sortowania każdego modelu w kolekcji. Jest to stale aktualizowane, a kolekcja pozostaje we właściwej kolejności na podstawie komparatora.

Informacje o Array.prototype.sort () można znaleźć tutaj: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?redirectlocale=en-US&redirectslug=JavaScript% 2FReference% 2FGlobal_Objects% 2FArray% 2Fsort

Wiele odpowiedzi na to pytanie po prostu odwraca kolejność tuż przed wyświetleniem go na stronie. To nie jest idealne.

Korzystając z powyższego artykułu o Mozilli jako przewodnika, byłem w stanie posortować moją kolekcję w odwrotnej kolejności za pomocą następującego kodu, następnie po prostu renderujemy kolekcję na stronę w jej bieżącej kolejności i możemy użyć flagi (reverseSortDirection) do odwrócenia kolejności .

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

},

Komparator z wyjątkiem dwóch modeli, po zmianach w kolekcji lub modelu działa funkcja compartor i zmienia kolejność kolekcji.

Przetestowałem to podejście z liczbami i ciągami i wydaje się, że działa idealnie dla mnie.

Naprawdę mam nadzieję, że ta odpowiedź może pomóc, ponieważ wiele razy zmagałem się z tym problemem i zwykle używam obejścia.


1
I tak, ponieważ logika mieszka w kolekcji, ale można z łatwością ustawić sortKeyi reverseSortDirectioni połączenia .sort()z dowolnym widoku, który ma uchwyt na zbiorach. Dla mnie prostsze niż inne odpowiedzi, które uzyskały wyższą liczbę głosów.
Noah Heldman

5

Można to zrobić elegancko, zastępując metodę sortBy. Oto przykład

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

});

Możesz więc używać tego w ten sposób:

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

To rozwiązanie nie zależy od typu pola.


1
Dobry ! „void 0” można zastąpić „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

Oto naprawdę proste rozwiązanie dla tych, którzy chcą po prostu odwrócić obecną kolejność. Jest to przydatne, jeśli masz tabelę, której kolumny można uporządkować w dwóch kierunkach.

collection.models = collection.models.reverse()

Możesz połączyć to z czymś takim, jeśli interesuje Cię etui na stół (coffeescript):

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

  collection.sort()

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

2
W takim przypadku, gdy nowy model zostanie dodany do kolekcji, nie zostanie on poprawnie posortowany, ponieważ metoda .set zachowuje kolejność nowych dodanych modeli za pomocą metody sortowania, uzyskując wynik sprzed zastosowania reverseOrder.
Fabio

1

W odwrotnej kolejności na id:

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

0

Miałem nieco inne wymaganie, ponieważ wyświetlam moje modele w tabeli i chciałem mieć możliwość sortowania ich według dowolnej kolumny (właściwości modelu) i rosnąco lub malejąco.

Zajęło sporo czasu, aby wszystkie przypadki działały (gdzie wartościami mogą być łańcuchy, puste łańcuchy, daty, liczby. Jednak myślę, że jest to dość blisko).

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

posługiwać się:

    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

Po prostu nadpisałem funkcję sortowania, aby odwrócić tablicę modeli po zakończeniu. Ponadto wstrzymałem wywołanie zdarzenia „sort” do czasu zakończenia operacji odwrotnej.

    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

Niedawno napotkałem ten problem i po prostu zdecydowałem się dodać metodę 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;

            }
    });

Mam nadzieję, że ktoś uzna to za przydatne


0

Oto szybki i łatwy sposób odwrotnego sortowania modeli kolekcji za pomocą UnderscoreJS

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.