Skip to content
Advertisement

All buttons only affect one input instead of respective input

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 = ""; 
}

1

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>
Advertisement