Keep the "i" value when adding functions to elements - javascript

My case:
var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++)
{
tds[i].onclick = function()
{
alert(i);
};
}
Expected outcome: Alert the number of TD.
However if there are 6 TDs, the returned value will always be the last value of "i". (6)
How could i make the "i" value to remain at it's value when added to the function?
Thanks!
http://jsfiddle.net/nuKEK/11/

You need to make a closure to capture the i value. Something like this
function createFunction(i){
return function(){
alert(i);
};
}
var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++){
tds[i].onclick = createFunction(i);
}
DEMO: http://jsfiddle.net/nuKEK/12/

You can pass i to another function in order to get its value rather than a reference to it. In javascript, numbers are passed by value.
tds[i].onclick = (function(x) {
return function() {
alert(x); // alerting x, i's value
};
})(i); // passing i as parameter x
If that self-executing anonymous function looks a little hairy in the context of your loop, you could try Array.prototype.forEach() instead:
[].forEach.call(document.getElementsByTagName("td"), function(td, i) {
td.onclick = function() {
alert(i);
};
});
[edit] Have a look at these options and their performance.

This is one of the common mistakes in Javascript in that it is not block scoped like most languages. It is function-scoped.
You need to create a closure around the onclick to achieve this.
for (i = 0; i < tds.length; i++) {
(function (index) {
tds[index].onclick = function() {
alert(index);
};
})(i);
}

var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++)
{
addClick(tds, i);
}
function addClick(where, i) {
where[i].onclick = function()
{
alert(i);
};
}

You will have to force the value into a scope that will still exist when the callback is fired.
for(i=0;i<tds.length;i++){
(function(){
var _i = i;
tds[i].onclick = function(){
alert(_i);
};
})();
}
Otherwise, the value for i will always be the last index

Related

alert -1 instead of their respective counter inside of the loop?

Why do the anchors, when clicked on, alert -1 instead of their respective counter inside of the loop? How can you fix the code so that it does alert the right number?
text<br>link
<script>
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) {
as[i].onclick = function() {
alert(i);
return false;
}
}
</script>
That's a closure problem: every anonymous function you define inside your loop reads the value of "i" at the end of the for loop.
You need another scope, for example calling a function that sets the onclick handler.
function add_onclick(el, i) {
el.onclick = function() {
alert(i);
return false;
}
}
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) {
add_onclick(as[i],i);
}
An alternative way to achieve the same result as Keeper's answer is to use a function expression for the closure, which you immediately invoke, using your value as a parameter to create the closure
var as = document.getElementsByTagName('a');
for ( var i = as.length; i--; ) (function (i) {
as[i].onclick = function() {
alert(i);
return false;
}
}(i));
This way requires minimal code refactoring
You are printing the value of variable i. After the loop, it's value is -1.

Variable scoping and event handler

Please see the jsfiddle:
http://jsfiddle.net/LsNCa/2/
function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
var myDiv = $('<div>');
myDiv.click(function(e) {
alert(i); // both the two divs alert "2", not 0 and 1 as I expected
});
$('body').append(myDiv);
}
}
var myFunc = new MyFunc();
I want the divs to alert "0" and "1" respectively when I click them, but both of them alert "2".
When I click the divs and the event is triggered, how and where do the handler find the value of the variable i?
I'm aware that adding a closure achieves my goal. But why?
function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
(function(j) {
var myDiv = $('<div>');
myDiv.click(function(e) {
alert(j);
});
$('body').append(myDiv);
})(i);
}
}
var myFunc = new MyFunc();
The code above is how you get it work correctly. Without an closure, you always the the last value of i. What we do is to post i into the closure and let the runtime "remember" the value of that very moment.
You need a closure because all your event handler functions are referencing the same variable i. The for loop updates this, and when the loop is done the variable contains 2. Then when someone clicks on one of the DIVs, it accesses that variable.
To solve this, each event handler needs to be a closure with its own variable i that contains a snapshot of the value at the time the closure was created.
I suggest that you read this article
JavaScript hoists declarations. This means that both var statements
and function declarations will be moved to the top of their enclosing
scope.
As #Barmar said in his answer above, the variable i is being referenced by both the event handlers.
You should avoid declaring functions inside loops. Below there is some code that does what you need.
I assume that you're using jQuery.
function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
var myDiv = $('<div>');
$('body').append(myDiv);
}
$('div').on('click', function() {
alert($(this).index());
});
}
var myFunc = new MyFunc();
The "alert()" call happens after the for-loop completed, which means that the value of "i" will be the last value for anything after that. In order to capture individual values of "i", you must create a closure for each value by creating a new function:
function MyFunc() {
function alertFn(val) {
return function () {
alert(val);
};
}
for (var i = 0; i < 2; i++) {
var myDiv = $('<div>');
myDiv.click(alertFn(i));
$('body').append(myDiv);
}
}
var myFunc = new MyFunc();
The closure captures the value of "i" at the time it was passed into the function, allowing alert() to show the value you expect.

variable closure using jQuery object notation

