0

I'm writing a simple JavaScript form, it is a list of devices with a button next to each where the button should take you to a text field where you can edit info about a device. Instead all of the buttons take you to the info for the last device on the list. Here is the snippet where I'm creating the list:

for(var i in portal.clients){
  var row = document.createElement('tr');
  var cell2 = document.createElement('td');
  var button = document.createElement('button')
  var title = document.createTextNode("Edit Config")
  button.appendChild(title)
  button.onclick = function(){displaySettingsPage(portal.clients[i]); console.log("Showing client: " + clientNum)}
  cell2.appendChild(button)
  row.appendChild(button)

  var cell1 = document.createElement('td');
  var client = document.createTextNode(portal.clients[i].info.description.name)
  cell1.appendChild(client)
  row.appendChild(cell1)
  table.appendChild(row);
}

I assume the problem is that the i in the onClick function declaration is getting evaluated when the button is clicked instead of when the function is declared like I meant it to. Is there a way that I can force a variable to be evaluated when the function is declared? Or is there some other method that I should use to pass the client index to the function?

azdle
  • 2,139
  • 4
  • 17
  • 23
  • take a look at this question http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – agradl Oct 22 '12 at 18:56

3 Answers3

4

Anytime you want to use your iterative variable i in a callback (i.e. something that will not be evaluated until after your loop is complete) you need to capture the variable in a new scope.

for(var i in portal.clients){
    (function(i){
        // do something later with i
    })(i);
}

There are a great many discussions here on SO regarding scoping in JavaScript. I highly recommend you read up on it, as it is essential to understand JavaScript in any significant manner.

Matt
  • 41,216
  • 30
  • 109
  • 147
  • 1
    Thanks! Besides scoping, is there something that I should have been searching for to find the answer for this? My JavaScript terminology is really weak still. – azdle Oct 22 '12 at 19:04
  • @Azdle "JavaScript closures" and "JavaSCript scope" are a probably good places to start. – Matt Oct 22 '12 at 19:08
1

You'll need to create a new scope where a copy of i lives for each iteration. In the code you provide, you can create a new scope by writing a function that runs immediately and returns the actual function you wanted to use with the variable in the new scope attached:

for(var i in portal.clients) {
  var row = document.createElement('tr');
  var cell2 = document.createElement('td');
  var button = document.createElement('button')
  var title = document.createTextNode("Edit Config")
  button.appendChild(title)
  button.onclick = (function(i){return function(){displaySettingsPage(portal.clients[i]); console.log("Showing client: " + clientNum)}; })(i);
  cell2.appendChild(button)
  row.appendChild(button)

  var cell1 = document.createElement('td');
  var client = document.createTextNode(portal.clients[i].info.description.name)
  cell1.appendChild(client)
  row.appendChild(cell1)
  table.appendChild(row);
}
Mahn
  • 16,261
  • 16
  • 62
  • 78
  • Ok, that works, but I don't understand why. Why is the i evaluated when it is declared in this case, but not in my original code? EDIT: Ah, I understand it from Matt's response. Thanks! – azdle Oct 22 '12 at 18:59
  • @Azdle see the edit, basically there's an extra function that runs immediately, by the syntax `(function() {})();`, which is esentially the same as writing the code inside it directly, except that a new scope is created. By using `(function() {})(i);` we pass `i` to the new scope and use it to return the actual function. – Mahn Oct 22 '12 at 19:03
0

You might be able to get around it by putting the loop in a self-executing function. Untested example:

for(var i in portal.clients){
  (function (i) {
    var row = document.createElement('tr');
    var cell2 = document.createElement('td');
    var button = document.createElement('button')
    var title = document.createTextNode("Edit Config")
    button.appendChild(title)
    button.onclick = function(){displaySettingsPage(portal.clients[i]); console.log("Showing client: " + clientNum)}
    cell2.appendChild(button)
    row.appendChild(button)

    var cell1 = document.createElement('td');
    var client = document.createTextNode(portal.clients[i].info.description.name)
    cell1.appendChild(client)
    row.appendChild(cell1)
    table.appendChild(row);
  })(i);
}
gpojd
  • 22,558
  • 8
  • 42
  • 71