scope with javascript functions - javascript

I am trying to pass I into the inputItems[i].on so i can set it to do a specific task (defined by createSelectedInputItem(i)), how do you pass the variable i into the function() { ..operation here }?
for(var i=0; i< 6; i++){
console.log("setting mouse event for : " + i);
// Bring in all the input items
inputItems[i].on('click', function() {
console.log("i is still:" + i );
input.tween.reverse();
console.log("pressed" + i);
createSelectedInputItem(i);
for(var j=0; j< 6; j++){
inputItems[j].tween.reverse();
}
});
}
LOG
//When the page is loaded
setting mouse event for : 0
setting mouse event for : 1
setting mouse event for : 2
setting mouse event for : 3
setting mouse event for : 4
setting mouse event for : 5
//When pressing one of the inputItems
i is still:6
pressed6
this isnt even suppose to exist

The problem you are facing is that of closures, which are a slightly non-intuitive aspect of Javascript's scoping.
Consider how many variables you have there. How many times does the variable i exist? The answer is "as many times as there are scopes that define i". In this case, that means there is only one variable i, which is referred to throughout your code.
You do a loop with i:
for(var i=0; i< 6; i++){
After this loop has completed (which happen before you do any clicks), i is 6. It will never change again, and it will never refer to any other number.
So the click handler fires, and this line of code is run:
console.log("i is still:" + i );
i is the same variable, and it will therefore be the value 6.
The way around this is to introduce a new variable for each iteration of the loop.
for (var i = 0; i < 6; i++) {
(function (innerI) { // create a function with an argument called innerI
console.log("setting mouse event for : " + i);
// Bring in all the input items
inputItems[innerI].on('click', function () {
console.log("i is still:" + i);
console.log("innerI is: " + innerI);
input.tween.reverse();
console.log("pressed" + innerI);
createSelectedInputItem(i);
for (var j = 0; j < 6; j++) {
inputItems[j].tween.reverse();
}
});
}(i)); // invoke the function with i as the argument
}
In this code, we create an anonymous function. The function takes one argument, innerI. We then immediately invoke that function, and pass i as the argument. This creates a new scope and a new variable, so it is not changed when i++ happens at the end of the loop.

inputItems[i].on('click', function() {
// By the time this runs, any variable outside its scope may change,
// which in this case is 'i' which runs in a loop.
});
You should capture the i by wrapping it like this:
(function(i) {
inputItems[i].on('click', function() {
// Now i is an argument to the wrapping closure
console.log(i);
});
})(i);

Related

How do I toggle a class within a loop between a table row and map points?

I have 10 rows in a table that contains the address of each tag on a map. I am trying to add a click event to each tag on the map to it's corresponding table row. Instead of writing each click event separately I'm attempting to do it through a for loop. What's the best way to do this? The for loop below works for only the last iteration but not for all.
for (var i=0; i < 10; i++) {
var maptag = "#maptag";
var maprow = "#maprow";
$(maptag + +i).click(function() {
console.log('in the hole!');
$(maprow + +i).toggleClass('highlight');
return false;
});
You may need a closure
for (var i = 0; i < 10; i++) {
var maptag = "#maptag";
var maprow = "#maprow";
(function(x) { // IIFE
$(maptag + +x).click(function() {
console.log('in the hole!');
$(maprow + +x).toggleClass('highlight');
return false;
})
}(i))
}
Your problem is scoping. What's happening is when you create your click event, i is the current value in the loop. However, when the click even is triggered, that i is set to the state it was at the end of the loop (i.e., your last one).
What you need to do is wrap it in a function to affect the scope. Something like this:
for (var i=0; i < 10; i++) {
var maptag = "#maptag";
var maprow = "#maprow";
$(maptag + i).click((function (i) { return function() {
console.log('in the hole!');
$(maprow + i).toggleClass('highlight');
})(i));
}
This looks a little weird, but what you're doing is wrapping the function that'll actually trigger in another function, which is self-calling:
(function (i) {
// will trigger immediately
}(i);
You pass in your i there, but then the function inside which is returned uses an i which is actually a different variable. The inner i doesn't change when the loop changes, so it'll have the value you expect when the click is actually triggered.
P.S., never seen variableName + +i before. Not sure if that was a typo, or some special syntax I'm unaware of, but if it is, it'd seem unnecessary here. Your return false is also unnecessary, as without it, it'll implicitly return null which is also a falsey value (unless you're explicitly checking for false somewhere with an ===).

Javascript/jQuery attach different function args to .click() method

What's wrong with this code? Reading this it seems that for each loop the jquery should attach a function with assigned values for each iteration. Instead it's attaching i = 2 to every object. Why is it doing that and how can I get it to attach the expected values (e.g., 0, 1, ...)?
//data.length is 2.
for (i=0; i<data.length; i++) {
// Attach the click function
linkId = 'a#' + pk;
$(linkId).click(function(e) {
e.preventDefault();
console.log(i, pk, data);
});
};
console.log -- each link has the same parameters
2 "52fef25e391a56206f03be6e" [object Array]
You're assuming that a block creates a new variable scope. It doesn't in JavaScript. Only a function execution does.
If you use $.each() instead, the callback you give it will be invoked for each iteration, and so you'll have a new scope for every one.
$.each(data, function(i,item) {
// ^---^---function parameters are local to this scope
// v--declare a variable local to this scope
var linkId = 'a#' + pk;
// v--the function made in this scope can access the local vars
$(linkId).click(function(e) {
e.preventDefault();
console.log(i, pk, data, linkId, data[i]);
});
});
console will be displayed only when you click the $(linkId) but at the time is is already equal to data.length (aka 2 in your case) so it will always display 2
Try to isolate the variable's scope. Declare a var j = i; inside the loop context so it enters the click-callback's scope.
So in order to create a separate scope for each iteration you can also use ordinary closure-functions, and pass current i into it:
for (var i = 0; i < data.length; i++) {
(function(j) {
$('a#' + pk).click(function(e) {
e.preventDefault();
console.log(j, pk, data);
});
})(i);
};

parameter error with addEventListener

Okey so I got this following javaScript code.
function test(id)
{
alert(id);
}
var elem = document.getElementsByClassName('outsideDiv');
for(var i=0; i < elem.length; i++)
{
elem[i].addEventListener('mouseover', function(){test(i);}, false);
}
this gives all divs with the class a mouse over but the function always returns the latest i index. in this case i got 5 div elements and the alert is allways 5 no mather witch one i hover. Can anyone explain why?
Try using this instead:
function mouseOverFunc(i) {
return function () {
test(i);
};
}
function test(id) {
alert(id);
}
var elem = document.getElementsByClassName('outsideDiv');
for(var i=0; i < elem.length; i++) {
elem[i].addEventListener('mouseover', mouseOverFunc(i), false);
}
Just because you add event listeners to the elements doesn't mean the value of i is preserved for each listener. You need to create a closure that will create a new scope with i.
The reason this is happening is because the function bound to each listener is just a reference. When the event happens (mouseover), the function is finally called, but what's the value of i? The for loop finished executing a long time ago, so the value of i is the end value - 5.

Wrong Parameter is passed to function

I have the following code that adds an onmouseover event to a bullet onload
for (var i = 0; i <= 3; i++) {
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
}
This is the function that it is calling. It is supposed to add a css class to the menu item as the mouse goes over it.
function addBarOnHover(node) {
document.getElementById('menu').getElementsByTagName('li')[node].className = "current_page_item"; }
When the function is called, I keep getting the error:
"document.getElementById("menu").getElementsByTagName("li")[node] is
undefined"
The thing that is stumping me is I added an alert(node) statement to the addBarOnHover function to see what the value of the parameter was. The alert said the value of the parameter being passed was 4. I'm not sure how this could happen with the loop I have set up.
Any help would be much appreciated.
This is a common problem when you close over an iteration variable. Wrap the for body in an extra method to capture the value of the iteration variable:
for (var i = 0; i <= 3; i++) {
(function(i){ //here
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
})(i); //here
}
an anonymous function is created each time the loop is entered, and it is passed the current value of the iteration variable. i inside the anonymous function refers to the argument of this function, rather than the i in the outer scope.
You could also rename the inner variable for clarity:
for(var i=0; i<=3; i++){
(function(ii){
//use ii as i
})(i)
}
Without capturing the iteration variable, the value of i when it is finally used in the anonymous handler has been already changed to 4. There's only one i in the outer scope, shared between all instances of the handler. If you capture the value by an anonymous function, then the argument to that function is used instead.
i is being passed as a reference (not by value), so once the onmouseover callback is called, the value of i has already become 4.
You'll have to create your callback function using another function:
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('li');
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = (function(i) {
return function() {
addBarOnHover(i);
};
})(i);
}
You could make it a little more readable by making a helper function:
var createCallback = function(i) {
return function() {
addBarOnHover(i);
};
};
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = createCallback(i);
}

Defining anonymous functions in a loop including the looping variable?

I know that this code doesn't work and I also know why.
However, I do not know how to fix it:
JavaScript:
var $ = function(id) { return document.getElementById(id); };
document.addEventListener('DOMContentLoaded', function()
{
for(var i = 1; i <= 3; i++)
{
$('a' + i).addEventListener('click', function()
{
console.log(i);
});
}
});
HTML:
1
2
3
I want it to print the number of the link you clicked, not just "4".
I will prefer to avoid using the attributes of the node (id or content), but rather fix the loop.
Wrap the loop block in its own anonymous function:
document.addEventListener('DOMContentLoaded', function()
{
for(var i = 1; i <= 3; i++)
{
(function(i) {
$('a' + i).addEventListener('click', function() {
console.log(i);
})
})(i);
}
}
This creates a new instance of i that's local to the inner function on each invocation/iteration. Without this local copy, each function passed to addEventListener (on each iteration) closes over a reference to the same variable, whose value is equal to 4 by the time any of those callbacks execute.
The problem is that the inner function is creating a closure over i. This means, essentially, that the function isn't just remembering the value of i when you set the handler, but rather the variable i itself; it's keeping a live reference to i.
You have to break the closure by passing i to a function, since that will cause a copy of i to be made.
A common way to do this is with an anonymous function that gets immediately executed.
for(var i = 1; i <= 3; i++)
{
$('a' + i).addEventListener('click', (function(localI)
{
return function() { console.log(localI); };
})(i);
}
Since you're already using jQuery, I'll mention that jQuery provides a data function that can be used to simplify code like this:
for(var i = 1; i <= 3; i++)
{
$('a' + i).data("i", i).click(function()
{
console.log($(this).data("i"));
});
}
Here, instead of breaking the closure by passing i to an anonymous function, you're breaking it by passing i into jQuery's data function.
The closure captures a reference to the variable, not a copy, which is why they all result in the last value of the 'i'.
If you want to capture a copy then you will need to wrap it in yet another function.

Categories