0

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/

Jonathan Mui
  • 2,471
  • 3
  • 19
  • 27

2 Answers2

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.

Sam Dufel
  • 17,560
  • 3
  • 48
  • 51
-2

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;
    });
});
MarshallOfSound
  • 2,629
  • 1
  • 20
  • 26