Thursday, October 1, 2009

Event Central

by Ingo Gsedl 
10/2009


Vision:
Find a simple solution to dispatch events between components in a large project.


Description: 
I wanted to be able to send a custom event in the form of

dispatchEvent(new Event("eventString"));

between two not closely related components.
My goal was to circumvent all bubbling, to be able to fire the event from any component and listen for it anywhere in the project.


Requirements:
Read this article http://flex.sys-con.com/node/114245 by Danny Patterson on singletons in Flash.
A singleton is a design pattern used in object oriented programming (like Flash and Flex) to define an object that allows only one instance of itself.
Everything else in this article is basic Flex/Flash.


Steps:
An overview:
  • open a new Flex project
  • for the purpose of this article you'll need two more components (call them 'Sender.mxml' and 'Receiver.mxml') and one actionscript class 'EventCentral.as'.
  • Connect Sender and Receiver to the same instance of EventCentral.
  • By the click of a button Sender causes EventCentral to dispatch an Event.
  • Receiver listens to the event and takes action when it occurs.
Do you see it already? Sender and Receiver can be anywhere in the most complex tree of display objects and still talk to each other as if their relationship would be as close as parent/child.

Details:
Start a new Flex project, call the application file what ever you like, and write the following code in it:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:local="*"
    layout="absolute" >


    <!-- this stands for any component sending an event -->
    <local:Sender x="12" y="12" />


    <!-- this stands for any component receiving an event -->
    <local:Receiver x="12" y="42" />
</mx:Application>


On the same level as the application file, add a MXML Cmponent, call it Sender.mxml, and add the following code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">


    <mx:Button label="SEND EVENT" click="sendEvent()"/> 

    <mx:Script>
        <![CDATA[


        // instantiate a singleton (see article link above)
        private var evc:EventCentral=EventCentral.getInstance();
        

        private function sendEvent():void
        {

            evc.fire("testEvent");
        }
    ]]>
    </mx:Script>


</mx:Canvas>

Sender.mxml contains a button 'SEND EVENT'. Hitting it will call a function in EventCentral called fire(), passing a string to it. EventCentral then dispatches an event of the same name. (We will get to EventCentral in a minute).
Receiver.mxml is listening for this event:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete="init()">


    <mx:Label text="{labelText}"/>


    <mx:Script>
        <![CDATA[


        import mx.controls.Label;


        // instantiate singleton
        private var evc:EventCentral=EventCentral.getInstance();
        [Bindable] 
        private var labelText:String="...press button."
        private var counter:int=0;


        private function init():void
        {
            setListener();
        }


        private function setListener():void 
        {
            // listen to event from singleton
            evc.addEventListener("testEvent", onTestEvent);
        }


        private function onTestEvent(e:Event):void

            counter++;
            labelText=counter.toString()+". test event received."
        }


    ]]>
    </mx:Script>
</mx:Canvas>


EventCentral is the link between Sender and Receiver
EventCentral is a singleton. There is always only one instance of it. When Sender writes to it, Receiver can read it. Both are talking to the same instance. 
When Sender calls EventCentral's public function fire(eventString), EventCentral uses eventString to dispatch an Event by the same name. 

(Tip: It's up to you to keep track of events you are using - their name, the sending and receiving components)


package{
    import mx.core.UIComponent;
    import flash.events.Event;


    public class EventCentral extends UIComponent
    { 

        // variables for singleton instantiation
        private static var instance:EventCentral;
        private static var allowInit:Boolean;


        // singleton instantiation

        public static function getInstance():EventCentral
        {
            if (instance==null)
            {
                allowInit=true;
                instance=new EventCentral();
                allowInit=false;
            }
            return instance;

        }

        public function EventCentral()
        {
            if(!allowInit)
            {
                throw new Error("Error: Instantiation of EventCentral singleton failed.");
            }
        }


        // this function takes any string and sends out an event by the same name
        public function fire(eventString:String):void
        {
            dispatchEvent(new Event(eventString));
        }
    }
}



This is a straightforward solution, decoupling event flow from the display objects structure. 

Please see also the next blog about 'Event Central caveats'.It lays out a few points to be aware of when using Event Central