1

Using CreateJS, I have output a canvas with instances of various objects, animating in various ways. Almost all is working ok, except for one issue, which I think is a scope issue, but I'm not sure how to fix. I have an instance referenced by '_pulsars', containing 11 instances of an object 'pulsar', and code runs like this:

function attachPulseFuncs() {

    var defaultFreq = 0.05; // controls the likelihood of a 'pulse' happening

    for (var i = 0; i < _pulsars.children.length; i++) {

        var myParent = _pulsars.children[i];

        myParent.isPulsing = false;

        _myParent.pulse = function() {
            if (myParent.isPulsing == false) {
                myParent.isPulsing = true;
                myParent.__frame = 1
                var tween = createjs.Tween.get(myParent).to({__frame:65}, 1500).call(myParent.resetPulse);
                tween.addEventListener("change", function() {
                myParent.gotoAndStop(myParent.__frame);
                });
            }
        }

        myParent.resetPulse = function() {
            myParent.gotoAndStop(1);
            myParent.isPulsing = false;
        }

        myParent.callRandomPulse = function() {
            var ran = Math.random();

            if (ran < freqNumber) {
                myParent.pulse();
            }
        }

        createjs.Ticker.addEventListener("tick", myParent.callRandomPulse);
    }
}

What happens is that only the last pulsar animates (no matter how many are in the group). I'm wondering if it's because only one event listener is being attached? Or somehow all the listeners are being added to one 'pulsar'? Please help!

EDIT: Success! Thanks to Robert (and also Bergi's link to the similar issue), I was helped to understand a little more about closure and unbinding the variable being passed into the function - as the link on the other thread says:

"if we pass a parameter [the] function makes its own local copy of the variable (if it is not object type which pass by reference)"

This allows each pulsar to have a discrete reference to its parent as defined by each iteration of myParent, rather than the single outer-scope reference that was being held by the final value of myParent. (@Robert Koritnik please let me know if I have misunderstood or this is incorrect! Thanks for your help, I would +1 if I wasn't so new to the site. Soon, hopefully)

Ollywood
  • 541
  • 5
  • 10

1 Answers1

1

The problem you're having is that you iterate over your pulsars incrementing i along the way and adding those event listeners. So when your listers actually fire, they use myParent (mind your likely typo sometimes using underscore in front of it and sometimes not) variable which was last set to the last pulsar when your for loop completed (last value of i);

You'll have to change this piece of code

myParent.pulse = function() {
    if (myParent.isPulsing == false) {
        myParent.isPulsing = true;
        myParent.__frame = 1
        var tween = createjs.Tween.get(myParent).to({__frame:65}, 1500).call(myParent.resetPulse);
        tween.addEventListener("change", function() {
            myParent.gotoAndStop(myParent.__frame);
        });
    }
}

to this:

myParent.pulse = (function(parent) {
    return function() {
        if (parent.isPulsing === false) {
            parent.isPulsing = true;
            parent.__frame = 1
            var tween = createjs.Tween.get(parent).to({__frame:65}, 1500).call(parent.resetPulse);
            tween.addEventListener("change", function() {
                parent.gotoAndStop(parent.__frame);
            });
        }
    };
})(myParent);

What this change does it creates a new function scope capturing current value of myParent and storing it in local variable (parameter) parent and then returning a function that uses this new captured value.

Maybe I've captured even too much in this and I should only create a new function scope for the tween.addEventListener function call. You should know which part need capturing. My code captures much more code than likely required so it should work just as well, but it may seem confusing after a while when you'll get back to it introducing some change.

Therefore reduce scoping to the minimum set of statements like:

myParent.pulse = function() {
    if (myParent.isPulsing == false) {
        myParent.isPulsing = true;
        myParent.__frame = 1
        var tween = createjs.Tween
            .get(myParent)
            .to({__frame:65}, 1500)
            .call(myParent.resetPulse);
        tween.addEventListener("change", (function(parent) {
            return function() {
                parent.gotoAndStop(parent.__frame);
            };
        })(myParent));
    }
}

But as mentioned this may not work as it uses __frame variable of the last myParent pulse instance. So you may end up with my first code change.

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • I was typing the same, but deleted after posting and realising both answers are duplicates. – web-nomad Jan 23 '15 at 11:18
  • Ah, ok - so somehow I need to create a discrete 'parent' variable for each pulsar? I tried creating an array of variables (eg "myParent[i]") but that suffered from the same issue (I guess because "myParent[i]" would be the same as "myParent" in the current code). Thanks for pointing it out, how would I get around it? – Ollywood Jan 23 '15 at 11:24
  • @OllySkinner: It took me a while to complete my answer but I wanted to first give you the hint and provide an answer and then edit on it to give you all the details to resolve it. – Robert Koritnik Jan 23 '15 at 11:26
  • Thanks Robert! I think I understand, altho if the scope issue applies in that function ('pulse'), I should need to do similar adjustment to the other functions, particularly the createjs.Ticker event listeners that I'm adding at the bottom? (and well spotted with the var typo - that was actually just from me clearing up the code trying to make it easier to read, removing all the console log traces and extra variable definitions! Thanks again). – Ollywood Jan 23 '15 at 11:33
  • @OllySkinner: Yes. As mentioned in my answer, you should know better which parts seem to require variable capturing. This is another one of them as you correctly found out yourself. Apparently the main objective of my answer has been met: *to make you understand why it doesn't work.* – Robert Koritnik Jan 23 '15 at 11:34
  • You have indeed explained it well, thanks Robert. – Ollywood Jan 23 '15 at 11:37