KnockoutJS ObservableArray data grouping

http://stackoverflow.com/questions/9877301/knockoutjs-observablearray-data-grouping

by Doug Hill

HTML

<script src="http://knockoutjs.com/downloads/knockout-2.0.0.debug.js"></script>
<ul data-bind="foreach: people">
    <li>
        <input data-bind="value: name" />
        <select data-bind="options: $root.choices, value: type"></select>
        <a href="#" data-bind="click: $root.removePerson"> x </a>
    </li>
</ul>
<button data-bind="click: addPerson">Add Person</button>

<hr/>

<ul data-bind="foreach: choices">
    <li>
        <h2 data-bind="text: $data"></h2>
        <ul data-bind="foreach: $root.people.index.type()[$data]">
            <li data-bind="text: name"></li>
        </ul>
        <hr/>
    </li>
</ul>

CSS

h2 { font-size: 1.2em; font-weight: bold; }

JavaScript

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;
};    
 
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")
    ]).distinct('type');

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

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


ko.applyBindings(new ViewModel());