Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to assign event callbacks iterating an array in javascript (jQuery)

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!

like image 954
JorenB Avatar asked Dec 07 '22 06:12

JorenB


2 Answers

This is a classic "you need a closure" problem. Here's how it usually plays out.

  1. Iterate over some values
  2. Define/assign a function in that iteration that uses iterated variables
  3. You learn that every function uses only values from the last iteration.
  4. WTF?

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( ' ' ) );
});
like image 126
Peter Bailey Avatar answered Dec 11 '22 08:12

Peter Bailey


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.

like image 36
geowa4 Avatar answered Dec 11 '22 09:12

geowa4