Зворотний порядок сортування за допомогою Backbone.js


89

З Backbone.js у мене створена колекція з функцією порівняння. Це чудово сортує моделі, але я хотів би змінити порядок.

Як я можу сортувати моделі за спаданням, а не за зростанням?


2
Для зворотного сортування на рядки (включаючи поля created_at) див. Цю відповідь на інше питання
Eric Hu

2
З початку 2012 року існує підтримка компараторів у стилі сортування. Просто прийміть 2 аргументи та поверніть -1, 0 або 1. github.com/documentcloud/backbone/commit/…
geon

Це те, що у мене спрацювало (Рядки та цифри): stackoverflow.com/questions/5013819/…
Ящур

Відповіді:


133

Ну, ви можете повернути негативні значення з компаратора. Якщо ми візьмемо, наприклад, приклад із сайту Backbone і хочемо змінити порядок, це буде виглядати так:

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 за використання того самого прикладу коду, що і мій!, і різниця протягом 15 секунд. Думаючи, я повинен сказати.
DhruvPathak

О, звичайно, просто знак мінус. Дякую вам обом за швидкі відповіді!
Drew Dara-Abrams

Також зауважте, що (як згадувалося на сайті Backbone) це порушується, якщо ви змінюєте будь-який з атрибутів моделі (якщо ви додаєте атрибут "length", наприклад, до розділу). У цьому випадку вам доведеться вручну викликати "sort ()" у колекції.
cmcculloh

9
З початку 2012 року підтримується порівняння стилів сортування. Просто прийміть 2 аргументи та поверніть -1, 0 або 1. github.com/documentcloud/backbone/commit/…
geon

@Fasani відповідь працює для інших , ніж числа типів, будь ласка, подивіться: stackoverflow.com/questions/5013819 / ...
JayC

56

Особисто я не дуже задоволений будь-яким із наведених тут рішень:

  • Рішення множення на -1 не працює, коли тип сортування нечисловий. Хоча існують способи обійти це (наприклад, зателефонувавши Date.getTime()), ці випадки є специфічними. Я хотів би загального способу змінити напрямок будь-якого виду, не турбуючись про конкретний тип поля, що сортується.

  • Для рішучого рішення побудова рядка по одному символу здається вузьким місцем продуктивності, особливо при використанні великих колекцій (чи справді великих рядків поля сортування)

Ось рішення, яке чудово працює для полів String, Date та Numeric:

По-перше, оголосіть компаратор, який переверне результат результату функції sortBy, наприклад:

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

Тепер, якщо ви хочете змінити напрямок сортування, все, що нам потрібно зробити, це:

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

Причиною цього є те, що Backbone.Collection.sortповодиться по-різному, якщо функція сортування має два аргументи. У цьому випадку він поводиться так само, як і переданий компаратор Array.sort. Це рішення працює шляхом адаптації функції одного аргументу sortBy до порівняльника двох аргументів та реверсування результату.


7
Мені, як людині, яка має найбільше голосів відповідей на це питання, дуже подобається такий підхід і вважаю, що він повинен мати більше голосів.
Ендрю Де Андраде

Щиро дякую, Ендрю
Ендрю Ньюдігейт

Дуже приємно, я в кінцевому підсумку трохи змінив це і абстрагував його в прототипі Backbone.Collection. Спасибі купу!
Кендрік Ледет,

46

Порівняльник колекцій Backbone.js спирається на метод Underscore.js _.sortBy. Спосіб реалізації sortBy закінчується "загортанням" javascript .sort () таким чином, що ускладнює сортування рядків у зворотному напрямку. Просте заперечення рядка повертає NaN і розбиває сортування.

Якщо вам потрібно виконати зворотне сортування за допомогою рядків, наприклад зворотне алфавітне сортування, ось справді хакерський спосіб зробити це:

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

Це далеко не красиво, але це "рядовий негагатор". Якщо у вас немає жодних сумнівів щодо модифікації власних типів об’єктів у javascript, ви можете зробити код чіткішим, витягнувши рядок negator і додавши його як метод у String.Prototype. Однак ви, мабуть, хочете зробити це, лише якщо знаєте, що робите, оскільки зміна власних типів об’єктів у javascript може мати непередбачені наслідки.


Це дуже зручно. Дякую Андрію!
Абель

1
Без проблем. Я хотів би лише додати, що ви повинні запитати себе, чому ви застосовуєте зворотний алфавітний сорт і чи існує, можливо, більш елегантне рішення з точки зору користувацького досвіду.
Ендрю Де Андраде

1
@AndrewDeAndrade Про те, чому: я хочу відсортувати колекцію за атрибутом 'created_at', спочатку подаючи найновіші. Цей атрибут є одним із типових для Backbone і зберігається як String. Ваша відповідь наближається до того, що я хочу. Дякую!
Eric Hu

