Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with:

FLEX On jQuery: Extending The jQuery Event Object

Posted by Ben Nadel

Last week in my FLEX On jQuery series with Joe Rinehart, I explored the idea of creating non-coupled UI elements that communicate indirectly through the use of event listeners and modifiable event behaviors. In that experiment, I used the core jQuery Event object with custom event types to model custom events; but, I get the feeling from several conversations that FLEX goes far beyond custom event types. Over the weekend, I had the pleasure of talking to Eric Belair on this topic (chat reproduced at the end) and he gave me a quick introduction to how custom events might be triggered within a component as well as how custom Event classes are defined.

 
 
 
 
 
 
 
 
 
 

The more I think about events, the more I start to view them as falling into one of two distinct categories: "report events" and "request events". Report Events are events that simply report what the user has done to the User Interface. Mouseover, mouseout, and click would all be instances of a report event as they report the user's behavior. These kinds of events don't have any implicit meaning at the conceptual level. Request Events, on the other hand, indicate the intent and desire of the user as he or she navigates through the User Interface. Seeing as the UI cannot possibly know the user's intent, it is our application's responsibility to translate "report events" into "request events" as needed.

For example, if a user clicks on the link, "View Girl", our application might need to translate that click event (a report event) into some sort of ViewGirl event (a request event). Seeing as the conditions surrounding the report event - the click - are tightly coupled to the implementation of the parent component, it seems that this event translation should be done locally to said component. If, instead, the greater application were required to make this translation, it would become highly coupled to the component implementation and its code would need to be changed every time the internals of the component changed.

So, how do we define these custom report events? In jQuery, as I demonstrated in my previous FLEX On jQuery post, there are number of ways to trigger custom events. For starters, you can simply use a custom "event type" value. This will create a core jQuery Event object with a custom type. You can also define additional parameters to be passed to the event handlers; while not a custom event type itself, this event meta-data could be used to differentiate events. Lastly, you can define extra event properties of the core event by using an event hash in the trigger() method.

NOTE: I am not considering "Event.data" here because Event.data is part of the event handler definition, not the event itself.

While all of these methods get the job done, I suspect that there might be something rather elegant to creating an actual Custom Event class. In Javascript, using prototypal inheritance, creating a custom event class simply requires extending an instance of the core jQuery Event object:

  • function CustomEvent( eventType ){
  • // Apply the super constructor
  • jQuery.Event.call( this, eventType );
  • };
  •  
  • // Extend the core jQuery event object.
  • CustomEvent.prototype = new $.Event( "" );

This architecture allows our CustomEvent class to encapsulate a custom event type as well as any custom properties required to model the event. And, best of all, it does so while still being able to take full advantage of all the core jQuery event goodness - stopPropagation(), preventDefault(), result, etc..

