12.4 Handling Component Events

 <  Day Day Up  >  

In this chapter, we handled component events in two different ways:

  • With a generic listener object (in the case of the TextInput component):

     var enterHandler:Object = new Object( ); enterHandler.enter = function (e:Object):Void {   thisConverter.convert( ); } input.addEventListener("enter", enterHandler); 

  • With an event handler function (in the case of the Button component):

     convertButton.clickHandler = function (e:Object):Void {   thisConverter.convert( ); }; 

Handling component events with generic listener objects in ActionScript 2.0 is somewhat analogous to handling Java Swing component events with anonymous inner classes. In Swing, an anonymous instance of an anonymous inner class is created simply to define a method that responds to a component event. In ActionScript 2.0, an instance of the generic Object class is created for the same reason (to define a component-event-handling method). But in ActionScript 2.0, the anonymous class is not required because new methods can legally be added dynamically to instances of the Object class at runtime.

In general, the listener object approach is favored over the event handler function approach, primarily because multiple listener objects can receive events from the same component, whereas only one event handler function can be defined for a component at a time. This makes listener objects more flexible and scalable than event handler functions. Hence, Macromedia formally discourages use of event handler functions. However, you'll definitely see both approaches thriving in the wild. The older v1 components that shipped with Flash MX did not support listener objects, so all older v1 code uses event handler functions. The v2 components support event handler functions for backward compatibility. Moving forward, you should use listener objects rather than event handler functions. That said, even if you're not working with components , you'll still encounter event handler functions when working with the Flash Player's built-in library of classes. Many of the built-in classes, including MovieClip , Sound , XML , and XMLSocket use event handler functions as their only means of broadcasting events.

As an alternative to defining an event handler function on a component instance, you can also use a so-called listener function , which logically lies somewhere between an event handler function and a listener object. A listener function is a standalone function (i.e., a function not defined on any object) registered to handle a component event. For example, our earlier event handler function for the Convert Button component looked like this:

 convertButton.clickHandler = function (e:Object):Void {   thisConverter.convert( ); } 

The analogous listener function would be:

 function convertClickHandler (e:Object):Void {   thisConverter.convert( ); }; convertButton.addEventListener("click", convertClickHandler); 

Listener functions are preferred over event handler functions because multiple listener functions can be registered to handle events for the same component. However, when using listener functions, you should be careful to delete the function once it is no longer in use. Or, to avoid cleanup work, you might simply pass a function literal to the addEventListener( ) method of the component in question, as follows :

 convertButton.addEventListener("click", function (e:Object):Void {   thisConverter.convert( ); }); 

However, using this function literal approach prevents you from ever removing the listener function from the component's listener list. Hence, when registering for an event that you may later want to stop receiving, you should not use the preceding function literal approach.

Whether you're using a listener object, an event handler function, or a listener function, the fundamental goal is the same: to map an event from a component to a method call on an object. For example, in our Convert button example, we want to map the button's click event to our CurrencyConverter object's convert( ) method. Yet another way to make that mapping would be to define a click( ) method on the CurrencyConverter class and register the CurrencyConverter instance to handle button click events. Here's the click( ) method definition:

 public function click (e:Object):Void {   convert( ); } 

And here's the code that would register the CurrencyConverter instance to receive click events from the Convert button:

 convertButton.addEventListener("click", this); 

In the preceding approach, because the click( ) method is called on the CurrencyConverter instance, the click( ) method can invoke convert( ) directly, without the need for the thisConverter local variable that was required earlier. However, problems arise when the CurrencyConverter instance needs to respond to more than one Button component's click event. To differentiate between our Convert button and, say, a Reset button, we'd have to add cumbersome if or switch statements to our click( ) method, as shown in the following code. For this example, assume that the instance properties convertButton and resetButton have been added to the CurrencyConverter class.

 public function click (e:Object):Void {   if (e.target == convertButton) {     convert( );   } else if (e.target == resetButton) {     reset( );   } } 

