0

I have some code that generates a bunch of button elements and appends them to a div in the body via some DOM functions. It dynamically sets a bunch of anonymous functions (event handlers) to the onclick event of each button.

This is performed by iterating from 0 to 20.

The problem is, I want to use the loop counter, i, in the event handler -- but, as the value it is at that iteration in the loop not as the final value i attains. At first, I thought that I could do this through closure, but then I realized that it did not perform how I thought it initially would. i keeps changing each iteration, and all the other anon functions (through closure?) must see the same i in that the i in scope to it all points to the same value. Since they all report the same value when you trigger the onclick.

So I guess, how can I go about fixing that? I tried setting i to another variable and so on, but that all results in the same because that new variable just has its value updated each iteration just as i. I think I'm missing something simple here.

<!DOCTYPE html>
<html>
<head>
    <title>loop closure test</title>

<script language="javascript" type="text/javascript">
window.onload = function() {

    var div = document.getElementById("myDiv");

    for( var i = 0; i <= 20; i++ ) {
        var b = document.createElement("button");
        b.setAttribute("type","button");
        b.appendChild(document.createTextNode("Button" + i.toString()));
        b.onclick = function(event) {
            var date = new Date( 2012, 3, i );
            alert( date.getMonth() + "/" + date.getDate() + "/" + date.getFullYear() );
        }       
        div.appendChild(document.createElement("br"));
        div.appendChild(b);
    }       
}
</script>

</head>

<body>
<div id="myDiv"></div>
</body>

</html>

I thought about it some more, and the example answer below. Basically I needed to trap i inside another closure. I decided to go with this route:

<!DOCTYPE html>
<html>
<head>
    <title>loop closure test</title>

<script language="javascript" type="text/javascript">
window.onload = function() {

    var div = document.getElementById("myDiv");

    for( var i = 0; i <= 20; i++ ) {        
        div.appendChild( (function() {
            var day = i;
            var b = document.createElement("button");
            b.setAttribute("type","button");
            b.appendChild(document.createTextNode("Button" + i.toString()));
            b.onclick = function(event) {
                var date = new Date( 2012, 3, day );
                alert( date.getMonth() + "/" + date.getDate() + "/" + date.getFullYear() );
            }


            return b;
        }()));

        div.appendChild(document.createElement("br"));

    }

}
</script>

</head>

<body>
<div id="myDiv"></div>
</body>

</html>
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
user17753
  • 3,083
  • 9
  • 35
  • 73
  • 1
    possible duplicate of [Assign click handler in for loop in javascript](http://stackoverflow.com/questions/8881306/assign-click-handler-in-for-loop-in-javascript) – Pointy Mar 22 '12 at 20:30
  • Yes, it would work with a closure in each loop. – Bergi Mar 22 '12 at 20:30
  • The key thing to know is that JavaScript local variables are scoped at the function level. Thus, if you want to give each handler its own "i", you need to instantiate the handler functions in *another* function. There are many, many StackOverflow questions on exactly this issue. – Pointy Mar 22 '12 at 20:32
  • @Pointy This is not jQuery at all and the question you linked is completely foreign to me. – user17753 Mar 22 '12 at 20:32
  • 1
    It's exactly the same question whether you're using jQuery or not; jQuery is just JavaScript, after all. – Pointy Mar 22 '12 at 20:33
  • [Here](http://stackoverflow.com/questions/6163720/javascript-every-event-handler-defined-in-for-loop-is-the-same-uses-last-itera) is another such question. – Pointy Mar 22 '12 at 20:36
  • @Pointy I realize jQuery is written in Javascript. A reply like that one is completely nonconstructive. If I wanted a jQuery answer I would have tagged it. The question is not the same. – user17753 Mar 22 '12 at 20:37
  • @user1169578 well you'll get it at some point and then you'll see that it really is the same fundamental issue. Note that the solution that Just_Mad posted uses the same technique mentioned there: another function to create a separate closure and build your event handler as its return value. There are several cosmetically different ways to do it, but they're all basically the same because the basic problem is fundamental to how JavaScript works. – Pointy Mar 22 '12 at 20:41
  • Oh also, and I meant to type this earlier but I was distracted, to your credit in your question you've very accurately diagnosed exactly what's going on :-) – Pointy Mar 22 '12 at 20:43
  • @Pointy Yea I got it now, thanks. I understand closure in functional programming for the most part, just in this case I had to nest closure inside another closure to get the desired result which obviously must be a common hang-up if there's this many questions on the topic. – user17753 Mar 22 '12 at 20:48

1 Answers1

1

Try to use a closure like this:

var div = document.getElementById("myDiv");

for( var i = 0; i <= 20; i++ ) {
    var b = document.createElement("button");
    b.setAttribute("type","button");
    b.appendChild(document.createTextNode("Button" + i.toString()));
    b.onclick = function(pos) {
        return function(event) {
                var date = new Date( 2012, 3, pos );
                alert( date.getMonth() + "/" + date.getDate() + "/" + date.getFullYear() );
        }
    }(i);      
    div.appendChild(document.createElement("br"));
    div.appendChild(b);
}   ​
Just_Mad
  • 4,029
  • 3
  • 22
  • 30