sean
sean

Reputation:

Is there a way to remove unknown event listeners from objects?

I want to have a reusable button which can be registered for one of many different callbacks, determined by an external source. When a new callback is set, I want to remove the old. I also want to be able to clear the callback externally at any time.

public function registerButtonCallback(function:Function):void
{
  clearButtonCallback();

  button.addEventListener(MouseEvent.CLICK, function, false, 0, true);
}

public function clearButtonCallback():void
{
  if (button.hasEventListener(MouseEvent.CLICK) == true)
  {
    // do something to remove that listener
  }
}

I've seen suggestions on here to use "arguments.callee" within the callback, but I don't want to have that functionality tied to the callback - for example, I might want to be able to click the button twice.

Suggestions?

Upvotes: 6

Views: 25402

Answers (8)

M.Raju
M.Raju

Reputation: 21

private function callFunction(function:Function):void
{
     checkObject();
     obj.addEventListener(MouseEvent.CLICK,function);
}

private function checkObject():void
{
    if(obj.hasEventListener(MouseEvent.CLICK))
   {
      //here remove that objects
   }
}

Upvotes: 0

Daniel Szmulewicz
Daniel Szmulewicz

Reputation: 3961

The following doesn't address the fundamental issue of removing unknown event listeners, but if what you need is disabling all mouse related events, including unknown ones, just use: mouseEnabled=false on your event target.

More good stuff here: http://www.thoughtprocessinteractive.com/blog/the-power-and-genius-of-mousechildren-and-mouseenabled

Upvotes: 0

I written a subclass called EventCurb for that purpose, see my blog here or paste below.

package
{
   import flash.events.EventDispatcher;
   import flash.utils.Dictionary;
   /**
    * ...
    * @author Thomas James Thorstensson
    * @version 1.0.1
    */
   public class EventCurb extends EventDispatcher
   {
      private static var instance:EventCurb= new EventCurb();
      private var objDict:Dictionary = new Dictionary(true);
      private var _listener:Function;
      private var objArr:Array;
      private var obj:Object;

      public function EventCurb() {
         if( instance ) throw new Error( "Singleton and can only be accessed through Singleton.getInstance()" );
      }

      public static function getInstance():EventCurb {
         return instance;
      }

      override public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
      {
         super.addEventListener(type, listener, useCapture, priority, useWeakReference);
      }

      override public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
      {
         super.removeEventListener(type, listener, useCapture);
      }

      public function addListener(o:EventDispatcher, type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
         // the object as key for an array of its event types
         if (objDict[o] == null)  objArr = objDict[o] = [];
         for (var i:int = 0; i <  objArr.length; i++) {
            if ( objArr[i].type == type)
            trace ("_______object already has this listener not adding!")
            return
         }
         obj = { type:type, listener:listener }
         objArr.push(obj);
         o.addEventListener(type, listener, useCapture, priority, useWeakReference);
      }

      public function removeListener(o:EventDispatcher, type:String, listener:Function, useCapture:Boolean = false):void {
         // if the object has listeners (ie exists in dictionary)
         if (objDict[o] as Array !== null) {
            var tmpArr:Array = [];
            tmpArr = objDict[o] as Array;
            for (var i:int = 0; i < tmpArr.length; i++) {
               if (tmpArr[i].type == type) objArr.splice(i);
            }

            o.removeEventListener(type, listener, useCapture);
            if (tmpArr.length == 0) {
               delete objDict[o]
            }
         }else {
            trace("_______object has no listeners");
         }
      }

      /**
       * If object has listeners, returns an Array which can be accessed
       * as array[index].type,array[index].listeners
       * @param   o
       * @return Array
       */
      public function getListeners(o:EventDispatcher):Array{
         if (objDict[o] as Array !== null) {
            var tmpArr:Array = [];
            tmpArr = objDict[o] as Array;
            // forget trying to trace out the function name we use the function literal...
            for (var i:int = 0; i < tmpArr.length; i++) {
               trace("_______object " + o + " has event types: " + tmpArr[i].type +" with listener: " + tmpArr[i].listener);
            }
            return tmpArr

         }else {
            trace("_______object has no listeners");
            return null
         }

      }

      public function removeAllListeners(o:EventDispatcher, cap:Boolean = false):void {
         if (objDict[o] as Array !== null) {
            var tmpArr:Array = [];
            tmpArr = objDict[o] as Array;
            for (var i:int = 0; i < tmpArr.length; i++) {
               o.removeEventListener(tmpArr[i].type, tmpArr[i].listener, cap);
            }
            for (var p:int = 0; p < tmpArr.length; p++) {
               objArr.splice(p);
            }

            if (tmpArr.length == 0) {
               delete objDict[o]
            }
         }else {
            trace("_______object has no listeners");
         }
      }
   }
}

