Tutorials, extensions, and source files for ActionScript, Flash, and other Adobe products.

 

Pages: 1 | 2 | 3 | 4 | 5

Events

Events indicate when something happen.  Clicking on a button indicates a "click" event.  When an external text file loads into the Flash player, there's a "complete" event indicating that the contents of that text file have been completely read into the player.

Virtually all ActionScript is executed based on events.  Whether it's an event handler used to run code when a button is clicked or just commands written on the timeline in frames (executed during the event of the Flash player displaying that frame), you're writing code that reacts to events.

Events then

For non frame-based actions, most events (specifically mouse and keyboard events) were originally handled within on([event]){} code blocks placed on button symbols.

// Starting with Flash 2 and available for
// ActionScript 1 and 2, a press event
// placed on a selected button symbol
on(press) {
	// play timeline in which this button exists
	play();
}

Events used with on (some introduced after Flash 2) include:

  • press
  • release
  • releaseOutside
  • rollout
  • rollover
  • dragOut
  • dragOver
  • keyPress ["whichKey"]

When Flash 5 came around, ActionScript featured a new kind of event handler that could be added, not to button symbols, but movie clips. This was done using the onClipEvent([event]){} code block.

// Flash 5; ActionScript 1
// Placed on a selected movie clip symbol
onClipEvent(enterFrame) {
	// move this movie clip 5 frames to
	// the right every time the Flash player
	// draws a frame based on the frame rate
	_x += 5;
}

With Flash 5 you could also load XML into the Flash player with the new (at the time) XML object.  In order for a programmer to know when the XML has been loaded and parsed, an onLoad event handler would be defined for the XML object.  This handler was in the form of a dynamic function definition that could be defined, deleted and changed for the XML object at any time.

// Flash 5; ActionScript 1
// XML onLoad handler
var myXML = new XML();

// event handler called when
// XML has been loaded into myXML
myXML.onLoad = function(success){
	// if XML has been loaded and parsed
	// successfully, success is true
	if (success){
		trace(this); // trace XML
	}
}

// load the XML into the XML object when
// loaded and parsed, onLoad will be called
myXML.load("xmlFile.xml");

Flash MX (6) greatly improved event handling. Not only were on code blocks allowed to be used on both movie clips and buttons (as opposed to just buttons) but, Flash MX also allowed dynamic event handlers to be created for buttons and movie clips in the same way the onLoad event handler was defined for XML objects in Flash 5.  Whereas on and onClipEvent were static and couldn't be changed during runtime, using these function-based event handlers meant an event could be deleted or redefined as the Flash movie was playing, something especially useful for onEnterFrame events used to handle animation.

// Flash MX; ActionScript 1
// onEnterFrame event handler
// assigned for for a movie clip instance
myBall_mc.onEnterFrame = function(){
	// move this movie clip to the right
	// by 5 pixels every frame
	this._x += 5;

	// if the x position of this movie clip
	// is greater than 200, delete the event handler
	if (this._x > 200){
		delete this.onEnterFrame;
	}
}

More importantly, Flash MX introduced a new concept to ActionScript called listeners.  Listeners are objects with methods (and, later, can include just methods or functions) that listen for and have a handler called as a result of an event. The key concept behind listeners is that you can have more than one event handler associated with any one event.  With normal event handlers, like the onLoad event handler with the XML object, you can only define one onLoad function. If you define it again, you will be overwriting the old onLoad and replacing it with the new definition.  It was very difficult to have more than one event handler associated with a single event. Listeners solved that problem.

With listeners, instead of defining an event handler for the object receiving the event, you assigned the event handler to another object – any other object – and then that object was added as a listener of the object originally receiving the event.

// Flash MX; ActionScript 1
// Mouse listeners; more than one
// object/handler reacting to the same event
var handler1 = new Object();
handler1.onMouseDown = function(){
	trace("Handler 1");
}

var handler2 = new Object();
handler2.onMouseDown = function(){
	trace("Handler 2");
}

Mouse.addListener(handler1); 
Mouse.addListener(handler2);

// Clicking the mouse once results in the traces:
// Handler 1
// Handler 2

Since both handler1 and handler2 in the example above were listeners of the Mouse object, their onMouseDown methods were called as event handlers of the Mouse's onMouseDown event. If you ever wanted to remove an object from being an event listener, you could use removeListener().

Assigning listeners with addListener was a great concept but was limited to a few objects. They included:

  • Key
  • Mouse
  • Selection
  • Stage
  • TextField
  • FStyleFormat

Enter ActionScript 2 with Flash MX 2004 (7).  With ActionScript 2 came a new set of components that used an architecture known as V2 ("Version 2").  This architecture used a new kind of listener format which was similar to using addListener but instead of adding objects as listeners of other objects, you listened to specific events from other objects.  This used a new method called addEventListener that was part of a new (V2) class called EventDispatcher.  The addEventListener method accepted both an object with event handlers (or just an event handler function) as well as a name of the event you wanted to listen for.  The inclusion of the event prevented unnecessary events to be sent to objects that didn't have event handlers for them (e.g. handler1 and handler2 in the previous example received onMouseDown and onMouseUp events from the Mouse object even though they had no event handlers for them). Additionally, an event object was passed to the event handlers added as listeners when called for an event to provide information about the event occuring.

// Flash MX 2004; ActionScript 2
// button component event handlers 

// Listener object with event handler
var handlerObject:Object = new Object();
handlerObject.click = function(event:Object):Void {
	trace(event.type +": "+ this);
}

// Listener function
function handlerFunction(event:Object):Void {
	trace(event.type +": "+ this);
}

