Search code examples
knockout.jsknockout-2.0debouncing

KnockoutJS - how do I debounce clicks?


I want to change what happens by default when a button is pressed - I need to prevent buttons from being double-clicked.

One option is wrapping the functions the buttons are bound to in _.once() or _.throttle() from underscore, but it would be better if this were handled higher up the stack.

So I found this question: knockoutjs overriding bindinghandlers and the fiddle referenced in the answer (http://jsfiddle.net/rniemeyer/PksAn/) and tried to do something with it. Below is the original code from the fiddle.

(function(){
var originalInit = ko.bindingHandlers.click.init,
    originalUpdate = ko.bindingHandlers.click.update;

ko.bindingHandlers.click = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        var wrappedValueAccessor = function() {
            return function(data, event) {
                ko.bindingHandlers.click.preOnClick.call(viewModel, data, event);
                valueAccessor().call(viewModel, data, event);
                ko.bindingHandlers.click.postOnClick.call(viewModel, data, event);
            };
        };

        originalInit(element, wrappedValueAccessor, allBindingsAccessor, viewModel);
    },
    update: originalUpdate,
    preOnClick: function(data, event) {
    },
    postOnClick: function(data, event) {
    }
};
})();

I tried to wrap wrappedValueAccessor with _.throttle() and I also tried wrapping the nested function but it turns out that valueAccessor() is returning a new function each time and I think that this is what I'm supposed to wrap.

That code is here: (from knockout-2.2.1.debug.js starting at line 2043)

function makeValueAccessor(bindingKey) {
    return function () { return parsedBindings[bindingKey] }
}

Which means I'd have to get into the internals of knockout which seems wrong.

So my question is, am I on to the right idea here or is the approach completely wrong. Is there a better way to do this?


Solution

  • You can just create a debounce version of your valueAccessor returned handler function and store it, and return that debounced version from the wrappedValueAccessor function:

    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        var debouncedAccessor = _.debounce(valueAccessor(), 800, true)
        var wrappedValueAccessor = function() {
            return debouncedAccessor;
        };        
        originalInit(element, wrappedValueAccessor, allBindingsAccessor, viewModel);
    },
    

    Demo JSFiddle.