BoundForGlory
BoundForGlory

Reputation: 4417

Why is last element of an array only being populated for onClick in Javascript

I am looping through an array of objects and i'm experiencing strange behavior. Here is my code:

  window.onload = function () {

    document.getElementById('btnAdd').onclick = function () {
        add("button");
    };
    function add(type) {
        var names = {};
        names['one'] = { id: "Button 1",msg:"#1" };
        names['two'] = { id: "Button 2", msg: "#2" };
        names['three'] = { id: "Button 3", msg: "#3" };
        //Create an input type dynamically.
        function addOnClick(element, currentName) {
              element.onClick = function () {
                 alert("button is " + names[currentName].msg);
            };
          };       

        for (name in names) {
            var element = document.createElement("input");

            element.type = type;
            element.value = names[name].id; 
            element.name = name;  
             addOnClick(element,name);

            var foo = document.getElementById("fooBar");
            //Append the element in page (in span).  
            foo.appendChild(element);
        }
    }
};

Heres the html:

 <p>
<input type="button" id="btnAdd" value="Add Buttons" />
<p id="fooBar">Buttons:   </p>

</p>

If you run this in your browser, and the "Add Buttons" button, 3 buttons are created and named appropriately. But if you click one of the 3 new buttons, the alert box always says "button is #3" and i have no idea why. Can someone explain why the onClick event is only remembering the last item in my array? Thanks

Upvotes: 1

Views: 2451

Answers (3)

Vivin Paliath
Vivin Paliath

Reputation: 95518

This is due to the way that closures work in Javascript (capture of local variables). The anonymous function that is assigned to element.onclick is:

function () { 
    // Note this is a function
    alert("button is "+ names[name].msg);
};

Here, the value of name is bound to the last value that is encountered in the loop, which is the last element. Essentially name in the loop points to a memory location that is used by name in the outer loop. That location is constantly updated and at the end, points to the value three. To get around this, you need an additional function (which creates a new scope) to preserve the value of name. This creates a version of name that is local to that new function and therefore preserved:

//A self-invoked function, into which you pass name. preservedName is the preserved
//value of name, which is local to the scope of this self-invoked function.
(function(preservedName) {
    element.onclick = function() {
        alert("button is " + names[preservedName].msg);
    };
})(name);

Upvotes: 2

JaredPar
JaredPar

Reputation: 754743

You are falling prey to the rules of closure capture in javascript. There is only one copy of name and when onclick fires it will be the last name value in the collection names. In order to solve this you need to create a new function scope that captures the current value of name

var addOnClick = function(currentName) { 
  element.onclick = function() { 
    alert("button is " + names[currentName].msg);
  };
};
addOnClick(name);

EDIT

You also need to use onclick not onClick. Case matters here

Fiddle: http://jsfiddle.net/Nzf3p/

Upvotes: 4

mrk
mrk

Reputation: 5117

The problem is due to the closure you're creating for the onclick handler.

element.onclick = function () { // Note this is a function
            alert("button is "+ names[name].msg);
        };

Should be wrapped by a function that passes in the name you're interested in:

addOnClickToElement(element,name);

Then add this named function at the same scope as 'add' so that names is accessible:

function addOnClickToElement(element,name) { // Note this is a function
    element.onclick = function(){alert("button is "+ names[name].msg);};
}

Upvotes: 1

Related Questions