javascriptcheckboxknockout.jsko.observablearray

Data binding Checkboxes using Knockout Observable Arrays


I'm trying to reimplement a student attendance example from the Udacity JavaScript Design Patterns course. So far I've managed to recreate the table and correctly populate it with some student data, however it appears when I change a checkbox value this isn't updated in the model.

For example, when I display

    debugVM.studentList()[0].days();

in the console the output displays the initial attendance data instead of the current state of the checkboxes. A JSFiddle for this can be found here.

index.html

 <table>
  <thead>
    <tr>
      <th>Student</th>
      <!-- ko foreach: attendance -->
      <th data-bind="html: $data"></th>
      <!-- /ko -->
    </tr>
  </thead>
  <tbody data-bind="foreach: studentList">
    <tr>
      <td data-bind="html: name, click: $parent.debugStudent"></td>
      <!-- ko foreach: days -->
      <td>
      <input type="checkbox" data-bind="value: $data, checkedValue: $data, checked: $data" />
      </td>
      <!-- /ko -->
    </tr>
  </tbody>
</table>

app.js

var Student = function(student){

  this.name = ko.observable(student.name);
  this.days = ko.observableArray(student.days);
};


var ViewModel = function() {
  var self = this;

  this.attendance = ko.observableArray([1,2,3,4,5,6,7,8,9,10,11,12]);
  this.studentList = ko.observableArray([]);

  students.forEach(function(student) {
    self.studentList.push(new Student(student));
  });

  self.debugStudent = function() {
    console.log(self.studentList()[0].days());
  };
};
var debugVM = new ViewModel();

ko.applyBindings(debugVM);

Solution

  • From the knockout documentation

    Key point: An observableArray tracks which objects are in the array, not the state of those objects

    In your case this means that days should be not observable array, but array of observables.

    For example you can add new viewModel Day:

    var Day = function(isChecked) {
        this.isChecked = ko.observable(isChecked);
    }
    

    And set the days property like this

    this.days = [];
    for (var i = 0; i< student.days.length; i++) {
       this.days.push(new Day(student.days[i] == 1));
    }
    

    See working fiddle