Creating Custom Knockout Bindings
Background
I've been using and enjoying knockout.js for some time now.
It's a great library that allows you to use MVVM in web applications and keeps you from writing spaghetti code to manipulate the DOM without requiring a switch to a monolithic framework and the associated downsides like lock-in and too many abstractions from plain html.
Using knockoutjs, you are still free to use DOM manipulation yourself if and when you need it. The great thing is, it's also easily extendable.
Extending Knockout
Why is being extendable a big plus and why would you want to extend knockout? Is something essential missing from knockout?
Nope, I don't think so.
Instead of growing to a monolithic framework, it just solves a particular problem, namely factoring out the UI glue code into reusable bindings. It comes with almost all bindings you could think of by default, but it doesn't try to be everything for everyone - and thats where custom bindings come in.
Using custom binding handlers, it offers you the chance to stick to DRY and to use declarations instead of repeating javascript snippets over and over again.
That often comes in handy, when you need to reuse some javascript code in multiple places and the code is tied to an element defined in html.
In the next few paragraphs, I am showing some small, exemplary binding handlers that have proven useful to me, nothing fancy.
Example BindingHandlers
I've been using some small knockout bindings that uses jquery's fadeIn() / fadeOut() methods and slideDown() / slideUp() to achieve simple animations on an element.
FadeVisible
The binding is defined in the following few lines:
ko.bindingHandlers.fadeVisible = {
init: function (element, valueAccessor) {
var value = valueAccessor();
$(element).toggle(ko.unwrap(value));
},
update: function (element, valueAccessor) {
var value = valueAccessor();
ko.unwrap(value) ? $(element).fadeIn() : $(element).fadeOut();
}
};
SlideDownVisible
The definition for the slideDown binding looks almost identical:
ko.bindingHandlers.slideDownVisible = {
init: function (element, valueAccessor) {
var value = valueAccessor();
$(element).toggle(ko.unwrap(value));
},
update: function (element, valueAccessor) {
var value = valueAccessor();
ko.unwrap(value) ? $(element).slideDown() : $(element).slideUp();
}
};
In turn, they both are very similar to the example knockout binding in knockouts custom-binding documentation which also provides a binding that uses slideDown() and slideUp().
Usage
As for usage, you'd replace the default 'visible' binding with 'fadeVisible' or 'slideDownVisible' respectively.
<div data-bind="fadeVisible: isVisible">
...
Nuget package
I've used the slideDownVisible binding already in a couple of projects and I've finally gotten sick of copy/pasting them, so I've packaged them as nuget packages named 'knockout-fadeVisible' and 'knockout-slideDownVisible' and uploaded them to nuget.org so that I can add it faster the next time I might need it. The (very short) source is on github as well.
Bootstrap Modal
Another example of transforming javascript glue code to a declarative knockout binding would be the following modalVisible binding:
ko.bindingHandlers.modalVisible = {
init: function (element, valueAccessor) {
var value = valueAccessor();
/* init the modal */
$(element).modal();
/* subscribe to the 'hidden' event and update the observable, if the modal gets hidden */
$(element).on('hidden.bs.modal', function (e) {
if (ko.isObservable(value)) { value(false); }
});
},
update: function (element, valueAccessor) {
var value = valueAccessor();
ko.unwrap(value) ? $(element).modal('show') : $(element).modal('hide');
}
}
It wraps bootstrap javascript code in a tidy, nice to use knockout binding after using the binding like:
<div class="modal fade" data-bind="modalVisible: isVisible"...
This takes care of initializing the modal and allows controlling it's visibility using an observable. It handles hiding and showing of the modal and therefore removes the need to manipulate the DOM from my ViewModels javascript code.
Conclusion
Knockoutjs is nice and flexible library that is not only easy to get started with, but also easy to extend.
Creating custom binding handlers may save you from writing repetitive and error prone code and allows you to stick view specific code declaratively right on the target html element, which makes reasoning about your view easier.
Decoupling jQuery and other DOM manipulation code from your normal code also makes that code simpler to test.
Take care,
Martin
References
- Knockout.js (http://knockoutjs.com)
- Creating a custom binding for Knockout (http://knockoutjs.com/documentation/custom-bindings.html)
- Knockout's 'visible' binding (http://knockoutjs.com/documentation/visible-binding.html)
- jQuery slideDown() (https://api.jquery.com/slideDown/)
- jQuery slideUp() (https://api.jquery.com/slideUp/)
- jQuery fadeIn() (https://api.jquery.com/fadeIn/)
- jQuery fadeOut() (https://api.jquery.com/fadeOut/)
- fadeVisible binding (https://github.com/8/ko-bindings/blob/master/ko-bindings/Scripts/knockout-fadeVisible.js)
- slideDownVisible knockout binding (https://github.com/8/ko-bindings/blob/master/ko-bindings/Scripts/knockout-slideDownVisible.js)
- Bootstrap Modal Javascript (http://getbootstrap.com/javascript/#modals)