What is the preferred way to store state between init and update for custom knockout binding?

Currently I am storing the state using jQuery data for the dom element.

ko.bindingHandlers.customValue = {

    init: function init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var state = { isEditing: false };        
        $(element).focus(function focus() {
            state.isEditing = true;
        }).blur(function blur() {
            state.isEditing = false;            
        }).data("customBinding", state);

    },

    update: function update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // ignore if updating
        if (!$(element).data("customBinding").isEditing) {
            // handle update if they are not updating                                
        }
    }

};​

Is there a better place to store state per binding that does not require the dom? Can the bindingContext be used to store state for the each instance of the binding?


The bindingContext is a possibility, but only for passing data from init to update the first time that the binding is triggered. The next time that update fires it would no longer be there.

There are really two choices for where to store this type of state:

1- On the element, as you stated. You can use jQuery's $.data or KO includes APIs for doing this as well ko.utils.domData.get(element, key) and ko.utils.domData.set(element, key, value).

2- Put this type of information in your view model, if appropriate. A flag to indicate isEditing is not necessarily out of place in a view model. I personally like to put this type of "meta-data" as sub-observables off of an observable like:

var name = ko.observable("Bob");
name.isEditing = ko.observable(false);

You would be able to bind against name and name.isEditing.

This has some advantages:

  • keeps the view model fairly clean, as you are not introducing new top-level properties
  • keeps the sub-observable tied to its parent observable (no need for nameIsEditing, etc.)
  • when turned into JSON with something like ko.toJSON the isEditing sub-observable will simply be dropped when its parent is unwrapped. So, you won't be sending unnecessary values back to the server.
  • in this case, it can also have the advantage of being available for other calculations in your view model or to bind against multiple elements in your UI.

Attaching data to the element is fine, and Knockout uses this method internally for the control-flow bindings (if, with, etc.), for example.

Another method is to only use the init function and use a computed observable to handle updates. I use this method in my repeat binding. Here are the important parts:

ko.bindingHandlers['repeat'] = {
    'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        ...
        // set up persistent data
        var lastRepeatCount = 0;
        ...
        ko.computed(function() {
            var repeatCount = ko.utils.unwrapObservable(valueAccessor());
            ...
            // Remove nodes from end if array is shorter
            for (; lastRepeatCount > repeatCount; lastRepeatCount--) {
                ...
            }
            ...
            // Add nodes to end if array is longer (also initially populates nodes)
            for (; lastRepeatCount < repeatCount; lastRepeatCount++) {
                ...
            }
        }, null, {'disposeWhenNodeIsRemoved': placeholder});
        ...
    }
};

I often use this pattern:

define(['knockout'], function(ko) {
  var interInstanceVariable = null;

  function Tree(element) {
    var privateInstanceVariable = null;

    function privateInstanceMethod() {}

    this.publicInstanceMethod = function() {}
  }


  ko.bindingHandlers.cannDendrogram = {
    init: function(element, valueAccessor) {
      $(element).data('tree', new Tree(element));
    },
    update: function(element, valueAccessor) {
      var tree = $(element).data('tree');
      tree.publicMethod();
    }
  };
});