In many respects, I haven’t been overly impressed with the F04 (Flash MX 2004) update, though I’m reserving my final opinion for a month or so, until I’ve really dug deeply into it. Tonight though, chatting with Phil Chung, I ran into something I’m really impressed by: eventDispatcher
I thought I’d should provide a run-down of it here, as it appears well thought out, and very flexible (though I’ll probably write an extension to it to fulfill my short wishlist of missing features). Whoever thought this one up at Macromedia should be proud.
Read on…
There are two aspects to using eventDispatcher: dispatching (broadcast) events from an object, and subscribing to those events.
Using EventDispatcher in your own classes to dispatch events is simple: use the mx.events.EventDispatcher class’s initialize() method in the class constructor to set up your instances as dispatchers, then simply call dispatchEvent to send out an event.
For example, the following class will dispatch an event every time its sendMessage() method is called.
// in MessageBroadcaster.as class MessageBroadcaster { // we have to declare the dispatchEvent, // addEventListener and removeEventListener // methods that EventDispatcher sets up: function dispatchEvent() {}; function addEventListener() {}; function removeEventListener() {}; function MessageBroadcaster() { // set instances up as dispatchers: mx.events.EventDispatcher.initialize(this); } function sendMessage(p_msgtxt:String):Void { // set up the event object: var eventObj:Object={target:this,type:"message"} eventObj.msgtxt = p_msgtxt; // dispatch the event dispatchEvent(eventObj); } } |
Notice the construction of the object that it passes as it’s parameter. This “event object” must include a target variable with a reference to the object that dispatched the event, and a type variable that indicates the type of event (ex. “click”, “change”, “update”). You can also include other variables containing data specific to the event (such as the msgtext variable in the example). This is a very flexible model in itself.
Now the most impressive part comes in when you look at the options to subscribe objects to events. Macromedia has provided three ways of doing so, and they’re all very useful:
The first method acts almost exactly like addListener() in AS1.0 – you subscribe an object to listen to an event, and the method corresponding to the type of that event will be triggered when they occur. For example:
// in the FLA: // instantiate a MessageBroadcaster: myMB = new MessageBroadcaster(); // define an object to listen to myMB: myObj = {}; myObj.message = function(p_eventObj) { trace("myObj received a message: "+p_eventObj.msgtxt); } // subscribe myObj to myMB: myMB.addEventListener("message",myObj); // trigger the event: myMB.sendMessage("I love everyone!"); |
The second approach is a nice twist on the above. Instead of having to define a function for each event you choose to subscribe to, you can define one function called “handleEvent” that will receive every event, and can sort them out based on the type and target data passed with the event object. This means you don’t have to create a set of otherwise useless objects to handle events. For example, in the following example, the message() function of myObj will not be triggered, because the handleEvent function is present.
// in a FLA: // instantiate a MessageBroadcaster: myMB = new MessageBroadcaster(); // define an object to listen to myMB: myObj = {}; myObj.message = function(p_eventObj) { trace("myObj received a message: "+p_eventObj.msgtxt); } myObj.handleEvent = function(p_eventObj) { trace(p_eventObj.type+" from: "+p_eventObj.target); } // subscribe myObj to myMB: myMB.addEventListener("message",myObj); // trigger the event: myMB.sendMessage("I love everyone!"); |
Very nice! But we have a third option, which I think is a stroke of genius. Instead of subscribing an object to receive events, you can subscribe a function or a method. This means that you can do brilliant things like assigning different methods in a class to deal with specific events from different objects. Let’s look at an example:
// in a FLA: // instantiate a couple of MessageBroadcasters: myMB1 = new MessageBroadcaster(); myMB2 = new MessageBroadcaster(); // define an object to listen to myMB: myObj = {}; myObj.message1 = function(p_eventObj) { trace("Message from myMB1: "+p_eventObj.msgtxt); } myObj.message2 = function(p_eventObj) { trace("Message from myMB2: "+p_eventObj.msgtxt); } // subscribe myObj to both MessageBroadcasters: myMB1.addEventListener("message",myObj.message1); myMB2.addEventListener("message",myObj.message2); // trigger the events: myMB1.sendMessage("I love everyone!"); myMB2.sendMessage("I hate everyone!"); |
Lovely! That almost made my day. For more information on this topic, please visit Phil’s blog.
In what scope do the handlers in the last example run?
What I would have liked to see (maybe I’m asking too much here) is being able to specify a method and object, something like:
myMB1.addEventListener(“message”,myObj,”message1″);
That would have the flexibility of a listener and the ease of use of a changeHandler.
I actually have a version of EventBroadcaster that does what you are asking. It is written in AS1.0. I havent had time to port it or post it. i’ll try and put it somewhere as a sample
Guess I’ll post it here ;). It allows you to do what you asked, and also pass additional arguments to the method. Honestly, it should encapsulate the additional args into the event object and each receiving method should require a specific type of event. However, I’m lazy and its easier this way.
/**
@class: Event
*/
function Event(sType){
this.type = sType;
this.target;
}
/**
******************************************
@class (SINGLETON): EventDispatcher Class
PUBLIC METHODS
initialize
PRIVATE METHODS
$dispatchEvent
$addEventListener
$removeEventListener
$removeAllEventListeners
$removeAllListeners
******************************************
*/
// IMPORTS
ASRequireClass(“com.XXX.events.Event”);
/**
@class (SINGLETON): EventDispatcher
WARNING :: do not create an instance from the constructor
*/
function EventDispatcher(){}
/**
@method (PUBLIC): initialize
@param (o):
– implements object with EventDispatcher class
*/
EventDispatcher.initialize = function(o){
var i;
if (EventDispatcher._$instance == undefined){
EventDispatcher._$instance = new EventDispatcher();
}
i = EventDispatcher._$instance;
o._$listeners = {};
o.dispatchEvent = i.$dispatchEvent;
o.addEventListener = i.$addEventListener;
o.removeEventListener = i.$removeEventListener;
o.$removeAllEventListeners = i.$removeAllEventListeners;
o.$removeAllListeners = i.$removeAllListeners
}
/**
@method (PRIVATE): $dispatchEvent
*/
EventDispatcher.prototype.$dispatchEvent = function(){
var event= arguments[0];
var list = this._$listeners[event.type];
var listener, each;
event.target = this;
// call handler of obj
this[event.type + “Handler”].apply(this, arguments);
if (list != undefined){
for (each in list){
listener = list[each]._receiver;
method = list[each]._method;
// call handler of listener
if (listener.handleEvent != undefined){
listener[“handleEvent”].apply(listener, arguments);
} else if (typeof (listener) == “function”){
listener.apply(null, arguments);
} else {
listener[method].apply(listener, arguments);
}
}
}
}
/**
@method (PRIVATE): $addEventListener
@param : sEvent – event to listen for
@param : o – object to assign listeners to
@param : [sMethod] – optional handler to handle event
*/
EventDispatcher.prototype.$addEventListener = function(sEvent, o, sMethod){
var handler = {
_receiver : o,
_method : ((sMethod == undefined) ? sEvent : sMethod)
}
if (this._$listeners[sEvent] == undefined) {
this._$listeners[sEvent] = [];
}
this.removeListener(sEvent, handler);
this._$listeners[sEvent].push(handler);
return (true);
}
/**
@method (PRIVATE): $removeEventListener
@param : sEvent – event to listen for
@param : o – object to assign listeners to
*/
EventDispatcher.prototype.$removeEventListener = function(sEvent, o){
var list= this._$listeners[sEvent];
var i;
if (list != undefined){
i= list.length;
while(i–){
if(list[i]._receiver == o._receiver && list[i]._method == o._method){
list.splice(i, 1);
return (true);
}
}
return (false);
} else {
return (true);
}
}
/**
@method (PRIVATE): $removeAllEventListeners
@param : sEvent – event to listen for
*/
EventDispatcher.prototype.$removeAllEventListeners = function(sEvent){
var list= this._$listeners[sEvent];
if (list != undefined){
this._$listeners[sEvent] = [];
}
return (true);
}
/**
@method (PRIVATE): $removeAllListeners
*/
EventDispatcher.prototype.$removeAllListeners = function(sEvent){
this._$listeners = {};
return (true);
}
whoops.. the remove alls shouldnt have params
“Whoever thought this one up at Macromedia should be proud.”
It was someone at w3c actually. EventDispatcher is based loosely on the w3c dom level2 event model
Hi Grant,
nice examples. In fact, EventDispatcher is even more powerful, because it allows you to trigger whole chains of events (using dispatchQueue), which is used extensively in the v2 components, afaik.
Imagine myObj passes an event on to another eventhandler (or multiple eventhandlers), EventDispatcher makes sure, that the whole chain is handled, before the event is being dispatched to the next listener of myMB.
Please correct me, if I´m wrong about this…
Cheers
Florian
I’m trying to build a paging class in AS 2.0 – and i need some help figuring out how i send/catch an event when buttons are pressed:
My problem is i can’t send the event dispatchEvent({type:”Back”, page:currentPage-1}); ect.
Any ideas/suggestions?
my code is the following:
import mx.events.EventDispatcher;
class Paging {
/* Init EventDispatcher */
private var addEventListener:Function;
private var removeEventListener:Function;
private var dispatchEvent:Function;
//
//private var targetMC:MovieClip;
private var pagingMC:MovieClip;
private var Back:MovieClip;
private var Forward:MovieClip;
private var currentPage:Number;
private var totalPages:Number;
private var xSpacing:Number = 3;
private var numDepth:Number = 0;
private var clipDepth:Number = 0;
private var butWidth:Number;
private var callBack:Object;
private var targetMC:MovieClip;
/*Public methods*/
public function Paging(mcTarget:MovieClip) {
EventDispatcher.initialize(this);
targetMC = mcTarget;
}
public function update(current, total) {
/*Init*/
numDepth = 0;
totalPages = total;
currentPage = current;
/*Back/forward navigation attach*/
backButton();
pageButton();
forwardButton();
}
/*Private methods*/
private function pieceX(row) {
return row*(xSpacing+butWidth);
}
private function backButton() {
Back = targetMC.attachMovie(“BackButton”, “Back”, numDepth++);
Back.currentPage = currentPage;
butWidth = Back._width;
//var callBacks = callBack;
if (currentPage>1) {
Back.onRelease = function() {
dispatchEvent({type:”Back”, page:currentPage-1});
};
} else {
Back._alpha = 50;
}
}
private function forwardButton() {
Forward = targetMC.attachMovie(“ForwardButton”, “Forward”, numDepth++, {_x:pieceX(numDepth)});
Forward.currentPage = currentPage;
var callBack = this;
if (currentPage<totalPages) {
Forward.onRelease = function() {
dispatchEvent({type:”Forward”, page:currentPage+1});
};
} else {
Forward._alpha = 50;
}
}
function test() {
trace(“OKI”);
dispatchEvent({type:”Forward”, page:currentPage+1});
}
private function pageButton() {
for (var i = 1; i<=totalPages; i++) {
if (i == currentPage) {
var Pages = targetMC.attachMovie(“PageButtonSel”, “Selected”+i, numDepth++, {_x:pieceX(numDepth), page:i});
} else {
var Pages = targetMC.attachMovie(“PageButton”, “Selected”+i, numDepth++, {_x:pieceX(numDepth), page:i});
Pages.onRelease = function() {
dispatchEvent({type:”Forward”, page:currentPage+1});
};
}
}
}
}
Any ideas how to best make it work?
//Demon3D
myFirstRIA: one step closer once more 😉
I’m starting to get close to finishing the guestbook i have planned for myFirstRIA. I’m starting to get the hang of the ‘eventdispatching’ and i’m starting to like it too! Wanna read more stuff from the guru’s? Check out this…
Grant,
I tried your script, created messageBroadcaster.as on my local machine, and I get an error when I publish 7/AS 2.0. It is pulling from an external 2.0 script, is there something I am missing?
which is …
**Error** C:\Documents and Settings\Patrick Lemiuex\Desktop\messageBroadcaster.as: Line 2: Classes may only be defined in external ActionScript 2.0 class scripts.
class MessageBroadcaster {
Total ActionScript Errors: 1 Reported Errors: 1
I used Grant’s example no. 2 and I can send messages to my handler ok except in one case.
Inside my custom class, if I try to call sendMessage from within the onData handler of my XML object, it does not get called. However, I verifed with trace that the onData handler itself is being called.
Any ideas ?
Great work grant, event broadcasters have never been my strong point, but your examples helped me understand. I just need to fully understand the conecpt of the event proxy thing now, and i’m away. Don’t suppose you fancy doing an equalling easy-to-understand-while-still-very-informative post about that one too do you??? 😉
Thanks for the info gSkinner. May not be a good way of using it but made this which is prob breaking a few good programming rules;
in the fla
import SendAndRecieve;
var send:SendAndRecieve = new SendAndRecieve();
var recieve:SendAndRecieve=new SendAndRecieve();
recieve.listenTo(send,”myEvent”);
send.onMyEvent(“myEvent”, function(obj:Object):Void{trace(obj)} ,”here is my silly test”);
in the class
class SendAndRecieve {
public var dispatchEvent:Function;
public var addEventListener:Function;
public var removeEventListener:Function;
public var myListener:Object=new Object();
public function SendAndRecieve() {
trace(“SendAndRecieve instance created “);
initEventDispatcher();
}
public function initEventDispatcher():Void{
// set instances up as dispatchers:
mx.events.EventDispatcher.initialize(this);
}
public function onMyEvent(myEvent:String,myfunc:Function,myParam:Object):Void {
// set up the event object:
var eventObj:Object={target:this,type:myEvent,func:myfunc,param:myParam};
// dispatch the event
dispatchEvent(eventObj);
}
public function listenTo(obj:Object,s:String){
// add listener
obj.addEventListener(“myEvent”,myListener);
// setup function
myListener[s]=function(eventObj:Object):Void{
eventObj.func(eventObj.param);
};
/*
//rather dangerous due to overwrite built in methods but can use this directly
obj.addEventListener(“myEvent”,this);
this[s]=function(eventObj:Object):Void{
eventObj.func(eventObj.param);
};
*/
}
}
Actually found this mod to what I was doing more usefull:
public function listenTo(obj:Object,s:String){
thisSendAndRecieve:SendAndRecieve=this
// add listener
obj.addEventListener(“myEvent”,myListener);
// setup function
myListener[s]=function(eventObj:Object):Void{
eventObj.func(eventObj,thisSendAndRecieve);
};
I don’t understand how you can broadcast an event from one object/movieclip to an entirely different movieclip that is listening.
In the above examples, the broadcaster and listener always exist in the same movieclip/object. What’s the point in that? I want different movieclips to react when one movieclip broadcasts an event.
bennie- I usually just scope the EventBroadcaster object globaly to solve this problem, however it’s best if you can avoid having to do it by having all the event interactions take place in wrapper classes, then in the handler functions call appropriate methods on the movie clip (or clips), using a variable to reference the actual clip on the stage.
Quick Example:
function DescriptionViewClass( ){
var mc = _level0.descriptionClip;
this.onDescriptionLoaded = function(description){
// assuming display description is on the
mc.displayDescription( description );
};
eventBroadcaster.addListener(“DescriptionLoaded”, this);
}
The code here doesn’t render well, so if anyone is interested in seeing my AS1 broadcaster class, send me a mail.
Newbee question: I´m a little bit confused about the second approach of the example, when you say “the message() function of “myObj” will not be triggered, because the handleEvent function is present. “.
When I test the movie I get:
message from: [object Object]
myObj received a message: I love everyone!
And i just copied and pasted the code. Wasn´t the message function NOT supposed to be triggered?