overview

learn how to implement events for a class

start by building a general event system

then apply the system to an example

java's delegation event model

event broadcasting system in java is called "delegation event model"

we'll apply that system to actionscript

participants

three participants in the event delegation model:

  • event source: class that broadcasts events
  • event listeners: classes that receive event notifications
  • event object: class that describes the event
  • general structure

    event occurs (e.g., mouse click, ending of a game, new user joins a chat...etc)

    event source broadcasts event by invoking an agreed-upon method on all event listeners

    event object is passed to agreed-upon method as an argument

    event listeners respond as they see fit (e.g., submit a form, show a game over screen, display a welcome message to the new chat user)

    the event source

    the event source includes two classes:

  • the class that broadcasts events (event source is user-defined)
  • EventListenerList: utility to manage event listeners
  • EventListenerList implemenatation

    EventListenerList resides in "event" pacakge

    class event.EventListenerList { }

    EventListenerList also imports the contents of the event package

    import event.*;

    array of listener objects stored in instance property, listeners

    private var listeners:Array;

    constructor creates empty listeners array:

    public function EventListenerList () { listeners = new Array(); }

    addObj() method adds a listener to the listeners array

    public function addObj (l:EventListener):Boolean { // Search for the specified listener. var len:Number = listeners.length; for (var i = len; --i >= 0; ) { if (listeners[i] == l) { return false; } } // The new listener is not already in the list, so add it. listeners.push(l); return true; }

    removeObj() method removes a listener from the listeners array

    public function removeObj (l:EventListener):Boolean { // Search for the specified listener. var len:Number = listeners.length; for (var i:Number = len; --i >= 0; ) { if (listeners[i] == l) { // We found the listener, so remove it. listeners.splice(i, 1); // Quit looking. return true; } } return false; }

    getListeners() method returns a copy of the listener list, used to broadcast an event

    public function getListeners ():Array { // Return a copy of the list, not the list itself. return listeners.slice(0); }

    the event object

    the event object includes two classes:

  • EventObject, a base class for all event objects
  • an EventObject subclass, whose properties and methods describe a specific event
  • the subclass is user-defined, per event implementation

    EventObject implementation

    EventObject resides in "event" pacakge

    class event.EventObject { }

    EventObject stores a reference to the event source in a property, source:

    private var source:Object;

    EventObject is passed event source reference at construction time

    public function EventObject (src:Object) { source = src; }

    getSource() method returns the event source

    public function getSource ():Object { return source; }

    event source reference used by event listeners to:

  • modify the source
  • distinguish among multiple sources for one event name
  • subclasses of EventObject will add methods to retrieve event-specific information

    the event listener

    event listener includes three participants:

  • the EventListener interface
  • a subinterface that lists the event source's event methods
  • an event listener class that implements the subinterface
  • the EventListener is a "marker interface":

  • it defines no methods
  • used for organizational purposes only, so that sub interfaces can be identified as part of the delegation event model system
  • EventListener implementation

    source code for EventListener interface

    interface event.EventListener { }

    events are listed in a subinterface

    for example, suppose OrderForm broadcasts two events: onSubmit() and onReset()

    corresponding methods are defined in an interface, as follows:

    import event.EventListener; interface OrderFormListener extends EventListener { public function onSubmit (e:OrderFormEvent):Void; public function onReset (e:OrderFormEvent):Void; }

    then, a class that wants to register for OrderForm events must implement OrderFormListener:

    class OrderGUI implements OrderFormListener { public function onSubmit (e:OrderFormEvent):Void { // Code to display submission status on screen goes here... } public function onReset (e:OrderFormEvent):Void { // Code to clear form contents goes here... } }

    NightSky: an example

    now let's use D.E.M. in an example

    a night sky with animated shooting stars

    shooting stars appear randomly

    participants:

  • util.Randomizer: event source. triggers an event when it's time to display a shooting star
  • util.RandomizerListener: interface listing Randomizer events
  • util.RandomizerEvent: contains the time since the last shooting star
  • NightSky: listens for Randomizer events, implements RandomizerListener
  • NightSky architecture

    RandomizerListener implementation

    RandomizerListener defines a single method, onRandomAction():

    import event.*; import util.*; interface util.RandomizerListener extends EventListener { public function onRandomAction (e:RandomizerEvent):Void; }

    parameter, e, must be a RandomizerEvent object

    guarantees that the event object will be used correctly

    NightSky implements RandomizerListener

    RandomizerEvent implementation

    RandomizerEvent transfers information from event source (Randomizer) to event listeners (NightSky)

    specifically, RandomizerEvent.getTimeSinceLast() tells NightSky when the last shooting star occurred

    class util.RandomizerEvent extends EventObject { // The number of milliseconds since the last event was broadcast. private var timeSinceLast:Number; public function RandomizerEvent (src:Randomizer, timeSinceLast:Number) { // Always pass event source to superclass constructor! super(src); // Record the time since the last event, as specified // by Randomizer. this.timeSinceLast = timeSinceLast; } public function getTimeSinceLast ():Number { return timeSinceLast; } }

    Randomizer implementation

    Randomizer uses setInterval() to check for random events

    because event broadcasting is our focus, we'll ignore the code that is not directly related to event broadcasting

    Randomizer stores an EventListenerList instance in a property, listenerList:

    private var listenerList:EventListenerList;

    the EventListenerList instance is created in Randomizer's constructor

    listenerList = new EventListenerList();

    to register to receive events, a class invokes addRandomizerListener():

    public function addRandomizerListener (l:RandomizerListener):Boolean { return listenerList.addObj(l); }

    to stop receiving events, a class invokes removeRandomizerListener():

    public function removeRandomizerListener (l:RandomizerListener):Boolean { return listenerList.removeObj(l); }

    notice that only RandomizerListeners can register for Randomizer events!

    because all event listeners must implement RandomizerListener, all are guaranteed to define onRandomAction()

    to broadcast the "random action" event, Randomizer uses fireOnRandomAction():

    private function fireOnRandomAction (elapsed:Number):Void { // Create an object to describe the event. var e:RandomizerEvent = new RandomizerEvent(this, elapsed); // Get a list of the current event listeners. var listeners:Array = listenerList.getListeners(); // Broadcast the event to all listeners. for (var i:Number = 0; i < listeners.length; i++) { listeners[i].onRandomAction(e); } }

    check() determines whether a random event has occurred; if so, broadcasts the event

    private function check (odds:Number):Void { // Local variables. var rand:Number = Math.floor(Math.random() * odds); var now:Date = new Date(); var elapsed:Number; // If the random event occurs... if (rand == 0) { // Determine the elapsed time since the last event. elapsed = now.getTime() - lastEventTime.getTime(); lastEventTime = now; // Fire the event. fireOnRandomAction(elapsed); } }

    NightSky implementation

    NightSky class listens for Randomizer class's events

    hence, NightSky must implement RandomizerListener interface

    class nightsky.NightSky implements RandomizerListener { }

    NightSky creates sky background and shooting stars by attaching movie clips

    NightSky defines the following properties:

  • target: the movie clip that will hold the sky background
  • private var target:MovieClip;

  • sky_mc: a reference to the sky background movie clip
  • private var sky_mc:MovieClip;

  • skyDepth: the depth at which to attach the sky clip
  • private var skyDepth:Number = 0;

  • startDepth: the depth, in sky_mc, to create shooting stars
  • private var starDepth:Number = 0;

    NightSky constructor receives sky's parent and sky/star depth as parameters

    public function NightSky (target:MovieClip, skyDepth:Number, starDepth:Number) { this.target = target; this.skyDepth = skyDepth; this.starDepth = starDepth;

    constructor also calls makeSkyBG():

    makeSkyBG(); }

    makeSkyBG() simply attaches the sky background clip

    private function makeSkyBG ():Void { sky_mc = target.attachMovie("skybg", "skybg", skyDepth); }

    when an event occurs, onRandomAction() executes

    onRandomAction() calls makeShootingStar(), which causes a star to appear on screen:

    public function onRandomAction (e:RandomizerEvent):Void { trace("New shooting star! Time since last star: " + e.getTimeSinceLast()); makeShootingStar(); }

    the time since the last shooting star is retrieved via e.getTimeSinceLast()

    makeShootingStar() source code:

    private function makeShootingStar ():Void { // Create the shooting star in the sky movie clip. sky_mc.attachMovie("shootingstar", "shootingstar" + starDepth, starDepth); // Randomly position the shooting star. sky_mc["shootingstar" + starDepth]._x = Math.floor(Math.random() * target.skybg._width); sky_mc["shootingstar" + starDepth]._y = Math.floor(Math.random() * target.skybg._height); // Put the next shooting star on a higher depth. starDepth++; }

    putting it all together

    to use NightSky in an application, we first import the necessary packages:

    import nightsky.*; import util.*;

    next, we create a NightSky instance:

    var sky:NightSky = new NightSky(bg_mc, 1, 0);

    then we create a Randomizer instance:

    var starRandomizer:Randomizer = new Randomizer(1000, 3);

    finally, we register the NightSky instance to receive events from the Randomizer instance:

    starRandomizer.addRandomizerListener(sky);

    summary

    the delegation event model provide event handling with complete datatype safety

    all participants in the system must follow the type rules, so errors are minimized