21 January 2008

How to Extend "Event", Once and For All

I've seen this information spread out in a bunch of different places online, but not in one place. So here you go:

The event-dispatching functionality in ActionScript 3.0 is wonderful. It creates a new paradigm for programming, one focused less on branching flowcharts and more on organic interactivity.

I'm assuming anyone reading this is familiar with how to use native events (e.g., addEventListener(Event.ENTER_FRAME, update); and all that good stuff). But there are a few "gotchas" when it comes to making custom events. There are also some good practices that are a bit obscure.

Say you want to make a special type of event that indicates an index number, in addition to the normal information (type, target, etc.). You might think that all you had to do is this:

This is the big one. If you don't override clone, then there is no guarantee that listeners will receive the right kind of event. This method is used during dispatching to ensure that all listeners receive the same (i.e., unchanged) information. By default, Event.clone() simply returns a basic Event object, so if you don't override clone to return your custom event type, listeners may not be able to access your custom event data, and you will probably get type errors and null pointer exceptions.

This one is not as important, since toString is only used for debugging. (Or should only be used for debugging, anyway.) But the default method will not report the type of the event, and will also not report custom data. It'll just show something like [Event type="select" bubbles=true cancelable=false eventPhase=2].

For this reason, it's a good idea to override the default method. Fortunately, there is a method called formatToString that makes it much easier to generate a standardized event string. Al arguments passed are strings. The first one should be the name of the custom event class, and the rest should be the names of all fields that you want reported in the string. Here's an example:

This will produce a much more informative string, something like [IndexEvent type="select" index=12 bubbles=true cancelable=false eventPhase=2]. (Sometimes I even omit some of the base Event fields. For example, if my custom event's constructor doesn't allow for cancelable events, I might leave "cancelable" out of the formatToString call.)

Tip #3. You should include static constants for event types.

The base Event class and all of Adobe's Event subclasses do this, e.g., Event.ENTER_FRAME, TextEvent.LINK, etc. The main reason for this is so that programmers can catch typos at compile-time rather than run-time. Typing addEventListener("enerFrame", update) won't generate a compile-time error, or even a run-time error -- your code will just fail silently. But addEventListener(Event.ENER_FRAME, update) will flag a compile-time error. For this reason, it's a good idea to follow in Adobe's footsteps and make your own constants:

The ViewStack object's selectedIndex field will now auto-update whenever ctrl dispatches a select event (per the Bindable metadata element -- note that multiple such elements may precede a property, if there are different trypes of event that signal the value changing).

Tip #5. You don't always have to extend flash.events.Event.

Sometimes it's fine to use a plain old Event instance! There are plenty of generic event types (e.g., Event.CANCEL) associated with it, and often that's all you really need. Even if you want a type that doesn't have a constant, you don't have to create a custom event type for it. The constant can be housed elsewhere. For example:

As long as the ASDoc comment with the @eventType attribute is included, Flex will autocomplete for the start event as well.

As a general rule of thumb, if you don't need the event object to carry extra data, you don't need to create a custom event class. Even if you do, there may be another subclass already in existence that suits your needs (e.g., TextEvent, ErrorEvent, etc.). On the other hand, though, if you do create a custom class with no added fields, it may come in handy down the road if you realize that you do need added fields after all. And it might also be handy if you want to restrict how default fields like type, bubbles, and cancelable are set.

I'm surprised it compiles with the superclass constructor call as the second line of the constructor (it should be the first line), but it looks fine otherwise. I think your problem is in knowing where to substitute the ResultEvent created by the web service with your own custom event. You could try something like this in your DxController class:

You'll notice, though, that that's completely redundant. The data and result fields will be exactly the same. Which makes me want to ask--why are you extending ResultEvent at all? What difference is there between its result and your data?

That's Adobe's namespace. You should make your own, or it might conflict with Adobe code. (So far Adobe only has Java code in its flex family of packages, I think, but that could change.) Most people just take their web address and invert it, which is a good ploy. I work with com.exopolis, org.marchofman, org.namesonnexus, com.dinosauricon, etc. and I can be pretty confident my packages won't ever conflict with anybody else's.

Hi thanks for responding. The webservice that I'm calling has a count method that returns the total number of rows for a particular object.

WS.count(whatObj, "query");

a real example whould look like

WS.count("Patient", "lastname like '%smith%'");

the count returns 694 rows. I then on that result want to use that same query to call another method that gives me the rows.

WS.find("Patient", "lastname like '%smith%'");

So I need to pass the query to the resultEvent as an additional param.

addEventListener("result", resultHander(query));

will not work. So I tried to subClass the resultEvent.

I hope this makes sense. I actually thought that the webservice should provide more information (the query used) about the the returned data, other than just the data itself (694 rows) or combine the methods into one and give me a count and the results.

Great summary, Mike. I wonder whether you also see a bug in Flex Builder 3 for your tip #5: Code completion does not show ProcessController.EVENT_START but Event.START . If it's really a bug, it makes metadata in this scenario somewhat useless.

thanks so much. flash is a lumbering hulking arse of a program designed by a team of sadistic monkeys with a depressingly effective marketing department, and what's more the manual is so crap (and online, so unsearchable) that random forum posts show up before pages on adobe.com when you google search.

posts like this help to make us poor developers lives just that little bit less painful..