admin管理员组

文章数量:1022784

(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: /

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();

(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/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();
Share Improve this question edited Aug 22, 2016 at 15:31 James Curran asked Dec 18, 2013 at 23:56 James CurranJames Curran 104k37 gold badges185 silver badges263 bronze badges 2
  • 1 I'm not sure to understand the problem. To update the UI, you have to update an observable object. If you want to redraw the UI you have to change the data that you wanted to draw. – Loïc Faure-Lacroix Commented Dec 19, 2013 at 0:02
  • Bindings are re-evaluated (read: their update is called) when an observable dependency changes values. – user2864740 Commented Dec 19, 2013 at 0:10
Add a ment  | 

1 Answer 1

Reset to default 4

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/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 plex 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 bee plicated.

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/2us8m/4/

(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: /

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();

(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/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();
Share Improve this question edited Aug 22, 2016 at 15:31 James Curran asked Dec 18, 2013 at 23:56 James CurranJames Curran 104k37 gold badges185 silver badges263 bronze badges 2
  • 1 I'm not sure to understand the problem. To update the UI, you have to update an observable object. If you want to redraw the UI you have to change the data that you wanted to draw. – Loïc Faure-Lacroix Commented Dec 19, 2013 at 0:02
  • Bindings are re-evaluated (read: their update is called) when an observable dependency changes values. – user2864740 Commented Dec 19, 2013 at 0:10
Add a ment  | 

1 Answer 1

Reset to default 4

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/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 plex 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 bee plicated.

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/2us8m/4/

本文标签: javascriptHow to force knockoutjs to update UI (reevaluate bindings)Stack Overflow