To experiment with these Custom Event classes, I set up a small demo with a data grid of girls. The data grid can trigger two custom events: "girlHover" and "girlView." The girlHover event is triggered when the user hovers over a girl record. The girlView event is triggered when the user indicates that they wish to view a girl (ie. clicking on the "view" link). I'll be using both of these custom events, plus a core event, "mouseleave", to alter the UI as the user interacts with the application.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>FLEX On jQuery: Extending The jQuery Event Object</title>
  • <style type="text/css">
  •  
  • table {
  • margin-bottom: 5px ;
  • }
  •  
  • #teaser {
  • color: #999999 ;
  • font-style: italic ;
  • margin-bottom: 10px ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="../jquery-1.4.2.js"></script>
  • <script type="text/javascript">
  •  
  • // Create a custom event class for the data grid of
  • // girlies. This will be dispatched for all events related
  • // to that data grid.
  • function GirlEvent( eventType, id, name, bestAttribute ){
  • // Apply the super constructor to get properly wire
  • // core event properties.
  • jQuery.Event.call( this, eventType );
  •  
  • // Store the custom attributes. We can store any custom
  • // attribute we want so long as we don't override core
  • // jQuery Event properties.
  • this.id = id;
  • this.name = name;
  • this.bestAttribute = bestAttribute;
  •  
  • // Prevent any bubbling of this event. There is not
  • // need for this event to bubble ... mostly, though,
  • // just want to demonstrate the power of custom event
  • // type creation.
  • this.stopPropagation();
  • };
  •  
  • // Set the event types as static values. We are doing this
  • // so we don't have to keep track globally of what these
  • // event types actually are.
  • GirlEvent.HOVER = "girlHover";
  • GirlEvent.VIEW = "girlView";
  •  
  • // Extend the core jQuery event object.
  • GirlEvent.prototype = new $.Event( "" );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I am the controller for the girl data grid.
  • function GirlGridController( target ){
  • var self = this;
  •  
  • // Store the target as the UI aspect of this component.
  • this.ui = target;
  •  
  • // Bind to the click event so that we can see if someone
  • // is trying to view the girl record.
  • this.ui.click(
  • function( event ){
  • // Prevent any default behavior of this click.
  • // There is nothing implicitly important about
  • // a click event until we say so.
  • event.preventDefault();
  •  
  • // Search for the closest link. When gathering
  • // the closest, only go as far as the local UI.
  • var viewGirl = $( event.target ).closest(
  • "a",
  • self.ui
  • );
  •  
  • // Check to see if the view girl link was the
  • // target of the click.
  • if (viewGirl.size()){
  •  
  • // Get the event attributes for the row
  • // that is housing the clicked link.
  • var eventAttributes = self.getEventAttributes(
  • viewGirl.closest( "tr" )
  • );
  •  
  • // Create a new view Girl event. When we
  • // do this, we need to pass in the following
  • // event properties:
  • // - TYPE
  • // - ID
  • // - Name
  • // - BestAttribute
  • var viewGirlEvent = new GirlEvent(
  • GirlEvent.VIEW,
  • eventAttributes.id,
  • eventAttributes.name,
  • eventAttributes.bestAttribute
  • );
  •  
  • // Dispatch girl view event.
  • $( self ).trigger( viewGirlEvent );
  •  
  • }
  • }
  • );
  •  
  • // Bind to the mouseenter event on the records.
  • this.ui.find( "tbody tr" ).mouseenter(
  • function( event ){
  • // Get the event attributes for the target row.
  • var eventAttributes = self.getEventAttributes(
  • $( event.target ).closest( "tr" )
  • );
  •  
  • // Create a new hover Girl event. When we
  • // do this, we need to pass in the following
  • // event properties:
  • // - TYPE
  • // - ID
  • // - Name
  • // - BestAttribute
  • var hoverGirlEvent = new GirlEvent(
  • GirlEvent.HOVER,
  • eventAttributes.id,
  • eventAttributes.name,
  • eventAttributes.bestAttribute
  • );
  •  
  • // Dispatch girl hover event.
  • $( self ).trigger( hoverGirlEvent );
  • }
  • );
  • };
  •  
  • // Define the controller class methods.
  • GirlGridController.prototype = {
  •  
  • // I get the event attributes based on the given row.
  • getEventAttributes: function( row ){
  • return({
  • id: parseInt( row.attr( "data-id" ) ),
  • name: $.trim(
  • row.find( "td:nth(0)" ).text()
  • ),
  • bestAttribute: $.trim(
  • row.find( "td:nth(1)" ).text()
  • )
  • });
  • },
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // When the DOM is ready, initialize the scripts.
  • $(function(){
  •  
  • // Create an instance of the girl grid controller.
  • var girlGridController = new GirlGridController(
  • $( "table" )
  • );
  •  
  • // Get a reference to the teaser div.
  • var teaser = $( "#teaser" );
  •  
  • // Get a reference to the photo view image.
  • var photo = $( "#girl-photo" );
  •  
  •  
  • // ----------------- //
  • // ----------------- //
  •  
  •  
  • // Bind to the hover event to present the user with a
  • // little teaser to get them to click.
  • $( girlGridController ).bind(
  • GirlEvent.HOVER,
  • function( event ){
  •  
  • // Set the teaser text.
  • teaser.text(
  • "Come on, you know you want to see " +
  • (event.name + "'s ") +
  • ("awesome " + event.bestAttribute) +
  • "!"
  • );
  •  
  • }
  • );
  •  
  •  
  • // Bind the core event, mouseleave, on the table
  • // itself so we can clear the teaser. Here, we can
  • // stil leverage events off of the component that are
  • // not necessarily powered by custom events.
  • girlGridController.ui.mouseleave(
  • function( event ){
  •  
  • // Clear the teaser text.
  • teaser.text( "List of girls." );
  •  
  • }
  • );
  •  
  •  
  • // Bind to the view event to present the user with a
  • // photo of the desired girl.
  • $( girlGridController ).bind(
  • GirlEvent.VIEW,
  • function( event ){
  •  
  • // Set the photo SRC.
  • photo.attr(
  • "src",
  • ("girl" + event.id + ".jpg")
  • );
  •  
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • FLEX On jQuery: Extending The jQuery Event Object
  • </h1>
  •  
  • <table border="1" cellpadding="5">
  • <thead>
  • <tr>
  • <th>
  • Name
  • </th>
  • <th>
  • Best Attribute
  • </th>
  • <th>
  • <!-- Action column. -->
  • <br />
  • </th>
  • </tr>
  • </thead>
  • <tbody>
  • <tr data-id="1">
  • <td>
  • Sarah
  • </td>
  • <td>
  • Legs
  • </td>
  • <td>
  • <a href="#">View Girl</a>
  • </td>
  • </tr>
  • <tr data-id="2">
  • <td>
  • Joanna
  • </td>
  • <td>
  • Butt
  • </td>
  • <td>
  • <a href="#">View Girl</a>
  • </td>
  • </tr>
  • </tbody>
  • </table>
  •  
  • <div id="teaser">
  • <!-- To be populated programmatically. -->
  • List of girls.
  • </div>
  •  
  • <!-- This is where we will show the girl. -->
  • <img id="girl-photo" />
  •  
  • <p>
  • Photos from:
  • http://www.flickr.com/photos/videoplacebo
  • </p>
  •  
  • </body>
  • </html>

As you can see, I have created a custom event class, GirlEvent. This GirlEvent class will represent both of our custom event types - girlHover and girlView. To make these event types as easy to use as possible, I have made them static constants off of the GirlEvent class. This way, both the event listeners and the event dispatchers can reference the static constants rather than worry about how the event type is actually implemented.

Once this custom event class has been defined, the Data Grid component, GirlGridController, can use core "report events" - mouseenter and click - to translate the user's actions into "request events", modelled by the GirlEvent class. Because this translation is kept local to the grid component, the application only needs to bind to the custom events without having to worry about how those custom events are initially precipitated.

As I was doing research on custom FLEX events, it was very unclear as to how event types were organized. To be honest, a lot of the content out there written on custom FLEX events is rather superficial; it talks only about the mechanics of custom events (something already explained quite well in the Adobe documentation), but barely touches on the "Why" and "When" of their use. Perhaps this is because custom events are not used nearly as often as I think they are. If this is true, please let me know - I really need all the help I can get as this mind set is a radical shift from how I typically build my web applications.

While this FLEX On jQuery post doesn't totally correlate with my conversation with Eric Belair, I have reproduced it here if anyone is interested:

Ben Nadel: So, let me kick this off
Ben Nadel: I understand that FLEX is an extremely Event-driven environment
Ben Nadel: I am curious to learn more about these events
Ben Nadel: It seems that pretty much all events are Custom objects
Eric Belair: yes, knowing your knowledge of jQuery/JavaScript, it's not TOOO different
Ben Nadel: Right, but even in jQuery, I still predominanly use core events like "click", and "mousemove". It seems like FLEX has special events custom-tailored for evey particular action... at least that's the feeling I get in conversation
Eric Belair: uhh, yeah, like there's a MouseEvent Class, that has Constants for each *type* of event - MOUSE_DOWN, CLICK, DOUBLE_CLICK
Ben Nadel: Ok, so let me try to paint a scenario
Eric Belair: and the Class has Methods and Properties that are common to all
Eric Belair: or can be used by all
Ben Nadel: Let's say I have a data grid that has people's names in it
Ben Nadel: and each data grid record has some link button that says "View Contact"
Ben Nadel: if the user clicks on said link button, the contact's details need to be displayed in a modal "window".
Ben Nadel: I assume clicking on the link button triggers an event off of the datagrid?
Ben Nadel: is that a mouse event? Or is that a custom event
Eric Belair: well, the event is a mouseEvent
Eric Belair: every click is a mouseEvent
Ben Nadel: And how does the datagrid know what information to report in that event?
Eric Belair: ahh, yes, this is an interesting concept
Eric Belair: there are several ways to do this
Ben Nadel: ok
Eric Belair: are you familiar with itemRenderers in a DatGrid?
Ben Nadel: i konw the very abstract concept - you use a renderer to define how the row is displayed against a data set?
Eric Belair: kinda...an itemRenderer is defined for how to display all cells in a given column
Ben Nadel: ah
Ben Nadel: and is that defined as part of the MXML?
Eric Belair: if you don't want just plain text (the default itemREnderer)
Eric Belair: you define it...
Eric Belair: this article explains it perfectly...http://www.adobe.com/devnet/flex/articles/itemrenderers_pt1.html
Ben Nadel: ok, so have a renderer that has to say "put a link button here"
Eric Belair: right
Eric Belair: like this (from that article):
Ben Nadel: I'll check out the artcicle
Eric Belair: [code removed]
Eric Belair: that's inline, or you can make it external, so that it can be reused by other datagrids, or other columns in the same datgrid
Ben Nadel: ok
Eric Belair: then, to do what you want, have the click perform a specific action using the data from the row in which you clicked, you create a Custom Event and bubble it up to the "owner" - the DataGrid
Ben Nadel: So the item renderer defines the event, not the datagrid... hmmm
Eric Belair: the renderer creates the instance of the event and bubbles it
Ben Nadel: and the event would have information like ... event.data.id = {data.id}?
Eric Belair: you can add any data you want to the Event
Eric Belair: you define the properties of the Custom Event
Eric Belair: The Custom Event is it's own separate Class
Ben Nadel: And I assume all custom events extend some Core event?
Eric Belair: YES
Ben Nadel: that has core methods for propgatation and what not
Eric Belair: yes
Eric Belair: exactly
Eric Belair: basically
Ben Nadel: Interesting
Eric Belair: public class MyEvent extends Event {}
Ben Nadel: And then at the datagrid level, you'd have something like .... datagrid.addEventListener( MyEvent, eventHandler )
Eric Belair: exactly
Ben Nadel: gotcha
Ben Nadel: Taking it up one level, then, this event binding would typically be done by the Application? Or one of its child containers?
Eric Belair: the key is to remember to override the set data function, so that the data is available to you when you need to specify which row you're referencing
Ben Nadel: gotcha
Ben Nadel: Assuming that the datagrid renderer only has access to Name and ID, I assume who ever does the binding then would have to catch that event, perform some HTTP / AMF call for the actual contact record, and then display the modal window
Eric Belair: yes
Eric Belair: if the data has not already been retrieved and is sitting in the background
Ben Nadel: assuming not (for sake of argument)
Ben Nadel: I see... good stuff
Eric Belair: yeah
Ben Nadel: And MXML file where this is defined, would this be considered the "Application"? Or is a child of the application?
Ben Nadel: .... in the simplest possible wriing
Ben Nadel: *wiring
Eric Belair: the application is the whole application. In general, you create a file with <mx:application>, and that is the file that contains EVERYTHING. But, it's best practice to compartmentalize everything into components. You technically could put everything into that one file.
Eric Belair: I did that for my first application, and ended up with likw 2000-3000 lines of code in one file.
Ben Nadel: ha ha, nice :) I'm ok with that concept, at the simple level for now :)
Eric Belair: and, of course, MVC makes it MUCH easier
Ben Nadel: Without MVC, would this MXML file also be considered the "controller"?
Ben Nadel: I just am trying to get a feel for the terminology
Eric Belair: hmm, i don't know how to explain that...i can't think of the concept of a controller without MVC
Ben Nadel: ha ha, no worries :)
Eric Belair: basically, if you have events, and triggers, and handlers, and the handlers do everything, there is no controller.
Ben Nadel: I am just a bit confused about where the line between "Application" and "Controller" is... but that is perhaps an entirely different conversation
Eric Belair: haha, yes
Eric Belair: the controller in Cairngorm is basically one file that says "this event goes with this command" and "this other event goes with this command"
Ben Nadel: Would you be able to point me to an Article on creating custom AS events?
Eric Belair: and all your http calls, and data handling are in the commands
Ben Nadel: And "commends" would be something in your model / service layer?
Eric Belair: hmmm
Eric Belair: now that i think about it...not sure how to define "where" the commands exist...
Ben Nadel: no problem at all - MVC is a whole set of conversations on their own
Ben Nadel: no need to go there
Eric Belair: i guess now that i think about it, commands are part of the controller....
Eric Belair: when I discovered MVC, i loved it, especially for Flex
Eric Belair: i'm a huge MVC evangelist for Flex
Ben Nadel: Perhaps one day we can talk more about that
Ben Nadel: I've a lot of your time though, you've given me some good stuff to think about
Eric Belair: Here's the documentation on Custom AS Events...
Eric Belair: http://livedocs.adobe.com/flex/3/html/help.html?content=createevents_3.html
Eric Belair: it explains it very well
Ben Nadel: Ahhh, very cool :)
Eric Belair: you can create any properties or methods you want
Eric Belair: as long as they don't already exist
Ben Nadel: Excellent. Well, I appreciate you taking time out of your Saturday to chat :)

As always, if you have something you'd like to teach me about FLEX, I'd love to hear it!




Reader Comments

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.