Search code examples
knockout.jsknockout-validation

How To Remove A Knockout Validation validatedObservable From An Observable Array


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?


Solution

  • 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.