0
votes

I've read through a number of questions and answers that have mentioned a number of reasons for observableArrays not updating the UI. I've worked through some of these and at first glance it appeared I was not working in the observable scope, so changes to the underlying array where not being updated in the observable as would be expected.

I started using the replace method on observableArray to try and solve the problem.

var self = this;

this.rightMenu = ko.observableArray();

this.activate = function(data, event) {
    var index = self.rightMenu.indexOf(data),
    active = _.findWhere(self.rightMenu(), {active : true}),
    toActivate = self.rightMenu()[index];

    data.active = true;

    self.rightMenu.replace(toActivate, data);
    self.rightMenu.replace(active, $.extend(active, { active : false}));
};

Essentially I am just updating the active property on the objects. I was lead to believe that using the observable methods would trigger knockouts DOM listeners and update those elements. This is not happening in the above scenario.

If I console.log self.rightMenu() after the replaces, I see exactly what I should. Active classes in the DOM however are not updated.

If I do this:

 self.rightMenu.push({name : 'test', href : 'test', active : true});

The DOM does update with the new value I push to it.

The DOM node this VM is bound to looks like this:

<ul class="sidebar right" data-bind="foreach: rightMenu">
    <li data-bind="css: $data.active ? 'active' : ''">
        <a data-bind="click:$root.activate, text:$data.name, attr { href : $data.href}"></a>
    </li>
</ul>

Can anyone give me a pointer as to how I could fix this?

1
From the docs: Key point: An observableArray tracks which objects are in the array, not the state of those objects. Simply putting an object into an observableArray doesn’t make all of that object’s properties themselves observable. Of course, you can make those properties observable if you wish, but that’s an independent choice. An observableArray just tracks which objects it holds, and notifies listeners when objects are added or removed. - janfoeh
@janfoeh absolutely, which is why I replace the actual array index with an (albeit) similar instance of the same object. In my eyes that should be enough to trigger both a remove and an added event... obviously knockout doesn't think so . :) anyway, thanks for the comment - David Barker
AFAIK Knockout coalesces multiple changes together for performance reasons. Since you remove and re-add the same object, nothing really changes as far as the observable array is concerned. If I understand your code and what you are trying to achieve correctly, the easiest way would be to make the active property on the array elements an observable and simply toggle that. This way, you would not need to add and remove array elements at all. - janfoeh
Yes, this would definitely seem to be true, and in honesty it's probably a good thing. Your suggestion is the way I have finally ended up solving this. This would appear (taking an educated guess here) to be far lighter than removing and adding an object to the array. Thanks again for the comments. - David Barker

1 Answers

2
votes

I'm not a knockout expert but I could solve your problem by copying your data object into a new object.

self.activate = function(data, event) {
        var index = self.rightMenu.indexOf(data);

        var obj = {
            active: true,
            name: data.name,
            href: data.href
        }

        self.rightMenu.replace(self.rightMenu()[index], obj);
}

Fiddle