December 31, 2006

Removing Anonymous Event Listeners

One of the features that I really like about AS3 is its support of closures. There are many instances when you want to do something to an argument but don't really need or want a full blown class to encapsulate the logic. Lately I've been trying to apply this approach to event listeners and in general it just works. My problem can up when I wanted to have the event listener run only once or stop listening after some point in time. Since the removeEventListener method requires a reference to the listener that you want to remove and the listener in this case is a closure I need a way to get a reference to it. Thankfully closures provide a way to do that but it requires introducing another variable into the mix. Thankfully ActionScript provides another way to get access to the same information arguments.callee.

Arguments is a variable available in every function that is automatically supplied by the system. It is the older style of getting access to the arguments of a function that takes in an arbitrary number. In this case though the callee also gives you access to yourself. In the case of an anonymous event listener, this is all that is needed to remove it as an event listener. Now for the example:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="handleCreationComplete();">
    <mx:Script>
        <![CDATA[
            private function handleCreationComplete():void {
                sampleButton.addEventListener(MouseEvent.CLICK, createClickListener(1));
                sampleButton.addEventListener(MouseEvent.CLICK, createClickListener(2));
            }

            private function createClickListener(clickLimit:int):Function {
                var clickCount:int = 0;
                return function(mouseEvent:MouseEvent):void {
                    clickCount++;
                    trace("Click limit", clickLimit, "registering click", clickCount);
                    if (clickCount >= clickLimit) {
                        trace("Click limit", clickLimit, "no longer listening");
                        mouseEvent.target.removeEventListener(MouseEvent.CLICK, arguments.callee);
                    }
                }
            }
        ]]>
    </mx:Script>
    <mx:Button id="sampleButton" label="Go"/>
</mx:Application>

That gives this output:

Click limit 1 registering click 1
Click limit 1 no longer listening
Click limit 2 registering click 1
Click limit 2 registering click 2
Click limit 2 no longer listening

In this example I'm programmatically adding two anonymous event listeners to my button class. They are anonymous in the fact that the function being executed isn't named. Unlike the creationComplete listener that is attached to the Application, you can only pass around the reference to the function, instead of naming it directly. As I mentioned above, since I want to remove the event listener I can't name it directly. Also since I'm creating multiple instances of it, I can't easily pass the reference to the function around. By instead using arguments.calee I avoid the need to name the function and the need to pass the function reference around.

I'll admit that this is not the most compelling example usage of this technique, but I'm sure you can extrapolate from this.

Tags: as3 event flex