Question

Background

I have a pretty basic application I'm developing as a learning exercise for Knockout. In my view model, I have an observable array of students. This object is comprised of many students, which are my model. Each student has an observable array of scores. The scores array consist of observable numbers that are dynamically pushed to the scores array.

The information above describes my intent. Unfortunately, something's breaking, but I cannot decipher where. My console log shows that the values in my array of scores are not being updated when I push to the array. It is unclear to me where my logic/methodology is wrong. The lack of updating is most clearly seen when I try to update the average score in the scores` array.

My content, as well as console log, can be accessed here: http://jsbin.com/fehoq/45/edit

HTML

<button data-bind="click: addWork">Add New Assignment</button>
<table>
    <thead>
        <tr>
            <th>Name</th>
            <!-- ko foreach: assignments -->
            <th><input data-bind="value: workName  + ' ' + ($index() + 1)"/></th>
            <!-- /ko -->
            <!--<th data-bind=>Overall Grade</th> -->
            <th>Class Grade</th>
        </tr>    
    </thead>
    <tbody data-bind="foreach: students">
        <tr>
          <td><input data-bind="value: fullName"/></td>  
            <!-- ko foreach: scores -->  
            <td><input data-bind="value: $data"/></td>
            <!-- /ko --> 
            <td><input data-bind="value: mean" /></td>
            <td><input type="button" value="remove" data-bind="click: $root.removeStudent.bind($root)". /></td>
        </tr>    
    </tbody>    
</table>

JS

Note that the code below are relevant snippets, not the whole of the application.

1. Model

var StudentModel = (function () {
    function StudentModel(fullName) {
        var _this = this;
        this.fullName = fullName;
        this.scores = ko.observableArray();
        this.mean = ko.computed(function (scores) {
            var n = 0;
            ko.utils.arrayForEach(_this.scores(), function (score) {
                n += score();
                console.log(n);
            });
            n = n / _this.scores().length;
            return n;
        });
    }

...

2. View Model

function StudentsViewModel() {
    var _this = this;
    this.students = ko.observableArray([
        new Gradebook.Model.StudentModel("Jeff Smith"),
        new Gradebook.Model.StudentModel("Gandalf")
    ]);
    this.assignments = ko.observableArray([
        new Gradebook.Model.WorkModel("Math"),
        new Gradebook.Model.WorkModel("Reading")
    ]);
    this.addStudent = function () {
        this.students.push(new Gradebook.Model.StudentModel(""));
        this.updateRows();
    };
    this.addWork = function () {
        this.assignments.push(new Gradebook.Model.WorkModel("Assignment "));
        this.updateRows();
    };
    this.updateRows = function () {
        ko.utils.arrayForEach(_this.students(), function (student) {
            while (student.scores().length < _this.assignments().length) {
                student.scores.push(ko.observable(100));
            }
        });
    };
}

Edit

Just as an added note, I spent a reasonable amount of time looking into this at SO but none of the solutions I encountered where helpful for me so far.

Was it helpful?

Solution

The issue is that value: $data in your binding refers to the unwrapped, non-observable value of your observable array entry, not the observable itself.

Essentially, this makes this a one-way binding - it reads the initial state, and is then never updated again.

Since Knockout 3.0, you can use $rawData instead:

<!-- ko foreach: scores -->  
  <td><input data-bind="value: $rawData"/></td>
<!-- /ko -->

From the Binding Context documentation:

$rawData

This is the raw view model value in the current context. Usually this will be the same as $data, but if the view model provided to Knockout is wrapped in an observable, $data will be the unwrapped view model, and $rawData will be the observable itself.

For reference: Knockout.js using value: binding in a foreach over a list of strings - does not update

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top