55039

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

Question:

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:

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

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

Answer1:

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

<strong>EDIT</strong>

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

<blockquote>

Fiddle: <a href="http://jsfiddle.net/Nzf3p/" rel="nofollow">http://jsfiddle.net/Nzf3p/</a>

</blockquote>

Answer2:

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

Answer3:

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

Recommend

  • mod_rewrite replace '_' with '-'
  • pure javascript dom dynamic insert, update and delete
  • Javascript - Waiting for event before proceeding
  • Add class element based on the name of the page
  • After converting my FBX file to a .gltf, the model is incredibly small, why?
  • PHP - Can't dynamcally instantiate class
  • react native create element with string
  • Can a variable be stored within an image or div tag?
  • Why does the following throw an “Object doesn't support property or method 'importNode
  • Creating a Multi-Step Modal Using Jquery
  • Django model for a Postgres view
  • How to retrieve information from antrun back to maven?
  • Getting error 'Cannot read property 'document' of undefined' while importing exp
  • Execute scripts AJAX returns
  • Invalid object name 'dbo.Item'
  • How to use JavaScript to determine whether a file exists in a directory?
  • Debug.DrawLine not showing in the GameView
  • Functions in global context
  • Android full screen on only one activity?
  • Meteor helpers not available in Angular template
  • FileReader+canvas image loading problem
  • Build own AppleScript numerical error handling
  • Adding custom controls to a full screen movie
  • Compare two NSDates in iPhone
  • How to get icons for entities from eclipse?
  • Proper way to use connect-multiparty with express.js?
  • Load html files in TinyMce
  • JTable with a ScrollPane misbehaving
  • Angular 2 constructor injection vs direct access
  • Java static initializers and reflection
  • Android Google Maps API OnLocationChanged only called once
  • JaxB to read class hierarchy
  • Easiest way to encapsulate a HTML5 webpage into an android app?
  • Busy indicator not showing up in wpf window [duplicate]
  • costura.fody for a dll that references another dll
  • Observable and ngFor in Angular 2
  • How to Embed XSL into XML
  • UserPrincipal.Current returns apppool on IIS
  • Conditional In-Line CSS for IE and Others?
  • java string with new operator and a literal