« Architectural Blueprint for Flex Applications (MVCS) | Main | Best Laid Plans »

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

Comments

Nice, that is better than the solution I had come up with. I was creating the function as a var and then referencing the var in the removeEventListener inside of the declaration, like this...
var resultFunc:Function = function(event:ResultEvent):void {
   ......
   op.removeEventListener("result", resultFunc);
}
op.addEventListener("result", resultFunc);
It accomplishes the same thing but if there is already a resource to do this then we should use it. Thanks.
Great, now if you could only answer a related question: in a closure, how would one refer to an enclosing instance? (http://tech.groups.yahoo.com/group/flexcoders/message/60623)
You can't reference "this" directly but you can easily wrap it by having an outer function that returns a function. You can even set it up as a "this" function factory if you don't mind casting. I'm not quite sure what you are trying to do but here is one approach:
package com.example {
    public class Foo {
        private var bar:Function = function(foo:Foo):Function {
            return function():void {
                trace("Foo inside: " + foo);
            }
        }(this);

        public function testBar():void {
            trace("Foo outside: " + this);
            bar();
        }
    }
}
thanks.. its really useful
Great post! But how do we remove the event listeners when using anonymous functions?
var m:MovieClip = new MovieClip();
m.graphics.beginFill(0x000000)
m.graphics.drawCircle(150, 150, 50);
m.graphics.endFill();
addChild(m);
m.addEventListener(MouseEvent.CLICK, function(){trace("hello fun");});
m.removeEventListener(MouseEvent.CLICK, ????? );
What do we change the ????? to in order to remove the listener? Thanks...

Victor:

I presume that the call to m.removeEventListener() happens somewhere else in the code. In that case this technique doesn't work since you somehow need to pass the reference across space. If where you add the listener and where you remove it share some state you could try something like:

var temp:Function = function(){trace("hello fun");};
m.addEventListener(MouseEvent.CLICK, temp);
m.removeEventListener(MouseEvent.CLICK, temp);
Just what I was looking for, thank you very much :-)