Reputation: 2155
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 student
s, 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
<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>
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));
}
});
};
}
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.
Upvotes: 1
Views: 1002
Reputation: 10328
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
Upvotes: 1