views:

118

answers:

3

Im trying to create a simple loop that creates 50 buttons, adds them to screen and then when a button is pressed, it traces out that number. I can get it to work by doing stuff I consider hacky (such as using the buttons X/Y location to determine its value), but I'd rather just be able to hold a single value in the function.

The code itself is:

for (var a:int = 0; a < 5; a++) {
    for (var b:int = 0; b < 10; b++) {
        var n = (a * 10) + b + 1;
        var btt:SimpleButton = new BasicGameButton();
        btt.x = 20 + b * 50;
        btt.y = 50 + a * 80;
        addChild(btt);
        btt.addEventListener(MouseEvent.CLICK, function f() { trace(n); } );
    }
}

At the moment, whenever a button is pressed, it simply outputs "50". Is there a way of "freezing" the value of n when the function is created, for that function? (BasicGameButton is just a square button, created in the flash library)

Many thanks.

A: 

this is an AS3 flaw comming from the abandoned ECMA-Script draft AS3 is based on ... n is scoped to the innermost function body it is declared in (thus as if it were declared before the loop), although declared in the innermost loop body of your code.

you can do the following:

var makeFunction:Function = function (n:int):Function {//this can of course also be a method
    return function (event:MouseEvent):void {
         trace(n);
    }
}

//in the loop body, use it like this:
btt.addEventListener(MouseEvent.CLICK, makeFunction(n));

As a side note: haXe does not have this flaw.

greetz
back2dos

back2dos
That works perfectly. Many thanks, back2dos!
A: 

I see that the answer has been selected, but I don't agree with the solution, though it does work. I wouldn't characterize this as a flaw with AS3 as this would happen the same way with Javascript, also based on ECMAScript. In any case, you can avoid the problem altogether by just creating a property for BasicGameButton that holds the value of n. Also, instead of adding a listener for every button, just create a single listener on the button container that listens for the mouse click (a mouse click "bubbles" up to a parent display object) and gets the clicked button using the event target.

/* inside the loop */
btt.n = n;

/* parent listener you only have to create once */
buttonContainer.addEventListener(MouseEvent.CLICK, onClick);

function onClick(e:Event):void {
    trace(BasicGameButton(e.target).n);
}
wmid
BasicGameButton wasn't a class, rather an instance of SimpleButton within the flash library and I was hoping to avoid having to make a class specifically for it. As such, btt.n = n doesn't work (unless theres a way to externally create new variables).Also, the actual code Im using is slightly more complex than the code I posted above, so I cant really use a eventListener on parent, since there will be multiple buttons and whatnot going on in the background.I appreciate the input nevertheless.
A: 

I agree with wpjmurray that this is not a flaw at all; rather it is a result of how scope works in EcmaScript.

Your click handler carries an implicit reference to a variable (n). When the function is called (triggered by a click), the value of that variable has changed. It makes perfect sense when you think about it this way.

The solution is to store the value on each iteration and associate it somehow with your button. There are three good ways to do this:

  1. Create a new closure that itself has an implicit reference to another variable whose value is set once and then left alone. back2dos's solution is one way to do this: makeFunction creates a new method closure that has an implicit reference to a new variable (makeFunction's n argument) which doesn't change.
  2. Store the value as a property on the object. (This is what wpjmurray proposed, but you aren't able to do.)
  3. Store the value in a map and look it up. You hinted at this when you mentioned mapping the X/Y coordinates to the index. However, ActionScript 3 provides a much more…elegant (: solution: the Dictionary class! Just map the object itself to the index, and then look it up in the handler.

Here's an example:

var dict:Dictionary = new Dictionary(true);
for (var a:int = 0; a < 5; a++) {
    for (var b:int = 0; b < 10; b++) {
        var n = (a * 10) + b + 1;
        var btt:SimpleButton = new BasicGameButton();
        btt.x = 20 + b * 50;
        btt.y = 50 + a * 80;
        addChild(btt);
        dict[btt] = n;
        btt.addEventListener(MouseEvent.CLICK, function f(event:MouseEvent) { trace(dict[event.currentTarget]); } );
    }
}

Because of how scope works in EcmaScript, each click handler that you create carries an implicit reference to the dictionary, which you can use to look up the index. Now the index is mapped directly to the button, without having to add a property or create a new function.

matthew