overview

v2 components offer many ways to handle events

this lecture explores the various techniques

discusses event handling best practice

responding to input

"handling an event" from a user interface component means mapping a user action to a program response

examples of user action:

  • clicking a button
  • scrolling a list
  • in an OOP application, "program response" means invoking a method

    examples of methods that produce a program response:

  • submitForm() (sends information to a server)
  • pauseVideo() (stops a video at the current frame)
  • to learn event handling techniques, we'll study a classic input/response example: clicking a "Submit" button to submit a form

    from input to method call

    as a best practise, each input event should be handled by a single method

    for example, clicking the submit button should invoke OrderForm.submitForm()

    many ways to handle an event, but the goal is always the same:

  • map user input to a method invocation on an arbitrary object
  • OrderForm Class Skeleton

    our example is an order form with a single button

    the form is represented by a custom OrderForm class

    the OrderForm class:

  • creates a Submit button to accept user input
  • creates a status text field for output
  • defines OrderForm.submitForm() to submit the form
  • OrderForm code listing:

    import mx.controls.Button; class OrderForm { private var submitBtn:Button; private var status:TextField; public function OrderForm (target:MovieClip, depth:Number) { // Create submit button. submitBtn = target.createClassObject(Button, "submit", depth); submitBtn.label = "Submit"; // Create status text field. target.createTextField("status", depth + 1, 0, submitBtn.height + 10, 300, 200); status = target.status; status.border = true; } // Submits the form. public function submitForm ():Void { status.text += "Now submitting form...\n"; submitBtn.enabled = false; } }

    now let's see how to connnect submit button clicks to OrderForm.submitForm() calls

    event handler functions

    technique #1: define an event handler function on the component

    event handler function takes the following form:

    componentInstance.eventNameHandler = function (e:Object) {...}

    eventName is the event being handled

    e is an object containing information about the event

    for example, e.target is a reference back to the component that broadcast the event

    so, to handle the our submit button's click event we use this basic approach:

    submitBtn.clickHandler = function (e:Object) { }

    inside the function, we want to invoke submitForm(), so we try this:

    submitBtn.clickHandler = function (e:Object) { submitForm(); }

    but that doesn't work because a function nested in a method cannot access the methods or properties of the class within which it is nested

    but, local varaibles are accessible to nested functions

    hence, we can give clickHandler() access to the current object's methods by storing the current object in a variable

    var theForm:OrderForm = this;

    so here's the entire event handler function:

    var theForm:OrderForm = this; submitBtn.clickHandler = function (e:Object) { theForm.submitForm(); }

    and here's what it looks like in the context of our class:

    class OrderForm { private var submitBtn:Button; private var status:TextField; public function OrderForm (target:MovieClip, depth:Number) { submitBtn = target.createClassObject(Button, "submit", depth); submitBtn.label = "Submit"; target.createTextField("status", depth + 1, 0, submitBtn.height + 10, 300, 200); status = target.status; status.border = true; // Event Handler Function var theForm:OrderForm = this; submitBtn.clickHandler = function (e:Object) { theForm.submitForm(); } } public function submitForm ():Void { status.text += "Now submitting form...\n"; submitBtn.enabled = false; } }

    event handler functions discouraged

    macromedia officially discourages the use of event handler functions

    event handler functions are limited: only one handler can be defined at a time

    only one handler means the click event cannot cause multiple separate program responses (e.g., a sound, an animation, and a form submission)

    event handler functions supported for legacy reasons: v1 components used event handler functions

    event listener functions

    exactly like event handler functions except more than one can respond to the same event

    to create an event listener function, define a standalone function, like this:

    function submitClickHandler (e:Object):Void { theForm.submitForm(); }

    then invoke addEventListener() to register the function for the desired event

    componentInstance.addEventListener(eventName, handlerFunction);

    for example:

    submitBtn.addEventListener("click", submitClickHandler);

    later, if desired, stop handling the event with:

    submitBtn.removeEventListener("click", submitClickHandler);

    here's what an event listener function looks like in the context of our class:

    class OrderForm { private var submitBtn:Button; private var status:TextField; public function OrderForm (target:MovieClip, depth:Number) { submitBtn = target.createClassObject(Button, "submit", depth); submitBtn.label = "Submit"; target.createTextField("status", depth + 1, 0, submitBtn.height + 10, 300, 200); status = target.status; status.border = true; // Event Listener Function var theForm:OrderForm = this; function submitClickHandler (e:Object):Void { theForm.submitForm(); }; submitBtn.addEventListener("click", submitClickHandler); } public function submitForm ():Void { status.text += "Now submitting form...\n"; submitBtn.enabled = false; } }

    listener objects

    event listener functions are still less flexible than event listener objects

    just like listener functions, objects can register to receive events from a component

    when an event occurs, the component invokes the appropriate event method on the registered object

    event methods match the name of the event, for example:

  • click() for the "click" event
  • change() for the "change" event
  • itemRollOver() for the "itemRollOver" event
  • benefits of listener object over listener function:

  • object has direct access to its class's properties and methods
  • object is an instance of a class that fits logically into the OOP structure
  • typed listener objects

    a typed listener object is an instance of a class designed to handle an event

    the class defines a method by the name of the event being handled

    an instance of the class is registered to handle the component event

    for example, this class handles a submit button click:

    class SubmitButtonHandler { private var form:OrderForm; public function SubmitButtonHandler (theForm:OrderForm) { form = theForm; } public function click (e:Object):Void { form.submitForm(); } }

    our OrderForm class registers a SubmitButtonHandler as follows:

    submitBtn.addEventListener("click", new SubmitButtonHandler(this));

    of all v2 event handling techniques, typed listener objects require the most effort to create

    but this approach is cleanest because:

  • event-handling code is encapsulated
  • event-handling code becomes exposed as part of the API
  • event-handling code is easier to find, change, replace, and document
  • current object listener

    in simple applications, a typed event object might be overkill

    instead, the current object can register to handle events directly

    for example, our OrderForm class could define a click() method:

    private function click (e:Object):Void { submitForm(); }

    then OrderForm would register itself as a click event listener:

    submitBtn.addEventListener("click", this);

    here's the code in context:

    class OrderForm { private var submitBtn:Button; private var status:TextField; public function OrderForm (target:MovieClip, depth:Number) { submitBtn = target.createClassObject(Button, "submit", depth); submitBtn.label = "Submit"; target.createTextField("status", depth + 1, 0, submitBtn.height + 10, 300, 200); status = target.status; status.border = true; // Current object listener submitBtn.addEventListener("click", this); } public function submitForm ():Void { status.text += "Now submitting form...\n"; submitBtn.enabled = false; } private function click (e:Object):Void { submitForm(); } }

    multiple event source conflicts

    when a single class handles events for multiple components, event name conflicts can occur

    for example, suppose our form has a "submit" button and a "reset" button:

    // Current object listener submitBtn.addEventListener("click", this); resetBtn.addEventListener("click", this);

    both button events invoke the same click() method!

    to distinguish the buttons, check the event source via the event object's target property:

    private function click (e:Object):Void { if (e.target == submitBtn) { submitForm(); } else if (e.target == resetButton) { resetForm(); } }

    but above code is clumsy and hard to read when many components are involved

    generic listener objects

    generic object listeners help avoid multiple event source conflicts without added effort of creating typed event listener classes

    create an Object instance:

    var clickHandler:Object = new Object();

    define an event method directly on it:

    clickHandler.click = function (e:Object):Void { theForm.submitForm(); };

    (above code is only allowed because Object class is dynamic)

    register the instance to handle the desired event:

    submitBtn.addEventListener("click", clickHandler);

    the generic listener object in context:

    class OrderForm { private var submitBtn:Button; private var status:TextField; public function OrderForm (target:MovieClip, depth:Number) { submitBtn = target.createClassObject(Button, "submit", depth); submitBtn.label = "Submit"; target.createTextField("status", depth + 1, 0, submitBtn.height + 10, 300, 200); status = target.status; status.border = true; // Generic Event Listener Object var clickHandler:Object = new Object( ); clickHandler.click = function (e:Object):Void { theForm.submitForm(); }; submitBtn.addEventListener("click", clickHandler); } public function submitForm ():Void { status.text += "Now submitting form...\n"; submitBtn.enabled = false; } }

    the case for a Delegate

    all of the event handling techniques we've seen so far do the same thing:

  • invoke OrderForm.submitForm() in response to a button click
  • in other words, they "delegate" the event to the submitForm() method

    in ellipsis (flash updater, 7.2), macromedia formalized the concept of delegation with a new class: mx.utils.Delegate

    Delegate.create() forwards an event to a particular method

    here's how to use Delegate.create() to handle our submit button click:

    submitBtn.addEventListener("click", Delegate.create(this, submitForm));

    Delegate.create() in context:

    class OrderForm { private var submitBtn:Button; private var status:TextField; public function OrderForm (target:MovieClip, depth:Number) { submitBtn = target.createClassObject(Button, "submit", depth); submitBtn.label = "Submit"; target.createTextField("status", depth + 1, 0, submitBtn.height + 10, 300, 200); status = target.status; status.border = true; submitBtn.addEventListener("click", Delegate.create(this, submitForm)); } public function submitForm ():Void { status.text += "Now submitting form...\n"; submitBtn.enabled = false; } }

    Delegate is the most convenient event handling technique, and is quite clean

    event architecture weaknesses

    no matter which technique you choose, all v2 component events suffer two important weaknesses

    1) event listeners are not typed

  • any type of object can register for an event
  • no guarantee that the listener defines the method required to handle the event
  • events broadcast by a source are not represented in code (i.e., no well-known event manifest)
  • 2) event objects are not typed

  • no compiler error if an event object is misused
  • for example, the typo e.trget does not cause an error
  • information offered by an event object unknown at compile time
  • supported properties and methods can only be found via for-in at runtime, or in docs or component source code
  • summary

    as a best practice, handle events with either mx.utils.Delegate or typed listener objects

    when creating event-broadcasting classes, consider using Java-style delegation event model for better type safety