Reputation: 4417
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
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
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
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