Це дивовижно, ось уже останню годину ми працюємо над вирішенням цього питання.

28

Моє рішення було змінити результати після сортування.

Щоб запобігти подвійному візуалізації, спочатку сортуйте за допомогою безшумного, а потім запускайте "скидання".

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

2
Кращим рішенням є заперечення результату порівняння.
Даніель X Мур

7
Даніелю, це не завжди можливо, наприклад, при сортуванні рядків або дат.
Gavin Schulz,

1
Звичайно, це так. Для дати ви можете повернути компаратор -Date.getTime(), можливо, з додатковим злому, якщо ви вважаєте, що дати у вашій системі перейдуть епоху unix. Для алфавіту зверніться до відповіді Ендрю вище.
аспарагіно

@DanielXMoore Чому це обов'язково краще рішення? Це швидше? Особисто мені дуже легко зберігати атрибут, за яким користувач востаннє сортував колекцію, а потім змінити поточне сортування, якщо він знову сортує за цим атрибутом; (для реалізації поведінки сортування, яке ви бачите в таблиці Вікіпедії, де напрямок сортування можна перемикати, багаторазово клацаючи атрибут у заголовку таблиці) Таким чином, я зберігаю свою логіку порівняння чистою і зберігаю собі операцію сортування, якщо ми переходимо з Asc до порядку сортування Desc
Mansiemans

13

Змініть функцію порівняння, щоб повернути якесь пропорційно пропорційне значення, замість того, щоб повертати дані, якими ви зараз є. Деякий код з: http://documentcloud.github.com/backbone/#Collection-comparator

Приклад:

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

Для мене добре працювали:

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

Я прочитав десь, де функція порівняння Backbone або використовує, або імітує функцію JavaScript Array.prototype.sort (). Це означає, що для використання порядку сортування кожної моделі в колекції використовується значення 1, -1 або 0. Це постійно оновлюється, і колекція залишається у правильному порядку на основі компаратора.

Інформацію про Array.prototype.sort () можна знайти тут: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?redirectlocale=en-US&redirectslug=JavaScript% 2FReference% 2FGlobal_Objects% 2FArray% 2Fsort

Багато відповідей на це питання просто змінюють порядок перед тим, як відтворити його на сторінці. Це не ідеально.

Використовуючи вищевказану статтю про Mozilla як керівництво, я зміг відсортувати свою колекцію в зворотному порядку, використовуючи наступний код, тоді ми просто робимо колекцію на сторінці в поточному порядку, і ми можемо використовувати прапор (reverseSortDirection) для сторнування порядку .

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

},

Порівняльник, за винятком двох моделей, при змінах колекції або моделі запускається функція compartor і змінюється порядок колекції.

Я перевірив цей підхід з Numbers and Strings, і, здається, він ідеально працює для мене.

Я дуже сподіваюся, що ця відповідь може допомогти, оскільки я вже багато разів боровся з цією проблемою, і зазвичай в кінцевому підсумку використовую роботу.


1
Мені подобається це , тому що логіка живе в колекції, але ви можете легко встановити sortKeyі reverseSortDirectionі виклик .sort()з будь-якої точки зору , яка має ручку колекції. Мені простіше, ніж інші голосові відповіді.
Ной Гельдман

5

Це можна зробити елегантно, перевизначивши метод sortBy. Ось приклад

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

});

Тож ви можете використовувати його так:

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

Це рішення не залежить від типу поля.


1
Хороший ! "void 0" можна замінити на "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

Ось справді просте рішення для тих, хто просто хоче змінити поточне замовлення. Це корисно, якщо у вас є таблиця, стовпці якої можна впорядкувати в двох напрямках.

collection.models = collection.models.reverse()

Ви можете поєднати це з чимось подібним, якщо вас цікавить корпус таблиці (coffeescript):

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

  collection.sort()

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

2
У цьому випадку, коли до колекції додається нова модель, вона не буде сортуватися правильно, оскільки метод .set підтримує нові додані моделі в порядку за допомогою методу сортування, отримуйте результат до того, як застосовується reverseOrder.
Фабіо

1

Для зворотного порядку на ідентифікаторі:

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

0

У мене була дещо інша вимога, оскільки я відображаю свої моделі в таблиці, і я хотів мати можливість сортувати їх за будь-яким стовпцем (властивість моделі) і за зростанням, і за спаданням.

Для того, щоб спрацювати всі справи (де значення можуть бути рядками, порожніми рядками, датами, числами, знадобилося чимало муру), проте я думаю, що це досить близько.

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

використання:

    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

Я просто замінив функцію сортування, щоб повернути масив моделей після завершення. Також я утримував спрацьовування події "сортування", поки не буде завершено зворотне.

    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

Нещодавно натрапив на цю проблему і просто вирішив додати метод 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;

            }
    });

Сподіваємось, комусь це стане в нагоді


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.