// assign to be listeners of 
// submit_button for the click event
submit_button.addEventListener("click", handlerObject); 
submit_button.addEventListener("click", handlerFunction);

// Clicking the submit_button component:
// click: _level0.submit_button
// click: [object Object]

Because this form of event handling was built into the V2 component architecture (the EventDispatcher class being part of that architecture), you really didn't see it much anywhere else in ActionScript outside of components.  The core language objects like Mouse and Key still used the older addListener method for handling events.  This all changed with ActionScript 3.

Events now

[TODO: fit in that root does not dispatch its own mouse events, event clean-up (oef running after removal)]

ActionScript 3 completely revamped the way events were handled in ActionScript.  There are two major themes that define ActionScript 3 events:

  1. Consistent event handling with a new, native EventDispatcher class
  2. Support for event propagation within display objects

Though the EventDispatcher class is not entirely new having existed with the V2 component architecture present in Flash MX 2004 components, with ActionScript 3 EventDispatcher has been updated and made a native part of the ActionScript language responsible for essentially all event handling.  What does this mean for events in ActionScript 3?

  • no on(event){}
  • no onClipEvent(event){}
  • no adding scripts on selected objects. Period.
  • no object.onEvent = function(){} (for native events)
  • no addListener
  • no object-based listeners (now only using methods/functions)

With ActionScript 3 all event handlers are associated with events using addEventListener and consist of a single function listener. With Events in ActionScript 3 you get the flexibility of listeners (more than one handler for any one event) and the efficiency of the V2 addEventListener (being able to single out events) plus an increase in performance as a result of the EventDispatcher class now being native to the Flash player.

Also, in ActionScript 2, when an event handler was called, it received an Object argument (of the type Object) containing some information about the event such as the type of event and any properties that might be specific to that event.  ActionScript 3 formalized this process now having pre-defined Event classes to be used within events instead of the generic objects used with ActionScript 2.  These event objects contain the event type, where the event originated, the current object receiving the event, as well as a few other properties used to describe the event that occurred.

Different kinds of events have different kinds of Event classes. The most basic event class is Event, but there are also other Event classes such as MouseEvent, KeyboardEvent, and many others.  These classes often also contain the event types as constant properties of the class.  The click event, for example, has an event type of "click" which is defined in the MouseEvent class as MouseEvent.CLICK.  The purpose of using these values instead of their string representations is that the ActionScript compiler will be able to warn you about spelling mistakes if you make them.

button.addEventListener("clikc", handler); // no error
button.addEventListener(MouseEvent.CLIKC, handler); // Error!

To summarize, event handling in ActionScript 3 means:

  • using addEventListener/removeEventListener
  • listening for event types stored in Event classes
  • using a function (not an object) event handler/listener
  • defining event handler/listeners to receive an argument of the type Event
// ActionScript 3 event handler
function clickSubmit(event:MouseEvent):void {
	gotoAndStop("submitFrame");
}

// add clickSubmit event handler as listener of the 
// click event received by submit_button instance
submit_button.addEventListener(MouseEvent.CLICK, clickSubmit);

Along with how event handlers are defined, you will also changes in how they work and how they are identified. Many event names have changed while other events have been added and even some removed. Here is a quick comparison of some common events between ActionScript 2 and ActionScript 3.

Common Interactive Events
ActionScript 2 ActionScript 3 Differences
onPress "mouseDown" (MouseEvent.MOUSE_DOWN)  
onRelease "mouseUp" (MouseEvent.MOUSE_UP), "click" (MouseEvent.CLICK) The click event is most like onRelease since it depends on the object first receiving a mouseDown event whereas mouseUp can occur regardless of what was pressed
onReleaseOutside "mouseUp" (MouseEvent.MOUSE_UP) This has been removed, but using the mouseUp event with the stage (because of propagation) will allow you to detect when the mouse has been released outside of the object originally pressed
onRollOver "mouseOver" (MouseEvent.MOUSE_OVER), "rollOver" (MouseEvent.ROLL_OVER) Two events can now be used for rolling over objects; mouseOver will propagate, rollOver will not
onRollOut "mouseOut" (MouseEvent.MOUSE_OUT), "rollOut" (MouseEvent.ROLL_OUT) Two events can now be used for rolling off of objects; mouseOut will propagate, rollOut will not
onDragOver "mouseOver" (MouseEvent.MOUSE_OVER), "rollOver" (MouseEvent.ROLL_OVER) Detecting a "drag" will have to be done manually in combination with the mouseDown event.
onDragOut "mouseOut" (MouseEvent.MOUSE_OUT), "rollOut" (MouseEvent.ROLL_OUT) Detecting a "drag" will have to be done manually in combination with the mouseDown event.
mouseDown "mouseDown" (MouseEvent.MOUSE_DOWN) Now only works when the mouse is over the object dispatching the event (like onPress); normal objects cannot be made listeners of the Mouse to be able to recieve this event, only InteractiveObject instances can
mouseUp "mouseUp" (MouseEvent.MOUSE_UP) Now only works when the mouse is over the object dispatching the event (like onRelease but not requiring that the object be clicked first); normal objects cannot be made listeners of the Mouse to be able to recieve this event, only InteractiveObject instances can
mouseMove "mouseMove" (MouseEvent.MOUSE_MOVE) Now only works when the mouse is over the object dispatching the event; normal objects cannot be made listeners of the Mouse to be able to recieve this event, only InteractiveObject instances can
onKeyDown "keyDown" (KeyboardEvent.KEY_DOWN) Object must now have focus to receive this event; normal objects cannot be made listeners of the Key to be able to recieve this event, only InteractiveObject instances can
onKeyUp "keyUp" (KeyboardEvent.KEY_UP) Object must now have focus to receive this event; normal objects cannot be made listeners of the Key to be able to recieve this event, only InteractiveObject instances can

