views:

2559

answers:

9

I tried doing this:

root.addEventListener("click", 
   function () 
   { 
      navigateToURL(ClickURLRequest,"_self"); 
   });

And it does add the event listener. I like using closures because they work well in this situation,

however, removing the event listener requires a reference to the original function, and since I used an anonymous closure, it does not work, I tried:

   root.removeEventListener("click", 
       function () 
       { 
          navigateToURL(ClickURLRequest,"_self"); 
       });

as well as:

   root.removeEventListener("click", function () {} );

The only way I found it would work was to ditch the anonymous closure and point the event listeners at a pre-existing function:

 function OnClick (e:Event)
 {
     navigateToURL(ClickURLRequest,"_self");
 }

 root.addEventListener("click", OnClick);
 root.removeEventListener("click", OnClick);

Does anyone know a way to use anonymous closures for event handlers while still retaining the ability to remove them?

+2  A: 

You can think of the function() keyword as a constructor, creating a new object (a closure) each time. Therefore, if you create the closure just for as a parameter and don't retain a reference anywhere, there's no way to get a hold of "the same" closure somewhere else.

The obvious solution is what you don't like, defining the function before using it. Of course, it can still be a full closure and not just a 'static-like' function. simply define it in the context you want, and assign it to a local variable.

Javier
Yes, i agree with Javier. If you don't retain the reference, you can't remove that function.
unkiwii
A: 

I'm not sure if this will work but its worth a shot:

root.removeEventListener("click", arguments.callee );

More information about it can be found Flex lang ref

JustFoo
No JustFoo, this doesn't work.
unkiwii
+13  A: 

Here's a generic way of removing event listeners that i have used on production projects


addEventListener
(
    Event.ACTIVATE, 
    function(event:Event):void
    {
     (event.target as EventDispatcher).removeEventListener(event.type, arguments.callee)    
    }
)
Brian Heylin
I second this. Also note that you've made a typo in the example: "argumnts"
hasseg
whoops my bad, it's fixed now ;)
Brian Heylin
Brilliant!Would never have thought of that one myself.
Martin
I use this all the time. It's incredibly useful, although in my opinion it's unnecessarily verbose which, of course, is the fault of ActionScript syntax, the technique is still sound.
macke
I clean it up by using a global function see my answer to this question: http://stackoverflow.com/questions/2476386/as3-event-listener-that-only-fires-once/2494155#2494155
Brian Heylin
A: 

It's not too much different than using a defined function, but maybe this will satisfy your needs. Remember that Functions are first-class objects in ActionScript and you can store and pass them around as variables.

protected function addListener()
{
    m_handler = function(in_event:Event) { removeEventListener(MouseEvent.CLICK, m_handler); m_handler=null}
    addEventListener(MouseEvent.CLICK, m_handler)
}
protected var m_handler:Function
A: 

Just a side note on your code that I came across in the Flex In A Week set of tutorials on the Adobe web site. There, they said you should always use the constants for the event types rather than the string. That way you get typo protection. If you make a typo in the event type string (like say "clse"), your event handler will get registered but of course never invoked. Instead, use Event.CLOSE so the compiler will catch the typo.

Jonathan
A: 

I dont know what you're actually doing but in this particular example perhaps you could have a _clickEnabled global variable.

Then inside the event handler you just check _clickEnabled, and if its false you just return immediately.

Then you can enable and disable the overall event without detaching and reattaching it.

Simon
A: 

I found myself doing this a lot so I tried this. Seems to work fine.

addSelfDestructiveEventListener('roomRenderer', 'complete', trackAction, 'floorChanged');

private function addSelfDestructiveEventListener(listenee:*, event:String, functionToCall:Function, StringArgs:String):void
{
    this[listenee].addEventListener(event, function(event:Event):void
     {
      (event.target as EventDispatcher).removeEventListener(event.type, arguments.callee);
      functionToCall(StringArgs);
     })
}
A: 

I use this sometimes:

var closure:Function = null;
root.addEventListener("click", 
   closure = function () 
   { 
      navigateToURL(ClickURLRequest,"_self"); 
   });

root.removeEventListener("click", closure);
Emerson Cardoso
A: 

As has already been suggested, you may remove the closure from the chain of listeners from within the closure itself. This is done through the use of arguments.callee:

myDispatcher.addEventListener("click", function(event:Event):void
{
    IEventDispatcher(event.target).removeEventListener(event.type, arguments.callee);

    // Whatever else needs doing goes here
});

This will effectively turn the closure into a one-time listener of the event, simply detaching itself once the event has fired. While syntactically verbose, it's an incredibly useful technique for those many events that really only fire once (or that you only care about once) like "creationComplete" in Flex, for instance. I use this all the time when downloading data, since I think having the callback code inline makes it easier to understand. It is like hiding away the asynchronous-ness:

myLoader.addEventListener("complete", function(event:Event):void
{
    /* Even though the load is asynchronous, having the callback code inline
     * like this instead of scattered around makes it easier to understand,
     * in my opinion. */
});

However, if you want to listen to the event multiple times, this will not be very effective for obvious reasons. In that case, you need to store a reference to the closure somewhere. Methods are objects like anything else in ActionScript and can be passed around. Thus, we can alter our code to look like this:

var closure:Function;

myDispatcher.addEventListener("click", function(event:Event):void
{
    closure = arguments.callee;

    // Whatever else needs doing goes here
});

When you need to remove the event listener, use the 'closure' reference, like so:

myDispatcher.removeEventListener("click", closure);

Obviously, this is an abstract example but using closures like this can be pretty useful. They do have drawbacks though, such as being less efficient than named methods. Another drawback is the fact that you actually have to store away a reference to the closure if you ever need it. Care must then be taken to preserve the integrity of that reference, just like with any other variable.

Thus, while the different syntax may have its uses, it's not always the best solution. It's an apples and oranges kind of thing.

macke