Сортировка строк в обратном порядке с помощью backbone.js

Я пытаюсь сортировать коллекцию Backbone.js в обратном порядке. Предыдущие ответы на вопрос о том, как это сделать с целыми числами, но не со строками.

var Chapter  = Backbone.Model;
var chapters = new Backbone.Collection;

chapters.comparator = function(chapter) {
  return chapter.get("title");
};

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

Вышеприведенный код сортирует главы из A → Z, но как я могу написать компаратор, который сортирует его из Z → A?

Ответ 1

Существуют две версии функции сравнения, которые вы можете использовать, либо sortBy, которая была показана в примере, которая принимает один параметр, или sort - который вы можете вернуть более стандартную функцию сортировки, которую документация говорит:

"sortBy" функции-компараторы берут модель и возвращают числовое или строковое значение, с помощью которого модель должна быть упорядочена относительно других. Функции сортировки "сортировать" принимают две модели и возвращают -1, если первая модель должна быть до второго, 0, если они имеют один и тот же ранг и 1, если первая модель должна последовать за ней.

Итак, в этом случае мы можем написать другую функцию компаратора:

var Chapter  = Backbone.Model;
var chapters = new Backbone.Collection;

chapters.comparator = function(chapterA, chapterB) {
  if (chapterA.get('title') > chapterB.get('title')) return -1; // before
  if (chapterB.get('title') > chapterA.get('title')) return 1; // after
  return 0; // equal
};

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

Итак, вы должны получить ответ:

"The Middle", "The End", "The Beginning"

Ответ 2

Вы можете:

  • возьмите код char для каждого символа в строке,
  • вычесть каждое значение из 0xffff (максимальное возвращаемое значение string.charCodeAt),
  • используйте String.fromCharCode, чтобы вернуть это обратно в строку "отрицательных" символов

и это будет ваш сортировочный ключ.

chapters.comparator = function(chapter) {
    return String.fromCharCode.apply(String,
        _.map(chapter.get("title").split(""), function (c) {
            return 0xffff - c.charCodeAt();
        })
    );
}

И вуаля:

> console.log(chapters.pluck('title'));
["The Middle", "The End", "The Beginning"]

Примечание. Если ваши строки сравнения длинны (как в 65 кбайтах или более), вы можете столкнуться с проблемами (см. комментарий Matt ниже). Чтобы избежать этого и немного ускорить сравнение, просто используйте более короткий фрагмент вашей строки сравнения. (В приведенном выше примере вы могли бы пойти вместо chapter.get("title").slice(0, 100).split("").) Сколько времени потребуется вам в срезе, будет зависеть от вашего приложения.

Ответ 3

Если вы работаете с не численными значениями, нет очевидного способа сделать обратную сортировку. Магистраль использует методы _.sortBy() и _.sortedIndex() из Underscore для заказа моделей на основе компаратора, и эти методы автоматически сортируются в порядке возрастания. Наивный способ сделать это - использовать chapters.pluck('title').reverse(), поскольку результатом pluck будет массив. Но вызов reverse в некоторых методах коллекции изменит модели коллекции на месте, поэтому в следующий раз, когда вы его вызовете, модели вернутся в порядке возрастания. Вы всегда можете сделать что-то вроде:

var results = [],
    titles  = chapters.pluck('title');

for(var i=0, len=titles.length; i<len; i++) {
  results.push(titles[i]);
}

results.reverse();

Это не повлияет на массив моделей в вашей коллекции Backbone, поскольку он создаст совершенно новый массив результатов в памяти, но сохранит ссылки на исходные модели, поэтому вызов таких вещей, как save, все равно будет обновлять состояние Collection.

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

Чтобы выполнить эту работу, для выполнения этой работы вам понадобится выполнить немного громоздкого JavaScript-ninjary в методе компаратора - обратите внимание, что это не проверено:

chapters.comparator = function(chapter) {
  var alphabet = '0123456789abcdefghijklmnopqrstuvwxyz',
      title = chapter.get('title').toLowerCase(),
      inverse_title = '',
      index;

  for(var i=0, len=title.length; i<len; i++) {
    index = alphabet.indexOf(title.charAt(i));

    if(index === -1) {
      inverse_title += title.charAt(i);
      continue;
    }

    inverse_title += alphabet.charAt(alphabet.length - index - 1);
  }

  return inverse_title;
};

Эта концепция, вероятно, нуждается в улучшении, чтобы принимать во внимание символы и т.д., но по существу она инвертирует строку компаратора таким образом, что "Z" становится "0", "Y" становится "1" и т.д., что должно создайте обратный сортир, который вам нужен.

Ответ 4

Поскольку Backbone просто использует метод .sortBy, просто прокси-сервер в вашей собственной логике:

collectionInQuestion.sortBy = function () {
  var models = _.sortBy(this.models, this.comparator);
  if (forSomeReason) {
    models.reverse();
  }
  return models;
};

.. или добавить его в другое место.

TweakedCollection = Backbone.Collection.extend({ sortBy: [...] })

Ответ 5

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

events: {

    'click th.sortable': function(e) {
        var $this = $(e.target),
            order = $this.hasClass('asc') ? 'desc' : 'asc',
            field = $this.data('field'); /* this is a string */

        $this.siblings().addBack().removeClass('asc desc');
        $this.addClass( order );

        this.bodyView.collection.comparator = field;
        this.bodyView.collection.sort();
        if ( order === 'desc' ) this.bodyView.collection.models.reverse();

        this.bodyView.render();
    }

},

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

Ответ 6

Просто добавьте минус до chapter.get

chapters.comparator = function(chapter) {
    return -chapter.get("title");
};