VivMedia Code: The EventBroker
May 26th, 2008 Posted in ActionScript, Flex Development, Rich Internet ApplicationsThe EventBroker, in the Vivisecting Media Library, is an Flash Event routing utility that can be used in any ActionScript 3.0 or a Flex Project. The code was developed to be SDK independent so that it does not rely on any Flex classes. The EventBroker is designed to solve the challenge of passing Events to objects that may not be aware of the dispatching item or are not within the parent/child hierarchy of the Event bubble system. In this post I will discuss the Flash Event Model, what limitations are part of the Event Model, how the EventBroker resolves these issues and considerations when using the EventBroker.
Events In the Flash Player
Before I give some examples for when to use the EventBroker, let me take a minute to talk about the Event model within the Flash player and some of the possible limitations that can occur within this model. The Flash Event Model is based around the classic broadcaster/listener pattern. A broadcaster is responsible for dispatching an Event object when a certain action occurs. A basic example is a Flex button and the button’s click Event. When the user clicks on the button in a running Flex application the button dispatches a click Event. This Event is designed to inform other objects that the click action has occurred.
For an item to receive the click Event notification the listening object registers to the Event via the addEventListener() method which is a public method on the button. When the object that wants to listen to the click event, the object calls addEventListener(), passes in the type of Event (ex: MouseEvent.CLICK) and what method to call on the listener when the Event is dispatched (ex: handleClickEvent()).
Going back to our example, let’s say our button is the play button for a video player and we want the video object to “listen” for the click Event. During setup, the video object would register to the play button using addEventListener(). When the user clicks the play button the button looks up all the items that have registered for the click Event and then calls the methods that were provided by the addEventListener() call.
// setup code for our video object public function videoInit():void { playButton.addEventListener(MouseEvent.CLICK, handleClickEvent); } ...
The issue with the above example is that the Flash Event model assumes we know about or have access to the Play Button. In 7-out-of-10 cases this is true, but there are situations where we don’t know where the Event broadcasting object is. The Flash Event model does provide another solution called Event Bubbling. When an object dispatches an Event and the Event is marked to Bubble then the Event will be passed up the parent/child hierarchy until it is either captured or reaches the top of the stack. Let’s say we have a UI where the Play Button is a child of an HBox. If the Video element is outside of the HBox but is within the parent wrapper of the HBox then the video object can register to the HBox for the click Event if the event is set to bubble. When the Click event is dispatched it will be passed to the parent and the parent will then dispatch the event which our video element would receive.
<mycustomui:VideoWidget id="my_video_widget" /> <mx:HBox> <mx:Button id="playButton" /> </mx:HBox>
Challenges with the Flash Event Model
Unfortunately, the click event does not bubble by default and this puts the developer into a conundrum. Does the developer need to add a router method to the parent class that knows of the existence of the play button and therefore can route the play button’s events? Does the developer expose the play button and allow the Video Widget to know where it exists and therefore register to it? In most cases, exposing the play Button is not a bad thing and is the desired solution but there are exceptions.
What happens if the play Button is nested deep with in multiple components? What happens if the play Button is within a Module that may not be loaded at the time of the Video Widget initialization? What if you have n number of modules that all need to know when the video starts playing? What happens if the button is dynamically created and the location is not known until creation and the listener is in a different module or location that may or may not know of the generated button? This is were things start getting really interesting and why I created the Event Broker.
A great solution, one that is used in Cairngorm, is to create a controller that is designed to route events to the proper listener. This solution is called the Observer pattern, where the controller “observers” a dispatcher and then routes the event to the proper location. This is a great pattern but the last examples I saw in Cairngorm required the developer to keep adding events to the controller as they are required. Note: Its been a while since I have worked with Cairngorm so this process may be automated now, it if is let me know. The EventBroker takes the Observer pattern and moves further into a more flexible solution by automating how objects can register and dispatch events.
What is the Event Broker
The EventBroker is a Singleton Facade that enables any object located in the application to register to any other object’s events that are dispatched through the EventBroker. The EventBroker is a completely duel opt-in system which means that both dispatcher and listener must use the EventBroker as the Event routing system for it to work. Also, because the EventBroker is a singleton, you do not instantiate the EventBroker. You just call the static methods subscribe(), broadcast() and unsubscribe() directly off the EventBroker class.
Using our play Button example, let’s walk through how to enable the EventBroker:
// the playButton's parent init() located in // one part of the application public function videoControlInit():void { playButton.addEventListener(MouseEvent.CLICK, handleClickEvent); ... } // used to re-dispatch the click event through the EventBroker protected function handleClickEvent(event:MouseEvent):void { // re-casting the event so that it is specific to the action var playEvent:Event = new Event("PLAY"); EventBroker.broadcast(playEvent); doSomethingElse(); } ... // this code is in another component/module that is completely unaware // of the playButton public function widgetInit():void { EventBroker.subscribe("PLAY", handlePlayEvent) } protected function handlePlayEvent(event:Event):void { // do something ... }
For our example, we first need to register to the play button’s click Event to call the handleClickEvent() method because this is a built-in event within the button type. If we created a custom component or object we could have the component cut out the middle man and directly jump to the EventBroker, but this example shows how to handle pre-existing events.
In the handleClickEvent() we re-cast the event to a new “PLAY” type. We want to do this because the user clicking on the playButton is really calling an action. We could, in theory, re-dispatch the click event to the EventBroker but the listeners would need to know that when a click event is broadcasted the code needs to look at the currentTarget and determine if it is set to “playButton” which would call the play action defined in the listener. Its just easier to define a new Event Type the represents the global action and use this as the Event. To broadcast the event we just call broadcast, very similar to the EventDispatcher’s dispatchEvent() call.
Now, in the Widget’s init() code we call the EventBroker’s subscribe() method and pass in the event type we want to listen to and what method to call. The method provided should always take one argument with the type of Event.
So in our example, this is how the EventBroker works. When the wigetInit() is called it subscribes to the “PLAY” and registers the handlePlayEvent method. The EventBroker stores the type and method closure in a lookup table. When the playButton dispatches the click event the handle method calls EventBroker.broadcast() passing the “PLAY”. The EventBroker looks up any objects that are currently registered to this type and then passes the event to the provided method closure, in effect calling our handlePlayEvent() on the Widget.
By using this process, we have virtually removed all cross-dependancies of the Widget and the playButton and they can be moved around, loaded independently, etc. without breaking our application’s functionality.
When you no longer want to listen to an Event, call the EventBroker’s unsubscribe() method passing in the Event type and the method closure. This then removes the paring from the EventBroker’s table and any further call to broadcast() will be ignored for the unsubscribed object.
Things to consider
The EventBroker is a very helpful tool but should be used sparingly due to some potentially challenging issues. First off, when an Event is broadcasted all subscribers are called. This doesn’t sound harmful unless you consider the following use case. Let’s say that you have four widgets. Each Widget broadcasts a “DATACHANGE” Event when the user changes something within the Widget. All the Widgets subscribe to the “DATACHANGE” event so that they can update their content based on the user’s action. We want all the widgets to be independent and have no direct linking to each other so the EventBroker is used.
Here is the challenge, when Widget Foo dispatches “DATACHANGE”, Widget Foo will also get its own “DATACHANGE” event. This means that the Widgets has to be self-aware enough to know when it is dispatching an event that it is also will receive the event. This means that code has to be added to ignore the incoming event for that specific case. Otherwise you may have some really nasty recursion.
I have looked into having the EventBroker try to ignore self calls but there is not a way for ActionScript to determine the callee of the method is the same instances as the closure in the registered subscribers. Also, there are cases where you may want the callee to call itself. So, just be aware of this issue.
The other thing to consider is that the EventBroker is NOT using the Flash Event model and there for is considered a synchronous action. This means that when the broadcast() method is called Flash calls all the subscribed methods before calling the next line after the original broadcast() call. In our example code this means that all the code in handlePlayEvent() on the Widget will be completed before the doSomethingElse() call is made in the handleClickEvent() for the button.
This is an import difference then the dispatchEvent() call because the Event propagation system in Flash happens after the current synchronous functionality is completed. In 99.9% of cases this is not an issue but there are edge cases that may arise due to this. So keep this in-mind during the debugging process.
Finally, the EventBroker is strong referenced and therefore you should ALWAYS unsubscribe listeners before you delete them. If you do not unsubscribe you will have a strong reference and the Garbage Collection will not clear them from memory. This is very, very important to remember for larger applications that are adding / removing objects dynamically.
So that’s the EventBroker… let me know of you like it or see any issues when using it!
2 Trackback(s)
You must be logged in to post a comment.