44

I have a list of objects, each with a few fields, like this:

function Person(id,name,age) {
    this.id = id;
    this.name = name;
    this.age = age;
}

var listOfPeople = [
    new Person(1, 'Fred', 25),
    new Person(2, 'Joe', 60),
    new Person(3, 'Sally', 43)
];

var viewModel = {
    this.people = ko.observableArray(listOfPeople);
    this.selectedPeople = ko.observableArray([]);
}

I would like to build a list of checkboxes, one for each person, something along these lines:

<ul data-bind="foreach: people">
    <li>
        <input type="checkbox" data-bind="value: id, checked: ?">
        <span data-bind="name"></span>
    </li>
</ul>

My confusion is that in the checkbox data-bind attribute, I would like to refer to both the object being selected (that is, the person or the person's id), as well as to the list of all selected people. How do I refer to that in the scope of the foreach binding?

I guess a corollary is: how do I refer to the object being bound? Here "this" seemed to be bound to the window, not to the object.

The example of the "checked binding on the knockoutjs site" deals with primitives and uses a named template. I am confused about how to do this with objects and with anonymous templates.

Jeroen
  • 60,696
  • 40
  • 206
  • 339
Gene Golovchinsky
  • 6,101
  • 7
  • 53
  • 81

1 Answers1

74

You can do something like:

<ul data-bind="foreach: people">
    <li>
        <input type="checkbox" 
               data-bind="checkedValue: id, checked: $parent.selectedPeople">
    </li>
</ul>

With this kind of ViewModel:

var viewModel = {
    people: ko.observableArray(listOfPeople),
    selectedPeople: ko.observableArray()
};

The value attribute controls what the checked binding adds/removes from an array when it is bound against an array. You could also write a dependentObservable that populates the array with the actual objects, if necessary.

Live Example:

function Person(id,name,age) {
    this.id = id;
    this.name = name;
    this.age = age;
}

var listOfPeople = [
    new Person(1, 'Fred', 25),
    new Person(2, 'Joe', 60),
    new Person(3, 'Sally', 43)
];

var viewModel = {
    people: ko.observableArray(listOfPeople),
    selectedPeople: ko.observableArray([1])
};

    
ko.applyBindings(viewModel);
<ul data-bind="foreach: people">
    <li>
        <input type="checkbox" value="" data-bind="checkedValue: id, checked: $parent.selectedPeople"><span data-bind="text: name"></span>
    </li>
</ul>

<hr/>

<div data-bind="text: ko.toJSON($root)"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • 8
    I recommend using `$parent.selectedPeople` because it will actually go up one level. `$root` will go all the way to the root view model, and if you have nested view-models, then `$parent` is guaranteed. – Laith Aug 15 '13 at 08:24
  • @RPNiemeyer, is there something about using `attr: {value: xx }` rather than just using the value binding? I was having issues getting the checked binding to work properly because all values were being checked in unison. Thanks for all your documented support! – Vinney Kelly Nov 06 '13 at 16:07
  • 1
    The `value` binding sets up a two-way binding meaning that it adds event handlers (which `checked` already does). So, conflicts can happen there. The `attr` binding will just make sure that the attribute (`value` in this case) is updated as the view model data changes. – RP Niemeyer Nov 06 '13 at 16:21
  • @ShaunMcCarthy: That's no longer true with the `checkedValue` binding. (It *was* true when you made that comment re Ryan's original answer using the old `attr: { value: id }` binding instead.) – T.J. Crowder Sep 10 '15 at 10:32