Tutorial: Nested Models

Because Backbone.Models can have attributes than contain nested Backbone.Models or Backbone.Collections, there are times where you need to specialize the ViewModel you want to use; for example, in a model/collection hierachy with relationships. When you do need to, you can use the `factories` option when creating akb.ViewModel orkb.CollectionObservable.

Specifying Mappings Paths for kb.CollectionObservables

In the following example, we specify bindings so the collection view models (path: 'models') use a PersonViewModel; whereas, the 'friends' relationship (path: 'models.friends.models') uses FriendViewModel.

<div id='kbnm_collection_observables' data-bind="foreach: people">
  <p>Type: <span data-bind="text: type"></span></p>
  <p>Name: <input class='input-small pull-right' data-bind="value: name, valueUpdate: 'keyup'"/></p>

  <div data-bind="foreach: friends">
    <p>Friend Type: <span data-bind="text: type"></span> <span class="pull-right" >Name: <span data-bind="text: name"></span></span></p>
  </div>
</div>
bob = new Backbone.Model({name: "Bob", friends: new Backbone.Collection()})
fred = new Backbone.Model({name: "Fred", friends: new Backbone.Collection([bob])})

FriendViewModel = (model) ->
  @name = kb.observable(model, 'name')
  @type = ko.observable('friend')
  return

class PersonViewModel extends kb.ViewModel
  constructor: (model, options) ->
    super
    @type = ko.observable('person')

view_model = {
  people: kb.collectionObservable(new Backbone.Collection([bob, fred]), {
    factories:
      'models': PersonViewModel
      'models.friends.models': FriendViewModel
  })
}

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

var bob = new Backbone.Model({name: "Bob", friends: new Backbone.Collection()});
var fred = new Backbone.Model({name: "Fred", friends: new Backbone.Collection([bob])});

var FriendViewModel = function(model) {
  this.name = kb.observable(model, 'name');
  this.type = ko.observable('friend');
};

var PersonViewModel = kb.ViewModel.extend({
  constructor: function(model, options) {
    kb.ViewModel.prototype.constructor.apply(this, arguments);
    this.type = ko.observable('person');
  }
});

var view_model = {
  people: kb.collectionObservable(new Backbone.Collection([bob, fred]), {
    factories: {
      'models': PersonViewModel,
      'models.friends.models': FriendViewModel
    }
  })
};

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

Live Result

Type:

Name:

Friend Type: Name:

Specifying Mappings Paths for ViewMoels

In the following example, we specify bindings so the collection view models (path: 'models') use a PersonViewModel; whereas, the 'friends' relationship (path: 'models.friends.models') uses FriendViewModel.

<div id='kbnm_view_models'>
  <p>Type: <span data-bind="text: type"></span></p>
  <p>Name: <input class='input-small pull-right' data-bind="value: name, valueUpdate: 'keyup'"/></p>

  <div data-bind="foreach: friends">
    <p>Friend Type: <span data-bind="text: type"></span> <span class="pull-right" >Name: <span data-bind="text: name"></span></span></p>
    <div data-bind="foreach: friends">
      <p>Friend Type: <span data-bind="text: type"></span> <span class="pull-right" >Name: <span data-bind="text: name"></span></span></p>
    </div>
  </div>
</div>
bob = new Backbone.Model({name: "Bob"})
fred = new Backbone.Model({name: "Fred", friends: new Backbone.Collection([bob])})
bob.set({friends: new Backbone.Collection([fred])})

FriendViewModel = (model, options) ->
  @type = 'friend'
  @name = kb.observable(model, 'name')
  @friends = kb.collectionObservable(model.get('friends'), kb.utils.optionsPathJoin(options, 'friends'))
  return

class PersonViewModel extends kb.ViewModel
  constructor: (model, options) ->
    @type = 'person'
    super

view_model = new PersonViewModel(fred, {
  factories:
    'friends.models': FriendViewModel
    'friends.models.friends.models': FriendViewModel
})

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

var bob = new Backbone.Model({name: "Bob"});
var fred = new Backbone.Model({name: "Fred", friends: new Backbone.Collection([bob])});
bob.set({friends: new Backbone.Collection([fred])});

var FriendViewModel = function(model, options) {
  this.type = 'friend';
  this.name = kb.observable(model, 'name');
  this.friends = kb.collectionObservable(model.get('friends'), kb.utils.optionsPathJoin(options, 'friends'));
};

var PersonViewModel = kb.ViewModel.extend({
  constructor: function(model, options) {
    this.type = 'person';
    kb.ViewModel.prototype.constructor.apply(this, arguments);
  }
});

var view_model = new PersonViewModel(fred, {
  factories: {
    'friends.models': FriendViewModel,
    'friends.models.friends.models': FriendViewModel
  }
});

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

Live Result

Type:

Name:

Friend Type: Name:

Friend Type: Name: