Update: Knockout 1.3 was eventually renamed to version 2.0
I am really excited about the changes coming in Knockout 1.3. In the previous post, I discussed the native template engine. It will likely change the way that we all write Knockout apps. The next feature that I find very intriguing is the ability to swap in a custom binding provider.
What is a binding provider?
To understand what a binding provider is all about, let’s talk about the default binding provider that comes with Knockout 1.3. The default binding provider looks for any elements that have a data-bind
attribute. When the binding provider finds an element with this attribute, then it will parse the attribute value and turn it into a binding object using the current data context. This works in the same way as bindings have always worked in Knockout. You declaratively add your bindings to the elements that you want and Knockout binds them to the current data at that level.
What does a custom binding provider look like?
A binding provider only has to answer two simple questions:
- Given a DOM node, does that node have any bindings?
- Given a DOM node (that passed #1) and the current data context, what does the binding object look like?
A binding provider is an object that implements two functions:
1 2 3 4 5 6 7 8 9 |
|
The nodeHasBindings
function takes in a DOM node. Remember that this is a node, so it is not necessarily an element. In fact, the default binding provider looks at comment nodes to see if they are containerless bindings.
The getBindings
function expects you to return an object that represents the bindings as applied to the current data context. For example, if you want to apply a text
binding with the value of the current data’s name
observable, then you would return an object like: { text: bindingContext.$data.name }
.
Writing a quick custom binding provider
Suppose that we are not satisfied with putting our logic as strings in the data-bind attributes. Maybe our bindings are getting too verbose or they contain more logic than we want in our view. What if we did something similar to CSS classes and explicitly assigned bindings by name to our elements? So, we don’t confuse our presentation classes and our data classes, I am going to assign our binding classes in a data-class
attribute.
Our nodeHasBindings
function can be pretty simple:
1 2 3 4 |
|
If the node supports the getAttribute
function (it is an element), then see if the data-class
attribute has a value.
Now we need to write the getBindings
function. Like CSS classes, it would be nice to support multiple space-separated classes in a single data-class
attribute. This could allow us to share binding specifications between elements.
First let’s take a look at what our bindings will look like. We are going to create an object to hold the bindings. The property names will match the keys that we will use in the data-class
attributes. Knockout uses a with
statement when parsing bindings that allows variables to be evaluated with the current data context at the top of the scope chain. For these bindings, let’s avoid using with
and instead rely on this
being set to the current data context when the binding object is returned.
Here are some sample bindings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
In the products
binding, we can simply return the binding object, as the observableArray that we care about is directly on our view model. In that case, we do not need to rely on this
being set correctly. For the other bindings, we return the binding object in an anonymous function where we rely on this
being set to the appropriate data. Now we have our bindings specified and we didn’t even need to put them in strings.
The next step is to write our getBindings
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
We loop through each key in the data-class
attribute and build up a result object from each one. If the binding object is a function, then we call it with our current data as this
, otherwise we just use the object that was provided. Currently this handler does not consider the containerless bindings supported in 1.3 that live in comment nodes, but that could be added with some additional work.
Here is what the completed binding provider looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
The constructor for our binding handler takes in the object used to look up our binidngs. Now, the final step is to tell Knockout to use our custom binding provider (before calling ko.applyBindings
) by overriding ko.bindingProvider.instance
like this:
1 2 |
|
Now, we can specify bindings in our HTML like:
1 2 3 4 5 6 7 8 9 |
|
This binding provider sample implementation is available on Github here https://github.com/rniemeyer/knockout-classBindingProvider.
Here is a sample using this technique:
Link to sample on jsFiddle.net
Is this better than the default binding provider?
I personally prefer using declarative bindings in the data-bind
attribute, but I think that it is great to have the flexibility to do it in a different way. Even with a technique like this, I still think that it is wise to write a clean and robust view model, possibly using some of the ideas from this post. The view model itself should not change either way. If using a custom binding handler like this one, it is a good idea to keep the object that holds the bindings cleanly separated from the view model.
What else could you do with a custom binding provider?
While the default binding mechanism in Knockout works well for most of us, some developers are looking for alternative ways of specifying their bindings. Some different possibilities for custom binding providers:
- Use id/class attributes to find bindings in a binding object. Maybe something like Brandom Satrom’s excellent Knockout.Unobtrusive plugin.
- Store bindings in jQuery $.data or expando properties on the elements (ko.utils.domData.get/set). This would allow you to add bindings directly in code that write to this location. A jQuery plugin could be used to apply bindings to a set of elements.
- use more specific attributes like
data-bind-text
and build the bindings based on the attribute name (like Aaron Powell’s KO pre-parser). - maybe something with conditional bindings. Based on some state, bindings turn on/off.
- maybe namespaced bindings where the actual view model applied to the binding depends on the attribute name (like knockout.namespaces).