You may notice a theme of reducing the number of events needed to work with in ActionScript. The older onPress and onMouseDown events have now been consolidated into one mouseDown event in ActionScript 3. Similarly, you are seeing events that are less global and more specific to a related object. The mouseMove event, for example, now only works when the mouse is over the object handling the event. Similarly, the key events, too, only respond to the objects which currenly have focus.

Other behaviors regarding mouse events and display objects have also changed. In ActionScript 2, for example, adding any onPress, onRelease, or similar "on" mouse event handler to a movie clip would cause that movie clip to display a hand (or finger) cursor when the mouse was over that movie clip. This is no longer the case with ActionScript 3. That hand cursor will now not be visible unless the display object's buttonMode and useHandCursor properties are true (useHandCursor is true by default but buttonMode is not). Setting buttonMode to true will also enable a focused object to receive a click event if the space bar or the Enter key is pressed on the keyboard.

// Show the hand cursor for myButton
// and allow clicking through Spacebar and Enter
myButton.buttonMode = true;

Similarly, by defining the same event handlers in ActionScript 2 that provided the hand cursor, you were also indicating to the Flash player that the movie clip was "mouse enabled" or that it is necessary for that instance to intercept and handle mouse events. Movie clips without those "on" mouse event handlers ignored mouse events and let them pass on through to any mouse enabled movie clip instances beneath them. With ActionScript 3, all mouse capable display objects (any instance on the screen that inherits from the InteractiveObject class, including Sprites and MovieClips) will be mouse enabled by default and prevent events from reaching instances arranged below them, even if they have no event handlers defined for them. To change this, you have to set the object's mouseEnabled property to false.

// Allow objects beneath the overlay
// instance to receive mouse events
overlay.mouseEnabled = false;

There's a similar mouseChildren property which relates to child objects within a display object container instance. When mouseChildren is set to false, child objects will not receive mouse events but the main parent object will (assuming mouseEnabled is true). This becomes useful when dealing with event propagation.

// Prevent the child objects in the
// window instance from receiving mouse events
window.mouseChildren = false;

[TODO: more about the Event object]

Example: Reacting to the Mouse

Most user interaction with Flash revolves around mouse events. This example demonstrates how mouse events can be used with the vector drawing API to make a drawing board application. It will rely on mouse clicking as well as dragging the mouse around on the screen (mouseMove) to make strokes on the drawing canvas.

To use this application, a user will first select a color from a collection of colors on the left side of the screen, then click and drag in the drawing board box to the right of the screen. As they move their mouse, lines of the selected color will be drawn on the board. To clear an existing drawing, the Clear button below the color selections can be clicked.

For the colors, this example will use a little bit of a shortcut. Instead of defining color variables in each color selection, an array will be used to contain all the color values being used. Those colors will then be associated with the selectable color movie clips by matching the movie clip's position in the display list with the color at that position in the array. The real benefit here, for all the lazy people out there, is that this will prevent the need to assign instance names to each of the wells since each well will be referenced and assigned a color using getChildAt(). Their positions within the display list will be used both to assign colors as well as to retrieve the color when that color is selected.

Note: Dynamic Creation

The process of creating the color selections could also be done dynamically which would also, at least in terms of authoring set up, prevent the need to assign instance names. However, in this example, the selections are added to the screen manually and using their index within the display list is used to prevent the need to reference them by name.

Because there are other elements on the screen, these selectable color movie clips will need their own display list. For that, the colorCollection movie clip was created to contain them. Each selection also contains a colorBlock movie clip (to show their color) and a border movie clip that will be highlighted when that color is selected. Each color selection within colorCollection, however, remains unnamed.


Figure: Color selections within the drawing application

There will be some initialization steps where colors in an array will be assigned to each selection. That selection, and the color it references, will be saved to variables for when the user draws to the screen or changes the selection from the current color to the next.

Steps:

  1. In the source files, open DrawingBoardApplication.fla in the Events directory. This file consists of one empty layer named ActionScript and an Interface layer with the contents of the drawing application's interface.
  2. Select the first frame in the ActionScript layer and open the Actions panel.
  3. Add the following script to the Actions panel.
// A variables to contain the
// selected color and color clip
var selectedColor:uint;
var selectedColorClip:MovieClip;
// Colors to be used in the selections
var colors:Array = [
	0x000000, 0xFFFFFF,
	0xFF0000, 0x00FF00,
	0x0000FF, 0xFF00FF,
	0xFFFF00, 0x00FFFF,
];

This sets up or current color (to be referenced when drawing), the currently selected color movie clip, and the colors to be assigned to the different color selections. Note that the selectedColor variable has a type of uint. Normal RGB color values such as 0xFFFFFF are numeric values that can fit into the positive values of the int datatype. However, when dealing with RGBA, or Red, Green, Blue, and Alpha, you start seeing color values which exceed the positive range within ints. Unsigned integers (uint) can account for these values which is why you will often see colors typed as uint.

Next a function is needed to make the connection between the colors and the selectable color clips. This function will need to loop through the children of the colorCollection movie clip and set the colorBlock of each of those children to the related color in the colors array. This function will also set up the interaction that selects the color when each of these clips are clicked using addEventListener(). This function can be called defineColorSelections().

  1. Add the following script to the Actions panel.
