javascriptknockout.jsknockout-3.0

Foreach Binding on a Knockout Computed Observable


I have an observableArray, scheduleDays, that looks like below. It represents 1 week of appointments for a selected simulator game.

enter image description here

If the user selects 2 simulator schedules to view, the returned view model looks like this:

enter image description here

this.scheduleDays = ko.observableArray([
  new ScheduleDay(1, 'Sunday', '02/09/2020', 1111, []),
  new ScheduleDay(4, 'Sunday', '02/09/2020', 2222, []),
  new ScheduleDay(2, 'Monday', '02/10/2020', 1111, []),
  new ScheduleDay(5, 'Monday', '02/10/2020', 2222, []),
  new ScheduleDay(3, 'Tuesday', '02/10/2020', 1111, []),
  new ScheduleDay(6, 'Tuesday', '02/10/2020', 2222, [])
]);

That causes the UI to display the results like this:

enter image description here

But what I want is for the data to be sorted by simulator, then by date like this:

enter image description here

The obvious solution is sort it but for learning purposes, I wanted to try solving it by grouping the view models by simulator. So I added this:

this.groupedScheduleDays = ko.computed(function() {
var result = {};
var original = self.scheduleDays();
console.log(original);

for (var i = 0; i < original.length; i++) {
  var item = original[i];
  result[item.simulatorId] = result[item.simulatorId] || [];
  result[item.simulatorId].push(item);
}

return result;
});

And that produced this:

enter image description here

That's what I was expecting. Here is what I came up with so far https://jsfiddle.net/krob636/o48unhsg/58/. My question is how can I bind groupedScheduleDays in a foreach?


Solution

  • You can use Object.keys to get the grouped items, and use a nested foreach:

    <div data-bind="foreach: Object.keys(groupedScheduleDays())">
      <div data-bind="foreach: $parent.groupedScheduleDays()[$data]">
        <div class="row">
          <div class="col-2">
            <span data-bind="text: dayOfWeekName"></span>
          </div>
          <div class="col-2">
            <span data-bind="text: simulatorId"></span>
          </div>
        </div>
      </div>
    </div>
    

    Fiddle: https://jsfiddle.net/thebluenile/L82emswo/

    Also, remember that your day properties are observables, so this: result[item.simulatorId] doesn't work, or rather, won't have the desired effect; since observables are technically functions, the actual function will be used as the key. You need to unwrap the observable: result[item.simulatorId()].