Rather than forcing our CurrencyConverter class to handle multiple like-named events from various components, we're better off reverting to our earlier generic listener object system, in which each generic object could happily forward events to the appropriate methods on CurrencyConverter . For example:

 // Convert button handler var convertClickHandler:Object = new Object( ); convertClickHandler.click = function (e:Object):Void {   thisConverter.convert( ); } convertButton.addEventListener("click", convertClickHandler); // Reset button handler var resetClickHandler:Object = new Object( ); resetClickHandler.click = function (e:Object):Void {   thisConverter.reset( ); } resetButton.addEventListener("click", resetClickHandler); 

To reduce the labor required to create generic listener objects that map component events to object method calls, Mike Chambers from Macromedia created a utility class, EventProxy . Using Mike's EventProxy class, the preceding code could be reduced to:

 convertButton.addEventListener("click", new EventProxy(this, "convert")); resetButton.addEventListener("click", new EventProxy(this, "reset")); 

The EventProxy class, shown in Example 12-2, does a good, clean job of mapping a component event to an object method call. However, the convenience of EventProxy comes at a price: reduced type checking. For example, in the following line, even if the current object ( this ) does not define the method convert( ) , the compiler does not generate a type error:

 convertButton.addEventListener("click", new EventProxy(this, "convert")); 

Hence, wherever you use the EventProxy class, remember to carefully check your code for potential datatype errors. For more information on EventProxy , see: http://www.markme.com/mesh/archives/004286.cfm.

