JavaScript closures. Access in loop to current i, j variables [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Access outside variable in loop from Javascript closure [duplicate]
(5 answers)
Closed 9 years ago.
I try to dynamically generate <table> with jQuery and I want to set click handlers to each cell, so when cell clicked the popup will appear with current index of cell. How I can access to CURRENT i and j variables in loop?
for(var i = 0; i < 5; i++) {
var tr = $('<tr></tr>');
for (var j = 0; j < 5; j++) {
var td = $('<td></td>');
td.click(function() {
alert(i + ' ' + j); // here I want to access to CURRENT i, j variables
})
td.appendTo(tr);
}
}

The other answers mostly explain how to fix the closure problem, although such methods are not particularly memory efficient since they end up creating a new closure for each possible combination of i and j.
However, since you're using jQuery you have a number of other options available to you:
Pass data parameters to the .on call
td.on('click', { i: i, j: j }, function(event) {
var i = event.data.i;
var j = event.data.j;
alert(i + ' ' + j);
});
The i and j values as you can see above will be available in event.data
Use $.each to iterate instead of a for
$.each(Array(5), function(i) {
// i is already bound here because it's a function parameter
$.each(Array(5), function(j) {
// and j available here
td.on('click', ...);
});
});
Use event delegation instead of a per-element handler
$('#myTable').on('click', 'td', function() {
// use jQuery methods to determine the index, e.g.
var j = this.cellIndex
var i = this.parentNode.rowIndex
...
});
which is more efficient than binding a separate handler to each <td> individually.
and more generically, use .data() to store per-cell information
You can store data values directly on elements, which would work very well if you wanted to retrieve values other than the cell and row indexes:
td = $('<td>').data({i: i, j: j});
and then extract these row and column values directly from the clicked element's .data:
for (var i = 0; i < 5; i++) {
var tr = $('<tr></tr>');
for (var j = 0; j < 5; j++) {
$('<td></td>').data({i: i, j: j}).appendTo(tr);
td.appendTo(tr);
}
}
$('#myTable').on('click', 'td', function() {
var data = $(this).data();
var i = data.i;
var j = data.j;
alert(i + ' ' + j);
});

Introducing them into a new scope will capture their current value:
(function(i, j) {
td.click(function() {
alert(i + ' ' + j); // here I want to access to CURRENT i, j variables
});
})(i, j);

You can create a new scope for the 'current' value of (i, j) by executing a function inside the loop like this
td.click((function (i, j) {
return function (event) {
console.log(i, j);
}
})(i, j));
You could make this a little more succinct by using Function.prototype.bind
td.click(function(i, j, event){
console.log(i, j);
}.bind(td, i, j));
Note This .bind solution requires ECMAScript 5, so if you want to support older browsers, please look into es5-shim
See this demo

Other option can be storing data within the elements.
for(var i = 0; i < 5; i++)
{
var tr = $('<tr></tr>');
for (var j = 0; j < 5; j++)
{
var td = $('<td></td>');
this.data("position",{row:i, column:j});
td.click(function()
{
var position = this.data("position");
alert(position.row + ' ' + position.column); // here I want to access to CURRENT i, j variables
})
td.appendTo(tr);
}
}

I would store the indexes as attributes and add a single click handler to the table :
var tableHtml = '';
for(var i=0; i<5; i++) {
tableHtml += '<tr>';
for (var j=0; j<5; j++) {
tableHtml += '<td data-i="' + i + '" data-j="' + j + '"></td>';
}
tableHtml += '</tr>';
}
$('table').append(tableHtml).on('click', 'td', function(ev) {
var i = this.getAttribute('data-i'),
j = this.getAttribute('data-j');
});

Related

Setting functions to onclick for Buttons within loops with strings in HTML/Javascript

var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
var NewButton = document.createElement("NewButton");
var IdString = "ID" + i + "_" +j;
NewButton.onclick = function() { DoStuff(IdString) };
RelevantDiv.appendChild(NewButton);
}
}
function DoStuff(IdForStuff) {
// Does stuff with id
}
The problem is that every time the NewButton.onclick is set, that it is setting it to the final IdString which is "ID4_4".
Worth noting, that you won't have such problems if you use let or constinstead of var:
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
const NewButton = document.createElement("NewButton");
const IdString = "ID" + i + "_" +j;
NewButton.onclick = function() { DoStuff(IdString) };
RelevantDiv.appendChild(NewButton);
}
}
Your underlying issue is that the variable being used within the onclick callback, IdString, is having its declaration hoisted to the top of the current scope, ie the function it's running within. That means that every time you're within the loop, its value is overwritten - it's functionally the same as this:
var IdString;
var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
var NewButton = document.createElement("NewButton");
IdString = "ID" + i + "_" +j;
NewButton.onclick = function() { DoStuff(IdString) };
RelevantDiv.appendChild(NewButton);
}
}
You need to ensure that you capture the right value of IdString when you need it, which is typically done through the use of a closure:
var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
(function(IdString) {
var NewButton = document.createElement("NewButton");
NewButton.onclick = function() { DoStuff(IdString) };
RelevantDiv.appendChild(NewButton);
})("ID" + i + "_" +j)
}
}
Here we create another inner function to create a new scope to hold each individual IdString value. It's then immediately called with the right value for each iteration of the loops. IdString is then captured within this closure, and the correct value will be used within the onclick callback.
Alternatively, you can bind the argument at the moment you know what the relevant value is:
var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
var NewButton = document.createElement("NewButton");
NewButton.onclick = DoStuff.bind(null, "ID" + i + "_" +j);
RelevantDiv.appendChild(NewButton);
}
}
This does away with both of the inner functions, and directly assigns the onclick event to a copy of the DoStuff function that will have the right argument.
The problem here is that, when the handler you assign runs, the loop has already looped through all iterations, and the variable at that point will be at its final state. To avoid this, you need to use a lock. It involves a self-executing function (function () { // code })() which saves the current state of the variable. When the variable in the current iteration, for example State 2 is given as an argument to the self-executing function, the scope of the function will save this state. When the for loop continues, the original variable will still change, but the scope of the new "inner function" will keep the value "locked". So even when the loop-variable will in the end be at its 4th state, the locked variable will still be in state 2.
for (var i = 0; i < 5; i++) {
button.addEventListener ("click", function () {
// Here, i would normally always be 5
(function (lockedIndex) {
// This lock will make i the way it should be
// You have to use lockedIndex instead of i
})(i)
});
}
For your code, this would look something like:
var RelevantDiv = document.getElementById("RelevantDiv");
for (var i = 0; i < 5; i++) {
for (var j = 0; j < 5; j++) {
(function (ix, jx) {
var NewButton = document.createElement("NewButton");
var IdString = "ID" + ix + "_" +jx;
NewButton.onclick = function() { DoStuff(IdString) };
RelevantDiv.appendChild(NewButton);
})(i, j)
}
}
function DoStuff(IdForStuff) {
// Does stuff with id
}

Assign for-loop condition to a variable

Is there any way to assign a variable as the conditional operator in a for loop in Javascript?
For example, take this for loop:
for(var i = 0, j = 5; i < 10; i++, j++) {
console.log(i + ", " + j);
}
I want to be able to set the i < 10 part as a variable. I might want the for loop to run until j < 8 instead of i < 10. The way I am currently doing it is using an if statement and then using two different for loops with the exact same code except for the conditional, and I just wanted to know if a more efficient way was possible. Thanks!
Use a function in the for loop and reassign it to whatever you want.
var predicate = function(i, j) { return i < 10; }
for(var i = 0, j = 5; predicate(i, j); i++, j++) {
console.log(i + ", " + j);
if (i === 5) { // Dummy condition
predicate = function(i, j) { return j < 8; };
}
}
The advantage is that you can completely change the logic if you need to.
You can replace the 10 with a variable. You cannot replace the operator (<) with a variable.
You can replace the entire expression with a function though, and that function can be stored in a variable.
var data = ["a", "b", "c"];
function loop(callback) {
for (var i = 0; callback(i); i++) {
console.log(data[i]);
}
}
loop(function (i) {
return (i < 10);
});
loop(function (i) {
return (i < 8);
});
Sure, did you try just sticking variables in there :
var total = true==true ? 8 : 10,
i = 0,
j = 5,
what = true==false ? i : j;
for(; what < total; i++, j++) {
console.log(i + ", " + j);
}
I guess you can create a random function for this?
function countdown = (function(){
var i =0, j = 5;
return function(){
++i; ++j;
return i < 10; // or j < 8
}
})();
while(countdown()) {
console.log('counting');
}
The only problem with that is that you cannot access i and j in the scope of your loop.

Pushing value into a multidimensional array

I have surfed the problem but couldn't get any possible solution ..
Let's say i have a var like this
var data = [
{
'a':10,
'b':20,
'c':30
},
{
'a':1,
'b':2,
'c':3
},
{
'a':100,
'b':200,
'c':300
}];
Now , i need a multidimensional array like
var values = [[10,1,100], //a
[20,2,200], //b
[30,3,300]]; //c
What i have tried is
var values = [];
for(var key in data[0])
{
values.push([]); // this creates a multidimesional array for each key
for(var i=0;i<data.length;i++)
{
// how to push data[i][key] in the multi dimensional array
}
}
Note : data.length and number of keys keeps changing and i just want to be done using push() without any extra variables. Even i don't want to use extra for loops
If you guys found any duplicate here , just put the link as comment without downvote
Try this:
var result = new Array();
for(var i = 0; i < data.length; i++) {
var arr = new Array();
for(var key in data[i]) {
arr.push(data[i][key]);
}
result.push(arr);
}
also if you don't want the 'arr' variable just write directly to the result, but in my opinion code above is much more understandable:
for(var i = 0; i < data.length; i++) {
result.push(new Array());
for(var key in data[i]) {
result[i].push(data[i][key]);
}
}
Ok, based on your comment I have modified the the loop. Please check the solution and mark question as answered if it is what you need. Personally I don't understand why you prefer messy and hard to understand code instead of using additional variables, but that's totally different topic.
for(var i = 0; i < data.length; i++) {
for(var j = 0; j < Object.keys(data[0]).length; j++) {
result[j] = result[j] || new Array();
console.log('result[' + j + '][' + i + ']' + ' = ' + data[i][Object.keys(data[i])[j]])
result[j][i] = data[i][Object.keys(data[i])[j]];
}
}

How i load in a loop vvariables that are passed as parameters its possible?

function Qnt_Box(e) {
var app = UiApp.getActiveApplication();
var Vpanel = app.getElementById('Vpanel');
app.getElementById('button2').setVisible(false);
var qnt = e.parameter.Quant;
var grid2 = app.createGrid(qnt, 2);
for (var i = 0; i < qnt ; i++) {
grid2.setWidget(i, 0, app.createLabel(i + 1 + ' :'));
grid2.setWidget(i, 1, app.createTextBox().setName('x' + i)); HERE!!!
}
Vpanel.add(grid2);
return app;
}
How do I load the variables, which were named in a looping, looping one another?
My Idea was:
function example() {
for(var i =0; i < qnt+1; i++)
var aux = e.parameter.'x' + i;
}
but not work XS
Thank you!!
If the name is in a property of e.parameter, you can use Bill's suggestion
e.parameter['x' + i];
If it's a global variable, you can access it through the window object
window['x' + i]
I don't completely follow your code, but if you are trying to access a property using a variable name, you can use array notation.
function example() {
for(var i =0; i < qnt+1; i++)
var aux = e.parameter['x' + i];
}

Javascript closure explanation with code sample

I am trying to understand the javascript closure. I read a example code:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
This code will print out "item3 undefined" alert 3 times. I do understand the "3" from the item variable at line 5, but I do not understand why does it print out "undefined" from the list[i] at line 5? Isn't this also uses the closure to access the list variable? Could some one explain this?
You do have access to all those variables. The problem is your i variable in the following loop:
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
The i is passed by reference and is increased every loop. So after you've pushed the closure to the loop 3 times i's value is 4 and every callback tries to alert the 4th element of [1,2,3] (the array you provided), which is undefined.
The quickest explanation is: a closure is created around a function and not around a statement!
Try this to know what it means:
function createClosure(item, val) {
return function() {alert(item + ' ' + val)};
}
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( createClosure(item, list[i]) );
}
return result;
}

Categories

Resources