Tuesday, October 27, 2015

Tying together custom knockout bindings

I ran across a problem recently where I had a bindingHandler that I wanted to depend upon other bindings on the same node. Here is an example:
<div data-bind="myHandler, visible: !enabled()">
</div>
So in general, you have access to all the other bindings when inside a bindinghandler:
ko.bindingHandlers.myHandler = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    var visible = allBindings().visible;
  }
};
The problem with calling allbindings().someBinding, is that you get the *result* of the binding. In my case, I wanted to subscribe to when any updates happen to the entire binding: "visible: !enabled()"

Knockout handles this for a normal bindingHandler in the update function. If the valueAccessor changes, update gets fired.

So I needed to have a way to get access to the valueAccessor of a different bindingHandler on the same node. The solution I came up with involved asking the knockout bindingProvider to give me the value accessors, then subscribing to the valueAccessor I wanted to listen to:
ko.bindingHandlers.myHandler = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    var bindingAccessors = ko.bindingProvider.instance.getBindingAccessors(element, bindingContext),
       visibleAccessor = bindingAccessors.visible,
       myObservable = viewModel.someObservable; // For complex reasons, this observable needs to update based on another binding in the same node 
    if (visibleAccessor) {
      // This simulates what the knockout update bindingHandler does:
      ko.computed(function() {
        myObservable(ko.unwrap(visibleAccessor)); // the computed fires because we have visibleAccessor in our scope
      });
    }
  }
};

So now when the visible binding updates, we get the result of the valueAccessor instead of a single result at one point in time of a valueAccessor (shown in the first js code block).

I know this sounds complicated, but this was exactly what I was looking for to be able to access the valueAccessors of the same node. Using allBindings().someHandler was forcing me to make trivial pureComputed variables:
var MyViewModel = function() {
  this.enabled = ko.observable(false);
  this.disabled = ko.pureComputed(function() {
    return !this.enabled();
  }, this);
}

I really wanted this trivial code to exist in the template, not in the viewModel. So the solution using getBindingAccessors allows me to keep this code in the templates.