Example 12-2. The EventProxy class
 class EventProxy {   private var receiverObj:Object;   private var funcName:String;     /**     * receiverObj  The object on which   funcName   will be called.    * funcName     The function name to be called in response to the event.    */   function EventProxy(receiverObj:Object, funcName:String) {     this.receiverObj  = receiverObj;     this.funcName = funcName;   }     /**    * Invoked before the registered event is broadcast by the component.    * Proxies the event call out to the   receiverObj   object's method.    */   private function handleEvent(eventObj:Object):Void {     // If no function name has been defined...     if (funcName == undefined) {       // ...pass the call to the event name method       receiverObj[eventObj.type](eventObj);     } else {       // ...otherwise, pass the call to the specified method name       receiverObj[funcName](eventObj);     }   } } 

To avoid the type checking problem presented by the EventProxy class, you can use the rewritten version of Mike Chambers' original class, shown in Example 12-3. The rewritten version uses a function reference instead of a string to access the method to which an event is mapped. Hence, to use the rewritten EventProxy class, we pass a method instead of a string as the second constructor argument, like this:

 // No quotation marks around   convert   ! It's a reference, not a string! convertButton.addEventListener("click", new EventProxy(this, convert)); 

Because the convert( ) method is accessed by reference, the compiler generates a helpful error if the method doesn't exist.

Example 12-3. The Rewritten EventProxy class
 class EventProxy {   private var receiverObj:Object;   private var funcRef:Function;     /**     * receiverObj  The object on which   funcRef   will be called.    * funcName     A reference to the function to call in response     *              to the event.    */   function EventProxy(receiverObj:Object, funcRef:Function) {     this.receiverObj  = receiverObj;     this.funcRef = funcRef;   }     /**    * Invoked before the registered event is broadcast by the component.    * Proxies the event call out to the   receiverObj   object's method.    */   private function handleEvent(eventObj:Object):Void {     // If no function name has been defined...     if (funcRef == undefined) {       // ...pass the call to the event name method       receiverObj[eventObj.type](eventObj);     } else {       // ...otherwise, pass the call to the specified method using       //   Function.call( )   .       funcRef.call(receiverObj, eventObj);     }   } } 

As evidenced by the sheer number of event-handling techniques just discussed, the v2 component-event-handling architecture is very flexible. But it also suffers from a general weakness: it allows type errors to go undetected in two specific ways.

First, any component's events can be handled by any object of any class. The compiler does not (indeed cannot) check whether an event-consuming object defines the method(s) required to handle the event(s) for which it has registered. In the following code, if the convertClickHandler object does not define the required click( ) method, no error occurs at compile time:

 var convertClickHandler:Object = new Object( );  // Oops! Forgot the second "c" in "click," but no compiler error occurs!   convertClickHandler.clik = function (e:Object):Void {  thisConverter.convert( ); } convertButton.addEventListener("click", convertClickHandler); 

In other words, in the v2 component architecture there's no well-known manifest of the events a component broadcasts and no contract between the event source and the event consumer to guarantee that the consumer actually defines the events broadcast by the source.

Second, event objects themselves are not represented by individual classes. All event objects are instances of the generic Object class. Hence, if you misuse an event object within an event-handling method, the compiler, again, does not generate type errors. For example, in the following code (which, so far, contains no type errors), we disable a clicked button by setting the button's enabled property to false via an event object. We access the button through the event object's target property, which always stores a reference to the event source:

 convertClickHandler.click = function (e:Object):Void {   thisConverter.convert( );   e.target.enabled = false; } 

But if the programmer specifies the wrong property name for target (perhaps due to a typographical error or a mistaken assumption), the compiler does not generate a type error:

 convertClickHandler.click = function (e:Object):Void {   thisConverter.convert( );  e.source.enabled = false;  // Wrong property name! But no compiler error!   e.trget.enabled = false;   // Oops! A typo, but no compiler error!  } 

In addition to suppressing potential compiler errors, the lack of typed event objects in the v2 component architecture effectively hides the information those objects contain. If the architecture used formal event classes, such as, say, Event or ButtonEvent , the programmer could quickly determine what information is available for an event simply by examining the v2 component class library. As things stand, such information can be found only in the documentation (which may be incomplete) or in an event-broadcasting component's raw source code (which is laborious to read).

One way to help make an application's handling of v2 component events more obvious is to define specific classes for event-consumer objects rather than using generic objects. For example, to handle events for the Convert button, an instance of the Button component, in our CurrencyConverter application, we might create a custom ConvertButtonHandler class as follows:

 import org.moock.tools.CurrencyConverter; class org.moock.tools.ConvertButtonHandler {   private var converter:CurrencyConverter;   public function ConvertButtonHandler (converter:CurrencyConverter) {     this.converter = converter;   }   public function click (e:Object):Void {     converter.convert( );   } } 

Then, to handle the Button component events for the Convert button, we'd use:

 convertButton.addEventListener("click", new ConvertButtonHandler(this)); 

By encapsulating the button-event-handling code in a separate class, we make the overall structure of the application more outwardly apparent. We also isolate the button-handling code, making it easier to change and maintain. However, in simple applications, using a separate class can require more work than it's worth. And it doesn't alleviate the other event type checking problems discussed earlier (namely, the compiler's inability to type check event-consumer objects and event objects).

In Java, every aspect of the Swing component event architecture includes type checking. Event consumers in Java must implement the appropriate event listener interface, and event objects belong to custom event classes. In simple Flash applications, Java's additional component event architecture would be cumbersome and hinder rapid, lightweight development. However, for more complex situations, Java's strictness would be welcome. Therefore, we'll see how to implement Java-style events in ActionScript 2.0 classes in Chapter 19. And we'll learn more about generating and handling custom user interface events in Chapter 18.

For reference, Table 12-1 summarizes the various component-event-handling techniques discussed in this chapter.

Table 12-1. Component-event-handling techniques

Technique

Example

Notes

Generic listener object

 var convertClickHandler:Object = new Object( ); convertClickHandler.click = function (e:Object):Void {   thisConverter.convert( ); } convertButton.addEventListener("click", convertClickHandler); 

Generally, the preferred means of handling component events

Typed listener object

 convertButton.addEventListener("click", new ConvertButtonHandler(this)); 

Same as generic listener object, but exposes event-handling code more explicitly

EventProxy class

 convertButton.addEventListener("click", new EventProxy(this, "convert")); 

or

 convertButton.addEventListener("click", new EventProxy(this, convert)); 

Functionally the same as generic listener object, but more convenient and easier to read

Listener function

 convertButton.addEventListener("click", function (e:Object):Void {   thisConverter.convert( ); }); 

The lesser evil of the two function-only event-handling mechanisms

Event handler function

 convertButton.clickHandler = function (e:Object):Void {   thisConverter.convert( ); } 

The least-desirable means of handling component events; discouraged by Macromedia


 <  Day Day Up  >  


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net