Stephen
Stephen

Reputation: 67

Javascript Array addEventListener

Interactive map with buttons in the shape of states, each button has the state abbreviation as an id, when a button/state is clicked I would like to fire the function "stateSelect" and send the state abbreviation with it so I know what's been pressed. Why doesn't the following work?

    var stateList = new Array("AK","AL","AR","AS","AZ","CA","CO","CT","DC","DE","FL","GA","GU","HI","IA","ID",
    "IL","IN","KS","KY","LA","MA","MD","ME","MH","MI","MN","MO","MS","MT","NC","ND","NE","NH","NJ","NM","NV","NY",
    "OH","OK","OR","PA","PR","PW","RI","SC","SD","TN","TX","UT","VA","VI","VT","WA","WI","WV","WY");

    for (var i = 0; i < stateList.length; i++) {
        document.getElementById(stateList[i]).addEventListener('mousedown', function() {stateSelect(stateList[i])}, false);
    }

I obviously want to avoid 50 some lines of code but I'm not sure why this simple loop isn't working.

Upvotes: 3

Views: 8196

Answers (2)

ninjagecko
ninjagecko

Reputation: 91149

You have a scoping issue. Javascript is not block-scoped; it is function-scoped. Basically, you must create a new function whenever you wish to create a new variable in a loop.

The most elegant way to do so is as follows:

stateList.map(function(abbrev){
    $(abbrev).mousedown(function(){stateSelect(abbrev)});
});

If you are not using jQuery, merely replace $(abbrev).mousedown with document.getElementById(abbrev).addEventListener.

(Just to preempt the people who go "map isn't standard"; it is in the javascript ECMA-262 standard 5th edition which has support from all browser vendors. If one is paranoid about supporting older browsers, one can just $.map.)

Here is how one would do so using a for loop; it's a bit uglier but it demonstrates the necessity of creating new closures via functions:

for(var i=0; i<stateList.length; i++)
    (function(i){
        $(stateList[i]).mousedown(...);
    })(i);

Like I said, a bit uglier than necessary; you could also do this which is slightly less ugly, but is basically the same thing:

function createListener(abbrev) {
    $(abbrev).mousedown(...);
}
for(var i=0; i<stateList.length; i++)
    createListener(stateList[i]);

Upvotes: 1

user113716
user113716

Reputation: 322612

Because when the handler runs, it looks up the value of i, which is wherever it was after the loop finished.

You need to scope the i variable in a function:

function listenerForI( i ) {
    document.getElementById(stateList[i]).addEventListener('mousedown', function() {stateSelect(stateList[i])}, false);
}
for (var i = 0; i < stateList.length; i++) {
    listenerForI( i );
}

Now the i referenced by the handler will be the parameter to the listenerForI function that was invoked. As such, that i will reference the value that was passed in from the for loop.

Upvotes: 5

Related Questions