jbprog
jbprog

Reputation: 61

Dynamically passing parameters to event listeners AS3

I have a 16x16 grid of buttons, and want to add an event listener to each of them so when clicked it will return its unique grid position number (anything between 0-255); What I have so far:

    public static const GRID_SIZE:Number = 16;
    private var i:int;
    private var j:int;

    // Constructor
    public function Grid()
    {
        for (i = 0; i < GRID_SIZE; i++)
        {
            for (j = 0; j < GRID_SIZE; j++)
            {
                // Creates new instance of GridButton, a custom class that extends SimpleButton
                var button:GridButton = new GridButton();

                // Arranges buttons into a grid
                button.x = (i + 1) * button.width;
                button.y = (j + 1) * button.height;

                // Create unique value for grid position
                var position:Number = i * GRID_SIZE + j;

                // Add listener to button, with position as extra param
                button.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) : void { buttonClick(e, position) } );

                // Adds button to Sprite
                addChild(button);
            }
        }
    }
}

Unfortunately every time the listener function is called by each button, it uses the last two values of i and j, which in this case return 255 for every button.

Any suggestions?

Upvotes: 3

Views: 5095

Answers (5)

user1837285
user1837285

Reputation:

By using a variable from outer scope inside an anonymous function you got big trouble with closures and memory leak already: position is used in function(e:MouseEvent), but it was created in function Grid(), so it'll obey function Grid()'s changes. And you're dealing with 256 function(e:MouseEvent)s that you added but can't remove, overheading memory!

Let's remake your code the simplest working way:

public static const GRID_SIZE:Number = 16;
private var i:int;
private var j:int;

public function Grid() {
  var functions:Object;

  for (i = 0; i < GRID_SIZE; i++) {
    for (j = 0; j < GRID_SIZE; j++) {
      var button:GridButton = new GridButton();
      button.x = (i + 1) * button.width;
      button.y = (j + 1) * button.height;
      var position:Number = i * GRID_SIZE + j;
      functions[position] = buttonClick(position);
      button.addEventListener(MouseEvent.CLICK, functions[position]);
      //button.removeEventListener(MouseEvent.CLICK, functions[position]);
      addChild(button);
    }
  }

  buttonClick(position:Number):Function {
    return function(e:MouseEvent):void {
      // Now you can use both unique "e" and "position" here for each button
    }
  }
}

Since you're adding 256 listeners, you have to store each in distinct variables (the list of properties functions[position] was made for it), so you can safely remove each one later to free memory in the same way you added them.

This was elaborated from this answer.

Upvotes: 0

catholicon
catholicon

Reputation: 1165

I tried this and it worked fine....i.e. clicking on different text fields traced different value. The idea is to create a new reference to pos:Number so that even after updating pos, the closure still points to the old

package
{
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.text.TextField;
    import flash.text.TextFieldType;

    public class AsDing extends Sprite
    {
        private static const GRID_SIZE:uint = 2;
        public function AsDing()
        {
            for(var i:uint=0; i<GRID_SIZE; i++)
            {
                for(var j:uint=0; j<GRID_SIZE; j++)
                {
                    var tf:TextField = new TextField();
                    var pos:Number = i*GRID_SIZE + j;
                    tf.text = pos.toString();
                    tf.type = TextFieldType.DYNAMIC;
                    tf.addEventListener(MouseEvent.MOUSE_DOWN, createMCHdlr(pos));
                    addChild(tf);
                    tf.x = i*100; tf.y = j*100;
                }
            }
        }

        private function createMCHdlr(pos:Number):Function
        {
            var ret:Function = function(evt:MouseEvent):void
            {
                trace('pos: ' + pos);
            };
            return ret;
        }
    }
}   

Upvotes: 0

Daniel
Daniel

Reputation: 35684

there are some other options...

1 use signals instead of flash event listeners, here's a post that describes it and gives more detail. Also a video tutorial to get you started quickly. The reason I'm suggesting this is that you can setup a signal to pass a parameter, so you don't need to use the event target, which doesn't always work the best.

2 use a function factory.

// factory:
public function fooFactory($mc:GridButton):Function{
    var $f:Function = new Function($e:MouseEvent):void{
        trace($mc.x, $mc.y);
    }
    // you might want to consider adding these to a dictionary or array, so can remove the listeners to allow garbage collection
    return $f;
}


// then assign the function to the button like this
button.addEventListener(MouseEvent.CLICK, fooFactory(button) );

this does essentially what you wanted to do, but it will work because the position var is not changing. In your code the position is changing every time the loop executes, then when you you fire the listener function it uses the last value that position was assigned, which is why you are getting 255 for all of them. Let me know if that's not making sense...

Upvotes: 2

localhost
localhost

Reputation: 169

You can get back the values of i and j from the same expression which is being used to assign x and y values to the buttons. For example:

button.addEventListener(MouseEvent.CLICK, clickHandler);

function clickHandler(e:MouseEvent) {
    var i:int   = (e.target.x / e.target.width) - 1;
    var j:int   = (e.target.y / e.target.height) - 1;

    var position:Number = i * GRID_SIZE + j;
}

Upvotes: 0

Bartek
Bartek

Reputation: 1996

You could add a name to this button, for example button.name = "button" + position; and in event handler function you can extract position parameter from target name (e.target.name):

button.name = "button" + position;
button.addEventListener(MouseEvent.CLICK, buttonClick);

and event handler:

function buttonClick(e:MouseEvent) {
    var position:Number = Number(e.target.name.replace("button", ""));
    // ... do wathever you want with position parameter
}

Upvotes: 1

Related Questions