I'm generating an unordered list through javascript (using jQuery). Each listitem must receive its own event listener for the 'click'-event. However, I'm having trouble getting the right callback attached to the right item. A (stripped) code sample might clear things up a bit:
for(class_id in classes) {
    callback = function() { this.selectClass(class_id) };
    li_item = jQuery('<li></li>')
                .click(callback);
}
Actually, more is going on in this iteration, but I didn't think it was very relevant to the question. In any case, what's happening is that the callback function seems to be referenced rather than stored (& copied). End result? When a user clicks any of the list items, it will always execute the action for the last class_id in the classes array, as it uses the function stored in callback at that specific point.
I found dirty workarounds (such as parsing the href attribute in an enclosed a element), but I was wondering whether there is a way to achieve my goals in a 'clean' way. If my approach is horrifying, please say so, as long as you tell me why :-) Thanks!
This is a classic "you need a closure" problem. Here's how it usually plays out.
Again, when you see this pattern, it should immediately make you think "closure"
Extending your example, here's how you'd put in a closure
for ( class_id in classes )
{
  callback = function( cid )
  {
    return function()
    {
      $(this).selectClass( cid );
    }
  }( class_id );
  li_item = jQuery('<li></li>').click(callback);
}
However, in this specific instance of jQuery, you shouldn't need a closure - but I have to ask about the nature of your variable classes - is that an object?  Because you iterate over with a for-in loop, which suggest object.  And for me it begs the question, why aren't you storing this in an array?  Because if you were, your code could just be this.
jQuery('<li></li>').click(function()
{
  $(this).addClass( classes.join( ' ' ) );
});
                        Your code:
for(class_id in classes) {
    callback = function() { this.selectClass(class_id) };
    li_item = jQuery('<li></li>')
                        .click(callback);
}
This is mostly ok, just one problem. The variable callback is global; so every time you loop, you are overwriting it. Put the var keyword in front of it to scope it locally and you should be fine.
EDIT for comments: It might not be global as you say, but it's outside the scope of the for-loop. So the variable is the same reference each time round the loop. Putting var in the loop scopes it to the loop, making a new reference each time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With