spengos spengos - 5 months ago 31
jQuery Question

Extracting a debounce function from a keydown event listener

I have a table where rows can be selected using up/down arrows and when a row is selected this goes off and gets the record via ajax.

To stop users spamming ajax requests I have a debounce function that I am calling from a directive. This is triggered through a keydown event. This all works but not the way I need it to. I want the preventDefault to be called before the debounce function is called. So a user can still move up/down rows without the delay and the ajax still only fires after the delay.

I think I need to extract my code out a to allow for this but after trying a few things I can't get it working. This is the original version:

In the directive:

$('table').keydown(scope.debounce(function (e) {

if(e.keyCode == 38) { // Up arrow
// Do Ajax stuff

e.preventDefault();
e.stopPropagation();
}

if(e.keyCode == 40) { // Down arrow
// Do Ajax stuff

e.preventDefault();
e.stopPropagation();
}

}, 250));


In the controller:

$scope.debounce = function (fn, delay) {
var timer = null;

return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
};


I have tried extracting the function that gets called on keydown but now it's just ignoring the delay. Can't get my head around how to get it working. This is what I have so far:

In the controller:

$('table').keydown(someFunction);

function someFunction(e) {
e.preventDefault();
e.stopPropagation();

var blahblah = scope.debounce(function (e) {

if(e.keyCode == 38) { // Up arrow
// Do Ajax stuff
}

if(e.keyCode == 40) { // Down arrow
// Do Ajax stuff
}

}, 250);

blahblah();
}

Answer

It looks like every time keydown is pressed, someFunction is called and it creates a new debounced wrapper called blahblah only to be used exactly once.

Each of these debouncers get called once per keydown stroke, so nothing really get debounced, and everything gets delayed by 250 ms.

You should define blahblah outdide of someFunction and that should fix your timing problem. Don't forget to pass the event object to blahblah.

$('table').keydown(someFunction);

var blahblah = scope.debounce(function (e) {

    if(e.keyCode == 38) { // Up arrow
        // Do Ajax stuff
    }

    if(e.keyCode == 40) { // Down arrow
        // Do Ajax stuff
    }
}, 250);

function someFunction(e) {
    e.preventDefault();
    e.stopPropagation();

    blahblah(e);
}

Also, there is an additional problem but the solution above should have taken care of it. For completeness, I'd like to point it out. The way someFunction is written in the OP, you have a name shadowing problem. Let's rename blahblah's parameter e to demonstrate the point:

var blahblah = scope.debounce(function (innerE) {

    if(innerE.keyCode == 38) { // Up arrow
        // Do Ajax stuff
    }

    if(innerE.keyCode == 40) { // Down arrow
        // Do Ajax stuff
    }

}, 250);

Because of this, when you subsequently call blahblah() without passing in an event object, innerE is undefined. It's not the event object e of the outer scope.

Comments