1

I'm quite new in web development, I'm trying to create multiple buttons in a javascript function and define their onclick function in a for loop.

What I want : When I click on the button, I want to recover the value associated with this button.

What I have : When I click on any button, I am recovering the value associated with the last button.

I have made a simple example of what I'm trying to do :

HTML :

<html>
  <body>
    <div id="container">
    </div>
    <script src="script.js" charset="utf-8"></script>
  </body>
</html>

JS :

const data = [1, 2, 3, 4, 5];
var i;
for (i = 0;i < data.length;i++) {
  const root = document.createElement('button');
  root.setAttribute("class", "exerciceBox");
  root.textContent = i;
  root.onclick = function() {
    console.log(i);
  }
  document.getElementById("container").append(root);
}

I don't know if there is a best way to declare .onclick function for button created in JS files or maybe another way to do this.

I hope you could help me ! Thank you in advance, Sincerely, Valentin

6 Answers 6

2

The problem lies in var statement and it has scoping issue. The var statement declares a function-scoped or globally-scoped variable. To solve this declare the variable i by let statement to maintain the scope in for-loop.

A possible solution like below

const data = [1, 2, 3, 4, 5];
for (let i = 0;i < data.length;i++) {
  const root = document.createElement('button');
  root.setAttribute("class", "exerciceBox");
  root.textContent = i;
  root.onclick = function() {
    console.log(i);
  }
  document.getElementById("container").append(root);
}
<html>
  <body>
    <div id="container">
    </div>
    <script src="script.js" charset="utf-8"></script>
  </body>
</html>

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

Comments

1

you need to add a value attribute to your buttons here is how to do it :

const data = [1, 2, 3, 4, 5];
for (i = 0;i < data.length;i++) {
  const root = document.createElement('button');
  root.setAttribute("class", "exerciceBox");
  // set the value of your button to i
  root.setAttribute("value", i);
  root.textContent = i;
  root.onclick = function() {
  // get the value using 'this' 
  console.log(this.value);
  }
  document.getElementById("container").append(root);
}

Comments

0

Indexes start at 0 in js.

const data = [1, 2, 3, 4, 5];

for (let i = 0; i < data.length; i++) {
  const root = document.createElement('button');
  root.setAttribute("class", "exerciceBox");
  root.textContent = i;
  root.onclick = function() {
    console.log(i++); // this prints out i (index) + 1
  }
  document.getElementById("container").append(root);
}

Comments

0

You are always getting the last value of i because the for increments it until the end of the loop, then, when you click the button, you see the incremented value. Try this:

for (let i = 0; i < data.length; i++) {
    const root = document.createElement('button');
    root.setAttribute("class", "exerciceBox");
    root.textContent = i;
    let thisButtonI = i; //Make a copy of the actual value of i
    root.onclick = function() {
        console.log(thisButtonI); //use the copy, not the original i
    }
    document.getElementById("container").append(root);
}

Comments

0
  • Don't create functions inside loops. It's bug prone since once the loop exits the function could be overridden exiting with the last loop values attached.
  • Don't modify the DOM inside loops - such causes cluttering, unnecessary relayout and repaints

Here's a better solution on how to create a series of Elements given an array and insert them in the DOM

  • Create a blueprint of your button (EL_dataItem) and assign the necessary attributes and event handlers
  • Use Array.prototype.reduce() to reduce your data Array into a DocumentFragment collection.
  • Use DocumentFragment API and its .append() method
  • Once the DocumentFragment is populated use Element.append() to insert the buttons where needed - all at once.

// Helpers
const ELNew = (sel, attr) => Object.assign(document.createElement(sel), attr || {});
const EL = (sel, EL) => (EL || document).querySelector(sel);

// Your data Array
const data = [1, 2, 3, 4, 5];

// Button Blueprint
const EL_dataItem = (item) => ELNew("button", {
  type:        "button",
  className:   "exerciceBox", // cice?..
  textContent: item,
  onclick()    { console.log(item); },
});

// Create all buttons (Append into DocumentFragment)
const DF_items = data.reduce((DF, item) => {
  DF.append(EL_dataItem(item));
  return DF;
}, new DocumentFragment());

// Append DocumentFragment once.
EL("#container").append(DF_items);
<div id="container"></div>

Comments

0

The reason behind this is that the value of variable i changes by the time the user gets to click on the buttons as you are referencing the variable directly in the onlick event handler.

root.onclick = function() {
    console.log(i);
}

You can replace the onlick handler and solve this issue by closure and IIFE. This code will bind the value of i to the value when the loop is running by passing it as an argument and returning a function as the handler.

root.onclick = (function (i) {
    return function () {
      console.log(i);
      }; 
})(i);

Comments

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.