This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I'm new to this so i guess i'm missing something simple. The foor loop works fine but inside it i get an undefined variable
var categories_info = ["historia","excelencia","arte","social","instalaciones","padres","familia"];
for ( var i = 0; i < categories_info.length; i++) {
$("#showMe-"+categories_info[i]).click(function(){
$(".info."+categories_info.[i]).addClass("info-show");
console.log(".info."+categories_info[i]); //debug is undefinded
});
};
You need to create a closure like
var categories_info = ["historia", "excelencia", "arte", "social", "instalaciones", "padres", "familia"];
for (var i = 0; i < categories_info.length; i++) {
(function(i) {
$("#showMe-" + categories_info[i]).click(function() {
$(".info." + categories_info[i]).addClass("info-show");
console.log(".info." + categories_info[i]);
});
})(i);
};
This method is known as an IIFE
Basically, what was happening is the variable i was unavailable to the callback when the actual click happened.
However, by passing i in a self-executing anonymous function, you have created a closure which will preserve i and is accessible to the click handler.
Use a closure. Change:
$("#showMe-"+categories_info[i]).click(function(){
$(".info."+categories_info.[i]).addClass("info-show");
console.log(".info."+categories_info[i]); //debug is undefinded
});
To:
(function( i ) {
$("#showMe-"+categories_info[i]).click(function(){
$(".info."+categories_info.[i]).addClass("info-show");
console.log(".info."+categories_info[i]);
});
})( i );
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I have the javascript function in my jsp page as
<script type="text/javascript">
$(document).ready(function(){
for(i=0; i<20; i++ ) {
$(".plus"+i).click(function(){
$(".details"+i).slideToggle(500)
});
}
});
for each iteration i want the output like this:
.plus0
.details0
Next iteration :
.plus1
.details1 and so on. But this is not working. Please help.
There will be no error in the console. The issue is that by the time the click handler has been triggered, the for() loop has already completed, so i will always equal 19.
You can circumvent this behaviour using event data in jQuery. You can update your code as follows:
$(function() {
for( i = 0; i < 20; i++ )
{
$('.plus' + i).click( { theIndex : i }, function(e) {
$('.details' + e.data.theIndex).slideToggle(500)
});
}
});
The problem is caused because you are using closures. The reference of i is held by the click event handler. So the latest value of i is seen by all event handlers.
To solve the problem, write a factory function that returns an event handler.
var createEventHandler = function (param1) {
return function (e) {
$(".plus"+param1).click(function(){
$(".details"+param1).slideToggle(500)
});
}
}
$(document).ready(function(){
for(i=0; i<20; i++ ) {
$(".plus"+i).click(createEventHandler(i));
}
});
Read about closure variables.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I'm trying to grasp this bit of code, I know it's a closure but I'm not getting the result I think I should get.
This code returns me an [object MouseEvent], I can't understand why?
I'm adding a function call (updateProduct) to an .addEventListener using this code, and it returns an [object MouseEvent]
function addEventListenerToMinPlus(){
var x, y
for(var i = 0; i < productItemAll.length; i++){
x = productItemAll[i].querySelector(".boxNumbers-min")
x.addEventListener("click", function(i){return function(i){updateProduct(i)}}(i))
console.log(x)
}
}
function updateProduct(jow){
alert(jow)
}
jsFiddle
The browser invokes the event handler with an event object as the first parameter. Your function is declared to take a single parameter ("i"), so when you display it, that's what it is.
I suspect that what you meant was for the "i" inside the event handler to refer to the "i" in the outer function (the loop index). That also won't work, because the various handlers the loop creates will all refer to the same shared variable "i". See this old SO question.
The line
x.addEventListener("click", function(i) { return function(i) { updateProduct(i); }(i) }
produces a closure of the inner function
function(i) { updateProduct(i); }
The outer i is in the scope of this inner function, but it is shadowed by its parameter. So, in effect, the inner i represents the first argument passed to the click handler (the MouseEvent). If you want it to retain the value of the index, you have to change its name. Something like this:
x.addEventListener("click",
function(i) { return function(e) { updateProduct(i); }(i)
}
Now, in the inner function, e is the MouseEvent, and i is the outer index. I have updated the JSFiddle: http://jsfiddle.net/Cdedm/2/. Clicking the minus alerts 0 for the first item and 1 for the second, as expected.
that is because you are sending the element as parameter.
You should try doing this:
function addEventListenerToMinPlus(){
var x, y
for(var i = 0; i < productItemAll.length; i++){
x = productItemAll[i].querySelector(".boxNumbers-min")
x.addEventListener("click", function(){return updateProduct(i)})
console.log(x)
}
}
Hope this works,
Regards,
Marcelo
I think you are trying to do something like this. Your i will change by the time the click happens so it needs to be set to another local variable, in this case through the parameter on the new function. The click event handler will pass the event object you are getting currently
function addEventListenerToMinPlus() {
var x, y;
for(var i = 0; i < productItemAll.length; i++) {
x = productItemAll[i].querySelector(".boxNumbers-min");
x.addEventListener("click", function(i){return function(){updateProduct(i)}}(i));
}
}
function updateProduct(jow) {
alert(jow);
}
Unless you really really really know what you're doing, you can play about with closures all day and still not get it right with this sort of thing.
A more understandable appraoch by far, with more readable code for most people, is to use jQuery's .data() method to associate data (i in this case) with the element in question so it can be read back when the click event fires, for example :
function addEventListenerToMinPlus() {
var x, y;
for(var i = 0; i < productItemAll.length; i++) {
x = productItemAll[i].querySelector(".boxNumbers-min");
x.data('myIndex', i);//associate `i` with the element
x.addEventListener("click", function(i) {
var i = $(this).data('myIndex');//read `i` back from the element
updateProduct(i);
});
console.log(x);
}
}
For the record, a working closure would be as follows :
function addEventListenerToMinPlus() {
var x;
for(var i = 0; i < productItemAll.length; i++) {
x = productItemAll[i].querySelector(".boxNumbers-min");
x.addEventListener("click", function(i) {//<<<< this is the i you want
return function() {//<<<< NOTE: no formal variable i here. Include one and you're stuffed!
updateProduct(i);
}
}(i));
console.log(x);
}
}
This question already has answers here:
passing index from for loop to ajax callback function (JavaScript)
(3 answers)
Closed 8 years ago.
I'm sure this has been asked before, but I don't know what to search for.
So I want function to be called with a string that corresponds with the item clicked, but I want to simply add any new items to an array of strings.
var menuList = ["overview", "help", "search"];
var functionCalls = [
function() { toggleMenu(menuList[0]); },
function() { toggleMenu(menuList[1]); },
function() { toggleMenu(menuList[2]); },
];
which is used like this in a loop: $("something").click(functionCalls[i])
This is what I want to do (but obviously it doesn't work):
for (var i in menuList) {
// This does not work because the closure references 'i'
// which, at the end, is always the index of the last element
$("something").click(function() {
toggleMenu(menuList[i]);
});
// this works, but I have to define each closure
$("something").click(functionCalls[i]);
}
How can I create an anonymous function that accepts a value based on a variable - but doesn't retain the reference to the variable?
You could use an IIFE like this:
for (var i=0; i<menuList.length; i++) {
!function( index ) {
$("something").click(function() {
toggleMenu( menuList[index] );
});
}( i );
}
By calling the anonymous function, you create a local copy of the current value for i with the name index. Hence, all handlers receive their respective version of i.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript: closure of loop?
I have following code inside javascript:
for (var i=0; i < images_array.length; i++) {
$('#thumb_'+ i).live('click', function(){
$('#image_container_' + current_image_index).hide();
current_image_index = i;
alert(current_image_index);
$('#image_container_' + current_image_index).show();
});}
when I click on any thumb, i get images_array.length value. Does anyone know what is happenning?
You need to create a closure for the click handler function, like this:
for (var i=0; i < images_array.length; i++) {
$('#thumb_'+ i).live('click',
(function(i) {
return function(){
$('#image_container_' + current_image_index).hide();
current_image_index = i;
alert(current_image_index);
$('#image_container_' + current_image_index).show();
}
})(i)
);
}
The problem is that, without the closure, the variable is shared across every handler function -- it continues getting updated, which is why every handler ends up getting the array.length value. Using the closure creates a locally-scoped copy of the variable i.
Here's a demo that shows the difference:
Original
With closure
$.each(images_array,function(value,i) {
$('#thumb_'+ i).live('click', function(){
$('#image_container_' + current_image_index).hide();
current_image_index = i;
alert(current_image_index);
$('#image_container_' + current_image_index).show();
});}
As others have said, you need a closure. Now, you're already using jQuery so forget about for() and directly use $.each.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript closure inside loops - simple practical example
I have an array of 4 objects (that.pairs), and each object has a .t property which is a jQuery object/element. I'm trying to set an event on each t being clicked.
The problem is that when one of the them gets clicked, it's always the last pair (index 3) that gets passed into my doToggle() function.
Why is this happening? How can I fix it?
for (var i = 0; i < that.pairs.length; i++) {
var p = that.pairs[i];
p.t.click(function() {
that.doToggle(p);
});
}
It's because the p variable is shared by your closures, there's just one p variable. By the time your handlers are called, p has changed.
You have to use a technique I call freezing your closures
for (var i = 0; i < that.pairs.length; i++) {
// The extra function call creates a separate closure for each
// iteration of the loop
(function(p){
p.t.click(function() {
that.doToggle(p);
});
})(that.pairs[i]); //passing the variable to freeze, creating a new closure
}
A easier to understand way to accomplish this is the following
function createHandler(that, p) {
return function() {
that.doToggle(p);
}
}
for (var i = 0; i < that.pairs.length; i++) {
var p = that.pairs[i];
// Because we're calling a function that returns the handler
// a new closure is created that keeps the current value of that and p
p.t.click(createHandler(that, p));
}
Closures Optimization
Since there was a lot of talk about what a closure is in the comments, I decided to put up these two screen shots that show that closures get optimized and only the required variables are enclosed
This example http://jsfiddle.net/TnGxJ/2/ shows how only a is enclosed
In this example http://jsfiddle.net/TnGxJ/1/, since there's an eval, all the variables are enclosed.
Use $.each instead of a for loop so that you get a new variable scope with each iteration.
$.each(that.pairs, function(i, p) {
p.t.click(function() {
that.doToggle(p);
});
});
This way each click handler closes over a unique variable scope instead of the shared outer variable scope.
for (var i = 0; i < that.pairs.length; i++) {
var p = that.pairs[i];
(function(p){
p.t.click(function() {
that.doToggle(p);
});
}(p));
}
This trick with IIFE would solve the closure "issue" you're experiencing now.
for (var i = 0; i < that.pairs.length; i++) {
(function(num){
var p = that.pairs[num];
p.t.click(function() {
that.doToggle(p);
});
})(i)
}
Classic closure issue
Enclose them in an anonymous function and assign the current iteration in context. That should solve the problem..