August 16, 2009

My Critique of AS3 Events - Part 1

I've been thinking about AS3 Events a lot lately. I'm finding them a little bit annoying.

Don't get me wrong. After so many changing event models in Flash 5 through 8 (4 if tell target counts as an event), having one formalized in Flash Player 9 was a huge relief. I'm also aware that Macromedia didn't invent this event model. They used a standard: DOM Level 3 Events.

But even standards have room for improvement, on occasion. What follows is a good-natured roasting.

Event constants are inconsistent


Let's say I'm creating my first custom event, and I want to name it according to standard conventions.
What better place to start than the constants in flash.events.Event, beginning with "A":
  • ACTIVATE (verb, present tense)
  • ADDED (verb, past tense)
That didn't take very long. Past or present? Pick a side; we're at war. War with time-traveling virtual machines.

There must be a hidden logic to this. Perhaps I can decipher the pattern by looking at DisplayObject events:
  • render - Dispatched when the display list is about to be updated and rendered.
Oh, I see! It's present-tense because the render hasn't happened yet.

Wait, what's this?
  • removed - Dispatched when a display object is about to be removed from the display list.
I give up. Time paradox = retcon fail.

addEventListener() ordered options awkwardly are


Here is EventDispatcher.addEventListener() in all its glory:
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void

A general goal in designing a method is to arrange the parameters from the most important or frequently used to the least.

Think fast!
Which optional parameter of addEventListener() do you use the most?

I'll bet you dollars to donuts you said useWeakReference. If not, you need to heed the G. [EDIT: Many have told me it's better to use strong references and meticulously clean up after yourself. I don't use weak listeners anymore.] When you do, you'll get to type this, over and over:

addEventListener(yada, yada, false, 0, true);
addEventListener(yada, yada, false, 0, true);
addEventListener(yada, yada, false, 0, true);

You'll say to yourself: "One of these days, I'm going to write true, 0, true. I just know it."

Tech interview question!
How often do you useCapture with your custom events? [EDIT: I was thinking of events not dispatched from a DisplayObject.]

[Dwight voice]
Trick question. It is impossible. FACT: Only DisplayObjects can have a capturing phase and a bubbling phase in addition to the targeting phase.

But impossibility doesn't mean I have to forget about them! The useCapture will be with you, always.

Event forces irrelevance on you


Want to create your own event class? No problem! You can do whatever you like, as long as what you like is to extend one special class. "Interface is hard!" says Developer Barbie.

Are you wondering how these exotic and intriguing APIs can benefit your custom event class?
  • bubbles
  • currentTarget
  • eventPhase
  • stopImmediatePropagation()
  • stopPropagation()
[EDIT: Sönke and Matthew commented that the "default" APIs can be used in non-DisplayObjects.]

Keep wondering! You don't get to use them. Players Club only.

[EDIT: To clarify, I'm saying that these APIs are applicable only to events dispatched from DisplayObjects.]

But look on the bright side. The code examples sucked anyway.

[EDIT: Here's a good article by Darron Schall: Creating Default, Cancelable Event Handlers.]

Sorry, that's not exactly true. If you follow the official recommendations, you'll spend time using these APIs even though they're no use to you.
  • You'll put bubbles and cancelable in your constructor and pass them to super().
  • That means you'll get to put bubbles and cancelable in your clone() override as well.
  • Top it off by overriding toString(), bringing in bubbles, cancelable, and (oh, neat!) eventPhase.
  • Now your event class looks busy. Busy equals important, right?
These are Adobe's documented recommendations. But all this typing has no benefit to you. None. If I missed something, please comment and enlighten me.
[EDIT: Commenters have pointed out that cancelable can be useful. But there's no need to put it in the constructor if you're not going to use it.]

I don't want this to sound like a big whinge. I mean, really, this stuff isn't horrible. When it comes to events, AS3 is way better than Java events. But not as good as C#.
To be continued.

30 comments:

Richard Lord said...

Hi Robert

Agree with everything you said. I have occasionally needed to use bubbles and stopImmediatePropagation, but the rest is unused. But nearly always, listeners are weak references.

My original thought was that it's too late to change the event implementation, but the FlashPlayer could include an IEvent interface without breaking existing code, which would fix some of the issues you mention.

Personally, I like the idea of events as self-dispatching objects, as described here, but that's too different from the current api to be of practical interest.

Look forward to see what you suggest in Part 2.

Frank Pepermans said...

Another annoyance is when you create a custom event and declare an event constant with a name that already exists in another event class.
For example MyCustomEvent.COMPLETE would interfere with the built-in Event.COMPLETE since the event listener only looks at the String value, not the event class.
Flex 4 CSS has namespaces now, maybe events could have them too, passing the event class to each addEventListener as an alternative looks bloated...

szabi said...

You have a stray "” />" on the top of your page; thanks for the article on the other hand ! :)

Alec McEachran said...

Event.RENDER is even more absurd of course, since you need to call stage.invalidate() in order for it to happen at all! Why not object.invalidate() since it's the particular object that's being invalidated? It's completely counter-intuitive OO as far as I'm concerned.

I do use stopPropogation and stopImmediatePropogation in the core events relatively frequently though, I think you're a touch unfair on them!

Thanks for the article Robert, you brought another frustration of mine into focus: why are the capture phase, bubbling and propogation methods all in flash.events.Event? They are clearly defined for a particular sub-type of events (ie. flash.display.DisplayEvent), and have no place in a core Event class.

Pimm said...

Hi Robert, nice post.

I agree with you on some of the points, disagree with you on others.

First of all, the time-traveling virtual machine war is terrible. I picked a side myself. Past tense. When something is about to happen, I call it a request that took place. For instance, RENDER_REQUESTED. I strongly encourage everyone else to pick a side, too, and stick to it. However, since the default event constants are at war we'll never be completely in peace, so I totally agree with you on this one.

However, I almost never use the optional arguments of the addEventListener method. And the one I use most? I guess it's priority. I know a lot of developers, including Skinner, encourage you to use weak references. I encourage you to remove your event listeners, instead.

Also, if currentTarget, preventDefault and stopPropagation are players club only, I am definitely a player. I use them all. Not every day (except for currentTarget, I use that one all the time), but still, I use them.

Finally, the AS3 event model is not half bad. I agree. I'm feeling lucky (and I don't mean the button on Google) that I don't develop in JavaScript.

