3

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.

2 Answers 2

5

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.

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

2 Comments

I tried the above code and now only Alaska (AK) is clickable.
Got it, thought I already had the divs created to manipulate but that first block did the trick. Many, many thanks.
1

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

1 Comment

"If one is paranoid about supporting older browsers..." It isn't paranoia. IE6-8 don't support Array.prototype.map, and that represents an enormous base.

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.