KnockoutJS ObservableArray data grouping

https://stackoverflow.com/questions/47681651/how-to-sort-in-a-group-with-knockout-js/47682052#47682052

by jlspake

HTML

<script src="https://code.jquery.com/jquery-3.0.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-debug.js"></script>
<div class="displayTable" data-bind="foreach: choices">
  <div class="displayRow">
    <div class="displayCell" data-bind="text: $data"></div>
    <div class="displayCell">Age</div>
    <div class="displayCell">Phone</div>
  </div>
  <div class="displayRow" data-bind="foreach: $root.people.index.type()[$data]">
    <div class="displayTable">
      <div class="displayRow">
        <div class="displayCell" data-bind="text: name"></div>
        <div class="displayCell" data-bind="text: age"></div>
      </div>
    </div>
  </div>
</div>

CSS

.displayTable {
  display: table;
  border: 1px solid black;
  border-collapse: collapse;
  width: 100px;
}

.displayRow {
  display: table-row;
}

.displayCell {
  display: table-cell;
  border: 1px solid black;
}

.displayCell.age,
.displayCell.section {
  width: 50px;
}

JavaScript

ko.observableArray.fn.distinct = function(prop, sort) {
  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]);
      propIndex[key] = propIndex[key] || [];
      propIndex[key].push(item);
    });
    
    // Sort within groups
    for(var i in propIndex){
      if(propIndex.hasOwnProperty(i)){
        propIndex[i].sort(function(l, r) {
          return ko.unwrap(l[sort]) == ko.unwrap(r[sort]) ? 0 : (ko.unwrap(l[sort]) < ko.unwrap(r[sort]) ? -1 : 1);
        });
      }
    }

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

  return target;
};

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

var ViewModel = function() {
  var self = this;
  this.choices = ["Friend", "Enemy", "Other"];
  this.people = ko.observableArray([
    new Person("Jimmy", "Friend", 35),
    new Person("George", "Friend", 40),
    new Person("Mike", "Friend", 30),
    new Person("Zippy", "Enemy", 20)
  ]).distinct('type', 'age');

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

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

ko.applyBindings(new ViewModel());