For a contextual meaning of the following you’ll probably want to check out this post by Jesse Warden.
While in the course of building flex applications there almost always comes a time when events just don’t line up properly for whatever reason. For example Global Event Listeners that listen for events that reside in modules that may or may not ever get loaded. Renderers that are use in repeater/list/grid controls that may don’t define our events in their metadata. And also, events that are fired from NonVisual Components that may or may not reside in the Display List.
In all these cases, the difficulty resides in the fact that as a developer you may not have / know about the specific instance of the object you want to add your listener to. In most cases this is OK, because as long as the component is a visual component you can still capture that event by adding your listener above it and using capturing/bubbling. The backstop in this scenario is always the SystemManager where you can add listeners to. However, you have to make sure you bubble or useCapture.
In the case of Global Listeners using the SystemManager is fine. But if we’re nested in three or four screens and we want to have specific instance code run and no more, because we may have two or more instances of the same object on the screen at one time, we don’t want the event getting all the way to the SystemManager, ( and we need to remember to stop the event) A better solution would be to confine our event to specific context. A localized SystemManager of sorts. In the case of using a DataGrid custom renderer and dispatching the event we want to listen for the the event in the document that houses the DataGrid. The document object gives an instance level where we can dispatch and listen for events safely. The document object also makes sense from a coder’s point of view. If I create a Panel called MyPanel and stick a button in it that dispatches an event, and then create two or more instances of MyPanel, what we get is objects named like MyPanel0, MyPanel1, MyPanel2, etc.
To get to the basics of how and where we dispatch our events there are only a few things we need to worry about. First, there is always a point up the Application tree where our events can be captured. Thus, the listener is the only one that needs to know about where the rendezvous point is. The object doing the dispatching doesn’t need to care. The way I think about this is:
Listeners, need a rendezvous point from which they can capture the event. Dispatchers must know how to define their target.
To help me remember how to set these two events properly I created an EventManager class that hides the implementation of dispatching these events from me. I also created an AppEvent class from which I can extend my AppEvent classes. An AppEvent might be something like a UserEvent.LOGIN that is global, or might be specific to different sets of code in my app, like a ProjectEvent.PROJECT_ITEM_CHANGE event. All classes in my app can then import my EventManager class to make sure they can dispatch and subscribe to events in the context they need to.
The Signature on my Event Manager class looks like
static function addEventListener( type:String, listener:Function, rendezvous:EventDispatcher = null, priority:int=0, useWeakReference:Boolean=true ) : voidpublic
public
static function dispatchEvent(event : AppEvent, target:EventDispatcher = null ) : void
Some noticeable things about these two methods because at first look they look pretty harmless, in fact almost useless, and duplicated. First with my addEventListener, the noticeable thing is the rendezvous object that you can pass in and that defaults to Application.application. This allows you to look up and down the object chain to find the exact point where you want to add your listener. The addEventListener always uses useCapture, and the AppEvent class always bubbles so we don’t need to worry about whether the event will be caught or not. ( This is especially good for team scenarios where there’s always a fat fingered member that fancies himself a proctologist )
Second on the dispatchEvent method you’ll see that a target is allowed to get passed in ( it defaults to Application.application, which means Global) , which creates some interesting side effects and a lot of flexibility. Essentially passing in a target allows me to set the target of the event in scenarios where an event might not originate on the target. This is real handy when dispatching events that are defined off data as in ValueObjects, a good example might be when I want to dispatch that UserEvent.LOGIN event and I want my target to be a UserVO object. The actual login event might originate when the data arrives from the server with our user info. I can then propagate the UserVO throughout my system and anybody who cares can do something. Our UserVO might contain permissions and we might need to change our layout, or it might contain user preferences, etc..
The ability to set the target of an event is also a handy technique when using NonVisual components. I pointed out in this article how to create a non visual component that is implemented in mxml. Jesse pointed out in his article that he’s changing his mind about using IMXMLObject, which is a fair argument, but when you think about events as Listener : Rendezvous, Event : Target and then realize you can switch out the rendezvous and target, it doesn’t take Tom Sawyer to paint the white fence white. Using our IMXMLObject and the dispatch event technique that I described above gives us the perfect vehicle to dispatch our events. IMXMLObject interface defines the following method
function initialized(document:Object, id:String):voidpublic
We can then use the document object to dispatch our event into the proper context of where we want to dispatch it. Like I mentioned above, the document object comes in instances of objects we defined. From my previous MyPanel example, a non visual component that is implemented in mxml would get a document named something like MyPanel0 or MyPanel1. As long as we have an instance of MyPanel we can add our listeners to it.
In most cases, I just use the EventManager class to dispatch to the proper context. Rarely do I actually listen through it, but it’s there and can be handy.
I’d like to point out that this is by no means a substitute for the built in event lifecycle. It’s important to use this technique only when is necessary. And be very afraid of global events. Like most things global, Global Variables, Global Constants, Global Singletons, Global Warming, they can be very bad, if you don’t believe me, just ask him.
I’ve included a sample application demonstrating some of these techniques with links below. There’s also some more of my value object techniques I’ve been demonstrating, like setting a value object by passing in a form or xml and some other techniques that make app building simple, but I won’t get into detail on them unless someone asks or I run into one of these.
Here’s the demo: I left traces everywhere so you can download the app and run it.
Comments