// Adds colors to the color movie clips
// in the colorCollection clip and sets
// them up with click listeners
// for selecting a current color
function defineColorSelections():void {
	
	// set up some variables to be used
	var colorClip:*;
	var colorForm:ColorTransform;
	
	// loop through all the child
	// instances within colorCollection
	// using this loop, instance names do
	// not have to be given for each child
	var i:int = colorCollection.numChildren;
	while(i--) {
		
		// get the current child in the loop
		colorClip = colorCollection.getChildAt(i);
		
		// set the color of the colorClip's
		// colorBlock instance to the color
		// specified in the colors array
		// at this same position.
		colorForm = colorClip.colorBlock.transform.colorTransform;
		colorForm.color = colors[i];
		colorClip.colorBlock.transform.colorTransform = colorForm;
		
		// set up an event handler so that when
		// the colorClip is clicked, it will set
		// itself to be the selected color
		colorClip.addEventListener(MouseEvent.CLICK, selectColor);
	}
}

Notice at the end of this function, addEventListener was used with colorClip - the current child movie clip of colorCollection in the loop - to set up an event handler called selectColor with the click event. This means that whenever one of those child clips is clicked, the selectColor event handler function is run. The sole purpose of this handler is to call a function called setCurrentColor which will set the current color based on the selection clicked

The setCurrentColor function will do a couple of things. For one, it sets the selectedColor property defined earlier to be the color related to the clicked movie clip. It will also apply a highlight to the border of that clip, but not before removing any highlighted border of the last selection. Here is where the selectedColorClip comes into play. By keeping the selected clip within that variable, it can be referred to later when its highlight need to be removed in favor of a newly selected color movie clip.

  1. Add the following script to the Actions panel.
// Event handler for selecting a color
function selectColor(event:MouseEvent):void {
	// the currentTarget property of the event
	// object represents the color selection clicked
	// or the object from which addEventListener was
	// called; pass it to setCurrentColor to select it
	setCurrentColor(event.currentTarget);
}

// Sets the passed color selection clip
// as the currently selected color
function setCurrentColor(colorClip:*):void {
	
	// if there is a selected color clip already
	// remove the highlight from its border
	// be assigning a default ColorTransform
	if (selectedColorClip) {
		selectedColorClip.border.transform.colorTransform = new ColorTransform();
	}
	
	// assign the new selected color clip
	selectedColorClip = colorClip;
	
	// get the selected color for the new clip 
	// by referencing the color in the colors
	// array that has the same index as the
	// clip in the display list (since they match)
	var index:int = colorCollection.getChildIndex(selectedColorClip);
	selectedColor = colors[index];
	
	// add a highlight to the border
	// of the new selection by adding some
	// rgb to the black color using ColorTransform
	var lighter:ColorTransform = new ColorTransform(1, 1, 1, 1, 120, 160, 200, 0);
	selectedColorClip.border.transform.colorTransform = lighter;
}

The selectColor function here does one thing; calls setCurrentColor which is defined with one parameter, colorClip. That clip needs to be the color selection movie clip clicked. The selectColor function is able to access this through the currentTarget property of the event object. This property represents the object on which addEventListener was used - the object receiving the event, or in this case, one of the children in colorCollection.

In setCurrentColor you can see where the last selectedColorClip has its highlight removed. Then the variable gets reassigned with the value of colorClip which then get its own highlight through its colorTransform property. The selectedColor property is assigned based on that new clips position within the colorCollection's display list.

And with that, you have a functioning color selection. At this point, it's far enough along to test out - setting up and selecting colors at least, not drawing anything.

  1. Add the following script to the Actions panel
// Set up color selections to have the
// colors defined in the colors array 
defineColorSelections();
// Set the current selection to
// be the first clip in colorCollection
setCurrentColor(colorCollection.getChildAt(0));
  1. Test your movie. You should see the following


Figure: Drawing Application with color definitions

The next step is making it possible to draw with these wonderful color selections. For that we will use the drawingBoard movie clip along with the mouseDown, mouseMove, and mouseUp (and rollOut) events. When the mouse is pressed on the drawingBoard instance, a drawing is started. When this is the case and the mouse moves, drawings are made. When the mouse is released (or rolls off the drawing board), drawing stops.

The act of drawing happens during the mouseMove event. To make sure that drawing only occurs then after the mouse has been pressed, that event handler will only be added as a listener to the mouseMove event during the mouseDown event. Similarly, when the mouse is released or rolls off the drawingBoard instance, the event handler would be removed as a listener to stop additional drawing.

Remember, in ActionScript 3, the mouseMove event only occurs while within the object receiving the event, unlike ActionScript 2 where the onMouseMove event occurred for any time the mouse was moved within the player window. This means no boundaries need to be coded if drawing is restricted to the mouseMove event of the drawingBoard movie clip.

Along with defining the event handlers for drawing, one additional step is needed: creating a place to draw. There's two problems with drawing directly within the drawingBoard instance. The firs is that the graphics layer of a display object (those that have it) is below all of its other content. The drawingBoard movie clip already consists of a white square. Any dynamic drawing created within that instance would be placed below that square and would not be visible. Secondly, by drawing in the object you are using as boundaries, its possible that the drawing itself could create graphics that extend the boundaries beyond their original extents making it possible to create an ever expanding drawing canvas.

We don't want that, so we can make a dynamic Sprite instance called canvas that will contain the drawing's graphics. This will be placed directly over the drawingBoard instance and all drawings created from the related events will be drawn in the canvas's graphics object.

  1. Add the following script to the Actions panel
// Create a shape to draw in and
// position it over the drawingBoard
var canvas:Shape = new Shape();
canvas.x = drawingBoard.x;
canvas.y = drawingBoard.y;
addChild(canvas);

