James Dickens James Dickens - 3 months ago 10
Javascript Question

Setting functions to onclick for Buttons within loops with strings in HTML/Javascript

var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
var NewButton = document.createElement("NewButton");
var IdString = "ID" + i + "_" +j;
NewButton.onclick = function() { DoStuff(IdString) };
RelevantDiv.appendChild(NewButton);
}
}

function DoStuff(IdForStuff) {
// Does stuff with id
}


The problem is that every time the
NewButton.onclick
is set, that it is setting it to the final
IdString
which is
"ID4_4"
.

Answer

Your underlying issue is that the variable being used within the onclick callback, IdString, is having its declaration hoisted to the top of the current scope, ie the function it's running within. That means that every time you're within the loop, its value is overwritten - it's functionally the same as this:

var IdString;

var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
   for (var j = 0; j < 5; j++) {
      var NewButton = document.createElement("NewButton");
      IdString = "ID" + i + "_"  +j;
      NewButton.onclick = function() { DoStuff(IdString) };
      RelevantDiv.appendChild(NewButton);
   }
} 

You need to ensure that you capture the right value of IdString when you need it, which is typically done through the use of a closure:

var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
   for (var j = 0; j < 5; j++) {
      (function(IdString) {
          var NewButton = document.createElement("NewButton");
          NewButton.onclick = function() { DoStuff(IdString) };
          RelevantDiv.appendChild(NewButton);
      })("ID" + i + "_"  +j)
   }
} 

Here we create another inner function to create a new scope to hold each individual IdString value. It's then immediately called with the right value for each iteration of the loops. IdString is then captured within this closure, and the correct value will be used within the onclick callback.


Alternatively, you can bind the argument at the moment you know what the relevant value is:

var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
   for (var j = 0; j < 5; j++) {
      var NewButton = document.createElement("NewButton");
      NewButton.onclick = DoStuff.bind(null, "ID" + i + "_"  +j);
      RelevantDiv.appendChild(NewButton);
   }
} 

This does away with both of the inner functions, and directly assigns the onclick event to a copy of the DoStuff function that will have the right argument.

Comments