13

I have very simple html page with js code:

<html>
    <head>
        <title></title>
    </head>
    <body>

        <div id="divButtons">

        </div>

        <script type="text/javascript">
            var arrOptions = new Array();

            for (var i = 0; i < 10; i++) {
                arrOptions[i] = "option" + i;
            }

            for (var i = 0; i < arrOptions.length; i++) {
                var btnShow = document.createElement("input");
                btnShow.setAttribute("type", "button");
                btnShow.value = "Show Me Option";
                var optionPar = arrOptions[i];
                btnShow.onclick = function() {
                    showParam(optionPar);
                }

                document.getElementById('divButtons').appendChild(btnShow);
            }

            function showParam(value) {
                alert(value);
            }        
        </script>
    </body>
</html>

That page binds 10 buttons, but when you click on any button it always shows alert "option9". How is it possible assign onclick event to show correspondent option !?

Thanks!

ihorko
  • 6,855
  • 25
  • 77
  • 116
  • Does this answer your question? [JavaScript closure inside loops – simple practical example](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) – Donald Duck May 15 '23 at 15:28

8 Answers8

38

You'll have to do something like this:

btnShow.onclick = (function(opt) {
    return function() {
       showParam(opt);
    };
})(arrOptions[i]);
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
  • 5
    What is actually happening here? This worked for me aswell, but i want to understand aswell :-) – MartinElvar Aug 08 '12 at 16:56
  • 2
    @MartinElvar He created A function which arguments are opt, then when we apply the (...)(arrOptions[i]) then it's passed to opt and then when we click on btnShow then this function is called with the params that have been set. – Yehonatan Dec 11 '13 at 15:45
  • 1
    A better way, that isn't as confusing is using the "data" attribute of the node, and then using the this.data as the parameter of the function. Here's an example: `btnShow.data = arrOptions[i]; btnShow.onclick = function() { showParam(this.data); }` – ray_voelker May 02 '16 at 13:26
3

Consider the fact that when the onclick() function is executed, all it has is:

showParam(optionPar);

, verbatim. The optionPar will be resolve at the time the click event is executed, and at this point it most likely be the latest value you assigned to it. You should generally avoid passing variables in such a way.

The problem you are trying to solve is best solved by re-writing the piece such as:

            btnShow.value = "Show Me Option";
            var optionPar = arrOptions[i];
            btnShow.optionPar = optionPar;
            btnShow.onclick = function(e) {
                // if I'm not mistaking on how to reference the source of the event.
                // and if it would work in all the browsers. But that's the idea.
                showParam(e.source.optionPar);
            }
Pawel Veselov
  • 3,996
  • 7
  • 44
  • 62
2

The accepted answer seems to work, but seems to be confusing and a somewhat cumbersome way to do it. A better way perhaps might be to use the data attribute for the element you're looking to assign the event listener for. It's simple, easy to understand, and way less code. Here's an example:

btnShow.data = arrOptions[i];

btnShow.onclick = function() {
  showParam(this.data);
}
ray_voelker
  • 495
  • 3
  • 12
1

I attach an event handler:

        window.onload = function() {
            var folderElement;
            tagFolders = document.getElementById("folders");
            for (i = 0; i < folders.length; i++) {
                folderElement = folderButtons[i];
                folderElement = document.createElement("button");
                folderElement.setAttribute("id", folders[i]);
                folderElement.setAttribute("type", "button");
                folderElement.innerHTML = folders[i];
                if (typeof window.addEventListener !== "undefined") {
                    folderElement.addEventListener("click", getFolderElement, false);
                } else {
                    folderElement.attachEvent("onclick", getFolderElement);
                }
                tagFolders.appendChild(folderElement);
            }

which can retrieve anything from the element that triggered the event:

// This function is the event handler for the folder buttons.
function getFolderElement(event) {
    var eventElement = event.currentTarget;
    updateFolderContent(eventElement.id);
}

in which case you have to embed the option inside the element / tag. In my case I use the id.

Günter
  • 119
  • 2
  • 7
1

For jquery, check out the adding event data section from the API:

...
for (var i = 0; i < arrOptions.length; i++) {

    $('<input id="btn" type="button" value="Show Me Option"><input>').appendTo("#divButtons")

    $('#btn').bind("click", {
        iCount: i},
    function(event) {
        showParam(arrOptions[iCount]);
    });
}
Roman
  • 8,826
  • 10
  • 63
  • 103
1

The accepted answer is correct but I feel that no real explanation was done.

Let me try to explain, the issue here is classical missing closure.

The variable 'i' is getting increased by 1 per loop iteration, and the on-click event actually is not being executed, whether only applied to the a element, it getting summarize up to the length of arrOptions which is 10.

So, the loop continues up until 'i' is 10, Then, whenever the on-click event is being triggered, it takes the value of i which is 10.

now, for the solution, in the solution we are using a closure, so that when we apply the value of 'i' to the on-click event of the a element, it actually gets the exact value of i at in time.

The inner function of the onclick event create a closure where it references the parameter (arrOptions[i]), meaning what the actual i variable is at the right time.

The function eventually closes with that value safely, and can then return its corresponding value when the on-click event is being executed.

john Smith
  • 1,565
  • 7
  • 34
  • 54
0

You pass just the reference of the variable to the function, not it's value. So every time the loop is iterated, it assigns a reference to your anonymous function and all of them point to the same value in memory. But since you use the same variable name in the loop, you overwrite the value of the variable. You can concatenate the variable to a string to preserve it's value. For example like that:

btnShow.onclick = new Function("", "showParam(" + arrOptions[i] + ");");

The first parameter is the name of the function but afaik it is optional (it can be left blank or omitted at all).

StanE
  • 2,704
  • 29
  • 38
0
  pp();

  function pp()
  {

    for(j=0;j<=11;j++)
    {

      if(j%4==0)
      {
        html+= "<br>";
      }
      html += "<span class='remote' onclick='setLift(this)' >"+ j+"</span>";
    }
    document.getElementById('el').innerHTML = html;
  }

  function setLift(x)
  {
    alert(x.innerHTML);
  }
Clean Coder
  • 496
  • 5
  • 12