Upvotes: 2

Triynko
Triynko

Reputation: 19204

No. You need to hold a reference to the listener in order to remove it. Unless you store a reference to the listener function in advance, there is no documented public method available to retrieve such a reference from an EventDispatcher.

addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
dispatchEvent(event:Event):Boolean
hasEventListener(type:String):Boolean
removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
willTrigger(type:String):Boolean 

As you can see, there are two methods to tell you whether a type of event has a listener registered or one of its parents has a listener registered, but none of those methods actually return a list of registered listeners.

Now please go harass Adobe for writing such a useless API. Basically, they give you the ability to know "whether" the event flow has changed, but they give you no way of doing anything with that information!

Upvotes: 4

Jonathan Dumaine
Jonathan Dumaine

Reputation: 5756

Something I like to do is use a dynamic Global class and add a quick reference to the listener function inline. This is presuming you like to have the listener function in the addEventListener method like I do. This way, you can use removeEventListener inside the addEventListener :)

Try this out:

package {

import flash.display.Sprite;
import flash.events.Event;
import flash.text.TextField;

[SWF(width="750", height="400", backgroundColor="0xcdcdcd")]
public class TestProject extends Sprite
{   
    public function TestProject()
    {
        addEventListener(Event.ADDED_TO_STAGE, Global['addStageEvent'] = function():void {
            var i:uint = 0;
            //How about an eventlistener inside an eventListener?
            addEventListener(Event.ENTER_FRAME, Global['someEvent'] = function():void {
                //Let's make some text fields
                var t:TextField = new TextField();
                    t.text = String(i);
                    t.x = stage.stageWidth*Math.random();
                    t.y = stage.stageHeight*Math.random();
                addChild(t);
                i++;
                trace(i);
                //How many text fields to we want?
                if(i >= 50) {
                    //Time to stop making textFields
                    removeEventListener(Event.ENTER_FRAME, Global['someEvent']);
                    //make sure we don't have any event listeners
                    trace("hasEventListener(Event.ENTER_FRAME) = "+hasEventListener(Event.ENTER_FRAME));    
                }
            });

            //Get rid of the listener
            removeEventListener(Event.ADDED_TO_STAGE, Global['addStageEvent']);
            trace('hasEventListener(Event.ADDED_TO_STAGE) = '+hasEventListener(Event.ADDED_TO_STAGE));

        });
    }

}   

}

// looky here! This is the important bit dynamic class Global {}

The secret is the dynamic class Global. With that you can dynamically add properties in at runtime.

Upvotes: 1

geraldalewis
geraldalewis

Reputation: 856

Store the listener as a prop. When another event is added, check to see if the listener exists, and if it does, call removeEventListener.

Alternatively, override the addEventListener method of you button. When addEventListener is called, store the closure before adding it to the event in a Dictionary object. When addEventListener is called again, remove it:


var listeners:Dictionary = new Dictionary();

override public function addEventListener( type : String, listener : Function, useCapture : Boolean = false, priority : int = 0, useWeakReference : Boolean = false) : void {

  if( listeners[ type ] ) {

     if( listeners[ type ] [ useCapture ] {

        //snip... etc: check for existence of the listener

        removeEventListener( type, listeners[ type ] [ useCapture ], useCapture );

        listeners[ type ] [ useCapture ] = null;

        //clean up: if no listeners of this type exist, remove the dictionary key for the type, etc...

     }

  }

  listeners[ type ] [ useCapture ] = listener;

  super.addEventListener( type, listener, useCapture, priority, useWeakReference );

};

Upvotes: 3

Simon
Simon

Reputation: 80889

I am presuming that you want only one callback function at any given time. If that's teh case then why not have a single callback function associated with the click event on the button which itself called a function and have that function be settable...

<mx:Button click="doCallback()" .../>

public var onClickFunction:Function = null;
private function doCallback():void
{
    if (onClickFunction != null)
    {
        onClickFunction(); // optionally you can pass some parameters in here if you match the signature of your callback
    }
}

A consumer of your control which houses your button would set the onClickFunction with the appropriate function. In fact you could set it as often as you liked.

If you wanted to go one step further you could subclass the AS3 Button class and wrap all of this inside it.

Upvotes: 8

Related Questions