Обратный порядок сортировки с Backbone.js

89

С Backbone.js у меня есть коллекция с функцией компаратора. Он хорошо сортирует модели, но я бы хотел изменить порядок.

Как я могу отсортировать модели по убыванию, а не по возрастанию?

Дрю Дара-Абрамс
источник
2
Для обратной сортировки по строкам (включая поля created_at) см. Этот ответ по другому вопросу
Эрик Ху
2
Компараторы в стиле сортировки поддерживаются с начала 2012 года. Просто примите 2 аргумента и верните -1, 0 или 1. github.com/documentcloud/backbone/commit/…
geon
Это то, что сработало для меня (строки и числа): stackoverflow.com/questions/5013819/…
Fasani

Ответы:

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
Ну конечно, просто минус. Спасибо вам обоим за ваши быстрые ответы!
Дрю Дара-Абрамс
Также обратите внимание, что (как упоминалось на сайте Backbone) это нарушается, если вы измените любой из атрибутов модели (например, если вы добавите атрибут «длина» в главу). В этом случае вам придется вручную вызвать 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 использует метод _.sortBy в Underscore.js. Реализация 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, вы можете сделать код более понятным, извлекая строковый отрицатель и добавив его как метод в String.Prototype. Однако вы, вероятно, захотите сделать это, только если знаете, что делаете, потому что изменение типов собственных объектов в javascript может иметь непредвиденные последствия.

Эндрю Де Андраде
источник
Это очень удобно. Спасибо, Андрей!
Авель
1
Без проблем. Я бы просто добавил, что вы должны спросить себя, почему вы реализуете обратную сортировку по алфавиту и есть ли, возможно, более элегантное решение с точки зрения пользовательского опыта.
Эндрю Де Андраде
1
@AndrewDeAndrade Почему: я хочу отсортировать коллекцию по атрибуту created_at, сначала обслуживая самые новые. Этот атрибут является одним из атрибутов Backbone по умолчанию и хранится как String. Ваш ответ ближе всего к тому, что я хочу. Благодарность!
Эрик Ху
Это потрясающе, вот уже час я работаю над решением этой проблемы.
28

Мое решение заключалось в отмене результатов после сортировки.

Чтобы предотвратить двойной рендеринг, сначала выполните сортировку с отключением звука, а затем активируйте «сброс».

collections.sort({silent:true})
collections.models = collections.models.reverse();
collections.trigger('reset', collections, {});
Томаш Трела
источник
2
Лучшее решение - отрицать результат компаратора.
Дэниел Икс Мур,
7
Дэниел, это не всегда возможно, например, при сортировке строк или дат.
Гэвин Шульц
1
Конечно да. Для Date вы можете получить возврат компаратора -Date.getTime(), возможно, с дополнительным взломом, если вы считаете, что даты в вашей системе пересекают эпоху unix. В алфавитном порядке посмотрите ответ Эндрю выше.
asparagino
@DanielXMoore Почему это обязательно лучшее решение? Это быстрее? Лично я считаю, что очень легко сохранить атрибут, по которому пользователь последний раз отсортировал коллекцию, а затем отменить текущую сортировку, если он снова сортирует по этому атрибуту; (чтобы реализовать поведение сортировки, которое вы видите в таблице Википедии, где направление сортировки можно переключать, многократно щелкая атрибут в заголовке таблицы) Таким образом я сохраняю свою логику компаратора в чистоте и сохраняю операцию сортировки, если мы переключаемся с Asc в Desc sort order
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
Rbhitchcock
источник
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;
      }

},

Компаратор, кроме двух моделей, при изменении коллекции или модели запускается функция отсека и изменяется порядок коллекции.

Я протестировал этот подход с числами и строками, и, похоже, он отлично работает для меня.

Я действительно надеюсь, что этот ответ может помочь, поскольку я много раз боролся с этой проблемой и обычно в конечном итоге использую обходной путь.

Фасани
источник
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;
};
wprl
источник
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.
Fabio
1

Для обратного порядка по id:

comparator: function(object) { return (this.length - object.id) + 1; }
mzzl
источник
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 до тех пор, пока не будет выполнено обратное.

    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
источник
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;

            }
    });

Надеюсь, кому-то это пригодится

Диего Алехос
источник
0

Вот быстрый и простой способ обратной сортировки моделей коллекции с помощью UnderscoreJS

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

Джон Райан Коттам
источник