Elison Lee
Elison Lee

Reputation: 23

How to changing value of global variable with onclick button function with value of variables in Javascript

I am new to this platform and I just encountered a problem like this: I want to make a 9x9 button grid so when the user clicks onto a button with coordinate (x,y), the value of x and y is saved into two global variables. Below is my code:

However, when I test it, no matter where I click, the value of clicked_row and clicked_col will always be 9 and 9. I think it is because the function only makes the two values equal to i and j and their value stopped at 9 in the for loop. Is there a way to pass the value i and j into the function? Thanks a lot.

for (var i = 0; i < 9; i++) {
  for (var j = 0; j < 9; j++) {
    let btn = document.createElement("button");
    btn.innerHTML = ("(").concat((i + 1).toString(), ", ", (j + 1).toString(), ")"); //(i, j)
    btn.className = "button";
    btn.onclick = function() {
      clicked_row = i;
      clicked_col = j;
    };
    document.body.appendChild(btn);
  }
  document.body.appendChild(document.createElement("BR"));
}

Upvotes: 2

Views: 136

Answers (3)

mplungjan
mplungjan

Reputation: 177975

It is called closure

Here is a simple solution without needing one

If you use data attributes, you can store the col and row there.

Alternatively show the text content or use let in the loop like my second example

for (let i = 1; i < 10; i++) {   // these lets also help
  for (let j = 1; j < 10; j++) { // avoiding having to create a closure 
    let btn = document.createElement("button");
    btn.innerHTML = ("(").concat((i).toString(), ", ", (j).toString(), ")"); //(i, j)
    btn.className = "button";
    btn.dataset.clicked_row = i;
    btn.dataset.clicked_col = j;
    btn.onclick = function() {
      console.log(this.dataset.clicked_row,this.dataset.clicked_col)
    };
    document.body.appendChild(btn);
  }
  document.body.appendChild(document.createElement("BR"));
}

This code will show that using let in the loops also removes the need for the closure

for (let i = 1; i < 10; i++) {
  for (let j = 1; j < 10; j++) {
    let btn = document.createElement("button");
    btn.innerHTML = ("(").concat((i).toString(), ", ", (j).toString(), ")"); //(i, j)
    btn.className = "button";
    btn.onclick = function() {
      let clicked_row = i;
      let clicked_col = j;
      console.log(clicked_row,clicked_col)
    };
    document.body.appendChild(btn);
  }
  document.body.appendChild(document.createElement("BR"));
}

Upvotes: 1

edemaine
edemaine

Reputation: 3120

You're correct that the problem is that the i and j variables are shared by all the function()s, and so they all get the final values of those variables, which is 9.

The traditional (ECMAScript 4) way to save the current values is to use a function into which you pass the current values of i and j:

    for (var i = 0; i < 9; i++) {
      for (var j = 0; j < 9; j++) {
        let btn = document.createElement("button");
        btn.innerHTML = ("(").concat((i + 1).toString(), ", ", (j + 1).toString(), ")"); //(i, j)
        btn.className = "button";
        btn.onclick = function(i, j) {
          return function() {
            clicked_row = i;
            clicked_col = j;
          };
        }(i, j);
        document.body.appendChild(btn);
      }
      document.body.appendChild(document.createElement("BR"));
    }

But you are using let, which provides a simpler way: use let to define local copies of the variables (which are different variables in each iteration of the loop):

    for (var i = 0; i < 9; i++) {
      for (var j = 0; j < 9; j++) {
        let iNow = i, jNow = j;
        let btn = document.createElement("button");
        btn.innerHTML = ("(").concat((i + 1).toString(), ", ", (j + 1).toString(), ")"); //(i, j)
        btn.className = "button";
        btn.onclick = function() {
          clicked_row = iNow;
          clicked_col = jNow;
        };
        document.body.appendChild(btn);
      }
      document.body.appendChild(document.createElement("BR"));
    }

But JavaScript offers shorthand for this in the case of for loops:

    for (let i = 0; i < 9; i++) {
      for (let j = 0; j < 9; j++) {
        let btn = document.createElement("button");
        btn.innerHTML = ("(").concat((i + 1).toString(), ", ", (j + 1).toString(), ")"); //(i, j)
        btn.className = "button";
        btn.onclick = function() {
          clicked_row = i;
          clicked_col = j;
        };
        document.body.appendChild(btn);
      }
      document.body.appendChild(document.createElement("BR"));
    }

The only difference from your original code is changing var to let. See What's the difference between using "let" and "var"?

Upvotes: 2

Samathingamajig
Samathingamajig

Reputation: 13243

Since you're using var, you need to wrap that function in a closure to deference the value of i from the variable named i.

for (var i = 0; i < 9; i++) {
  for (var j = 0; j < 9; j++) {
    let btn = document.createElement("button");
    btn.innerHTML = ("(").concat((i + 1).toString(), ", ", (j + 1).toString(), ")"); //(i, j)
    btn.className = "button";
    btn.onclick = (function(i, j) {
      return function() {
        clicked_row = i;
        clicked_col = j;
        console.log({clicked_row, clicked_col});
      };
    })(i, j);
    document.body.appendChild(btn);
  }
  document.body.appendChild(document.createElement("BR"));
}

If you want the top left to be (1, 1), use this instead:

for (var i = 1; i <= 9; i++) {
  for (var j = 1; j <= 9; j++) {
    let btn = document.createElement("button");
    btn.innerHTML = ("(").concat((i).toString(), ", ", (j).toString(), ")"); //(i, j)
    btn.className = "button";
    btn.onclick = (function(i, j) {
      return function() {
        clicked_row = i;
        clicked_col = j;
        console.log({clicked_row, clicked_col});
      };
    })(i, j);
    document.body.appendChild(btn);
  }
  document.body.appendChild(document.createElement("BR"));
}

Upvotes: 0

Related Questions