// This starts the drawing process
// for when the user clicks on the 
// drawing board on the screen
function startDrawing(event:MouseEvent):void {
	
	// set up the drawing API with that
	// color starting at the mouse location
	canvas.graphics.lineStyle(1, selectedColor);
	canvas.graphics.moveTo(canvas.mouseX, canvas.mouseY);
	
	// create an event listener for the mouse
	// moving so that it can draw lines in the canvas
	drawingBoard.addEventListener(MouseEvent.MOUSE_MOVE, whileDrawing);
}

// When the user has clicked the drawing
// board, this function will be called when
// the mouse moves to draw lines in canvas
function whileDrawing(event:MouseEvent):void {
	// draw a line in the canvas to
	// the new mouse location
	canvas.graphics.lineTo(canvas.mouseX, canvas.mouseY);
}

// When drawing has stopped, this function
// removes the event calling userDrawing
function stopDrawing(event:MouseEvent):void {
	// prevent mouse movements from drawing
	// anymore lines until startDrawing
	// is called again during mouseDown
	drawingBoard.removeEventListener(MouseEvent.MOUSE_MOVE, whileDrawing);
}

These events, or at least start startDrawing and stopDrawing, now need to be associated with the necessary events.

  1. Add the following script to the Actions panel
// Define event handlers
// drawingBoard event handlers control drawing
// on the drawing board (to canvas) based
// on the occurrences respective event types
drawingBoard.addEventListener(MouseEvent.MOUSE_DOWN, startDrawing);
drawingBoard.addEventListener(MouseEvent.MOUSE_UP, stopDrawing);
drawingBoard.addEventListener(MouseEvent.ROLL_OUT, stopDrawing);

Now, when the mouse is pressed down on drawingBoard, startDrawing makes whileDrawing a listener of the mouseMove event. The user will move the mouse and cause lines to be added to the canvas instance (based on selectedColor) until the mouse is released over drawingBoard or rolls out of drawingBoard at which point whileDrawing is removed as a listener of the mouseMove event and drawing stops.

About all that remains is the clear button, defined with the instancename clearButton. This button will use a click event to clear the graphics object in canvas. As a button symbol, the clear button will inherently have a hand cursor when the mouse is over it. To make the interface a little more consistent and to help indicate that the color selections are in fact interactive, they too should have a hand cursor. That can be added by setting the buttonMode property of the colorCollection instance to true.

  1. Add the following script to the Actions panel
// Clears the canvas of all drawings
function clearCanvas(event:MouseEvent):void {
	// clear the canvas graphics
	canvas.graphics.clear();
}

// clearButton handler clears the
// canvas when clicked 
clearButton.addEventListener(MouseEvent.CLICK, clearCanvas);

// Give the color selections a finger cursor
colorCollection.buttonMode = true;
  1. When you've completed adding the code, test your movie and start drawing. You should see something like the following (though possibly more visibly appealing)


Figure: Completed Drawing Application with drawing

Event Propagation

Also new is the support for event propagation for display objects.  Event propagation is a process where a single event travels through a hierarchy of objects as opposed to being constrained to the one originating object.  For example, in ActionScript 3, if you have a button within a movie clip and you click the button, that button will dispatch a click event.  Not only will the clicked button receive the click event, but also the movie clip in which it exists and that movie clip's parent, and its parent if it exists, and so on until there are no more parents for the event to reach.  This is a dramatic change from how events, especially button events like onPress, onRelease, onRollOver, etc. worked for ActionScript 2.  Then, any parent object assigned with an event handler for those events would prevent those events from reaching the children.


Figure: ActionScript 3 events can now propagate through to parent instances

This behavior may take some time to get used to as those events that do propagate (not all events do) may fire unexpectedly as children of your target object dispatch them. If you want to see if an event has originated from the object you're listening to, one thing you can do is compare the target and currentTarget properties of the Event object passed to your event handler.  The target property indicates the object where the event originated and the currentTarget property references the object currently receiving the event, or the object you used addEventListener with.

// Detecting when an event hasn't been propagated
function handler(event:Event):void {
	if (event.target == event.currentTarget) {
		// event came from object
		// addEventListener was used with and
		// is not result of propagation
	}
}

You can also use the eventPhase property.  This property specifies the phase of the current event, or its position within the hierarchy of objects through which it propagates.

Propagation occurs in 3 phases: capture phase, target phase, and bubbling phase.  The capture phase is when an event starts at the top of a display list with the top-most parent and works its way down through the children until reaching, but not including the event target where the current event originated.  When at the target, it’s the target phase.  Though the capture phase can consist of many display objects, the target phase consists of only one.  Between the capture and target phases, you have all the display objects that are affected by a propagated event.  After the target, however, the event doesn't stop and continues to work back up the hierarchy up through all the parents again until there are no more left in what is known as the bubbling phase.  This means, each non-target object affected by a propagating event receives that event twice, once during the capture phase, and once during the bubbling phase.

When you use addEventListener to assign event handlers to listen for events, by default, you are listening for events only within the target and bubbling phases.  This means propagating events with the default addEventListener usage will start with the target and then be sent to each parent of that target.  If you want a handler to listen for events in the capture phase, for events reaching the parents before reaching the target, you have to specify to addEventListener that you want to use the capture phase by passing true for its third parameter, the useCapture parameter.  If you want to listen for events for all phases, you have to use addEventListener twice, once using the default useCapture (false) and again with useCapture set to true.

// Default phases: target and bubbling
object.addEventListener(MouseEvent.CLICK, onClick);
// Also listen for events in capture phase
object.addEventListener(MouseEvent.CLICK, onClick, true);

Note that in setting useCapture for addEventListener to true, you will not receive events for the target instance, only the events for its parents.  The target instance's events would be handled through a listener added with useCapture as false since that usage handles both the target and bubble phases.