I have the following:
for (var i = 0; i <= 10; i += 1) {
var $page_button = $('<a>', {
html : i,
click : function () {
var index = i;
console.log(index);
return false;
}
});
$page_button.appendTo($wrapper);
}
I thought that var index would be defined separately for each iteration of the loop because it is enclosed within a function. In this case the value of index that is printed is always 10.
The link text is the correct value of i, because this is written to the DOM and is then immutable .
Why is this, and what should I change to fix my problem?
I know this is similar to lots of other questions but the behaviour of using this notation is causing a different result. I am using jQuery 1.7.2 (Can't use any newer unfortunately.)
You need to enclose that in a closure to solve the problem..
var $page_button = $('<a>', {
html : i,
click : (function (num) {
return function(){
var index = num;
console.log(index);
return false;
}
})(i)
});
A reference to i is closed up as part of the anonymous function. Note: not to its value, but a reference to i itself. When the function is run, the value is evaluated. Because the function runs after the loop has ended, the value will always be the last value of i. To pass just the value around, you do something like this:
click : (function (index) {
return function () {
console.log(index);
return false;
};
})(i)
You create an anonymous function which you execute immediately, which takes a value as argument and returns your actual function.
The variable index is defined separately for each execution of the function, but you copy the value from the variable i inside the function, so you will use the value of i as it is when the function runs, not when the function is created.
You need a function that is executed inside the loop to capture the value of the variable:
for (var i = 0; i <= 10; i += 1) {
(function(){
var index = i;
var $page_button = $('<a>', {
html : i,
click : function () {
console.log(index);
return false;
}
});
})();
$page_button.appendTo($wrapper);
}
Every handler is sharing the same i variable. Each one needs its own variable scope in order to reference a unique index.
for (var i = 0; i <= 10; i += 1) {
var $page_button = $('<a>', {
html : i,
click : makeHandler(i) // invoke makeHandler, which returns a function
});
$page_button.appendTo($wrapper);
}
function makeHandler(index) {
return function () {
console.log(index);
return false;
};
}
Here I made a makeHandler function that accepts the index argument, and returns a function that is used as the handler.
Because a function invocation sets up a new variable scope, and because a function is created and returned inside the makeHandler, each handler returned will reference its own scoped index number.

Jquery Scope Issue

I can't figure out this scope issue:
var menuLinks = new Array("about.php", "contact.php");
function setClickListeners()
{
for(var i=0; i<menuItems.length; i++)
{
$("#" + menuItems[i]).click( function () {
window.alert(menuLinks[i]);
});
}
}
Notes: menuItems and menuLink is the same length. This code is stripped down to make understanding it easier.
The outcome of this code when an item is clicked is an alert "undefined". It should be the data from menuLinks.
Help!!!!
Frankie
for (var i=0; i < menuItems.length; i++) {
(function(i) {
$("#"+menuItems[i]).click(function() {
alert(menuLinks[i]);
});
}(i));
}
You need to make the current value of i local to your anonymous function in .click.
JavaScript only has function scope. So if you don't make i local then whenever you press click the value of i is the current value which in this case is menuItems.length - 1.
What your doing above is creating a new functional scope and passing the value of i into it so that the current value of i stays constant in that function scope. That way your click function picks up the constant value of i from the closure.
jslint
Let's over complicate the code and satisfy jslint.
var wrapper = function(i) {
$("#"+menuItems[i]).click(function() {
alert(menuLinks[i]);
});
};
for (var i=0; i < menuItems.length; i++) {
wrapper(i);
}
A cleaner code:
var menuLinks = new Array("about.php", "contact.php");
function setClickListeners()
{
$.each(menuLinks, function(i, element)
{
$("#" + menuItems[i]).click( function (e) {
alert(menuItems[i]);
e.preventDefault();
});
}
}

javascript: Using the current for-loop counter-value inside a function() { }?

on a website i want to do this: (simplified)
myHandlers = new Array();
for(var i = 0; i < 7; i++) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc: function(bla) { /*...*/ alert(i); } // doesn't work,all return 7
}
}
I could set the counter as another attribute of my Handler (which would copy the current value) and use it inside my function, but I guess, there is also a way to actually copy this value, no?
When handlerFunc is called, the i inside the function refers to the i of the for loop. But that i does probably not have the same value any more.
Use a closure to bind the current value of i in the scope of an anonymous function:
handlerFunc: (function(i) { return function(bla) { /*...*/ alert(i); }; })(i)
Here an anonymous function (function(i) { … })(i) is used and called immediately. This function binds the value of i of the for loop to the local i. That i is then independent from the i of the for loop.
var myHandlers = new Array();
for (var i = 0; i < 7; i++) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc:
(function(i) {
return function(blah) {
alert(i)
}
})(i)
}))
}
Use a closure to bind the i so the value stays intact
In your example, i in the functions is the same variable as i outside the functions. As i is incremented in the loop, so is it incremented within the functions. As a result, if the functions are called after the loop has finished, they will all alert "7".
You need to create a new variable with appropriate scope and copy the value of i into it.
Something like this would create the desired effect.
...
var pushHandler = function(i) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc: function(bla) { /*...*/ alert(i); } // doesn't work,all return 7
}
}
...
for(var i = 0; i < 7; i++) {
pushHandler(i);
}
...

Categories

Resources