Passing parameters to a MouseEvent listener

Problem:

Here is how we construct a MouseEvent handler in ActionScript 3.0:

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:MouseEvent):void
{
   navigateToURL(new URLRequest("http://www.mysite.com"), "_blank");
}

Note that we set the fourth parameter, useWeakReference, to true. This ensures that if this listener is the the only reference to the myButton remaining, myButton can still be garbage collected if required.

The trouble with this is that every time myButton is clicked, the same URL will open. We can’t easily pass a parameter to the function which is acting as a listener. How can the same function be used by many buttons to open a different URL for each button? How can we pass a parameter?

Solution 1: Custom events (won’t work):

It would be nice if we could dispatch a custom event on mouse click. Custom events extend the event class enabling us to dispatch an event with additional properties (like a URL). This event is received by the handler then the property is accessed. The trouble is this approach won’t work here because the Flash Player automatically dispatches MouseEvents, we don’t dispatch them ourselves. (If anyone sees a way this may be done, please comment below.)

Solution 2: Embed the function call (not advised):

An interesting strategy is used here, whereby a call to a function is used as the event handler, and this returns the function to use, including the desired parameter. The trouble with this approach is that it is not really good form, in that there is no way we can reference the handler if we did want to remove it using removeEventListener().

We’re beginning to see that there is actually no way to “pass” a parameter, really. We have to rather detect the button-specific value by “association”. The following solutions all achieve this by using the MouseEvent’s target property:

Solution 3: Use a switch statement on the event’s target property

When the button is clicked, providing there is no further mouse-enabled object inside it, that button is the event’s target. We can say it dispatched the event. We can access the target object that dispatched the event using a property of the event itself:

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:MouseEvent):void
{
   trace(e.target + "dispatched this event!");
   navigateToURL(new URLRequest("http://www.mysite.com"), "_blank");
}

If we can access the target we can decide what to do in the handler depending on the target:

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:MouseEvent):void
{
   var u:URLRequest = new URLRequest;

   switch(e.target)
   {
      case myButton:
         u.url = "http://www.mysite.com";
         break;

      case myOtherButton:
         u.url = "http://www.myothersite.com";
         break;

      default:
         throw new Error("No URL set for " + e.target);
         return;
   }

   navigateToURL(u, "_blank");
}

This works well.

Solution 4: Use a public custom property inside the dispatching object

We could avoid a switch statement and instead have a property inside the dispatching class which can then be detected. So if our button extends Sprite, let’s say, we could have an instance property in there called url and we could detect that, as follows:

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:Event):void
{
   var u:URLRequest = new URLRequest;
   navigateToURL(new URLRequest(e.target.url), "_blank");
}

This is fine too, but we might need to throw an error if the object dispatching the event doesn’t have a url property.

Solution 5: Use a dictionary object to track variables

Dictionary objects in ActionScript are similar to associative arrays but they can accept complex objects, as opposed to strings, as their keys. After creating our button we could push a reference to it into a dictionary and specify a value to associate with it, like this:

var dict:Dictionary = new Dictionary(true);  // use weak references to the objects
var myButton:Sprite = new Sprite;
dict[myButton] = "http://www.mysite.com";
addChild(myButton);

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:MouseEvent):void
{
   navigateToURL(new URLRequest(dict[e.target]), "_blank");
}

This is perfectly workable too.

Solutions 3, 4 and 5 all seem equally good solutions to this problem. There is just one final option, which is to avoid the problem altogether by using separate listeners for each button. But that, of course, is what we were trying to solve in the first place.

Comments on this post

  1. TT says:

    Excellent explanation of the problem, and the possible solutions, none of which are REALLY elegant, probably the dictionary if my favourite.
    Would have been nice if Adobe allowed us to set objects to fire a custom MouseEvent.
     
    Thanks
    Tony

  2. Chris says:

    Thank you, thank you, thank you!
    I just spent two hours trying to figure out  how not to create 22 separate functions for my objects. Nothing came close until this.
    Poifect

  3. Hyperoptix Design says:

    A slightly streamlined approach to #5 is to add properties directly to the Button object:

    myButton.myParam1 = "wine";
    myButton.myParam2 = "brie";
    myButton.addEventListener(MouseEvent.CLICK, Picnic, false, 0, true);
    private function Picnic(evt:MouseEvent):void
    {
       Eat(evt.target.myParam1, evt.target.myParam2);
       trace ("yum");
    }
    
    • Orland Media says:

      This is the same as Solution 4, but note it wouldn’t work with fl.controls.Button as that is not a dynamic class (you can’t arbitrarily add properties to instances).

Leave a Reply to Hyperoptix Design Cancel reply

*