KnockoutJS Custom Double Click Binding

February 2, 2016 in KnockoutJS


While working on a project the other day I was presented with an interesting issue. In our project we are using Knockout JS for 2-way data binding between a set of data and a Leaflet JS map. With all of this data, we have many different click events needed so the user can interact with and manipulate the data in different ways.

I needed a way to have 2 different bindings on one item for both a single and a double click. The single click would open a popup, and the double click would open a sidebar with more data about that item. By default, knockout only supports one type of click binding per item. Basically I wanted something like this.

<a href="#" data-bind="click: {single: onClick, double: onDoubleClick}">
    <p data-bind="text: someAtrribute"></p>
</a>

How can this be done? The answer is a custom binding! So I went to researching. I came across a stack overflow post that had an example of a single or double click binding. I decided to take it a step further and override the default click binding to suit my needs. Here's what I came up with.

ko.bindingHandlers.click = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, context) {
        var accessor = valueAccessor();
        var clicks = 0;
        var timeout = 200;

        $(element).click(function(event) {
            if(typeof(accessor) === 'object') {
                var single = accessor.single;
                var double = accessor.double;
                clicks++;
                if (clicks === 1) {
                    setTimeout(function() {
                        if(clicks === 1) {
                            single.call(viewModel, context.$data, event);
                        } else {
                            double.call(viewModel, context.$data, event);
                        }
                        clicks = 0;
                    }, timeout);
                }
            } else {
                accessor.call(viewModel, context.$data, event);
            }
        });
    }
};

This binding overrides the default click binding. It will still work as the default does, but will also take a Javascript object with single and double click attributes. It does this by checking the type of the valueAccessor. If it is an object, it will look for the single and double attributes. If it is a function, it will act like the default click binding.

Here's a quick example of how it can be used.

First we create a basic view model that will hold our points and has function for both single and double clicks.

var MainViewModel = function(points) {
    var self = this;
    self.points = ko.observable(points);
}

var Point = function(lat, lng) {
    var self = this;
    self.lat = lat;
    self.lng = lng

    self.onClick = function(){
        console.log("single click");
    }

    self.onDoubleClick = function(){
        console.log("double click");
    }
}

var viewModel = new ViewModel([
    new Point(80, -20),
    new Point(60, -40),
]);

ko.applyBindings(viewModel);

Here is what our html would look like for a single click binding. It will still work exactly as the default click binding does.

<div class="list-group" data-bind="foreach: points">
    <a href="#" data-bind="click: onClick">
        <p data-bind="text: lat"></p>
        <p data-bind="text: lng"></p>
    </a>
</div>

And here is where the magic happens. Note: it is the same binding, but instead of a single function, you can pass it an object with functions to call for single and double clicks!

<div class="list-group" data-bind="foreach: points">
    <a href="#" data-bind="click: {single: onClick, double: onDoubleClick}">
        <p data-bind="text: lat"></p>
        <p data-bind="text: lng"></p>
    </a>
</div>

The only drawback to using this binding is that there is a 200ms delay on single clicks due to it waiting for another click, but that time is fairly negligible in my situation.