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.
Related
This question already has answers here:
What is the scope of variables in JavaScript?
(27 answers)
What is the purpose of the var keyword and when should I use it (or omit it)?
(19 answers)
Closed 4 months ago.
This is the full code of the function (the event_option function itself works fine, but it just isn't recieving the proper parameters):
function fire_event(event, scopes) {
if (scopes[scopes.length-1] == data_player) {
opts = ""
i = 0;
data_events[ event ].options.forEach(option => {
opts += `<br><button id="event-${event}-option-${i}">${localisation[data_events[ event ].options[i].name]}</button>`
i += 1;
});
eventhtml = `<div id="event-${event}" class="event">
<h2>${localisation[ data_events[ event ].title ]}</h2>
<p>${localisation[ data_events[ event ].desc ]}</p>
${opts}
</div>`
$("#events").html($("#events").html()+eventhtml);
i = 0;
data_events[ event ].options.forEach(option => {
$(`#event-${event}-option-${i}`).click(function() { event_option(event, i, scopes) });
i += 1;
});
$(`#event-${event}`).on('mousedown', handle_mousedown);
}
}
This function, specifically the lines
data_events[ event ].options.forEach(option => {
$(`#event-${event}-option-${i}`).click(function() { event_option(event, i, scopes) });
i += 1;
});
is meant to make it so that clicking on the button will fire event_option(event, i, scopes) where the three values are meant to be as they were when the click function was added. However, clicking the button makes it use the values of the variables as they are when the click occurs.
(I also apologise for using the word "event" for one of the parts of my code despite not meaning actual code events.)
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 );
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
For some reason, I'm not understanding the scope of local and global variables with relation to jQuery click handlers. When I click the buttons, I'm getting "undefined", but I expect it to show the category of the button clicked. How do I address this?
I've setup an example to show my confusion. Fiddle here: http://jsfiddle.net/zvhsqeyn/
Simple html buttons:
<button class="hobbies">hobbies</button>
<button class="sledding">sledding</button>
<button class="personal">personal</button>
<button class="food">food</button>
Some javascript to handle the event where the buttons are clicked.
var GLOBAL_CATEGORIES = ['hobbies', 'sledding'];
$(window).load(function() {
var categories = ['personal', 'food'];
// local categories
for (var i = 0; i < categories.length; i++) {
$('.' + categories[i]).click(function() {
alert(categories[i]);
return false;
});
}
// Global categories
for (var i = 0; i < GLOBAL_CATEGORIES.length; i++) {
$('.' + GLOBAL_CATEGORIES[i]).click(function() {
alert(GLOBAL_CATEGORIES[i]);
return false;
});
}
});
Edit:
After taking in the feedback, here is the properly working http://jsfiddle.net/zvhsqeyn/1/
This has to do with the nature of closures.
// local categories
for (var i = 0; i < categories.length; i++) {
$('.' + categories[i]).click(function() {
alert(categories[i]);
return false;
});
}
You're assigning an anonymous function as the click handler for each of these buttons. That function has access to the variable i, because it's visible in the parent scope. However - by the time you click one of these buttons, your loop has long since finished, and the value of i is now equal to categories.length. There isn't any element there, so your click functions return undefined.
In general, it's not safe to rely on the value of index variables when assigning event handlers in a loop. As a workaround, you can use the jquery $.each function:
$.each(categories, function(index, value) {
// local categories
$('.' + value).click(function() {
alert(value);
return false;
});
});
This works because it creates a separate closure for each click handler function.
This is a very common ish problem and here is the explanation
If we follow the process through what you are doing is adding a click listener to the ith GLOBAL_CATEGORIES and then moving on to the next one. You do this until i is too big for the GLOBAL_CATEGORIES array.
You have to remember that i has only been set once and so now i is 1 more than the length of GLOBAL_CATEGORIES. This means that when the alert fires GLOBAL_CATEGORIES[i] is undefined because i is too big.
A better way to do this is with the jQuery each syntax like below
$.each(GLOBAL_CATEGORIES, function(i, category) {
$('.' + category).click(function() {
alert(category);
return false;
});
});
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:
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.