Leks12lk
Leks12lk

Reputation: 13

Knockout.js binding does not work while working with collections

I'm a beginner to Knockoutjs. While working with collections it seems to me binding does not work (text is shown but it's not updated after value updating). As example the following code:

<h2>People</h2>
<ul data-bind="foreach: people">
    <li>
        <div>          
            <span data-bind="text: name"> </span> has <span data-bind='text: children().length'>&nbsp;</span> children:
            <a href='#' data-bind='click: addChild '>Add child</a>
            <span class='renderTime' data-bind='visible: $root.showRenderTimes'>
                (person rendered at <span data-bind='text: new Date().getSeconds()' > </span>)
            </span>
        </div>
        <ul data-bind="foreach: children">
            <li>
              <input type="text" data-bind="value: $data">
                <span data-bind="text: $data"> </span>
                <span class='renderTime' data-bind='visible: $root.showRenderTimes'>
                    (child rendered at <span data-bind='text: new Date().getSeconds()' > </span>)
                </span>
            </li>
        </ul>
    </li>
</ul>
<label><input data-bind='checked: showRenderTimes' type='checkbox' /> Show render times</label> 

It's a code from example on the official Knockout.js site http://knockoutjs.com/examples/collections.html The source js code is this:

// Define a "Person" class that tracks its own name and children, and has a method to add a new child
var Person = function(name, children) {
    this.name = name;
    this.children = ko.observableArray(children); 
    this.addChild = function() {
        this.children.push("New child");
    }.bind(this);
} 
// The view model is an abstract description of the state of the UI, but without any knowledge of the UI technology (HTML)
var viewModel = {
    people: [
        new Person("Annabelle", ["Arnie", "Anders", "Apple"]),
        new Person("Bertie", ["Boutros-Boutros", "Brianna", "Barbie", "Bee-bop"]),
        new Person("Charles", ["Cayenne", "Cleopatra"])
    ],
    showRenderTimes: ko.observable(false)
}; 
ko.applyBindings(viewModel);

I've just made one change - added

<input type="text" data-bind="value: $data">

Everything is fine displayed. But text is not updated after value changing. It seems that binding does not work. I faced with the same problem in a real project as well. It is confused me. What's going wrong? Thanks in advance.

Upvotes: 0

Views: 64

Answers (1)

Jeroen
Jeroen

Reputation: 63719

The example you took isn't particularly well suited to make a two-way binding work, but let's try anyways. Note that children is an observable array (so additions/removals are noticed) but the items inside it are plain strings, not observables. For a two-way binding to work you'd need that.

In addition, a "child" is just a plain string, further complicating things.

I suggest editing the example a bit to make sure children are mapped to new Person(...) instances and have their name property be an ko.observable(...). The addChild function should then also create new Person(...) instances.

See this example:

// Define a "Person" class that tracks its own name and children, and has a method to add a new child
var Person = function(name, children) {
    this.name = ko.observable(name);
    this.children = ko.observableArray(children.map(c => new Person(c, []))); 
    this.addChild = function() {
        this.children.push(new Person("New child", []));
    }.bind(this);
} 
// The view model is an abstract description of the state of the UI, but without any knowledge of the UI technology (HTML)
var viewModel = {
    people: [
        new Person("Annabelle", ["Arnie", "Anders", "Apple"]),
        new Person("Bertie", ["Boutros-Boutros", "Brianna", "Barbie", "Bee-bop"]),
        new Person("Charles", ["Cayenne", "Cleopatra"])
    ],
    showRenderTimes: ko.observable(false)
}; 
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>

<h2>People</h2>
<ul data-bind="foreach: people">
    <li>
        <div>          
            <span data-bind="text: name"> </span> has <span data-bind='text: children().length'>&nbsp;</span> children:
            <a href='#' data-bind='click: addChild '>Add child</a>
            <span class='renderTime' data-bind='visible: $root.showRenderTimes'>
                (person rendered at <span data-bind='text: new Date().getSeconds()' > </span>)
            </span>
        </div>
        <ul data-bind="foreach: children">
            <li>
              <input type="text" data-bind="value: name">
                <span data-bind="text: name"> </span>
                <span class='renderTime' data-bind='visible: $root.showRenderTimes'>
                    (child rendered at <span data-bind='text: new Date().getSeconds()' > </span>)
                </span>
            </li>
        </ul>
    </li>
</ul>
<label><input data-bind='checked: showRenderTimes' type='checkbox' /> Show render times</label>

Upvotes: 1

Related Questions