Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript loop with binded click event always returning last result

I have a for loop running in javascript. In this loop I am creating a list item and binding a click event to it. When I click this list item I want it to call a function with the data from the current loop object as a parameter.

The problem is, no matter what list item I click. The data that is being passed as the parameter is the last element of the object I am looping rather than the current one that is being clicked.

for(e in data) {

    var suggestItem = $('<li>'+ data[e]['name'] +'</li>');

    suggestItem.click(function() {
        $(this).addClass('activeSuggestion');
        suggestSelect(suggestField, data[e]);      
    });

    suggestList.append(suggestItem);

}

I think I understand why this happens but not sure how I should handle it.

like image 982
Justin Carlson Avatar asked Dec 26 '11 19:12

Justin Carlson


People also ask

Why does JavaScript loop only use the last value?

Variable i Always Has The Last IndexBy the time the for loop reaches the last iteration, the i variable will end up holding the last index value. That's why the output will always be the last index, in my case, 5.

Can we use for loop in HTML?

Approach 1: Using the for loop: The HTML elements can be iterated by using the regular JavaScript for loop. The number of elements to be iterated can be found using the length property. The for loop has three parts, initialization, condition expression, and increment/decrement expression.

What is the function of jQuery?

jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers.


2 Answers

This is a classic Javascript closure question. When the click event is triggered (when you click on something), the loop has already finished executing. The value of e is whatever the last key in data is. The solution to the problem is to create a new scope inside of your loop.

for(var e in data) {
    (function(datum) {
        suggestList.append(
            $('<li>' + datum.name + '</li>')
            .click(function() {
                $(this).addClass('activeSuggestion');
                suggestSelect(suggestField, datum);      
            });
        );
    })(data[e]);
}

Javascript is function scoped, so whenever a new function is encountered, a new scope is created. The above code "traps" the value of data[e] into datum because it passes it as a parameter to the function. Another way to code it which may be less confusing:

for(var e in data) {
    (function() {
        var datum = data[e];
        suggestList.append(
            $('<li>' + datum.name + '</li>')
            .click(function() {
                $(this).addClass('activeSuggestion');
                suggestSelect(suggestField, datum);      
            });
        );
    })();
}

It doesn't pass in a parameter to the function, but because Javascript is function scoped, every time the click event is triggered it will look for where datum was assigned.

Also please note the for(VAR e in data) which will stop e from becoming a global variable.

like image 80
Andy Ray Avatar answered Sep 28 '22 06:09

Andy Ray


You need to break the closure over e.

Since you're using jQuery, the easiest way would be to save the current value of e with jQuery's data function. Since all function parameters in JavaScript are passed by value, this effectively breaks the closure; now your click handler will work with what the value of e is when the handler was created, and not the value e holds when the loop ends.

for(e in data) {

    var suggestItem = $('<li>'+ data[e]['name'] +'</li>');

    suggestItem.data('savedE', e).click(function() {
        $(this).addClass('activeSuggestion');
        suggestSelect(suggestField, data[$(this).data('savedE')]);
    });

    suggestList.append(suggestItem);

}
like image 31
Adam Rackis Avatar answered Sep 28 '22 07:09

Adam Rackis