Looking forward to your other posts (keep me updated on Twitter).

Milan Orszagh (TheAustrianGuy) said...

Great post, can't agree more with you.

Sönke Rohde said...

Great post Robert. However I am using cancelable and preventDefault here and then like:

if(dispatch(new MyEvent(...)){
// event not canceled
}else{
// event canceled (preventDefault called)
}

Anonymous said...

Nothing to comment, but 'so true'

flexy said...

I wholeheartedly agree with your comments. I think weak referencing took a back seat during the development of FP9 and AS3.0, hence why it’s off by default, and only the 3rd optional parameter on the addEventListener method. You’re right to say it’s not a complete disaster, but it is annoying that I have to assign values to useCapture and priority when I seldom need to (also see why I think priorities can be evil http://jodieorourke.com/view.php?id=110&blog=news). I hope that there will be some further refinement to the API in the coming releases of the player.

Anonymous said...

A reliance on a string constant for the event type isn't good either.

MyCustomEvent.COMPLETE and MyOtherCustomEvent.COMPLETE could have the same string value and trigger the same listener, even though they might relate to two totally different tasks.

I realize that this is probably a poor design decision, but it shouldn't have to be a decision at all.

Keith Peters said...

Haha. Very nice. Adobe should hire you... oh wait. :)

zedia.net said...

I totally agree with you on the useWeakReference problem. Either make it the third parameter or default it to true, it is quite annoying. Ted Patrick wants stuff to improve in the Flash Platform http://onflash.org/ted/2009/08/future-of-flash-platform.php; that would definitely be it for me, that and bringing back the help files in Flash CS4 and not on the web.

matthew said...

Thank you! I've almost written this article at least half a dozen times. I think, though, that one of my biggest grievances is that EventDispatchers (and IEventDispatchers) shouldn't even be event dispatchers! Can you imagine the havoc that I would create if I actually utilized the public dispatchEvent function that the interface requires?

My second biggest complaint is that there are essentially two ways of specifying type -- the class and event.type -- the latter of which is not guaranteed to be unique (though many attempt to sidestep this by namespacing their event constant values). That means that by having my component dispatch a (legitimate) bubbling event, I can cause a runtime error in one of its ancestors on the display list that listens for the event with a differently typed handler. For example, a handler with the signature changeHandler(event:MyEvent) listening for MyEvent.CHANGE (whose value, by convention would be "change") will throw an error when a nested TextField dispatches an Event.CHANGE event because the event will not be of type MyEvent. The only real way to get around this is to type all of your handlers' event arguments as flash.events.Event, and do checking within the function. Yuck!

Like I said, I've almost written this article a ton of times so I could go on, but since your article is "Part I," I won't. Hopefully I didn't steal any of your thunder!

matthew said...

One more thing -- I have to disagree with you about your inclusion of isDefaultPrevented() and preventDefault() in your "irrelevant list." While the other methods are certainly only applicable to DisplayObjects (and there are problems with including these two methods in the Event class), they can be useful for non-DisplayObjects. Since these methods are really just setting and reading a flag, it's up to the developer whether to use them. In fact, there have been a few occasions when I've done the following:

var event:MyEvent = new MyEvent();
this.dispatchEvent(event);
if (!event.isDefaultPrevented())
{
// Take the action that the event represents (i.e. the "default" action)
}


Unless I'm mistaken, this kind of thing is perfectly in keeping with the expected behavior and documentation of these methods.

darron said...

Whenever I create components that respond to their own events, I use priorities and the cancelable flag.

I outlined this process in "Creating Default, Cancelable Event Handlers": http://www.darronschall.com/weblog/2008/01/creating-default-cancelable-event-handlers.cfm

I agree on the main points you raise though. In general, AS3 events are better than dealing with the bazillion event models of the past, but not as good as C# events. I think the key difference, to me, centers around compile-time checking of events. I would prefer it if events weren't strings, and could be verified at compile-time. The [Event] meta-data is a step in that direction, but it only works in MXML and you can still dispatch events from components aren't in the [Event] list without generating compile errors.

While I appreciate having a flexible system, in all of the applications I've developed, I don't believe I've ever dynamically constructed the event type String at runtime. I've been burned by this flexibility more often than I have used it.

Daniel Gasienica said...

Playing the devil's advocate here, you can't blame them for the argument order in addEventListener, after all, as you've correctly mentioned it, it's a standard:
http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-EventTarget-addEventListener

Cheers,
Daniel

P.S. Although I'm a fan of weak listeners myself, having them enabled by default could cause havoc with beginners, think randomly garbage collected objects because of them.

Robert Penner said...

Thanks everyone for your insightful comments; I've got a few things to learn about events yet. I've updated the article to incorporate your corrections.

Thanks szabi for spotting the markup problem. The quotes in my template got mangled somehow.

Jonathan Branam said...

I'm not sure I follow everything that you are complaining about here.

Using strings for events is really handy in that it is simple and easy. Obviously, AS3 does not have Enums, but really enums don't work anyway since they don't work with inheritance. It can be annoying, but it can be useful.

As for the names, it would be nice if they were named better, such as BEFORE_RENDER rather than RENDER, but the short names are rather nice and we can (and must!) all read the descriptions.

The order of parameters in addEventListener is quite unfortunate, but if that is the standard then I can accept that. Also, I don't know if the designers of AS3 would have known exactly which parameters would have the highest usage.

But the most confusing thing is when you talk about your custom events. When I create a custom event and dispatch it from any DisplayObject, all of the properties and methods work perfectly. If I create an event ChangeNavigationEvent and set bubbles to true, then it will bubble when dispatched from a DisplayObject. This is something that is very common to do when building custom components. Capturing, stopping propogation, stopping immediate propogation, preventing default, and currentTarget also work just fine.

If you are dispatching events from a non-DisplayObject class, then you are right, there is no capturing or bubbling. I don't know if stopImmediatePropogation() works, but it could in theory, since it does not rely on the display list.

On my current project, we have a hierarchy of business objects that also dispatch events. We have not implemented a capture phase, but we have implemented bubbling and we use the bubbling flag to control this behavior. Of course, it is manual work and we have a parent property on our business object class for this, but it is quite useful.

I think the article is confusing since you use the term "custom events" rather than "custom EventDispatcher objects." Every flag and method on Event can apply to any custom event, as long as it is dispatched from a DisplayObject. However, some of them don't work if you are using a custom EventDispatcher that is not a DisplayObject, unless you do the hard work yourself.

Alan Shaw said...

@Jonathan Branam: I have used bubbling on non-visual objects without having to implement it manually, by making them Sprites and putting them on the display list. E.g. a hierarchical state machine.

Danny Miller said...

I agree mostly as well. I created an EventManager class last year that handles some of the things you mentioned:

http://k2xl.com/wordpress/2008/07/02/as3-eventmanager-class-removealllisteners/

Grant Skinner said...

Unless I'm misunderstanding your point, you are incorrect in asserting that you can't use bubbling, stopPropagation, and etcetera in custom events. All you have to do is set the bubbling property to true on your custom event object. Of course, your event has to be dispatched from a DisplayObject, but that's the whole point of bubbling in this context.

Otherwise, good article. The discrepancy in tense is frustrating, especially when some of the events could actually benefit from both tenses (CHANGE and CHANGED for instance). Likewise, I obviously agree with making weakReference more prominent.

Cheers.

Groady said...

I agree with Grant. Yes you can use bubbling, stopPropagation etc. on custom events provided the event target is a DisplayObject. I was seriously scratching my head at that part. But otherwise I 110% agree with you. I especially think the order of params in the addEventListener method good benefit from a reordering in the future.

Brenton said...

I agree wholeheartedly with your remarks about tense inconsistency (RENDER v RENDERED) and weak references. The only time I don't use weak references is when I've got an asynchronous anonymous function that I'm trying to protect from the Great Garbage-Eating Monster.

Daniel mentioned earlier that parameter order is determined by the ECMAScript spec. useCapture is defined there, but priority is not. At the very least, weak could have been #4 rather than #5.

Having used Python/AppEngine for the backends of a few apps, I must say I really like keyword arguments. It's such a cleaner, more explicit way of calling a function, and it doesn't require you to memorize arbitrary orderings. I'd really like to see it in Flash.

For those that don't know, keyword arguments would let you do this:

target.addEventListener(Event.CHANGE, onChange, useWeakReference = true);

(Secret shortcut - if your editor has tab triggers, write one for addE[->] that sets weak to true by default)

And as far as custom events go, I always define clone and usually define toString for obvious reasons, but I have been known to simplify constructor signatures. For instance, if UndoableEvent's only type is OCCURRED and it always bubbles, I may not give it any arguments at all. The constructor can handle default cases - that's what it's there for.

Finally, if you are defining a custom event that conflicts with an inbuilt event, give it a custom value. In the previous example "undoable event occurred" make a hell of a lot more sense than just "occurred" for the value. You never actually type it again, so character length isn't a huge deal.

ozgur uksal said...

I disagree about what you wrote. you argued, "When it comes to events, AS3 is way better than Java events. But not as good as C#".

For one thing, C# doesn't have display list concept, therefore, it doesn't have the powerful "event-propagation". (it is true that if your event dispather object is not on the display list, then no need to think about "event propagation.")

You should see how to get the benefit of using the properties and methods of the Event object with using the abstract "event propagation" mind tool. As a result, you won't need a removeAllListeners() method because you will have one dispatcher as the parent object as the part of the display list, and it will be used to manage all your event needs.

Robert Penner said...

@ozgur,
When I referred to AS3 vs. Java and C#, I meant core event dispatching apart from a DOM. Put another way, for each language, there is a dominant, "official" implementation of the Observer pattern for general notifications. C# put events and delegates into the core language in a way that is quite flexible yet type-safe. Java's various listener interfaces are inflexible and inelegant by comparison. AS3 is in-between.

I don't follow your comment about propagation. C# operates on a DOM in Windows Forms, ASP.NET, WPF and Silverlight. Google "C# event bubbling" and you'll find some good reading.

Maciek said...

@Brenton
>You never actually type it again, so character length isn't a huge deal.

Except in MXML, where the string values are used to specify listeners rather than the event constants. I don't think you could even add a listener in MXML if your event name has characters that are not valid in an XML attribute name.

Nikos said...

not sure if this is the best place to ask but I just had some trouble understanding the syntax of

...valueClasses

not sure what that means



public function Signal(...valueClasses)
{
listeners = [];
onceListeners = new Dictionary();
// Cannot use super.apply(null, valueClasses), so allow the subclass to call super(valueClasses).
if (valueClasses.length == 1 && valueClasses[0] is Array)
valueClasses = valueClasses[0];
setValueClasses(valueClasses);
}

Anonymous said...

I'd just like to mention that Flash event dispatching system has to be observed in a broader sense: there is a Flex framework. I suppose Flex team had an influence in making decisions on Flash event system.

Flex supports event bubbling on GUI components (thus 'bubbles' parameter), you can bubble both ways ('capture' and 'bubbling' phase - thus 'phase' parameter), there's also the event cancellation.

The most important, there is a concept of DATA BINDING. It's all about events and auto-generated code. This concept is so important that MS copied it all - right from the source - when decided to have Silverlight.

Here's an excellent presentation about data binding: http://tv.adobe.com/watch/360flex-conference/diving-in-the-data-binding-waters-by-michael-labriola/

So, that are my thoughts. However, I tend to use signals-like stuff (observable/observer) for internal wiring, because it is so much faster.

Danko Kozar

Anonymous said...

bubblin, stopImmidiatePropagation and use capture can be used, for example, if you are building composite model tree: http://code.google.com/p/blooddy/source/browse/trunk#trunk%2Fblooddy_core%2Fsrc%2Fby%2Fblooddy%2Fcore%2Fdata

all other api's are also commonly used.

Anonymous said...

bubblin, stopImmidiatePropagation and use capture can be used, for example, if you are building composite model tree: http://code.google.com/p/blooddy/source/browse/trunk#trunk%2Fblooddy_core%2Fsrc%2Fby%2Fblooddy%2Fcore%2Fdata

all other api's are also commonly used.