-1

I have few pieChart objects stored in shapes array... I want to add event listeners for each of the object. I tried doing it like this:

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

The problem here is that, even when I click on pieChart2 (id->2), it will always give the id->1 (as it pops last). Kindly explain this behavior and what can possibly be the efficient way to do this.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Abdul Jabbar
  • 2,573
  • 5
  • 23
  • 43

4 Answers4

1

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.

Jono Brogan
  • 341
  • 7
  • 19
1

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

Marat Tanalin
  • 13,927
  • 1
  • 36
  • 52
0

You can use

e.target.id

instead

S.id

in event listener

Mykyta Shyrin
  • 312
  • 1
  • 14
0

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).

Community
  • 1
  • 1
Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166