StackOverflower
StackOverflower

Reputation: 5761

Knockout and observable array mismatch

Consider the following properties inside a viewmodel

self.allValues = ko.observableArray();
self.selectedValues = ko.observableArray();

On edit, selectedValues contains values coming from database. Here is the problem: selectedValues contains elements than are included on allValues, but they are not the same instances. They are the same from properties values point of view but are actually different objects.

This causes that every time knockout uses indexOf over allValues using objects from selectedValues always fails to find the object.

I'm using selectedValues on a checked binding but fails to check the correct elements included on this array.

<div class="vars-list" data-bind="foreach: allValues">
    <input type="checkbox" data-bind="checkedValue: $data...(etc) 
       checked: selelectedValues"  />
</div>

Is there any way for knockout to match objects by property values instead of memory address?

Upvotes: 2

Views: 251

Answers (1)

Michael Best
Michael Best

Reputation: 16688

Using a custom binding is one way to go. Here's a variation of the checked binding that uses a comparison function.

ko.bindingHandlers.checkedInArray = {
    init: function (element, valueAccessor, allBindings) {
        ko.utils.registerEventHandler(element, "click", function() {
            var observable = valueAccessor(), 
                array = observable(), 
                checkedValue = allBindings.get('checkedValue'), 
                isChecked = element.checked, 
                comparer = allBindings.get('checkedComparer');

            for (var i = 0, n = array.length; 
                i < n && !comparer(array[i], checkedValue); 
                ++i) { }

            if (!isChecked && i < n) {
                observable.splice(i, 1);
            } else if (isChecked && i == n) {
                observable.push(checkedValue);
            }
        });
    },
    update: function (element, valueAccessor, allBindings) {
        var array = valueAccessor()(), 
            checkedValue = allBindings.get('checkedValue'), 
            comparer = allBindings.get('checkedComparer');

        for (var i = 0, n = array.length;
            i < n && !comparer(array[i], checkedValue); 
            ++i) { }

        element.checked = (i < n);
    }
};

jsFiddle: http://jsfiddle.net/mbest/4mET9/

Upvotes: 5

Related Questions