Figure: Event phases through the display list hierarchy

Since the target phase of an event only occurs for the object instance where the event originated, as with comparing event.target and event.currentTarget, the event.eventPhase property can be used to see if your event handler is being called for the target instance.  It contains a number representing current phase of the event. These values are stored in more readable variables located in the EventPhase class as EventPhase.CAPTURING_PHASE, EventPhase.AT_TARGET, and EventPhase.BUBBLING_PHASE.

function handler(event:Event):void {
	if (event.eventPhase == EventPhase.AT_TARGET) {
		// event came from object
		// addEventListener was used with and
		// is not result of propagation
	}
}

[TODO: more?]

Example: TBD

[TODO: Buttons, oef]

Stage events

The stage's importance goes beyond just being a container for all display objects in a movie.  It also serves as the beginning and ending point for all propagated events.  All mouse clicks, even if the mouse is not over any object on the screen, will be received by the stage.  The same applies to keyboard events which would otherwise go to the focused object.  If no object is focused, however, only the stage will receive keyboard events.  This makes the stage a key object for global mouse and keyboard event detection.

One event in particular to be careful of is the mouseMove event.  This has changed in ActionScript 3.  Before the mouseMove event was triggered for any MovieClip (or Mouse listener) when the mouse moved, no matter where it was, just so long as it was within the Flash player window.  In ActionScript 3, the mouseMove event for display objects is only triggered when the mouse is over the display object.  If you need the mouseMove event to be more global as it was before, you need to listen to the stage.

// Create a listener for any mouse move 
// event within the Flash player
stage.addEventListener(MouseEvent.MOUSE_MOVE, anyMouseMove);
function anyMouseMove(event:MouseEvent):void {
	// mouse moved
}

[TODO: mouse up outside]

Example: Drag and drop

[TODO: drag and drop using stage events]

Keyboard Events

[TODO: complete; no Key.isDown, offer alternative]

Errors

One thing you may notice about ActionScript 3 how error prone it is or, rather, how error prone it perceives you to be.  You'll see a lot more errors, not only during compile, but also runtime.  ActionScript 3 is much less lenient in letting you get away with mistakes or code conflicts.  Whereas ActionScript 1 and 2 may silently fail or ignore many errors, ActionScript 3 will be sure to let you know something went wrong.

Synchronous Errors

Normal errors in code which occur as a code block is being executed are synchronous errors. When the Flash player encounters one of these errors in code, an error, or exception, is thrown.  At that point the Flash player will suspend all code in the current block and prevent it from continuing unless the exception is taken care of or caught.  To catch exceptions in ActionScript, you use a try..catch..finally statement. 

The try..catch..finally statement lets you try a block of code possible of throwing an error and react accordingly if an error occurs.  It consists of 2 or more blocks of code: an initial try block, consisting of the code that could throw an error, 1 or more catch blocks that catch errors of different types and run if that type of error is thrown, and an optional finally block which is run after the try and any catches whether or not an error occurs. Its format is as follows:

try {
	// potential error causing code
} catch (error:Error) {
	// error of type Error occurred
	// additional catch blocks may follow
} finally {
	// optional to follow try and catch blocks
}

If an exception is thrown within a try block, Flash will look through each catch block until a matching error type is found.  When a match is found, that code is run followed by the finally block if it exists.  Any additional code following a try..catch..finally statement will be executed as normal even if an error occurs because it was caught by the try..catch..finally statement.

Throw custom errors

Errors are not thrown exclusively by Flash. You also have the ability to throw errors in your own code if you so desire. To throw errors you would use the throw statement. The throw statement throws any value that precedes it (namely an Error object).  When thrown, the error will act just like any error and would require a try..catch..finally block to be correctly caught.

// Throwing your own errors in ActionScript
throw new Error("Error message");

Throwing your own errors can be a helpful debugging tool, especially as you work with more complicated applications.

Asynchronous Errors

Errors can also occur during asynchronous actions, such as loading external content, and take the form of events.  These errors cannot be caught with try..catch..finally statements.  Instead you have to create event handlers to handle and "catch" the error events. If you do not have an event listener assigned to an error event and that error occurs, the Flash player will inform you of the unhandled error event.

// creating listeners for error events handles
// asynchronous errors
target.addEventListener(ErrorEvent.TYPE, handler); 
function handler(event:ErrorEvent):void {
	// handle error
}

Creating Asynchronous Errors

If you want to invoke your own asynchronous errors, all you need to do is dispatch an event using dispatchEvent that is of the type ErrorEvent.  When an unhandled ErrorEvent reaches the Flash player when authoring in Flash, the output window will display the error.

// target object is an event dispatcher
// that needs to dispatch an error event
target.dispatchEvent(new ErrorEvent("type"));

Release and Debug Players

The release of the Flash player is available in two versions, the release and debug. The release player is the standard, public player. This player is what you will have installed for your browser(s) when you download and install Flash player from adobe.com. 

The debug player is an alternate player with additional debug information and capabilities embedded within it.  This makes the player a little heftier in file size (which is why its not preferred for distribution) but also provides popup dialogs for uncaught exceptions and unhandled asynchronous errors – something the public would rather not see when your Flash movie or application runs into a snag.  For developers, however, this player provides useful information when testing your movies.

You can find installers for both the release and debug players in the Players folder within your Flash CS3 (or older) install directory.

One thing to keep in mind is that just because the release player doesn't visually show an error when an exception is thrown, it doesn't mean that error line is simply ignored like with previous versions of ActionScript.  The code block where that error occurred will be exited and the remaining script ignored.

