I have an observableArray on property of my view model:
self.rates = ko.observableArray([])
The contents of the array are displayed in an HTML table. There is a button to add items to the array. These are validated observables:
self.newRate = function () {
var rate = new Rate({id: self.id});
rate.isEditing(true);
rate.isNew = true;
rate = ko.validatedObservable(rate);
self.vendor().rates.push(rate);
};
This works fine. The item is added to the array and the view updates. There is a cancel link next to the newly added item to let the user remove the row.
self.editRateCancel = function (item) {
if (item.isNew === true) {
self.vendor().rates.remove(item);
} else {
item.cancelEdit();
ko.utils.arrayForEach(self.unitsOfMeasure(), function (uom) {
if(item.cacheUnitOfMeasureID === uom.value) {
item.selectedUOM(uom);
}
});
}
};
The call to the remove(item)
doesn't remove the item. If I don't set the item as a validated observable, the remove succeeds. Looking at the remove
function shows the item being passed in (valueOrPredicate) is of type Object, (Rate)
but the value being returned from the underlying array to be of Object, (Function)
so the predicate(value)
returns false so the item is not removed.
KnockoutJS remove function:
ko.observableArray['fn'] = {
'remove': function (valueOrPredicate) {
var underlyingArray = this.peek();
var removedValues = [];
var predicate = typeof valueOrPredicate == "function" ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
for (var i = 0; i < underlyingArray.length; i++) {
var value = underlyingArray[i];
if (predicate(value)) {
if (removedValues.length === 0) {
this.valueWillMutate();
}
removedValues.push(value);
underlyingArray.splice(i, 1);
i--;
}
}
if (removedValues.length) {
this.valueHasMutated();
}
return removedValues;
},
How can I remove specific validated observables from an observable array? Are there any utility functions available?
I've encountered the same problem and these are the approaches that I've tried:
FIRST APPROACH
Create an observable array and with instances of Rate
and a computed observable which will return each item from Rate as a validatedObservable
. The problem with this approach is that each time you add or remove item to the array all validatedObservable
are recreated which isn't efficient and causes weird UI behavior.
SECOND APPROACH
Create an additional deleted
observable field for Rate
and have a visible
binding based on the value of this field. Then it will not be removed from the observable array but it will not be visible to the user.
THIRD APPROACH
Create an additional index
field for Rate
and in the parent view model (the one containing self.rates
) keep a lastIndex
with initial value set to 0. Then the function to add a rate would look like this:
self.newRate = function () {
var rate = new Rate({id: self.id});
rate.isEditing(true);
rate.isNew = true;
rate.index = lastIndex++;
rate = ko.validatedObservable(rate);
self.vendor().rates.push(rate);
};
And the function to remove the item would use a predicate function and look like this:
self.editRateCancel = function (item) {
if (item.isNew === true) {
self.vendor().rates.remove(function (value) {
// remember about parenthesis after value()
// because it's an instance of validatedObservable()
// and not an instance of Rate()
return value().index == item.index;
});
} else {
item.cancelEdit();
ko.utils.arrayForEach(self.unitsOfMeasure(), function (uom) {
if(item.cacheUnitOfMeasureID === uom.value) {
item.selectedUOM(uom);
}
});
}
};
I went ahead with the third approach but the second might be acceptable for you as well.