EventListeners within a loop [JavaScript]

<>

This question already has an answer here:

Answers


Considering that var declarations have function scope (or global if declared in the global scope), your code is equivalent to:

var tempS = shapes.slice(),
    i, S;

for (i = 0; i < shapes.length; i++) {
    S = tempS.pop();
    if (S.name == 'pieChart') {
        document.getElementById(S.id).addEventListener('mousedown', function (e) {
            alert(S.id);
        }, false);
    }
}

Now, it is clear that S is scoped outside of the for block, and as the event handlers will be triggered after the loop finishes, S will have the value of the last iteration.

You need to create a new scope, either through a callback factory (as in this related answer) or an IIFE, to trap the current S value of the iteration.

var tempS = shapes.slice();

for (var i = 0; i < shapes.length; i++) { (function() {//introduces a new scope
    var S = tempS.pop(); //now S is scoped inside this iteration's IIFE
    if (S.name == 'pieChart') {
        document.getElementById(S.id).addEventListener('mousedown', function (e) {
            alert(S.id); //will reference the IIFE scope's S
        }, false);
    }
}()); }

You can do this in many alternative ways as well, for example, using Array#forEach:

var tempS = shapes.slice();
shapes.forEach(function(S) { //S is now scoped in the forEach callback scope
    if (S.name == 'pieChart') {
        document.getElementById(S.id).addEventListener('mousedown', function (e) {
            alert(S.id);
        }, false);
    }
});

Note that this will change the iteration order, but I believe it won't matter for this use case.


In ES6 it will be possible to use let to create block-scoped variables:

for (var i = 0; i < shapes.length; i++) {
    let S = tempS.pop(); //scoped inside the `for` block
    if (S.name == 'pieChart') {
        document.getElementById(S.id).addEventListener('mousedown', function (e) {
            alert(S.id);
        }, false);
    }
}

I'm leaving this approach here for future readers, as let is not ready for production yet. Most recent browsers do have experimental implementations though (see ES6 compat table).


See here

Using underscore.js :

_.each(shapes, function(s) {
    if(s.name === 'pieChart') {
        document.getElementById(s.id).addEventListener('mousedown',function(e) {
            e = e || window.event;
            var target = e.target || e.srcElement;
            alert(target.id);
        });
    }
});

To explain the behaviour of your code, you have to remember that javascript uses function scope as opposed to block scope. So since your defining S outside of the eventListener callback function the reference to S inside the callback will be the current value of S, which after the loops execution will be the last pieChart object in the shapes array.


Inside callback, use this instead of accessing element by id again.


You can use

e.target.id

instead

S.id

in event listener


Need Your Help

Gzip compression for HTTP requests

silverlight http http-compression gzipstream

Is there any way to make browsers &amp;/or Silverlight application do a GZIP compression of HTTP requests? Don't confuse with GZIP compression of HTTP responses - I know how to set this up on the s...