1

I'm programming in Javascript.

I need to dynamically generate the event handlers for the onclick event.

Here above the code:

for (var i = 1; i < CurrValue; i++) {
    getId('btnReadRFIDTag_' + i).onclick = function () 
    {
        ReadRFIDTag(getId('txtCode_' + i));
    };

}

The problem, obviously is that at run time the i variable get the latest value and not the right value.

But I can't fix the problem, because the handler does not allow parameters for the function.

How can I solve this problem?

Thank you.

Have a good day.

Erik Schierboom
  • 16,301
  • 10
  • 64
  • 81
  • possible duplicate of [Assign click handlers in for loop](http://stackoverflow.com/questions/4091765/assign-click-handlers-in-for-loop) –  May 28 '13 at 14:35

1 Answers1

6

Three options:

Use a Builder Function

The usual way is to have a builder function for the handlers:

for (var i = 1; i < CurrValue; i++) {
    getId('btnReadRFIDTag_' + i).onclick = buildHandler(i);
}
function buildHandler(index) {
    return function() {
        ReadRFIDTag(getId('txtCode_' + index));
    };
}

The function built by the buildHandler closes over the index argument from that call to buildHandler, rather than over i, and so it sees the right value.

Use ES5's Function#bind

If you can rely on ES5 (or you include an ES5 shim, as this is a shimmable feature), you can do it with Function#bind:

// Using Function#bind (option 1)
for (var i = 1; i < CurrValue; i++) {
    var elm = getId('btnReadRFIDTag_' + i);
    elm.onclick = function(index) {
        ReadRFIDTag(getId('txtCode_' + index));
    }.bind(elm, i);
}

Used like that, it creates unnecessary extra functions, so you could also use it like this:

// Using Function#bind (option 2)
for (var i = 1; i < CurrValue; i++) {
    var elm = getId('btnReadRFIDTag_' + i);
    elm.onclick = myHandler.bind(elm, i);
}

function myHandler(index) {
    ReadRFIDTag(getId('txtCode_' + index));
}

Use Event Delegation

Instead of putting a handler on each button, if they're all in a container (and ultimately, of course, they are, even if it's just document.body), you can put the handler on that container and then use event.target to find out which button was clicked. I wouldn't do that with old-style onclick, but you can with addEventListener or attachEvent:

var container = /* ...get the container... */;
if (container.addEventListener) {
    container.addEventListener('click', clickHandler, false);
}
else if (container.attachEvent) {
    container.attachEvent('onclick', function(e) {
       return clickHandler.call(this, e || window.event);
    });
}
else {
    // I wouldn't bother supporting something that doesn't have either
}

function clickHandler(e) {
    var btn = e.target;
    while (btn && btn !== this && btn.id.substring(0, 15) !== "btnReadRFIDTag_") {
        btn = btn.parentNode;
    }
    if (btn && btn !== this) {
        ReadRFIDTag(getId('txtCode_' + btn.id.substring(15)));
    }
}

The way that works is by hooking the click event on the container, and then when the click bubbles up (down?) to it, it looks at where the click originated and sees if that (or an ancestor of it) is one of the buttons we care about. If it is, we act on it.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    While I normally wouldn't give pluses for answers to questions that have been asked a thousand times, I've got to give one for not being yet another answer that puts an IIFE in the loop... so +1. :-) –  May 28 '13 at 14:41
  • 1
    Consider adding this to the FAQs http://stackoverflow.com/tags/javascript/info (maybe adding a few words why exactly his code doesn't work). – georg May 28 '13 at 17:51