views:

54

answers:

3

JavaScript's late binding is great. But how do I early bind when I want to?

I am using jQuery to add links with event handlers in a loop to a div. The variable 'aTag ' changes in the loop. When I click the links later, all links alert the same message, which is the last value of 'aTag'. How do I bind a different alert message to all links?

All links should alert with the value that 'aTag' had when the event handler was added, not when it was clicked.

for (aTag in tagList) {
  if (tagList.hasOwnProperty(aTag)) {
    nextTag = $('<a href="#"></a>');
    nextTag.text(aTag);
    nextTag.click(function() { alert(aTag); });
    $('#mydiv').append(nextTag);
    $('#mydiv').append(' ');
  }
}
A: 

You need to keep a copy of this variable, like this:

for (aTag in tagList) {
  if (tagList.hasOwnProperty(aTag)) {
    nextTag = $('<a href="#"></a>');
    nextTag.text(aTag);
    var laTag = aTag;
    nextTag.click(function() { alert(laTag); });
    $('#mydiv').append(nextTag);
    $('#mydiv').append(' ');
  }
}

The aTag variable is changing each time you loop, at the end of the loop it's left as the last item in the loop. However, each of the functions you created point at this same variable. Instead, you want a variable per, so make a local copy like I have above.

You can also shorten this down a lot with chaining, but I feel it clouds the point in this case, since the issue is scoping and references.

Nick Craver
Won't the same problem happen with the variable `laTag`?
interjay
@interjay - Nope, that variable is **inside** the closure, the **same** reference isn't updated every time like the variable in the for delcaration, it's a **different** variable each loop, which is what you want.
Nick Craver
But there is no closure here - it's just a `for` loop.
interjay
@interjay - I was referring to the anonymous function for the click handler, you're creating a distinct reference to pass inside that closure whereas the original code doesn't and passes the reference of a changing variable (which is why you see the last value of the `for`).
Nick Craver
It is more tricky. I tried your code (in fact I tried it before, because I thought the same) and now I tried it again. It does not solve the problem, always the last value is shown. Strange!
Sven Larson
@Nick, there is only one variable `laTag` used by all the click handlers, because it is defined outside the handler. I tested this to confirm.
interjay
@interjay: exactly!
Sven Larson
This approach would work inside a `$.each` loop, since that involves creating a function which creates a closure for each iteration.
interjay
@interjay - Doh, you're correct, I was testing in a `$.each`, forgot it creates one: http://jsfiddle.net/cy2HJ/
Nick Craver
@Sven - You can do this using `$.each()` with the closure a bit cleaner, like this: http://jsfiddle.net/NAzeQ/
Nick Craver
+1  A: 

You can pass data to the bind method:

nextTag.bind('click', {aTag: aTag}, function(event) {
    alert(event.data.aTag);
});

This will make a copy of aTag, so each event handler will have different values for it. Your use case is precisely the reason this parameter to bind exists.

Full code:

for (aTag in tagList) {
  if (tagList.hasOwnProperty(aTag)) {
    nextTag = $('<a href="#"></a>');
    nextTag.text(aTag);
    nextTag.bind('click', {aTag: aTag}, function(event) {
      alert(event.data.aTag);
    });
    $('#mydiv').append(nextTag);
    $('#mydiv').append(' ');
  }
}
interjay
Thank you, it works. I am impressed.
Sven Larson
deserved upvote :)
Andy
A: 

You can also make a wrapper function that takes the text to alert as a parameter, and returns the event handler

function makeAlertHandler(txt) {
  return function() { alert(txt); }
}

and replace

nextTag.click(function() { alert(aTag); });   

with

nextTag.click(makeAlertHandler(aTag));
Jonas H
I tried it, it works also for me. I like it because it is more a 'Javascript' solution than 'jQuery'.
Sven Larson