Knockout catching errors

Photo Credit: Random image from https://unsplash.it

Knockout catching errors

What is this?

A couple tips for identifying issues with Knockout.

Why is it important?

Error handling is one of the areas where Knockout could do with some attention. These techniques have saved me quite a bit of time, and I hope you also find them useful.

How’s it work?

There are two major places where you can really hook in and catch issues:

  • Custom binding provider
  • Wrapping custom bindings

The custom binding provider is called when the bindings are first made, via

1
ko.applyBindings
. At this point you can catch whether the binding
1
valueAccessor
has any errors, and check whether there are actually any binding handlers for the node’s
1
data-bind
attribute.

The wrapping of custom bindings lets you track what is happening when a binding is being applied to a node with

1
binding.init
and
1
binding.update
. It can be tricky to track down where on the page these problems are occurring, and I add a custom class to bad nodes to highlight where bad bindings are cropping up.

Any tools needed?

I am using

1
lodash
,
1
jQuery
, on Google Chrome. I am also writing the below in Coffeescript, just for my own convenience. If you are not able to use these, I hope the following is nevertheless illustrative.

Custom Binding Provider

This custom binding provider sends the console handy error messages that indicate what went wrong. It also adds the class

1
binding-error
to the element, highlighting which elements went awry.

ErrorHandlingBindingProvider = ->
  # The original binding handler still does the real work; you
  # can use your own or an alternative such as knockout-secure-binding
  # here though.
  orig = new ko.bindingProvider()

  # We also pass through the `nodeHasBindings` call.
  @nodeHasBindings = orig.nodeHasBindings

  # This returns a map of binding names to respective handlers, given a node
  # and the bindingContext.
  @getBindings = (node, bindingContext) ->
    try
      # The bindings_map will be `null` if there are no bindings, otherwise
      # an object mapping { name: binding }.
      bindings_map = orig.getBindings(node, bindingContext)
    catch err
      # When there is an error, throw out some useful context.
      console.error("Binding error: ", err.message, "\nNode:", node,
        "\nContext:", ko.contextFor(node))
      $(node).addClass("binding-error")

    # Pass null straight up.
    if bindings_map == null then return null

    # If there isn't at least one good binding, print some helpful
    # debugging information.
    unless _(bindings_map).keys().any((m) -> _.has(ko.bindingHandlers, m))
      console.error("No bindings found:", bindings_map, "Node:", node)
      $(node).addClass("binding-error")
      return null

    return bindings_map
  return

# Replace the default Knockout binding provider.
ko.bindingProvider.instance = new ErrorHandlingBindingProvider()

As a matter of interest this started back with Knockout issue #793, with the credit for this technique owing to Ryan Niemeyer. Speaking of which, while you are at it you may want to check out Ryan’s post Knockout.js Troubleshooting Strategies.

Wrapping custom bindings

Our custom binding handlers are “wrapped” for the following purpose:

  1. make
    1
    this
    
    into the binding object (instead of
    1
    window
    
    );
  2. isolate any errors when
    1
    binding.init
    
    or binding.update` are called;
  3. add class
    1
    binding-error
    
    to any element where the binding
    1
    .init
    
    or
    1
    .update
    
    call fails.
# The `isolate_call` function wraps `binding.init` and `binding.update`
isolate_call = (binding, fn_name, key) ->
  # binding is the binding object
  # fn_name is one of `init` or `update`
  # key is the name of the binding

  fn = binding[fn_name]
  unless fn then return undefined

  unless _.isFunction(fn)
    console.error("%cERROR%c Binding #{key}.#{fn_name} is not a function",
      "color:red", "color:black", binding, fn_name, fn)
    return undefined

  wrapped_fn = (args...) ->
    try
      # Pass-through the call to the original handler.
      return fn.apply(binding, args)
    catch e
      console.error("%cERROR%c: Binding #{key}.#{fn_name} error",
        "color:red", "color: black", args[0], e, e.stack)

      # args = [element, valueAccessor, allBindings, view_model, context]
      # Add a class indicating an error.
      $(args[0]).addClass("binding-error")
      return
  return wrapped_fn


isolate_binding_errors = (target, binding, key) ->

  # Expose the original binding functions.
  binding.init_fn = binding.init
  binding.update_fn = binding.update_fn

  # Add our wrapped functions.
  binding.init = isolate_call(binding, 'init', key)
  binding.update = isolate_call(binding, 'update', key)

  # Add this to the new to-be bindingHandlers object.
  target[key] = binding
  return


# Where `bindings` is a Javascript object containing our custom bindings
_.extend(ko.bindingHandlers, _.transform(bindings, isolate_binding_errors))

It may be worth noting that I do not wrap the built-in Knockout bindings. Though there would probably be nothing wrong with doing so, I was wary of redefining

1
this
on them — which ordinarily otherwise seems to just be
1
window
. I have been mindful of the use of
1
this
on my custom bindings, which I find quite convenient as one can do things like this (and beg your pardon for the tangent):

custom_binding =
  init: (@element, @valueAccessor) ->
     @add_wrap()

  add_wrap: ->
    $(@element).wrap("<div>")

Which in my mind is quite a bit more concise and semantic than

1
custom_binding.add_wrap(@element)
. However, I am not certain whether the Knockout built-in bindings use
1
this
to any effect, so out of caution I do not wrap them as above.

Styles for
1
binding-error

For the

1
binding-error
class style I have the following visually obnoxious properties:

/* [data-bind] */.binding-error {
  border: 4px dashed red;
  background-color: #F88;
}

/* [data-bind] */.binding-error:after {
  font-size: 16px;
  color: red;
  background-color: white;
  padding: 2px 0.5em;
  border: 1px solid gray;
  font-style: italic;
  content: "This item may not be working as expected.";
}

This striking visual appearance comes in handy with the other techniques I use to debug.

Other techniques

When elements have the

1
binding-error
, I often right-click them in Chrome and click “Inspect element”. This opens it up in the Developer Tools, and
1
$0
is set to the inspected node. The following commands are then particularly helpful:

  • 1
    ko.dataFor($0)
    
  • 1
    ko.contextFor($0)
    

Alternatively, there is also the helpful Knockout Context Debugger.

jsFiddle example

Here is what it looks like when you put it all together.

Summary

With the above techniques it becomes quite a bit simpler to root out and resolve issues with Knockout bindings. I hope you find the above helpful, and of course I would be grateful if you could please comment if you have other thoughts on debugging with Knockout.