James Curran James Curran - 3 months ago 8
Javascript Question

How to force knockoutjs to update UI (reevaluate bindings)

(I know there are other questions here asking the same thing; I've tried them and they don't apply here)

I have a collection being displayed by a Knockout JS

foreach
. For each item, the
visible
binding is set by call a method, based on something external to the item itself. When the externality changes, I need the UI to be redrawn.

A striped down version can be seen in this Fiddle: http://jsfiddle.net/JamesCurran/2us8m/2/

It starts with a list of four folder names, and displays the ones starting with 'S'.

<ul data-bind="foreach: folders">
<li data-bind="text: $data,
visible:$root.ShowFolder($data)"></li>
</ul>
<button data-bind="click:ToA">A Folders</button>


Clicking the button should display the ones starting with 'A' instead.

self.folders = ko.observableArray(['Active', 'Archive', 'Sent', 'Spam']);
self.letter = 'S';

// Behaviours
self.ShowFolder = function (folder)
{
return folder[0] === self.letter;
}
self.ToA = function ()
{
self.letter = 'A';
}


UPDATE:
After Loic showed me how easily this example could be fixed, I reviewed the differences between this example and my actual code. I'm using an empty object as a dictionary to toggle if an item is selected
self.Selected()[item.Id] = !self.Selected()[item.Id];


The object being changed is already an observable. I assumed that Knockout didn't realize that the list is dependent on the external observable, but it does. What Knockout was missing was that the observable was in fact changing. So, the solution was simply:

self.Selected()[item.Id] = !self.Selected()[item.Id];
self.Selected.notifySubscribers();

Answer

Here's what I came up with:

What you have to understand is that Knockout is only "answering" to data changes in observables. If an observable changes, it will trigger every object that uses it. By making your self.letter an observable. You can simply change it's value and uses it somewhere like self.letter() and it will automagically redraw when needed.

http://jsfiddle.net/2us8m/3/

function WebmailViewModel() {
    // Data
    var self = this;

    self.folders = ko.observableArray(['Active', 'Archive', 'Sent', 'Spam']);
    self.letter = ko.observable('S');

    // Behaviours    
    self.ShowFolder = function (folder) 
        { 
            return folder[0] === self.letter(); 
        }

    self.ToA = function () 
        {
            self.letter('A');
        }
};

ko.applyBindings(new WebmailViewModel());

In case you have complex bindings, like storing an object inside an observable. If you want to modify that object you have multiple possible choices.

self.Selected()[item.Id] = !self.Selected()[item.Id];

You could change it to this by making everything "observables" but if my memory is right, it can become complicated.

self.Selected()[item.Id](!self.Selected()[item.Id]());

I remember I had one similar issue where I had dependency problem where I had to update a country, region, city. I ended up storing it as list inside an observable to prevent update on individual element change. I had something like this.

var path = PathToCity();
path[0] = 'all';
path[1] = 'all';
PathtoCity(path);

By doing this, the change would be atomic and there will be only one update. I haven't played a lot with knockout for a while. I'm not sure but I do believe that the last time I worked with knockout, it was able to "optimize" and prevent to redraw the whole thing. But be careful because if it is not able to guess that you didn't change many thing, it could redraw the whole observable tree (which could end up pretty bad in term of performance)

In your example, we could use the same behaviour with my modified example:

http://jsfiddle.net/2us8m/4/

Comments