To pass a parameter to event listener in AS3 the simple way... does it exist?

user985358 picture user985358 · Nov 21, 2012 · Viewed 39.1k times · Source

Expected / pseudo example:

stage.addEventListener(MouseEvent.CLICK, onClick.someWayToPassParameters(true, 123, 4.56, "string"));
function onClick(e:MouseEvent):void {
    trace("Received " + someWayToRetrieveParameters().b/i/n/s + ".");
}

For many years (3~4), on every website, forum, blog, wherever I searched, people tell me there's no simple way to do this. They usually suggest to:

  • Add the listener to a dynamic object, where you can set the value to an extra property and reference it (e.target.property / e.currentTarget.property) in the function.

    Not all classes are dynamic. It won't work on a Sprite, for example.

  • Extend the class of the object with a custom class to add property or to just make it dynamic.

    You'll have to create a whole new tweak class everytime.

  • Use an anonymous function as the event handler.

    There's no reference (and it's ugly). To remove the listener to free resources you're forced to do it from inside the function itself with arguments.callee.

  • Call another function, using the parameter, inside the event handler.

    And where in the event handler call does the parameter go?

  • Keep the event handler in the same scope as the parameter.

    Breach to a total semantic mess.

  • Encapsulate both event handler definition and addEventListener call in a function receiving the target and parameters.

    It can mix scopes, but it's a close one. You have to be careful, though.

...Among many other suggested workarounds.

All I want is just to pass an argument to the event handler so I can use it inside its function, just like any normal function!

Am I asking for too much?

Answer

user1837285 picture user1837285 · Nov 21, 2012

Out of the box: it only takes 2 extra lines of elegant code to solve this ancient puzzle.

stage.addEventListener(MouseEvent.CLICK, onClick(true, 123, 4.56, "string"));
function onClick(b:Boolean, i:int, n:Number, s:String):Function {
  return function(e:MouseEvent):void {
    trace("Received " + b + ", " + i + ", " + n + " and " + s + ".");
  };
}

But most importantly, you will very likely need to remove the listener later to free resources, so +1 line to store it in a variable:

var functionOnClick:Function = onClick(true, 123, 4.56, "string");
stage.addEventListener(MouseEvent.CLICK, functionOnClick);
function onClick(b:Boolean, i:int, n:Number, s:String):Function {
  return function(e:MouseEvent):void {
    trace("Received " + b + ", " + i + ", " + n + " and " + s + ".");
  };
}

And you'll be able to remove it normally:

trace("Before: " + stage.hasEventListener(MouseEvent.CLICK));
stage.removeEventListener(MouseEvent.CLICK, functionOnClick);
trace("After: " + stage.hasEventListener(MouseEvent.CLICK));

Here's a more elaborated, dynamic example to prove its use:

function onClick(s:String):Function {
  return function(e:MouseEvent):void {
    trace("The square " + s + " at x = " + e.currentTarget.x + "px was clicked");
  };
}
var myFunctions:Array = new Array();
for (var i:int = 0; i < 10; i++) {
  myFunctions.push(onClick("#" + (i+1)));
}
for (i = 0; i < myFunctions.length; i++) {
  var square:Sprite = new Sprite();
  square.name = "sqr" + i;
  square.addChild(new Bitmap(new BitmapData(20, 20, false, 0)));
  square.x = 5 + 25 * i;
  square.addEventListener(MouseEvent.CLICK, myFunctions[i]);
  stage.addChild(square);
}

No properties through dynamic objects, no custom class, no loose functions, no scope overlap. Just what logic expects it to be: you're simply passing arguments to it.

And to remove every listener properly, you can do it like this later:

for (i = 0; i < myFunctions.length; i++) {
  square = stage.getChildByName("sqr" + i) as Sprite;
  trace("Before (#" + (i+1) + "): " + square.hasEventListener(MouseEvent.CLICK));
  square.removeEventListener(MouseEvent.CLICK, myFunctions[i]);
  trace("After (#" + (i+1) + "): " + square.hasEventListener(MouseEvent.CLICK));
  stage.removeChild(square);
}

IMHO this is the simplest yet most solid way to do it.