Auto-Releasing View Models

When an element is created, Knockback provides functions to auto-release ViewModels using Knockout's dispose node callback:

# binds a callback to the node that releases the view model when the node is removed using ko.removeNode
ko.utils.domNodeDisposal.addDisposeCallback(node, -> kb.release(view_model) );
// binds a callback to the node that releases the view model when the node is removed using ko.removeNode
ko.utils.domNodeDisposal.addDisposeCallback(node, function() { kb.release(view_model)} );

There are three ways to do this in Knockback:

# Auto-released Template
el = kb.renderTemplate('template_name', view_model, options)

# OR: When applying bindings
kb.applyBindings(view_model, el)

# OR: Manually
kb.releaseOnNodeRemove(view_model, el)
// Auto-released Template
var el = kb.renderTemplate('template_name', view_model, options);

// OR: When applying bindings
kb.applyBindings(view_model, el);

// OR: Manually
kb.releaseOnNodeRemove(view_model, el);

This means you can use a Backbone.Router as follows and not need to explicitly call kb.release() because ko.removeNode calls it for you:

window.RouterBackboneJS = Backbone.Router.extend({
  constructor: ->
    Backbone.Router.prototype.constructor.apply(@, arguments)

    loadPage = function(el) {
      ko.removeNode(@active_el) if @active_el
      $('body').append(@active_el = el)
      $(el).addClass('active')
    }

    @route('', null, -> loadPage(kb.renderTemplate('home', new HomeViewModel())))
    @route('things', null, -> loadUrl(kb.renderTemplate('things_page', new ThingsPageViewModel())))
    @route('things/:id', null, (id) -> loadPage(kb.renderTemplate('thing_page', new ThingCellViewModel(things_collection.get(id)))))
})
window.RouterBackboneJS = Backbone.Router.extend({

  constructor: function() {
    var _this = this;
    Backbone.Router.prototype.constructor.apply(this, arguments);

    var loadPage = function(el) {
      if (_this.active_el) {ko.removeNode(this.active_el); }
      $('body').append(_this.active_el = el);
      $(el).addClass('active');
    }

    this.route('', null, function() { loadPage(kb.renderTemplate('home', new HomeViewModel())); });
    this.route('things', null, function() { loadPage(kb.renderTemplate('things_page', new ThingsPageViewModel())); });
    this.route('things/:id', null, function(id) { loadPage(kb.renderTemplate('thing_page', new ThingCellViewModel(things_collection.get(id)))); });
  }
});