1

I have this javascript function, which acts like a keyup but waits for the user to stop typing:

function afterDelayedKeyup(elements, action, delay){
  if(typeof(elements) == "string"){
    elements = $(elements);
  }
  elements.keyup(function(){
    if(typeof(window['inputTimeout']) != "undefined"){
      clearTimeout(inputTimeout);
    }  
    inputTimeout = setTimeout(action, delay);
  });
}

I call it like this:

afterDelayedKeyup(".foo", function(){ doSomething();}, 1000);

It works: if you start typing in a .foo input, then when you've not typed anything for 1 second, doSomething() is called.

What i want to do next, is to pass through the element it was called on into the doSomething() function, so doSomething knows which element triggered it to be called. I've tried this:

afterDelayedKeyup(".foo", function(){ doSomething(this);}, 1000);

and

afterDelayedKeyup(".foo", function(){ doSomething($(this));}, 1000);

But it doesn't seem to work. If my doSomething() function looks like this for example:

function doSomething(element){
  console.log("element.attr('class') = "+element.attr('class'));
  console.log("$(element).attr('class') = "+$(element).attr('class'));
}

Neither of them will log out 'foo' like i'd expect. I get either TypeError: undefined is not a function (for the first log line) or $(element).attr('class') = undefined for the second log line.

I think that the value of 'this' is getting lost somewhere in between being passed through to afterDelayedKeyup() and being passed through to setTimeout() but i can't work out what's happened.

Rocket Hazmat
  • 204,503
  • 39
  • 283
  • 323
Max Williams
  • 30,785
  • 29
  • 115
  • 186
  • Dup? http://stackoverflow.com/questions/3127429/javascript-this-keyword – elclanrs Jun 20 '14 at 16:35
  • Don't think its a dupe – Monarch Wadia Jun 20 '14 at 16:36
  • My question is not "what does 'this' mean in javascript?" so that's not a duplicate. My question is "My code doesn't work, i think it's got something to do with 'this' but i'm not sure, can anyone see the specific problem?". Thanks though :) – Max Williams Jun 20 '14 at 16:36
  • Well, maybe your question comes from the misunderstanding of how `this` works? – elclanrs Jun 20 '14 at 16:38
  • My question also comes from an incomplete understanding of Javascript in general, but directing me to the Javascript page on wikipedia wouldn't be a valuable comment. – Max Williams Jun 20 '14 at 16:40
  • 1
    I'm pointing you to other SO answers. In any case, check this one too http://stackoverflow.com/questions/337878/var-self-this. – elclanrs Jun 20 '14 at 16:41
  • The misunderstanding comes from the OP's misunderstanding of setTimeout. The callback in setTimeout does not accept a parameter, so we adjust for that and pass through $(this). See my answer. – Monarch Wadia Jun 20 '14 at 16:46
  • @monarch he is not trying to pass in a paramater, `delay` is the time – Andrei Nemes Jun 20 '14 at 16:47
  • @Andrei I'm actually talking about `action` – Monarch Wadia Jun 20 '14 at 16:48

7 Answers7

3

You call your function like this :

setTimeout(action, delay);

Which mean when you do that :

function(){ doSomething(this);}

this will be the window object.

Try using .bind like this :

setTimeout(action.bind(this), delay);

.bind() will change the value this inside the action function for the first arguments.

Here's the doc for .bind : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind


As Andrei Nemes said :

Also, it is not really necessary to wrap doSomething() in an anonymous function, you can get away with just passing the reference afterDelayedKeyup(".foo", doSomething, 1000);

Karl-André Gagnon
  • 32,531
  • 5
  • 47
  • 70
  • Also, it is not really necessary to wrap `doSomething()` in an anonymous function, you can get away with just passing the reference `afterDelayedKeyup(".foo", doSomething, 1000);` – Andrei Nemes Jun 20 '14 at 16:41
  • @AndreiNemes Totally true, may i had that to the answer? – Karl-André Gagnon Jun 20 '14 at 16:41
  • Yeah sure, go ahead! Also, note that `Function.prototype.bind` is not supported in Internet Explorer 8, so if you want to get around that you can use jQuery's proxy: `setTimeout($.proxy(action, this), delay);` – Andrei Nemes Jun 20 '14 at 16:42
1

The event object should tell you which input element you're typing in.

afterDelayedKeyup(".foo", function(e){ doSomething(e);}, 1000);

Just send it to your action:

elements.keyup(function(e){
  if(typeof(window['inputTimeout']) != "undefined"){
    clearTimeout(inputTimeout);
  }  
  inputTimeout = setTimeout(action(e.target), delay);
});
Wex
  • 14,875
  • 10
  • 56
  • 99
1

Before afterDelayedKeyup, set var t=$(this); and then use t in the callback function instead.

Pluto
  • 2,525
  • 22
  • 31
1

I guess that happens because of closure. The new function within the afterDelayedKeyup creates a new local context or scope so "this" will refer to the Window object since it is global. To solve this I believe you need to do something like this:

afterDelayedKeyup.call("object you want to pass to the function",[array of the arguments]);

so I guess it should be afterDelayedKeyup.call(this,".foo",1000) and your afterDelayedKeyup should then invoke the anonymous function

function afterDelayedKeyup(className, duration)
{
  function(){
    doSomething(this);
}(); //not sure if you want it to be immediately invoked
}
wahiddudin
  • 103
  • 6
1

doSomething doesn't get executed until it's fired off by the timer, at which point the this reference no longer exists (and even if it did, wouldn't point to the object you'd be expecting). Save this explicitly to another variable, then reference that variable in the function you pass.

var tempThis = this; afterDelayedKeyup(".foo", function(){ doSomething($(tempThis));}, 1000);

tophyr
  • 1,646
  • 14
  • 20
1

setTimeout's callback function does not accept a parameter in the OP's current structure. A new parameter is needed like so: inputTimeout = setTimeout(action, delay, $(this));

function afterDelayedKeyup(elements, action, delay){
  if(typeof(elements) == "string"){
    elements = $(elements);
  }
  elements.keyup(function(){
    if(typeof(window['inputTimeout']) != "undefined"){
      clearTimeout(inputTimeout);
    }  
    inputTimeout = setTimeout(action, delay, $(this));
  });
}

Then you can call

afterDelayedKeyup(".foo", doSomething, 1000);
Monarch Wadia
  • 3,184
  • 2
  • 29
  • 33
1

Your code follows some pretty weird pattern. Basically what you want to achieve is a basic debounce:

var keyUpHandler = function( originalEvent ) {
    var invoker = $( originalEvent.target ); //Your "this"
    console.log( invoker );
    // do whatever you need
};

var keyUpDebouncer = function( event ) {
    if ( keyUpDebouncer.timeout ) {
        clearTimout( keyUpDebouncer.timeout );
    }
    keyUpDebouncer.timeout = setTimeout( function() {
        keyUpHandler( event );
    }, 1000 );
};

$( '.my-keyboard-receiver' ).on( 'keyup', keyUpDebouncer );
Ingmars
  • 938
  • 5
  • 10