I am making a little project for my self. So basically its main function is to create a base counter for each game.
For example: If there are two players it should create three bases. (This is for the card game “smash up” if that helps you understand better.) But when the Buttons populate they all only effect the last input. I can not figure out how to make them effect their respective inputs.
The problem I am having is that every button I click only effects the last input.
<html> <title> Base Maker </title> <body> <div> <hl> Score Keeper </h1> <hr> <input type = "text" placeholder = "How many players?"> <button id = "enter" onclick = "baseMaker()"> Enter </button> </div> <p></p> </body> </html>
var parent = document.querySelector("p"); var input = document.querySelector("input"); var enter = document.getElementById("enter"); function baseMaker() { for(var i = 0; i <= input.value; i++) { //base var base = document.createElement("p"); base.textContent = "Base " + (i + 1) + ":"; //score var score = document.createElement( "input"); score.setAttribute("id", "score" + i); score.value = 20; //upbutton var upButton = document.createElement( "button"); upButton.textContent = "+"; upButton.setAttribute("id", "upButton" + i) upButton.addEventListener('click', function() { score.value++; }); //downbutton var downButton = document.createElement( "button"); downButton.textContent = "-"; downButton.setAttribute("id", "downButton" + i) downButton.addEventListener('click', function() { score.value--; }); //populate data parent.appendChild(base); parent.appendChild(score); parent.appendChild(upButton); parent.appendChild(downButton); } input.value = ""; }
Advertisement
Answer
This is a common thing to run into especially when not using a framework in javascript.
I am not sure why this happens but when a function is defined directly in a loop, the closure for these created functions becomes whatever it is after the last iteration. I believe it is because the closure for each callback function is only “sealed up” (for lack of a better word) at the end of the loop-containing-function’s execution which is after the last iteration. It’s really beyond me, though.
There are some easy ways to avoid this behavior:
- use bind to ensure a callback gets called with the correct input (used in solution at bottom)
- create a function which creates a handler function for you and use that in the loop body
function createIncrementHandler(input, howMuch){ return () => input.valueAsNumber += howMuch; } /// then in your loop body: downButton.addEventListener('click', createIncrementHandler(score, 1));
- get the correct input by using the event parameter in the handler
downButton.addEventListener('click', (event) => event.target.valueAsNumber += 1);
- make the entire body of the loop into a function, for example:
function createInputs(i) { //base var base = document.createElement("p"); base.textContent = "Base " + (i + 1) + ":"; //score var score = document.createElement("input"); score.type = "number"; score.setAttribute("id", "score" + i); score.value = 20; //upbutton var upButton = document.createElement( "button"); upButton.textContent = "+"; upButton.setAttribute("id", "upButton" + i) upButton.addEventListener('click', function() { score.value++; }); //downbutton var downButton = document.createElement( "button"); downButton.textContent = "-"; downButton.setAttribute("id", "downButton" + i) downButton.addEventListener('click', function() { score.value--; }); //populate data parent.appendChild(base); parent.appendChild(score); parent.appendChild(upButton); parent.appendChild(downButton); }
Here is a full example of one of the possible fixes.
<html> <title> Base Maker </title> <body> <div> <hl> Score Keeper </h1> <hr> <input type="text" placeholder="How many players?"> <button id="enter" onclick="baseMaker()"> Enter </button> </div> <p></p> <script> var parent = document.querySelector("p"); var input = document.querySelector("input"); var enter = document.getElementById("enter"); function incrementInput(input, byHowMuch) { input.valueAsNumber = input.valueAsNumber + byHowMuch; } function baseMaker() { for (var i = 0; i <= input.value; i++) { //base var base = document.createElement("p"); base.textContent = "Base " + (i + 1) + ":"; //score var score = document.createElement("input"); score.type = "number"; score.setAttribute("id", "score" + i); score.value = 20; //upbutton var upButton = document.createElement("button"); upButton.textContent = "+"; upButton.setAttribute("id", "upButton" + i) upButton.addEventListener('click', incrementInput.bind(null, score, 1)); //downbutton var downButton = document.createElement("button"); downButton.textContent = "-"; downButton.setAttribute("id", "downButton" + i) downButton.addEventListener('click', incrementInput.bind(null, score, -1)); //populate data parent.appendChild(base); parent.appendChild(score); parent.appendChild(upButton); parent.appendChild(downButton); } input.value = ""; } </script> </body> </html>