Jquery does not quite work inside for loop [duplicate] - javascript

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.

Related

JQuery: on click listener doesn't keep instanced variables from loop [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I'm making a point and click adventure/dating sim like website. I am currently making Dialogue Options. Options that come up at a certain point in conversation that allows the user to select their response, or chose some sort of prompt option.
My entire system seems to work fine except one step where I'm populating the Dialogue Options onto the prompt.
//Run through up to 10 dialogue options and add to dialogue option window.
for(var i = 1; i<3; i++)
{
var Option = eval(O['o'+i]);
if(typeof(Option) !== 'undefined')
{
$("#CAM_OptionsFrame").append("<p id='DialogueO"+i+"' class='clickable'>"+Option[0]+"</p>");
var funcString = Option[1];
var func = window[funcString];
$('#DialogueO'+i).on("click",function() {
$( this ).off("click");
endDialogueOption(time, O.noHide, function(){func();});
});
}
}
I load any number of Dialogue Options and their partner functions from an argument. Then loop through them listing the name in the HTML of the prompt and giving them a unique ID.
Then I select the element by the unique ID and give it an onclick listener which refers to the function passed in by the specific option.
However, ALL the elements run the last options function when you click them. I suspect the listener points to the "func" variable and updates every time I change it in the loop.
How do I make the listener refer to the instance of the variable so that every new listener I make refers to it's own "func" variable?
This is a classic closure issue. You have 2 options, once is invoke each function with the current index of the loop.
for (var i = 1; i < 3; i++) {
(function(index) {
var Option = eval(O['o' + index]);
if (typeof(Option) !== 'undefined') {
$("#CAM_OptionsFrame").append("<p id='DialogueO" + index + "' class='clickable'>" + Option[0] + "</p>");
var funcString = Option[1];
var func = window[funcString];
$('#DialogueO' + index).on("click", function() {
$(this).off("click");
endDialogueOption(time, O.noHide, function() {
func();
});
});
}
})(i)
}
If you have ES6 support in the code base, then the only thing you need to change, if var to let in the for loop.
for (let i = 1; i < 3; i++) {
.. everything else remains the same.

setTimeout function is not called

Before you jump onto the duplicate question, I must say I have looked through stackoverflow and other places before I came here.
So basically, I'm using a sprite image and need to loop through the image inside it. In the code block below you'll find the way I have approached it right now. However, after adding the setTimeout function it seems like everything inside the function parameter is no longer executed.
var headerTimeout = 1000/24;
jQuery('.headerGif').hover(function(){
for(var i = 1; i <= 41; i++){
setTimeout(function(){
if (jQuery(this).hasClass('.header-HeaderBedrijfsVideo00' + (i - 1))) {
jQuery(this).removeClass('.header-HeaderBedrijfsVideo00' + (i - 1));
}
jQuery(this).addClass('.header-HeaderBedrijfsVideo00' + i);
}, headerTimeout);
}
});
If there is a better way to approach this, I would appreciate it if someone could point me into the right direction. I am already looking into just using a plugin for this purpose.
EDIT: I have tried checking the question that is in the duplicate marking, but that's basically what a closure does is it not? I just added the closure from one of the answers and it still does not work.
Use closure , for loop would be executed before time out function then you can get last incremented i value 41 ,so in this context you have to use closure like multiple thread
jQuery('.headerGif').hover(function () {
var _this=this;
for (var i = 1; i <= 41; i++) {
(function (i) {
setTimeout(function () {
if (jQuery(_this).hasClass('.header-HeaderBedrijfsVideo00' + (i - 1))) {
jQuery(_this).removeClass('.header-HeaderBedrijfsVideo00' + (i - 1));
}
jQuery(_this).addClass('.header-HeaderBedrijfsVideo00' + i);
}, i*100);
})(i);
}
});
Reason
setTimeout function will be called only after i becomes 41(whatever is the end of the for loop)!!
Check out the below fiddle (check the console)
http://jsfiddle.net/szx19hzo/2/
Do not use . inside the hasClass function
You can remove timeout to make it work, check the below link(check console and inspect element to note that class is removed)
http://jsfiddle.net/szx19hzo/3/
Solution
If you want to retain the timeout, then use a different function and call it inside the loop and have the timeout given inside the function Use the below solution if you want to retain the timeout
http://jsfiddle.net/szx19hzo/4/
var headerTimeout = 1000/24;
jQuery('.headerGif').hover(function(){
for(var i = 1; i <= 41; i++){
var className='header-HeaderBedrijfsVideo00' + (i - 1);
removeClass(this,className,i);
}
});
function removeClass(item,className,i){
setTimeout(function(){
console.log(className);
if (jQuery(item).hasClass(className)) {
jQuery(item).removeClass('header-HeaderBedrijfsVideo00' + (i - 1));
console.log("removed");
}
jQuery(item).addClass('.header-HeaderBedrijfsVideo00' + i);
}, headerTimeout);
};

getting undefined array inside click function inside for loop [duplicate]

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 );

JavaScript variables do not persist in jQuery event handler [duplicate]

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;
});
});

Losing Scope of Array on Click Event Loop [duplicate]

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..

Categories

Resources