1

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

3
  • Why not make your names object an array? Commented Mar 12, 2013 at 17:32
  • I tried that, but i got the same results, so i omitted it. Commented Mar 12, 2013 at 17:34
  • it's element.onclick not element.onClick Commented Mar 12, 2013 at 18:01

3 Answers 3

4

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/

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for replying. I updated my code above, but no popup appears.
@user1202717 you also had an issue with onclick vs. onClick. I updated my answer and linked to a fiddle
2

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);

Comments

1

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);};
}

2 Comments

Thanks for the reply. I'm close but not quite there. I updated my code above
I commented on your question, but i'll also do it here -- it's element.onclick not element.onClick

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.