Stephen Porter Stephen Porter - 2 months ago 14
Javascript Question

Knockout.js | Observable array only triggers when last element changes

The Scenario

I have multiple input fields. The fields are NOT allowed to be empty. If any field is empty, I want to show some sort of error message.




The issue

The issue I am dealing with is that I have an observable array populating some inputs through a knockout foreach for the view.

Everything loads, displays, and saves properly, however, my validation (which is a computed) is only called when the last element in the observable array changes and not when any of the other elements change.

I found This SO Question, but OP's issue here was that he/she did not have their value as an observable which is not my problem as my value is wrapped as an observable.




The Code

Here's a fiddle

Here's the code:

View

<div data-bind="with: itemsModel">
<label data-bind="text: validMessage">Totally valid</label>
<div data-bind="foreach: items">
<div>
<label>Item: </label>
<input type="text " data-bind="value: name " />
</div>
</div>
</div>


JS

function ItemModel(item) {
self = this;
self.item = item;

self.name = ko.observable(item.name);

self.isValid = ko.computed(function() {
return self.name() && self.name().length <= 256;
});
}

function ItemsModel(itemsModel) {
var self = this;
self.itemsModel = itemsModel;

self.items = ko.observableArray([
new ItemModel(itemsModel.items[0]),
new ItemModel(itemsModel.items[1]),
new ItemModel(itemsModel.items[2])
]);

// This is only getting called when the last element in self.items changes
self.isValid = ko.computed(function() {
var isValid = true;

for (i = 0; i < 3; i++) {
isValid = isValid && self.items()[i].isValid();
}

return isValid;
});

self.validMessage = ko.computed(function() {
if (self.isValid()) {
return "Totally Valid";
}

return "Totally NOT Valid";
});
}

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

self.itemsModel = ko.observable(new ItemsModel(data.itemsModel));
}

var modelData = {
itemsModel: {
items: [{
name: "Item One"
}, {
name: "Item Two"
}, {
name: "Item Three"
}]
}
};

ko.applyBindings(new ViewModel(modelData));

Answer

You're not declaring your first self locally, so it's global.

function ItemModel(item) {
  self = this;

should be

function ItemModel(item) {
  var self = this;
Comments