Reputation: 23
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
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
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
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