The V2 Component Event Model Part 1- EventDispatcher
September 24, 2003

After spending a bit of time familiarizing myself with the V2 component event "model", I thought I would write down some observations just to clarify this for myself (and hopefully others too).

The V2 component event model is based on listeners instead of callbacks (which was the foundation for V1 components).

With V1 components (in Flash MX), you would use something like "setClickHandler" to "register" a single function to "callback" when (in this case), the component was clicked on. You could specify the object that contained that function, or if you didn't, it automatically looked for the function in the _parent scope of the component. This was fine and dandy, but it was a little limited because you had to have one central code block that was waiting for that event to fire and send messages to all of the appropriate destinations.

Enter V2 component architecture. Being based on listeners instead of callbacks, this allows for a more robust way to handling events. Now you can register multiple objects to "listen" for an event and each object can "do it's thang" when the event it's listening for is "dispatched".

Flash MX actually came with a built in mechanism for implementing listeners called ASBroadcaster. This was an internal (and undocumented) object that allowed you to quickly set up a broadcasting object and also provided a way to add/remove listening objects to it as well as broadcast events to those objects. However, there was a little bug discovered in it that resulted in "only every second eventhandler being called, if listeners are removed in the eventhandler". There were also several AS based listener event models that were written to try to overcome this deficiency.

ASBroadcaster still exists in FP7 and it seems to fix this bug when your movie is exported as a F7 swf. Note that you now have to use "AsBroadcaster" (lower case "s") in F7.

I said all that to say this...

V2 components implement a listener model, but do it without using AsBroadcaster. There is a class called "EventDispatcher" (core/mx/events) which is used to add this broadcasting (called dispatching in F7) functionality to UIObject (the base class for all components).

Essentially, it works in much the same way that AsBroadcaster does:

1) You set up an object as a dispatcher by running "EventDispatcher.initialize(dispatchingObj)".

2) You can add or remove listeners ("dispatchingObj.addEventListener('event', listeningObj)" and "removeEventListener(listeningObj)").

3) You can broadcast/dispatch events to those listeners ("dispatchingObj.dispatchEvent(eventObj)").

However, there are a few differences to be aware of:

1) It appears as though EventDispatcher "categorizes" it's listeners based on the type of event(s) it is registered to listen for. Whereas AsBroadcaster (as far as I know) just stuck all of it's listeners into a single "listeners" array (and broadcast ALL events to ALL listeners - if a listener didn't have a particular handler defined, it silently failed), EventDispatcher actually creates seperate arrays for each unique event, so that when a particular event is dispatched, it looks only in the array associated with that event. A subtle but important difference.

2) When dispatching via EventDispatcher, instead of passing the event and a bunch of parameters, you now pass a single "event object" that contains any properties that you want to pass to your listening objects. A couple of important properties to note in this object:

"type" - this is a required property that allows you to indicate which event you want to dispatch.
"target" - this is an optional property that allows you to pass a reference to an object into the listener's handler so that you can act upon that object. If you leave this out, it will default to the dispatching object.

3) Before your dispatcher passes the event being called onto it's listeners, it actually will call a method internal to itself (kinda like a "click handler" in V1 components). This is cool because it allows you to have the dispatcher do something on itself if you ever want it to (thereby emulating the functionality of the callback model in V1 components). In order to make use of this functionality, you need to create a method on your dispatching object called [eventName + "Handler"]. So if you have a "click" event, you would call it "clickHandler". It will pass the event object mentioned in #2.

4) The dispatcher will accept objects, movieclips or plain functions as listeners. Depending on which of these you use as your listener(s), there will be slight differences in how you set up your listeners.

If you set up your listener as a movieclip or an object, there are 2 ways in which you can set up your listener to capture the dispatched event: a) Create a method on that movieclip or object that is the same name as the event that the movieclip or object is listening for. So if you are listening for a "click" event, you can set up "myObj.click = function(eventObj) { };". An "eventObj" parameter is passed to the method. b) Create a method called "handleEvent(eventObj)" on your movieclip or object. This then becomes a generic "interface" through which your listener can handle event dispatches. All event dispatches can come through this method. This means that you must look at "eventObj.type" in order to find out which event has been triggered and run the appropriate code.

So what does this all mean when working with components? Well, here is a simple example. Let's say you have a button component on stage called "submitBtn". You could set up a listening object by doing this:

// Listening object contains a function with the same name as 
// the dispatched event
var listeningObj:Object = new Object();
listeningObj.click = function(eventObj:Object):Void {
 trace("submitted by " + eventObj.target);
}
submitBtn.addEventListener("click",listeningObj);
Or this:
// Use a central "handleEvent" function to mitigate all events
var listeningObj:Object = new Object();
listeningObj.handleEvent = function(eventObj:Object):Void {
 if (eventObj.type == "click") {
  trace("submitted by " + eventObj.target);
 }
}
submitBtn.addEventListener("click",listeningObj);
Or you could just do it the old V1 component way like this:
// Old school click handler style
submitBtn.clickHandler = function():Void {
 trace("submitted by " + this);
}
One other cool thing that I didn't realize until Grant and I were talking in the wee hours of the morning. As I mentioned above, you can set up a plain function as a listener. At first, I kinda brushed this off and didn't think much of it. However, here's where this little feature becomes kinda handy...

Let's say you want to have the ability to listen for the same event from different dispatching objects. If you set up your listening object, and then set up a "click" method on it, the only way to distinguish which dispatcher sent out the event is to look at the "target" property (mentioned above) of the event object passed to that function and run and if statement to do different stuff for different dispatchers. However, if you set up a series of plain functions as listeners, you can actually specify which one you want to execute for any given dispatching event.

// Use plain functions as listeners and specify which one
// you want to listen for any given event from any given
// dispatcher
function handleSubmit = function(eventObj:Object):Void {
 trace("submitted!");
}
function handleReset = function(eventObj:Object):Void {
 trace("reset!");
}
submitBtn.addEventListener("click",handleSubmit);
resetBtn.addEventListener("click",handleReset);
Of course, you could also do this by setting up a new object for every dispatcher, but this works too.

Whew. In future installments, i'll be looking at UIEventDispatcher and LowLevelEvents. Stay tuned...

Posted by philter at September 24, 2003 11:30 PM

Comments Disabled