Группировка данных NockoutoutJS ObservableArray

Есть ли функция KnockoutJS, в то время как я мог бы взять что-то вроде:

    var myArray = ko.observableArray([
      { name: "Jimmy", type: "Friend" },
      { name: "George", type: "Friend" },
      { name: "Zippy", type: "Enemy" }
    ]);

Затем выберите значение в поле "тип", создав результат, который выглядит следующим образом:

(pseudo code)
    var distinct = myArray.distinct('type')
      // Returns array of two arrays
      //  distinct[0] is an array of type=Friend
      //  distinct[1] is an array of type=Enemy 

Я знаю ko.utils.arrayGetDistinctValues, но это не совсем то, что я хочу. Я также знаю, что я мог бы написать несколько циклов, используя ko.utils.arrayGetDistinctValues, чтобы получить то, что я хочу, мне просто интересно, есть ли что-то еще, запеченное в KnockoutJS, которое я пропускаю.

Ответ 1

В KO ничего не встроено, чтобы сделать это проще.

Существует много способов сделать эту работу. Например, вы можете расширить наблюдаемые массивы, чтобы иметь функцию distinct. Затем вы можете просто создать свой наблюдаемый массив как:

this.people = ko.observableArray([
       new Person("Jimmy", "Friend"),
       new Person("George", "Friend"),
       new Person("Zippy", "Enemy")
]).distinct('type');

Функция distinct может выглядеть так:

ko.observableArray.fn.distinct = function(prop) {
    var target = this;
    target.index = {};
    target.index[prop] = ko.observable({});    

    ko.computed(function() {
        //rebuild index
        var propIndex = {};

        ko.utils.arrayForEach(target(), function(item) {
            var key = ko.utils.unwrapObservable(item[prop]);
            if (key) {
                propIndex[key] = propIndex[key] || [];
                propIndex[key].push(item);            
            }
        });   

        target.index[prop](propIndex);
    });

    return target;
};    

Он поддерживает цепочку, поэтому вы можете называть distinct несколько раз с различными свойствами.

Пример здесь: http://jsfiddle.net/rniemeyer/mXVtN/

Это перестраивает индекс один раз при каждом изменении, поэтому, если у вас есть огромный список элементов, то вы захотите изучить другие способы (ручные подписки) для добавления/удаления элементов из массивов "index".

Ответ 2

Я упростил версию RP Niemeyer в jsfiddle, чтобы сделать то же самое без использования отдельной функции. См. Здесь jsfiddle

<ul data-bind="foreach: choices">
<li>
    <h2 data-bind="text: $data"></h2>
    <ul data-bind="foreach: $root.people">
        <!-- ko if: $parent === type() -->
        <li data-bind="text: name"></li>
        <!-- /ko -->
    </ul>
    <hr/>
</li>

var Person = function(name, type) {
   this.name = ko.observable(name);
   this.type = ko.observable(type);    
}

var ViewModel = function() {
    var self = this; 
    this.choices = ["Friend", "Enemy", "Other" ];
    this.people = ko.observableArray([
           new Person("Jimmy", "Friend"),
           new Person("George", "Friend"),
           new Person("Zippy", "Enemy")
    ]);

    this.addPerson = function() {
        self.people.push(new Person("new", "Other"));
    };

    this.removePerson = function(person) {
      self.people.remove(person);  
    };
};


ko.applyBindings(new ViewModel());

Спасибо Нимейер.

Ответ 3

Просто хочу добавить к этому, что если вы дважды назовете этот метод .distinct() (например, возможно, из вычисленного наблюдаемого), вы получите дважды вызываемые индексы и связанную вычисленную функцию - полоскание и повторение, и у вас есть проблемы с производительностью на ваших руках.

Чтобы отсортировать это, добавьте эту строку в верхней части функции:

if (target.index && target.index[prop]) return target; //Group by already set up, bail out.