22

So I have a group of events like this:

$('#slider-1').click(function(event){   
        switchBanners(1, true);
});
$('#slider-2').click(function(event){   
        switchBanners(2, true);
});
$('#slider-3').click(function(event){   
        switchBanners(3, true);
});
$('#slider-4').click(function(event){   
        switchBanners(4, true);
});
$('#slider-5').click(function(event){   
        switchBanners(5, true);
});

And I wanted to run them through a loop I am already running something like this:

for(i = 1; i <= totalBanners; i++){
    $('#slider-' + i).click(function(event){    
            switchBanners(i, true);
    });
}   

In theory that should work, but it doesnt seem to once I load the document... It doesnt respond to any specific div id like it should when clicked... it progresses through each div regardless of which one I click. There are more event listeners I want to dynamically create on the fly but I need these first...

What am I missing?

5 Answers 5

44

This is a very common issue people encounter.

JavaScript doesn't have block scope, just function scope. So each function you create in the loop is being created in the same variable environment, and as such they're all referencing the same i variable.

To scope a variable in a new variable environment, you need to invoke a function that has a variable (or function parameter) that references the value you want to retain.

In the code below, we reference it with the function parameter j.

   // Invoke generate_handler() during the loop. It will return a function that
   //                                          has access to its vars/params. 
function generate_handler( j ) {
    return function(event) { 
        switchBanners(j, true);
    };
}
for(var i = 1; i <= totalBanners; i++){
   $('#slider-' + i).click( generate_handler( i ) );
}

Here we invoked the generate_handler() function, passed in i, and had generate_handler() return a function that references the local variable (named j in the function, though you could name it i as well).

The variable environment of the returned function will exist as long as the function exists, so it will continue to have reference to any variables that existed in the environment when/where it was created.


UPDATE: Added var before i to be sure it is declared properly.

Sign up to request clarification or add additional context in comments.

2 Comments

Did the job!!!! Thanks a BUNCH!!! I knew it had to do with variable scope, just could figure out how to work around it! ;)
Worked awesomely!
7

Instead of doing something this .. emm .. reckless, you should attach a single event listener and catch events us they bubble up. Its called "event delegation".

Some links:

Study this. It is a quite important thing to learn about event management in javascript.

4 Comments

Makes sense... Sorta... lol... I need to look into this deeper but the general concept I like, MUCH cleaner. I need to play with this more in the future as I do many things like this. Got to love the add more and it picks up the features!
Nice. I didn't know about this method and I've ran into this problem many a times and I usually come up with some ugly solutions. But never the less it's been a huge pain for me. This is a very simple solution and easy to understand. Thank you so much. Not that I would ever want to but I could have all my click listeners in one listener set to the body.
This quite useful answer lacks regrettable some inline-code, which is required according to the SO rules. When the answer was given in 2011 it was perhaps different ;-)
Yes, at that time Stackoverflow uses thought, that "just copy code from SO" was a bad practice and instead of giving people the fish, it is more responsible for to teach them how to fish.
5

[edit: saw this answer get an upvote and recognized it's using old syntax. Here's some updated syntax, using jQuery's "on" event binding method. The same principle applies. You bind to the closest non-destroyed parent, listening for clicks ON the specified selector.]

$(function() {
  $('.someAncestor').on('click', '.slider', function(e) {
    // code to do stuff on clicking the slider. 'e' passed in is the event
  });
});

Note: if your chain of initialization already has an appropriate spot to insert the listener (ie. you already have a document ready or onload function) you don't need to wrap it in this sample's $(function(){}) method. You would just put the $('.someAncestor')... part at that appropriate spot.

Original answer maintained for more thorough explanation and legacy sample code:


I'm with tereško : delegating events is more powerful than doing each click "on demand" as it were. Easiest way to access the whole group of slider elements is to give each a shared class. Let's say, "slider" Then you can delegate a universal event to all ".slider" elements:

$(function() {
    $('body').delegate('.slider', 'click', function() {
        var sliderSplit = this.id.split('-'); // split the string at the hyphen
        switchBanners(parseInt(sliderSplit[1]), true); // after the split, the number is found in index 1
    });
});

Liddle Fiddle: http://jsfiddle.net/2KrEk/

I'm delegating to "body" only because I don't know your HTML structure. Ideally you will delegate to the closest parent of all sliders that you know is not going to be destroyed by other DOM manipulations. Often ome sort of wrapper or container div.

Comments

1

It's because i isn't evaluated until the click function is called, by which time the loop has finished running and i is at it's max (or worse overwritten somewhere else in code).

Try this:

for(i = 1; i <= totalBanners; i++){
    $('#slider-' + i).click(function(event){    
        switchBanners($(this).attr('id').replace('slider-', ''), true);
    });
}   

That way you're getting the number from the id of the element that's actually been clicked.

1 Comment

It works better... I may have to look elsewhere to see where its breaking now... basically the switchBanners() function changes direction of it left and right based on its current position... its not moving back to where it came from with this either... BUT this fixes it to move it the first time left to right... but it wont move right to left on the second click
0

Use jQuery $.each

$.each(bannersArray, function(index, element) {
    index += 1; // start from 0
    $('#slider-' + index).click(function(event){    
        switchBanners(index, true);
    });
});

You can study JavaScript Clousure, hope it helps

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.