#Tutorial: kb.CollectionObservable

Knockback.js provides an kb.CollectionObservable that watches Backbone.Collections when models are added, removed, and changed. In addition, kb.CollectionObservables can be used to sort models independently from the Backbone.Collection order, to bind HTML select elements, to render/edit model relationships, and to filter collections.

Sorting Models

You can sort models using an attribute name or by providing a sorted_index function.

<div id='kbco_sorting' >
  <legend>Sort Attribute
    <select data-bind="options: sort_attributes, selectedOptions: sort_attribute"></select>
  </legend>
  <div data-bind="foreach: people">
    <div class='row-fluid'>
      <div class='span5'>
        <span>First: </span>
        <input class='input-small' data-bind="value: first, valueUpdate: 'keyup'"/>
      </div>
      <div class='offset1 span5'>
        <span>Last: </span>
        <input class='input-small' data-bind="value: last, valueUpdate: 'keyup'"/>
      </div>
    </div>
  </div>
</div>
people = new Backbone.Collection([{first: 'Jeremy', last: 'Sanderson'}, {first: 'Steven', last: 'Ashkenas'}])
sort_attribute = ko.observable('first')

view_model =
  sort_attribute: sort_attribute
  sort_attributes: ko.observableArray(['first', 'last'])
  people: kb.collectionObservable(people, {sort_attribute: sort_attribute})

ko.applyBindings(view_model, $('#kbco_sorting')[0])
var ko = kb.ko;

var people = new Backbone.Collection([{first: 'Jeremy', last: 'Sanderson'}, {first: 'Steven', last: 'Ashkenas'}]);
var sort_attribute = ko.observable('first');

var view_model = {
  sort_attribute: sort_attribute,
  sort_attributes: ko.observableArray(['first', 'last']),
  people: kb.collectionObservable(people, {sort_attribute: sort_attribute})
};

ko.applyBindings(view_model, $('#kbco_sorting')[0]);

Live Result

Sort Attribute
First:
Last:

Filtering Models

You can use kb.CollectionObservables to filter both ViewModels or Models (using the 'models_only' option) using the filters constructor option. Filters can be individual ids (observable or simple) or arrays of ids, functions, or arrays of functions.

<div id='kbco_filtering' >
  <legend>Filter Name
   <select data-bind="options: available_names, selectedOptions: filtered_names"></select>
  </legend>
  <div data-bind="foreach: people">
    <div class='row-fluid'>
      <div class='span5'>
        <span>First: </span>
        <input class='input-small' data-bind="value: first, valueUpdate: 'keyup'"/>
      </div>
      <div class='offset1 span5'>
        <span>Last: </span>
        <input class='input-small' data-bind="value: last, valueUpdate: 'keyup'"/>
      </div>
    </div>
  </div>
</div>
people = new Backbone.Collection([{first: 'Jeremy', last: 'Ashkenas'}, {first: 'Steven', last: 'Sanderson'}, {first: 'Kevin', last: 'Malakoff'}])
filtered_names = ko.observableArray(['Kevin'])

view_model =
  filtered_names: filtered_names
  available_names: ko.observableArray(people.map((model) -> return model.get('first')))
  people: kb.collectionObservable(people, {filters: (model) -> return model.get('first') is filtered_names()[0]})

ko.applyBindings(view_model, $('#kbco_filtering')[0])
var ko = kb.ko;

var people = new Backbone.Collection([{first: 'Jeremy', last: 'Ashkenas'}, {first: 'Steven', last: 'Sanderson'}, {first: 'Kevin', last: 'Malakoff'}]);
var filtered_names = ko.observableArray(['Kevin']);

var view_model = {
  filtered_names: filtered_names,
  available_names: ko.observableArray(people.map(function(model) { return model.get('first'); })),
  people: kb.collectionObservable(people, {filters: function(model) { return model.get('first') === filtered_names()[0]; } })
};

ko.applyBindings(view_model, $('#kbco_filtering')[0]);

Live Result

Filter Name
First:
Last:

HTML Select Statements

You can use kb.CollectionObservables to bind HTML select statements using Knockout.js selectOptions.

<div id='kbco_html_select'>
  <select multiple="multiple" data-bind="options: people, optionsText: 'name', selectedOptions: selected_people"></select>
  <div>
    <span>Selected people: </span>
    <!-- ko foreach: selected_people -->
    <span data-bind="text: name"></span>
    <!-- /ko -->
  </div>
</div>
people = new Backbone.Collection([{name: 'Bob'}, {name: 'Sarah'}, {name: 'George'}])
selected_people = new Backbone.Collection()

view_model =
  people: kb.collectionObservable(people)
  selected_people: kb.collectionObservable(selected_people)

ko.applyBindings(view_model, $('#kbco_html_select')[0])
var ko = kb.ko;

var people = new Backbone.Collection([{name: 'Bob'}, {name: 'Sarah'}, {name: 'George'}]);
var selected_people = new Backbone.Collection();

var view_model = {
  people: kb.collectionObservable(people),
  selected_people: kb.collectionObservable(selected_people)
};

ko.applyBindings(view_model, $('#kbco_html_select')[0]);

Live Result

Selected people:

One important constraint is that in order for Knockout.js select bindings to work, the same ViewModel instance needs to be included in each kb.CollectionObservable. You can use shareOptions to share the kb.Store and hence ViewModels between kb.CollectionObservables.

Here is an adapted sample from Knockback Reference App.

<div id='kbco_share_options'>
  <div data-bind="text: name"></div>
  <select multiple="multiple" data-bind="options: available_things, optionsText: 'name', selectedOptions: my_things"></select>

  <p>
    <span>My Things:</span>
    <!-- ko foreach: my_things -->
    <span data-bind="text: name"></span>
    <!-- /ko -->
  </p>
</div>
chair = new Backbone.Model({name: 'chair', my_things: new Backbone.Collection()})
things = new Backbone.Collection([{name: 'leg1'}, {name: 'leg2'}, {name: 'leg3'}])

available_things = kb.collectionObservable(things)
view_model =
  available_things: available_things
  name: kb.observable(chair, 'name')
  my_things: kb.collectionObservable(chair.get('my_things'), available_things.shareOptions())

ko.applyBindings(view_model, $('#kbco_share_options')[0])
var ko = kb.ko;

var chair = new Backbone.Model({name: 'chair', my_things: new Backbone.Collection()});
var things = new Backbone.Collection([{name: 'leg1'}, {name: 'leg2'}, {name: 'leg3'}]);

var available_things = kb.collectionObservable(things);
var view_model = {
  available_things: available_things,
  name: kb.observable(chair, 'name'),
  my_things: kb.collectionObservable(chair.get('my_things'), available_things.shareOptions())
};

ko.applyBindings(view_model, $('#kbco_share_options')[0]);

Live Result

My Things: