views:

143

answers:

4

I am trying to get the ID of an element bound with a jQuery delegate() function. I want to pass the element's ID to another function. The ID returned is always "undefined" and I'm not sure why. Here is my code:

$(document).ready(function() {
  var timeout = undefined;
  $('body').delegate(
    '#tab-form input[type="text"]',
    'keypress',
    function(){
      if(timeout != undefined) {
        clearTimeout(timeout);
      }
      timeout = setTimeout(function() {
        timeout = undefined;
        alert($(this).attr('id'));
      }, 500);
    }
  );
});

And my markup:

<form id="tab-form">
  <input type="text" value="" id="Tab_name" name="Tab[name]">
  <input type="text" value="" id="Tab_text" name="Tab[text]">
</form>

Making a keypress in the text input pops up a JS alert that says "undefined", instead of "Tab_name" or "Tab_text" like I imagined it would.

My initial Googling around leads me to believe that the reason for the attr('id') being undefined is that "this" is not actually a single DOM element, but is an array of the elements that delegate() is attached to. I have not been able to figure out how to access the current bound element's DOM object in the jQuery object array.

Any help is much appreciated!

A: 

The context is window because window owns setTimeout. Just cache it:

$(document).ready(function() {
  var timeout = undefined;
  $('body').delegate(
    '#tab-form input[type="text"]',
    'keypress',
    function(){
      var el = this;
      if(timeout != undefined) {
        clearTimeout(timeout);
      }
      timeout = setTimeout(function() {
        timeout = undefined;
        alert($(el).attr('id'));
      }, 500);
    }
  );
});
meder
this is what I ended up doing since it seemed like the most simple and straightforward approach to me in this case
thaddeusmt
+2  A: 

It's because this isn't what you want it to be in that anonymous function, it's window. There are a few ways to solve this, for example using $.proxy(), like this:

  timeout = setTimeout($.proxy(function() {
    timeout = undefined;
    alert(this.id);
  }, this), 500);
Nick Craver
nice, didn't know how to use proxy() before, thanks
thaddeusmt
A: 

meder already pointed out the reason for the behavior. You might also pass in the event object and use target:

$(document).ready(function() {
  var timeout = undefined;
  $('body').delegate(
    '#tab-form input[type="text"]',
    'keypress',
     function(event){
        if(timeout != undefined) {
           clearTimeout(timeout);
        }
        timeout = setTimeout(function() {
           timeout = undefined;
           alert($(event.target).attr('id'));
     }, 500);
   }
 );
}); 

Sidenote: using .delegate() on the document.body does not make sense at all. You could just bind those events with .live() to your elements.

jAndy
yet another slick way to fix the problem, thanks
thaddeusmt
On the sidenote, you are right (I think) after looking into the difference between live and delegate. I was copying the way Yii is doing binding, which they just changed from live to delegate for some bug fix (http://code.google.com/p/yii/issues/detail?id=1031). But yes, I might as well just bind to the form instead of body.
thaddeusmt
A: 

Since the other options are spoken for, I'll give the closure option. :o)

(function( th ) {
  timeout = setTimeout(function() {
    timeout = undefined;
    alert(th.id);
  }, 500);
})( this );

EDIT: To explain what is happening, basically you're creating a function, calling the function and passing in this as the argument to the function all at the same time.

Think of it this way:

  // Create a new function that accepts one argument
function myFunc( th ) {
  timeout = setTimeout(function() {
    timeout = undefined;
    alert(th.id);
  }, 500);
}

  // Immediately call the function, and pass "this" in to it
myFunc( this );

This is the exact same thing. Take this, wrap the entire function in (), make it anonymous by removing the name of the function myFunc, and then take the execution operator ( this ) from the function call, and place it directly after the function.

When you do that, you're essentially calling the function just after you've created it. It is just an easy way of immediately executing an unnamed (anonymous) function.

patrick dw
what syntax voodoo is this? just ran it and it works, but, man, I have not seen that before
thaddeusmt
@thaddeusmt - It is a closure, and is a *very* useful feature of javascript. Basically, you're creating a new scope inside of a function body, calling the function and passing in `this` as a argument, and referencing the argument with the `th` parameter. I'll update my answer with a further explanation of the closure.
patrick dw
thanks for the extra explanation! much clearer now
thaddeusmt