Example: Loading external text with error handling

To load text content from an external source into the Flash player, you would use the URLLoader class (flash.net.URLLoader).  An instance of URLLoader will load a text file into its data property to be read as a string.  Because the process of locating, downloading, and reading an external file into the Flash player takes time, events are used to indicate when the content has been loaded and is available.  Not only that, events are used to indicate loading progress and also any errors that occur. 

The URLLoader class uses the load method to start the loading process which is also capable of throwing exceptions of many different error types.  When using the URLLoader class to load external content, its always best to listen for all asynchronous errors as well as call the load method within a try..catch..finally statement. This example will show you how to load an external text file into the Flash Player with all the necessary error checking.

Steps:

  1. In the source files, open LoadTextFile.fla in the Errors directory. This file contains two text fields, one which will display the loaded text, and one which will display errors if there are any..
  2. Select the first frame in the ActionScript layer and open the Actions panel.
  3. Add the following script to the Actions panel.
// Load textfile.txt text into loader instance
var request:URLRequest = new URLRequest("textfile.txt");
var loader:URLLoader = new URLLoader();

// Listen for complete event (and others if desired)
loader.addEventListener(Event.COMPLETE, loadComplete);
// Listen for error events
loader.addEventListener(IOErrorEvent.IO_ERROR, loadIOError);
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadSecurityError);

// Complete event handler
function loadComplete(event:Event):void {

	// output loaded text to trace
	// window and contentText field
	trace(loader.data); // textfile.txt contents
	contentText.text = loader.data;
}
// Handler for IO error
function loadIOError(event:IOErrorEvent):void {

	// output errors to trace
	// window and errorText field
	trace("IOError: "+event);
	errorText.text = "IO Error: "+event;
}
// Handler for security error
function loadSecurityError(event:SecurityErrorEvent):void {

	// output errors to trace
	// window and errorText field
	trace("SecurityError: "+event);
	errorText.text = "Security Error: "+event;
}

// Try the load operation and
// catch any thrown exceptions
try {
	loader.load(request); 
}catch (error:ArgumentError){

	// output errors to trace
	// window and errorText field
	trace("ArgumentError: "+error);
	errorText.text = "Argument Error: "+error;
}catch (error:MemoryError){
	trace("MemoryError: "+error);
	errorText.text = "Memory Error: "+error;
}catch (error:SecurityError){
	trace("SecurityError: "+error);
	errorText.text = "Security Error: "+error;
}catch (error:TypeError){
	trace("TypeError: "+error);
	errorText.text = "Type Error: "+error;
}catch (error:Error){
	trace("Unknown Error: "+error);
	errorText.text = "Unknown Error: "+error;
}

Here, each error event has its own listener tracing the error if an error occurs during loading.  Additionally, each type of error thrown by the load operation is being checked within respective catch blocks.  An ending catch with the type Error (top-level Error) is used in case there was an error thrown not within the other catches.  Then that final block would catch the error no matter what it was.

  1. When you've completed adding the code, test your movie. You should see the following.


Figure: Text file loaded into the Flash player with error checking

As this example stands, you should not get any errors when the content loads. To see an error get caught, try breaking the file by misspelling the file name.

As an alternate approach, you can use one event handler could be used for all error events.  In doing this you can still catch and handle all errors but it would involve less code.

// Load textfile.txt text into loader instance
var request:URLRequest = new URLRequest("textfile.txt");
var loader:URLLoader = new URLLoader();

// Listen for complete event (and others if desired)
loader.addEventListener(Event.COMPLETE, loadComplete);
// Listen for error events using one handler
loader.addEventListener(IOErrorEvent.IO_ERROR, loadError);
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadError);

// Complete event handler
function loadComplete(event:Event):void {
	
	// output loaded text to trace
	// window and contentText field
	trace(loader.data); // textfile.txt contents
	contentText.text = loader.data;
}
// Handler for errors
function loadError(event:ErrorEvent):void {
	
	// output errors to trace
	// window and errorText field
	trace("Asynchronous Error: "+event);
	errorText.text = "Asynchronous Error: "+event;
}

// Try the load operation and
// catch any thrown exceptions
try {
	loader.load(request); 
}catch (error:Error){
	
	// output errors to trace
	// window and errorText field
	trace("Caught Error: "+error);
	errorText.text = "Caught Error: "+error;
}

Here, all errors are still accounted for but less code is involved. One thing to notice is that the event parameter of the loadError method is now typed as ErrorEvent (flash.events.ErrorEvent).  Just like all errors thrown by Flash are also of the type Error as well as their own type (their classes extend the Error class), the event errors are also of the type ErrorEvent.  By typing the event parameter as ErrorEvent, you can be sure that whatever type of error occurs, it will correctly match with the type of event used in the event handler.

Common errors

The Flex ActionScript 3 documentation has a list of some of the common errors you may see when working with ActionScript.  To learn more see:

http://livedocs.adobe.com/flex/2/langref/compilerErrors.html

Data Typing and Casting

[TODO: explain types used for both value usage as well as members of that value; also trace(29) example (error because number vs string conflict)]

Since ActionScript 2, ActionScript has supported strict data typing.  This allows developers to specify what types of data different variables will have.  For example, if you attempt to use a variable typed as a String in a method that expects an argument typed as Number, Flash will create an error.

var str:String = "string";
function doubleNumber(num:Number):Number {
	return num*2;
}
doubleNumber(str); // Error: Type mismatch

In ActionScript 2, data typing was used for compile-time error checking only.  Once compiled, all typing information is stripped and no type-specific errors would be recognized.  

Note: When type is maintained for ActionScript 2

The only exception in ActionScript 2 where type information is retained for runtime operations is with try..catch..finally statements.

ActionScript 3, on the other hand, supports not just compile-type type checking, but runtime type checking as well.  If a conflict exists with types during runtime in ActionScript 3, a runtime error will be thrown. In general, this is a good thing. But it can also require that you sometimes massage your variable typing to prevent unwarranted errors.

One thing about ActionScript 3 is that there are a lot more kinds of object types, or classes.  Usng the MovieClip class as an example, it now inherits from Sprite, DisplayObjectContainer, InteractiveObject, DisplayObject, EventDispatcher, and Object.  In ActionScript 2, MovieClip only inherited from Object.  What this means is that now MovieClip can be used as a value for any variable with any one of these types since in addition to being a MovieClip instance, MovieClip instances are also instances of those type (since they are inherited types).

var container:DisplayObject = new MovieClip(); // OK

A good example of this is the parent property of DisplayObject. It is typed as DisplayObjectContainer, but a MovieClip instance can easily be a parent of another instance because it too is a DisplayObjectContainer through inheritance. By being typed as a DisplayObjectContainer and not MovieClip, it also allows other containers, like Sprite or Loader, to be acceptable values.

But here is where there is a problem. Even though assigning values to a variable typed as an inherited type is OK, and even necessary in many cases, it also means that Flash doesn't have a complete understanding of that variable's type.  For all events and purposes Flash can only assume that any value has properties and methods that are specific to the definition provided by it's type.  The parent of some display object may be a MovieClip, but if you try to use the gotoAndPlay() method on that display object's parent property, the Flash player will throw an error because gotoAndPlay() is not a method of DisplayObjectContainer – the type associated with the parent property.

parent.gotoAndPlay(5); // Error: Call to a possibly undefined method

To get around this you have to cast parent to be of the correct type, in this case MovieClip.

// Cast to MovieClip
MovieClip(parent).gotoAndPlay(5);

Casting

To cast a value means to change its type, either by physically changing a value from one type to another, or just changing the type association provided for that value. With some primitive values, the actual value will change when casting.  For example, casting a String value to a Number will create a new Number primitive from the original string.  For objects, you can only change the type association provided for a variable that references that object, and no object can be associated with a type that it is not.  MovieClip instances can be typed as DisplayObject since MovieClip inherits from DisplayObject (DisplayObject is a subtype). But MovieClips cannot be typed as Arrays since the MovieClip class does not inherit from Array (and thus MovieClip instances are not Array instances as they are DisplayObject instances). 

Casting is necessary for objects when you have an object cast to one of its subtypes. Though a subtype will accurately work for an object value, it does not correctly describe all the properties and methods available to that value. By casting a value to it lowest-most class type, you will have the most accurate type information for that value.

The ActionScript 2 method of casting involved wrapping a value around parenthesis preceded by the class or type you wished that value to be – almost as if you were calling the class as a function, passing in the original value as an argument.

// Cast an object typed as Object to MovieClip
var typedAsMovieClip:MovieClip = MovieClip(typedAsObject);

This form of casting is still available in ActionScript 3.  Unlike ActionScript 2, if this form of cast fails, Flash will throw an error.

// Try to cast an Array to MovieClip
var typedAsMovieClip:MovieClip = MovieClip(typedAsArray);
// Error: Type Coercion failed

ActionScript 3 also provides a new form of casting using a new keyword, as.  The as keyword follows a variable which is then followed by the type.  The resulting value is the newly casted value or null if the cast failed. 

// Cast an object typed as Object to MovieClip
var typedAsMovieClip:MovieClip = typedAsObject as MovieClip;

Unlike the other form of casting, no error is thrown for failed casts.

// Try to cast an Array to MovieClip
var typedAsMovieClip:MovieClip = typedAsArray as MovieClip;
trace(typedAsMovieClip); // traces null, no error

By using as, you can avoid unwanted errors being thrown.  Casting with as, however, is slower in terms of performance.  If you are sure your variable is of the type being cast, it would be a good idea to avoid as.  Casting with as comes in handy when you expect the cast to fail sometimes and take possible resulting null values into consideration.

Casting to Dynamic Classes

Be careful when you are using variables typed as dynamic classes.  Because a dynamic class instance can have any variable defined for it, errors related to property access do not occur based on type.  For dynamic instances, type information only applies to where that value is used, not how its properties are used.  So, if you attempt to pass a Date instance (Date is dynamic) into a method that accepts only Numbers, an error will occur. However, attempting to access the property doesNotExist from the Date instance will result in no error as the instance is an instance of a dynamic class.

In terms of casting and accessing custom properties, especially with MovieClips, this can be an advantage.  Because the MovieClip class is dynamic, as long as you have an instance that inherits from MovieClip, you can cast a reference to that instance as MovieClip and access any property from it.  This is especially helpful for instances of movie clip symbols from the library that may have variables defined in them but no class defined within the linkage properties.  Though the actual movie clip symbol instance is not a direct instance of MovieClip, you could cast a reference to that instance as MovieClip and access any property you wanted from it.

// myClip is an instance of a movie clip symbol 
// with custom variable definition
MovieClip(myClip).anyProperty; // OK since MovieClip is dynamic

More than likely, the biggest use of casting to MovieClip will be for casting the parent property since it is typed as a DisplayObjectContainer and you will more than likely want to reference MovieClip methods from your MovieClip parents. If you are using a lengthy reference, you would want to cast to the end of that reference up to the point where the MovieClip property exists.

// Cast long reference to MovieClip
MovieClip(parent.parent.parent).play();

Continue reading »

Pages: 1 | 2 | 3 | 4 | 5