Chapter 1. ActionScript Basics

    Introduction

    Recipe 1.1. Using Mathematical Operators

    Recipe 1.2. Checking Equality or Comparing Values

    Recipe 1.3. Performing Actions Conditionally

    Recipe 1.4. Performing Complex Conditional Testing

    Recipe 1.5. Repeating an Operation Many Times

    Recipe 1.6. Performing an Action Once per Frame Update

    Recipe 1.7. Repeating a Task at Timed Intervals

    Recipe 1.8. Creating Reusable Code

    Recipe 1.9. Generalizing a Function to Enhance Reusability

    Recipe 1.10. Exiting a Function

    Recipe 1.11. Obtaining the Result of a Function

    Recipe 1.12. Avoiding Conflicting Variables

    Recipe 1.13. Reusing and Organizing Code in Multiple Movies


Introduction

Using ActionScript, you can create Flash movies that do just about anything you can imagine. But before launching into the vast possibilities, let's start with the basic foundation. The good news is that ActionScript commands follow a well-defined pattern, sharing similar syntax, structure, and concepts. Mastering the fundamental grammar puts you well on the way to mastering ActionScript.

This chapter addresses the frequent tasks and problems that relate to core ActionScript knowledge. Whether you are a beginner or master—or somewhere in between—these recipes help you handle situations that arise in every ActionScript project.

This book assumes you are familiar with the Flash authoring tool and have entered some basic ActionScript in the past, perhaps using the built-in Actions. ActionScript code is entered in the Actions panel (Window [image] Actions), as shown in Figure 1-1.

Figure 1-1. The Actions panel in Expert Mode

We'll be entering our code in Expert Mode, which allows us to type the desired code into the Actions panel's script pane, also shown in Figure 1-1. Activate Expert Mode using the pop-up Options menu in the Actions panel, as shown in Figure 1-2. The menu in Figure 1-2 also displays useful configuration options, such as whether to display line numbers or auto-format your ActionScript code.

Figure 1-2. Activating Expert Mode

ActionScript's trace( ) command is used to display text in the Output window during authoring, as seen in Figure 1-3. To enter a script, you'll highlight a frame in the timeline and then open the Actions panel (the easiest way to open the Actions panel is using the shortcut key, F9). Code can also be attached directly to a movie clip or button, but attaching code to the timeline is usually preferred. Many developers add a scripts layer to their timeline, and that is where they attach their code.

Figure 1-3. The Flash authoring tool's Output window

The Output window is useful for debugging and is displayed automatically whenever trace( ) actions are executed (but only in the Test Player.) Recipe 8.9 and Recipe 11.15 display text at runtime to provide user feedback or assist in debugging.

Now that you know where to enter code, for beginners, here is quick primer on terminology. These definitions are very approximate and are intended to orient people who have never programmed before. They are not technically exact or complete in every regard. If you already know what these terms mean, then please excuse what may be perceived as imprecise definitions, which are presented here for clarity and brevity.

Variables

Variables are convenient placeholders for data in our code, and we can name them anything we like, provided the name isn't already reserved by ActionScript and the name starts with a letter, underscore, or dollar sign (but not a number). Variables are convenient for holding interim information, such as a sum of numbers, or to refer to something, such as a text field or movie clip. Local variables are preferably declared with the var keyword the first time they are used in a script. Global variables and timeline variables are discussed in Recipe 1.12. You can assign a value to a variable using the equals sign (=), which is also known as the assignment operator.

Functions

For our purposes, functions are blocks of code that do something. We can call or invoke a function (that is, execute it) by using its name. Commands such as trace( ) are functions that come built into the Flash Player.

Scope

A variable's scope describes when and where the variable can be manipulated by the code in a movie. Scope defines a variable's life span and from where in our code we can set or retrieve the variable's value. A function's scope determines where and when the function is accessible to other blocks of code. Recipe 1.7 deals with issues of scope.

Handler

A handler is a function that is executed in response to an event, such as a mouseclick, a keystroke, or the movement of the playhead in the timeline.

Objects and classes

An object is something you can manipulate programmatically in ActionScript, such as a movie clip. There are other types of objects, such as those used to manipulate colors, dates, and text fields. Objects are instances of classes. That is, a class is a template for creating objects, and an object is a particular occurrence of that class. If you get confused, think of it in biological terms: you can consider yourself an object (instance) that belongs to the general class known as humans.

Methods

A method is a function associated with an object that operates on the object. For example, a text field object's replaceSel( ) method can be used to replace the selected text in the field.

Properties

A property is an attribute of an object, which can be read and/or set. For example, a movie clip's horizontal location is specified by its _x property, which can be both tested and set. On the other hand, a text field's length property, which indicates the number of characters in the field, can be tested but cannot be set directly (it can be affected indirectly, however, by adding or removing text from the field).

Statements

ActionScript commands are entered as a series of one or more statements. A statement might tell the playhead to jump to a particular frame, or it might change the size of a movie clip. Most ActionScript statements are terminated with a semicolon (;). This book uses the terms "statement" and "action" interchangeably.

Comments

Comments are notes within code that are intended for other humans and ignored by Flash. In ActionScript, single-line comments begin with // and terminate automatically at the end of the current line. Multiline comments begin with /* and are terminated with */.

Interpreter

The ActionScript interpreter is that portion of the Flash Player that examines your code and attempts to understand and execute it. Following ActionScript's strict rules of grammar ensures that the interpreter can easily understand your code. If the interpreter encounters an error, it often fails silently, simply refusing to execute the code rather than generating a specific error message.

Directives and pragmas

A line of code that begins with a # is a directive or pragma, which provides special instructions to Flash at compilation time (when the .fla is converted into a .swf). The most common directive is #include, which is used to include ActionScript code contained in an external .as file. Do not include a terminating semicolon on lines of code containing directives or pragmas.

Don't worry if you don't understand all the specifics. You can use each recipe's solution without understanding the technical details, and this primer should help you understand the terminology.

Many of the recipes in this book require you to access a movie clip's methods or properties. Sometimes these clips are created at runtime via ActionScript, but often you'll want to adapt a recipe to work with your author-time content. ActionScript can refer by name to a movie clip instance created in the authoring tool.

ActionScript refers to authoring-time movie clips using the name of the movie clip instance on the Stage (not the name of the Library symbol from which the clip is derived). Therefore, you must set a movie clip's instance name using the Property inspector, as shown in Figure 1-4, before you can access it via ActionScript. See Recipe 7.19 for information on creating movie clip instances from a Library symbol at runtime.

Figure 1-4 shows a movie clip on the Stage with its instance name set to "semiCircle_mc" in the Property inspector.

Figure 1-4. Setting a movie clip instance's name for ActionScript access
    [ Team LiB ]


Recipe 1.1 Using Mathematical Operators

1.1.1 Problem

You want to modify something over time, such as the rotation or position of a movie clip.

1.1.2 Solution

Use the compound assignment operators to change a variable or property in increments. Or, if incrementing or decrementing by one, use the prefix or postfix increment or decrement operators.

1.1.3 Discussion

Often, you'll want the new value of a variable or property to depend on the previous value. For example, you might want to move a movie clip to a new position that is 10 pixels to the right of its current position.

In an assignment statement—any statement using the assignment operator (an equals sign)—the expression to the right of the equals sign is evaluated and the result is stored in the variable or property on the left side. Therefore, you can modify the value of a variable in an expression on the right side of the equation and assign that new value to the very same variable on the left side of the equation.

Although the following may look strange to those who remember basic algebra, it is very common for a variable to be set equal to itself plus a number:

// Add 6 to the current value of myNum and assign that new value back to myNum. For
// example, if myNum was 3, this statement sets it to 9.
myNum = myNum + 6;

However, when performing mathematical operations, it is often more convenient to use one of the compound assignment operators, which combine a mathematical operator with the assignment operator. The +=, -=, *=, and /= operators are the most prevalent compound assignment operators. When you use one of these compound assignment operators, the value on the right side of the assignment operator is added to, subtracted from, multiplied by, or divided into the value of the variable on the left, and the new value is assigned to the same variable. The following are a few examples of equivalent statements.

These statements both add 6 to the existing value of myNum:

myNum = myNum + 6;
myNum += 6;

These statements both subtract 6 from the existing value of myNum:

myNum = myNum - 6;
myNum -= 6;

These statements both multiply myNum by anotherNum:

myNum = myNum * anotherNum;
myNum *= anotherNum;

These statements both divide myNum by anotherNum:

myNum = myNum / anotherNum;
myNum /= anotherNum;

There should be no space between the two symbols that make up a compound assignment operator.

Additionally, if you are incrementing or decrementing a variable by 1, you can use the increment or decrement operators (-- and ++).

This statement adds 1 to myNum:

myNum++;

and has the same effect as either of these statements:

myNum = myNum + 1;
myNum += 1;

This statement subtracts 1 from myNum:

myNum--;

and has the same effect as either of these statements:

myNum = myNum - 1;
myNum -= 1;

You can use the increment and decrement operators before or after the variable or property on which they operate . If used before the operand, they are called prefix operators. If used after the operand, they are called postfix operators. The prefix and postfix operators modify the operand in the same way but at different times. In some circumstances, there is no net difference in their operation, but the distinction is still important in many cases. When using prefix operators, the value is modified before the remainder of the statement or expression is evaluated. And if using postfix operators, the value is modified after the remainder of the statement has executed. Note how the first example increments myNum after displaying its value, whereas the second example increments myNum before displaying its value:

myNum = 5;
trace(myNum++);  // Displays: 5
trace(myNum);    // Displays: 6

myNum = 5;
trace(++myNum);  // Displays: 6
trace(myNum);    // Displays: 6

Getting back to our original problem, you can use mathematical operators to modify a property over time. This example causes the specified movie clip to rotate by 5 degrees for each tick of the frame rate:

myClip_mc.onEnterFrame = function (  ) {
  this._rotation += 5;
};


Recipe 1.2 Checking Equality or Comparing Values

1.2.1 Problem

You want to check if two values are equal.

1.2.2 Solution

Use the equality (or inequality) or strict equality (or strict inequality) operator to compare two values. To check whether a value is a valid number, use isNaN( ).

1.2.3 Discussion

Equality expressions always return a Boolean value indicating whether the two values are equal. The equality (and inequality) operators come in both regular and strict flavors. The regular equality and inequality operators check whether the two expressions being compared can be resolved to the same value after converting them to the same datatype. For example, note that the string "6" and the number 6 are considered equal because the string "6" is converted to the number 6 before comparison:

trace(5 == 6);    // Displays: false
trace(6 == 6);    // Displays: true
trace(6 == "6");  // Displays: true
trace(5 == "6");  // Displays: false

The logical inequality operator (!=) returns false if two values are equal and true if they are not equal. If necessary, the operands are converted to the same datatype before the comparison:

trace(5 != 6);    // Displays: true
trace(6 != 6);    // Displays: false
trace(6 != "6");  // Displays: false
trace(5 != "6");  // Displays: true

On the other hand, the strict equality and inequality operators first check whether the values being compared are of the same datatype before performing the comparison. Differences in datatype cause the strict equality operator to return false and the strict inequality operator to return true:

trace(6 === 6);    // Displays: true
trace(6 === "6");  // Displays: false
trace(6 !== 6);    // Displays: false
trace(6 !== "6");  // Displays: true

There is a big difference between the assignment operator (=) and the equality operator (==). If you use the assignment operator instead of the equality operator, you change the variable's value rather than testing its current value.

Using the wrong operator leads to unexpected results. In the following example, myVar equals 5 at first, so you might expect the subsequent if statement to always evaluate to false, preventing the trace( ) from being executed:

var myVar = 5;
// The following code is wrong. It should be if (myVar == 6) instead
if (myVar = 6) {
  trace("Rabbits are bunnies.");
}
trace("myVar is " + myVar);  // Displays: myVar is 6

However, the example mistakenly uses the assignment operator (=) instead of the equality operator (==). That is, the expression myVar = 6 sets myVar to 6 instead of testing whether myVar is 6. When used in an if clause, the expression myVar = 6 is treated as the number 6. Because any nonzero number used in a test expression converts to the Boolean true, the trace( ) action is called. Replace the test expression with myVar == 6 instead.

You can check an item's datatype using the typeof operator, as follows:

var myVar = 5;
if (typeof myVar == "number") {
  trace("Yippee. It's a number.");
}

But some numeric values are invalid. The following example results in myVar being set equal to NaN (a constant representing invalid numbers, short for "Not-a-Number") because the calculation cannot be performed in a meaningful way:

var myVar = 15 - "coffee";

Despite its name, NaN is a recognized value of the number datatype:

trace(typeof myVar);   // Displays: "number"

Therefore, to test if something is not only a number, but a valid number, you might try this:

var myVar = 15 - "coffee";
if (typeof myVar == "number") {
  // Nice try, but this won't work.
  if (myVar != NaN) {
    trace("Yippee. It's a number.");
  }
}

You can't simply compare a value to the constant NaN to check whether it is a valid number. Instead, you must use the special isNaN( ) function to perform the test.

To determine if a number is invalid, use the special isNaN( ) function, as follows:

var myVar = 15 - "coffee";
if (isNaN(myVar)) {
  trace("Sorry, that is not a valid number.");
}

To test the opposite of a condition (i.e., whether the condition is not true) use the logical NOT operator (!). For example, to check whether a variable contains a valid number, use !isNAN( ), as follows:

var myVar = 15 - "coffee";
if (!isNaN(myVar)) {
  // The number is not invalid, so it must be a valid number.
  trace ("That is a valid number.");
  // This jumps to another frame, assuming you've labeled a frame "SuccessScreen".
  gotoAndStop ("SuccessScreen");
}

Of course, you can perform comparisons using the well-known comparison operators. For example, you can use the > and < operators to check if one value is less than or greater than another value:

trace(5 < 6);    // Displays: true
trace(5 > 5);    // Displays: false

Similarly, you can use the >= and <= operators to check if one value is less than or equal to, or greater than or equal to, another value:

trace(5 <= 6);   // Displays: true
trace(5 >= 5);   // Displays: true

You should also be aware that ActionScript compares different datatypes differently. ActionScript data can be categorized into primitive datatypes (string, number, and Boolean) or composite datatypes (object, movieclip, and array). When you compare primitive datatypes, ActionScript compares them "by value." In this example, myVar and myOtherVar are considered equal because they both contain the value 6.

var myVar = 6;
var myOtherVar = 6;
trace (myVar == myOtherVar);         // Displays: true

However, when you compare composite datatypes, ActionScript compares them "by reference." Comparing items by reference means that the two items are considered equal only if both point to exactly the same object, not merely to objects with matching contents. For example, two arrays containing exactly the same values are not considered equal:

// Create two arrays with the same elements.
arrayOne = new Array("a", "b", "c");
arrayTwo = new Array("a", "b", "c");
trace(arrayOne == arrayTwo);          // Displays: false

Two composite items are equal only if they both refer to the identical object, array, or movie clip. For example:

// Create a single array
arrayOne = new Array("a", "b", "c");
// Create another variable that references the same array.
arrayOne = arrayTwo;
trace(arrayOne == arrayTwo);          // Displays: true

1.2.4 See Also

Recipe 6.8


Recipe 1.3 Performing Actions Conditionally

1.3.1 Problem

You want to perform an action only when a condition is true.

1.3.2 Solution

Use an if statement or a switch statement.

1.3.3 Discussion

You often need your ActionScript code to make decisions, such as whether to execute a particular action or group of actions. To execute an action under certain circumstances, use one of ActionScript's conditional statements: if, switch, or the ternary conditional operator (? :).

The conditional statements allow you to make logical decisions, and you'll learn from experience which is more appropriate for a given situation. The if statement is most appropriate when you want to tell your Flash movie to do something only when a certain condition is met (i.e., when the condition is true). When you have several possible conditions to test, you can use the switch statement instead. And you can use Flash's ternary conditional operator to perform conditional checking and assignment on a single line.

First, we'll look at the if statement. Of the conditional statements in ActionScript, the if statement is the most important to understand. In its most basic form, an if statement includes the keyword if followed by the test expression whose truthfulness you want to evaluate to determine which action or actions to execute. The test expression must be in parentheses, and the statement(s) to be executed should be within curly braces (the latter is mandatory if there is more than one statement in the statement block).

Here, we check whether animalName contains the word "turtle". This might be used to check whether the user answered a quiz question correctly (here, animalName is a variable assumed to contain the user's answer). Note that the double equals sign (==) is used to test whether two items are equal. It should not be confused with the single equals sign (=), which is used to assign a value to an item.

if (animalName == "turtle") {
  // This trace(  ) statement executes only when animalName is equal to "turtle".
  trace("Yay! 'Turtle' is the correct answer.");
}

Additionally, you can add an else clause to an if statement to perform alternative actions if the condition is false. Note that the trace( ) command has no effect when the Flash movie is running in a browser, so it is intended for testing purposes only. We use the gotoAndStop( ) command to jump to a different frame (presumably one that displays an appropriate message) depending on whether the user answered the question correctly or incorrectly.

if (animalName == "turtle") {
  // These statements execute only when animalName is equal to "turtle".
  trace("Yay! 'Turtle' is the correct answer.");
  gotoAndStop("CongratulationsScreen");
}
else {
  // These statements execute only when animalName is not equal to "turtle".
  trace("Sorry, you got the question wrong.");
  gotoAndStop("TryAgainScreen");
}

The gotoAndStop( ) command jumps to a frame with the specified label or frame number and is used in this example to illustrate typical real-world usage. The example assumes you've created a Flash movie with frames labeled "CongratulationsScreen" and "TryAgainScreen." To label a frame, highlight one of the layers in the timeline and add a label using the Frame field in the Property inspector.

You can add an else if clause to an if statement. If the if condition is true, the else if clause is skipped. If the if condition is false, the ActionScript interpreter checks to see if the else if condition is true.

if (animalName == "turtle") {
  // This trace(  ) statement executes only when animalName is equal to "turtle".
  trace("Yay! 'Turtle' is the correct answer.");
}
else if (animalName == "dove") {
  // This trace(  ) statement executes only when animalName is not "turtle" but is
  // "dove".
  trace("Sorry, a dove is a bird, not a reptile.");
}

What if the preceding example was written as two separate if statements (one to check if animalName is "turtle" and another to check if it is "dove")? The example would work as intended, but it would be less efficient. Using the else if statement guarantees that if animalName is "turtle", we don't bother checking if it is also equal to "dove".

If your two conditions are mutually exclusive, use an else if clause to check the second condition. If your two conditions are not mutually exclusive, and you want to perform both statement blocks when both conditions are met, use two separate if statements.

When you use an if statement with both else if and else clauses, the else clause must be the last clause in the statement. The final else clause is convenient as a catchall; it's where you can put statements that take the appropriate action if none of the other conditions are met.

if (animalName == "turtle") {
  // This trace(  ) statement executes only when animalName is equal to "turtle".
  trace("Yay! 'Turtle' is the correct answer.");
}
else if (animalName == "dove") {
   // This statement executes only when animalName is not "turtle" but is "dove".
  trace("Sorry, a dove is a bird, not a reptile.");
}
else {
  // This statement executes only when animalName is neither "turtle" nor "dove".
  trace("Sorry, try again.");
}

You can also include more than one else if clause in an if statement. However, in that case, you should most likely use a switch statement instead; generally, switch statements are more legible and succinct than the comparable if statement. Where performance is critical, some ActionScripters prefer to use if statements, which allow somewhat greater control for optimization purposes.

A switch statement is composed of three parts:

The switch keyword

Every switch statement must begin with the switch keyword.

Test expression

This is an expression, enclosed in parentheses, whose value you want to test in order to determine which actions to execute.

The switch statement body

The statement body, enclosed in curly braces, is composed of cases. Each case is composed of the following parts:

The case or default keyword

Each case must begin with a case keyword. The exception is the default case (analogous to an else clause in an if statement), which uses the default keyword.

Case expression

This is an expression whose value is compared to the switch statement's test expression. If the two values are equal, the code in the case body is executed. The default case (the case that uses the default keyword) does not need a case expression.

Case body

This is one or more statements, usually ending in a break statement, to be performed if the case is true.

The switch keyword is always followed by the test expression in parentheses. Then, the switch statement body is enclosed in curly braces. There can be one or more case statements within the switch statement body. Each case (other than the default case) starts with the case keyword followed by the case expression and a colon. The default case (if one is included) starts with the default keyword followed by a colon. Therefore, the general form of a switch statement is:

switch (testExpression) {
  case caseExpression:
    // case body
  case caseExpression:
    // case body
  default:
    // case body
}

Here is an example. Note that once a case tests true, all the remaining actions in all subsequent cases within the switch statement body also execute. This example is most likely not what the programmer intended:

var animalName = "dove";

/* In the following switch statement, the first trace(  ) statement does not execute
   because animalName is not equal to "turtle". But both the second and third trace(  )
   statements execute, because once the "dove" case tests true, all subsequent code
   is executed.
*/
switch (animalName) {
  case "turtle":
    trace("Yay! 'Turtle' is the correct answer.");
  case "dove":
    trace("Sorry, a dove is a bird, not a reptile.");
  default:
    trace("Sorry, try again.");
}

Normally, you should use break statements at the end of each case body to exit the switch statement after executing the actions under the matching case.

The break statement terminates the current switch statement, preventing statements in subsequent case bodies from being executed erroneously.

You don't need to add a break statement to the end of the last case or default clause, since it is the end of the switch statement anyway:

var animalName = "dove";

// Now, only the second trace(  ) statement executes.
switch (animalName) {
  case "turtle":
    trace("Yay! 'Turtle' is the correct answer.");
    break;
  case "dove":
    trace("Sorry, a dove is a bird, not a reptile.");
    break;
  default:
    trace("Sorry, try again.");
}

The switch statement is especially useful when you want to perform the same action for one of several matching possibilities. Simply list multiple case expressions one after the other. For example:

switch (animalName) {
  case "turtle":
  case "alligator":
  case "iguana":
    trace("Yay! You named a reptile.");
    break;
  case "dove":
  case "pigeon":
  case "cardinal":
    trace("Sorry, you specified a bird, not a reptile.");
    break;
  default:
    trace("Sorry, try again.");
}

ActionScript also supports the ternary conditional operator (? :), which allows you to perform a conditional test and an assignment statement on a single line. A "ternary" operator requires three operands, as opposed to the one or two operands required by unary and binary operators. The first operand of the conditional operator is a conditional expression that evaluates to true or false. The second operand is the value to assign to the variable if the condition is true, and the third operand is the value to assign if the condition is false.

varName = (conditional expression) ? valueIfTrue : valueIfFalse;

One common use of the ternary operator is in assigning default values to variables in functions if a parameter value was omitted when calling the function. For example:

function myFunction (param) {
  var val = (param != undefined) ? param : "default value";
}

The preceding example can be written using an if statement:

function myFunction (param) {
  var val;
  if (param != undefined) {
    val = param;
  } else {
    val = "default value";
  }
}

Either way is correct; however, the ternary operator requires fewer lines of code and executes faster.

Here is an example in which salutation defaults to "Hello" if no greeting was specified; otherwise, salutation is set to greeting's value.

function welcome (greeting) {
  var salutation = (greeting != undefined) ? greeting: "Hello";
  trace (salutation + " Sailor!")
}

Here are some examples of the results:

welcome(  );        // Displays: Hello Sailor!
welcome("Ahoy");    // Displays: Ahoy Sailor!


Recipe 1.4 Performing Complex Conditional Testing

1.4.1 Problem

You want to make a decision based on multiple conditions.

1.4.2 Solution

Use the logical AND (&&), OR (||), and NOT (!) operators to create compound conditional statements.

1.4.3 Discussion

Many statements in ActionScript can involve conditional expressions, including if, while, and for statements, and statements using the ternary conditional operator. To test whether two conditions are both true, use the logical AND operator (&&), as follows (see Chapter 10 for details on working with dates):

// Check if today is April 17th.
now = new Date(  );
if (now.getDate() == 17 && now.getMonth(  ) == 3) {
  trace ("Happy Birthday, Bruce!");
}

You can add extra parentheses to make the logic more apparent:

// Check if today is April 17th.
if ((now.getDate() == 17) && (now.getMonth(  ) == 3)) {
  trace ("Happy Birthday, Bruce!");
}

Here we use the logical OR operator (||) to test whether either condition is true:

// Check if it is a weekend.
if ((now.getDay() == 0) || (now.getDay(  ) == 6) ) {
  trace ("Why are you working on a weekend?");
}

You can also use a logical NOT operator (!) to check if a condition is not true:

// Check to see if the name is not Bruce.
if (!(name == "Bruce")) {
  trace ("This application knows only Bruce's birthday.");
}

The preceding example could be rewritten using the inequality operator (!=):

if (name != "Bruce") {
  trace ("This application knows only Bruce's birthday.");
}

Any Boolean value, or an expression that converts to a Boolean, can be used as the test condition:

// Check to see if a movie clip is visible. If so, display a message. This condition
// is shorthand for myMovieClip._visible == true.
if (myMovieClip._visible) {
  trace("The movie clip is visible.");
}

The logical NOT operator is often used to check if something is false, rather than true:

// Check to see if a movie clip is invisible (not visible). If so, display a message.
// This condition is shorthand for myMovieClip._visible != true or
// myMovieClip._visible == false.
if (!myMovieClip._visible) {
  trace("The movie clip is invisible. Set it to visible before trying this action.");
}

The logical NOT operator is often used in compound conditions along with the logical OR operator:

// Check to see if the name is neither Bruce nor Joey. (This could also be rewritten
// using two inequality operators and a logical AND.)
if (!((name == "Bruce") || (name == "Joey"))) {
  trace ("Sorry, but only Bruce and Joey have access to this application.");
}

Note that ActionScript does not bother to evaluate the second half of a logical AND statement unless the first half of the expression is true. If the first half is false, the overall expression is always false, so it would be inefficient to bother evaluating the second half. Likewise, ActionScript does not bother to evaluate the second half of a logical OR statement unless the first half of the expression is false. If the first half is true, the overall expression is always true.


Recipe 1.5 Repeating an Operation Many Times

1.5.1 Problem

You want to perform a task multiple times within a single frame.

1.5.2 Solution

Use a loop to perform the same task multiple times within a single frame. For example, you can use a for statement:

for (var i = 0; i < 10; i++) {
  // Display the value of i.
  trace(i);
}

1.5.3 Discussion

When you want to execute the same action (or slight variations thereof) multiple times within a single frame, use a looping statement to make your code more succinct, easier to read, and easier to update. You can use a while statement or a for statement for this purpose, but generally a for statement is the better choice. Both statements achieve the same result, but the for statement is more compact and more familiar to most programmers.

The syntax of a for statement consists of five basic parts:

The for keyword

Every for statement must begin with a for keyword.

Initialization expression

The loop typically employs an index variable (a.k.a. a loop counter) that is initialized when the statement is first encountered. The initialization is performed only once regardless of how many times the loop is repeated.

Test expression

A loop should include a test expression that returns true or false. The test expression is evaluated once each time through the loop. Generally, the test expression compares the index variable to another value, such as a maximum number of loop iterations. The overall expression must evaluate to true for the for statement's body to execute (contrast this with a do . . . while loop, which executes at least once, even if the test expression is false). On the other hand, if the test expression never becomes false, you'll create an infinite loop, resulting in a warning that the Flash Player is running slowly (which appears after 15 seconds).

Update expression

The update expression usually updates the value of the variable used in the test expression so that, at some point, the test expression becomes false and the loop ends. The update expression is executed once each time through the loop. An infinite loop is often caused by failing to update the appropriate variable in the update expression (usually the same variable used in the test expression).

Statement body

The statement body is a block of substatements enclosed in curly braces that is executed each time through the loop. If the test expression is never true, the for statement's body won't be executed.

The for keyword should come first, and it should be followed by the initialization, test, and update expressions enclosed in parentheses. Semicolons must separate the three expressions from one another (although the initialization, test, and update statements are optional, the semicolons are mandatory). The remainder of the for loop is composed of the statement body enclosed in curly braces. The general form is:

for (initialization; test; update) {
  statement body
}

Here is an example of a for statement that outputs the numbers 0 to 999. Because the index variable i is a local variable, we preface it with the var keyword in the initialization expression.

for (var i = 0; i < 1000; i++) {
  trace(i);
}
trace ("That's the end.");

To understand the for statement, you can follow along with the ActionScript interpreter as it processes the command. In the preceding example, the for keyword tells the interpreter to perform the statements within the for loop as long as the conditional expression is true. The initialization expression is executed only once, and it sets the variable i to 0. Next, the interpreter checks the test expression (i < 1000). Because i is 0, which is less than 1000, the expression evaluates to true and the trace( ) action within the for statement body is executed. The ActionScript interpreter then executes the update statement, in this case i++, which increments i by 1. The interpreter then repeats the process from the top of the loop (but skips the initialization step). So the interpreter again checks whether the test expression is true and, if so, executes the statement body again. It then executes the update statement again. This process repeats until the test expression is no longer true. The last value displayed in the Output window is 999, because once i is incremented to 1000, the test expression no longer evaluates to true and the loop comes to an end. Once the loop terminates, execution continues with whatever commands follow the loop.

Both the initialization and update expressions can include multiple actions separated by commas. The following example simultaneously increments i and decrements j, and displays their values in the Output window:

for (var i = 0, j = 10; i < 10; i++, j--) {
  trace("i is " + i);
  trace("j is " + j);
}

The preceding example is not the same as using two nested for statements (which is shown in the next code block).

It is also common to use nested for statements. When you use a nested for statement, use a different index variable than that used in the outermost for loop. By convention, the outermost for loop uses the variable i, and the nested for loop uses the variable j. For example:

for (var i = 1; i <= 3; i++) {
  for (var j = 1; j <= 2; j++) {
    trace(i + " X " + j + " = " + (i * j));
  }
}

The preceding example displays the following multiplication table in the Output window:

1 X 1 = 1
1 X 2 = 2
2 X 1 = 2
2 X 2 = 4
3 X 1 = 3
3 X 2 = 6

It is possible to nest multiple levels of for statements. By convention, each additional level of nesting uses the next alphabetical character as the index variable. Therefore, the third level of nested for statements typically uses k as the index variable:

for (var i = 1; i <= 3; i++) {
  for (var j = 1; j <= 3; j++) {
    for (var k = 1; k <= 3; k++) {
      trace(i + " X " + j + " X " + k + " = " + (i * j * k));
    }
  }
}

Additionally, you can use for statements to loop backward:

// Count backward from 10 to 1.
for (var i = 10; i > 0; i--) {
  trace(i);
}

You should not use a for statement to perform tasks over time.

Many programmers make the mistake of trying to use for statements to animate movie clips. For example:

for (var i = 0; i < 20; i++) {
  myMovieClip._x += 10;
}

Although the preceding code moves the movie clip 200 pixels to the right of its starting point, all the updates take place within the same frame. There are two problems with this. First of all, the Stage updates only once per frame, so only the last update is shown on the Stage (causing the movie clip to jump 200 pixels suddenly rather than moving smoothly in 20 steps). And second, even if the Stage updates more often, each iteration through the for loop takes only a few milliseconds, so the animation would happen too quickly. For actions that you want to take place over time, use an onEnterFrame( ) handler (see Recipe 1.6) or set an interval using the setInterval( ) function (see Recipe 1.7).

Moreover, tight repeating loops should not be used to perform lengthy processes (anything that takes more than a fraction of a second). The Flash Player displays a warning whenever a single loop executes for more than 15 seconds. Using an onEnterFrame( ) handler or the setInterval( ) function avoids the warning message and allows Flash to perform other actions in addition to the repeated actions that are part of the loop.

1.5.4 See Also

The for statement is used in many practical situations, and you can see examples in a great many of the recipes throughout this book. See Recipe 6.2 and Recipe 9.10 for some practical examples, and Recipe 2.3, in which the break command is used to exit the loop prematurely. Recipe 6.13 discusses for . . . in loops, which are used to enumerate the properties of an object or array.


Recipe 1.6 Performing an Action Once per Frame Update

1.6.1 Problem

You want to perform an action or actions at the frame rate of the movie.

1.6.2 Solution

Use an onEnterFrame( ) handler.

1.6.3 Discussion

The onEnterFrame( ) event handler method for movie clips is the appropriate choice when you want to perform actions continuously at the same speed as the frame rate of the movie. See Recipe 1.1 and Recipe 7.8.


Recipe 1.7 Repeating a Task at Timed Intervals

1.7.1 Problem

You want to perform an action or actions at a specific timed interval.

1.7.2 Solution

Use the setInterval( ) function.

1.7.3 Discussion

The setInterval( ) function allows you to specify an interval (in milliseconds) at which your Flash movie will invoke a function. Use setInterval( ) to perform a particular action over time but not necessarily at the frequency of the frame rate of the movie.

// Define a function.
function myIntervalFunction (  ) {

  // Output the difference between the current timer value and its value from the
  // last time the function was called.
  trace(getTimer(  ) - lastTime);
  lastTime = getTimer(  );
}

// Set up an interval that attempts to invoke myIntervalFunction(  ) once every
// millisecond.
setInterval(myIntervalFunction, 1);

In the preceding example, even though the interval is theoretically one millisecond, in practice, its accuracy and granularity depend on computer playback performance in relation to other tasks being demanded of the processor. There are two implications to this:

  • Don't rely on intervals to be extremely precise.

  • Don't rely on intervals to be smaller than a few milliseconds.

The setInterval( ) function returns an identifier for the newly created interval. If you want to be able to stop the interval at a later time, you must store the return value, as follows:

// Set an interval such that someFunction(  ) is called approximately once per second.
// Assign setInterval(  )'s return value to the variable myIntervalID for later use.
myIntervalID = setInterval(someFunction, 1000);

You can use the clearInterval( ) function to stop an interval if you know the interval's identifier:

clearInterval(myIntervalID);

If you want the interval to invoke the method of an object instead of a standalone function, you can use the variation of the setInterval( ) function in which you pass it three parameters—a reference to the object, the name of the function (as a string), and the interval in milliseconds— instead of just two:

// Create a simple object using the Object constructor.
obj = new Object(  );

// Assign a method named myMethod to an object, obj.
obj.myMethod = function (  ) {
  trace("obj.myMethod(  ) has been called");
};

// Use setInterval(  ) to tell the movie to invoke the myMethod(  ) method of the obj
// object approximately every six seconds.
setInterval(obj, "myMethod", 6000);

Whichever variation of the setInterval( ) function you use, any additional parameters that you pass to the setInterval( ) function are passed along to the function or method:

// Define a function that accepts a parameter and displays it in the Output window.
function displayValue (val) {
  trace(val);
}

// Use setInterval(  ) to call displayValue(  ) once per minute. The third parameter is
// passed to the function when it is called so that each time "Bunny rabbits go
// hippity-hop" is displayed in the Output window.
setInterval(displayValue, 60000, "Bunny rabbits go hippity-hop");

Be aware that any values that you pass to a function or method by way of the setInterval( ) function are evaluated only at the time the interval is initialized. So the same parameter values are always passed to a function or method that is called via setInterval( ):

obj = new Object(  );

obj.traceAnimalName = function (name) {
  trace(name);
};

myAnimalName = "cub";

setInterval(obj, "traceAnimalName", 30, myAnimalName);

// Even if myAnimalName is assigned a new value, the value "cub" is always passed to
// traceAnimalName(  ), because myAnimalName was "cub" when setInterval(  ) was first
// called.
myAnimalName = "puppy";

One of the neat things you can do with setInterval( ) is create animations that are independent of the movie's frame rate. Remember that the onEnterFrame( ) method executes at the same interval as the frame rate, so using that technique ties you to the movie's properties. But with setInterval( ) you can call a function or method at any interval you want. Here is an example in which two intervals are set—one for a square movie clip (every 50 milliseconds) and one for a circle movie clip (every 100 milliseconds):

// Define the function first. This function takes three parameters: a reference to
// the movie clip object, the change in x, and the change in y.
function moveObj (obj, dx, dy) {

  // Increment the movie clip's x and y coordinates.
  obj._x += dx;
  obj._y += dy;

  // In case the interval is less than the movie's frame rate, you need to use the
  // built-in updateAfterEvent(  ) method to refresh the Stage.
  updateAfterEvent(  );
}

// Create two intervals. Each invokes the moveObj(  ) function, but at different
// intervals and with different movie clip references as parameters.
squareInterval = setInterval(moveObj, 50,  square, 1, 1);
circleInterval = setInterval(moveObj, 100, circle, 1, 1);


Recipe 1.8 Creating Reusable Code

1.8.1 Problem

You want to perform a series of actions at various times without duplicating code unnecessarily throughout your movie.

1.8.2 Solution

Create a function and then call (i.e., invoke) it by name whenever you need to execute those actions.

There is more than one way to create (i.e., define or declare) a function. Here is how to create a named function:

function functionName (  ) {
  // Statements go here.
}

To call (i.e., execute) the named function, refer to it by name, such as:

functionName(  );

Here is how to create a function literal:

functionName = function (  ) {
  // Statements go here.
};

Although not strictly required, it is considered a best practice to include a semicolon following the closing curly brace when defining a function literal.

1.8.3 Discussion

Grouping statements into a function allows you to define the function once but execute it as many times as you'd like. This is useful when you need to perform similar actions at various times without duplicating the same code in multiple places. Keeping your code centralized in functions makes it easier to understand (because you can write the function once and then ignore the details when using it) and easier to maintain (because you can make changes in one place rather than in multiple places).

There are two common ways of defining ActionScript functions: as named functions or function literals (a.k.a. anonymous functions). Each of these ways of declaring a function has its own use.

The named function declaration is the most common choice (when not defining a function to be used as a method) and has at least one advantage over function literals: named functions are accessible within the entire keyframe (or on( ) or onClipEvent( ) handler) even if they come after the call to the function.

For example, even though the writeMessage( ) function is not declared until after it is invoked, the function is still available:

// Invoke the writeMessage(  ) function, which is declared later in the script.
writeMessage(  );

// Declare (define) the writeMessage(  ) function as a named function.
function writeMessage (  ) {
  trace("Hello, friend.");
}

// The function is available before or after it has been declared.
writeMessage(  );

In contrast, a function literal is accessible only from lines of code that come after the declaration:

// The ActionScript interpreter will not be able to find a function with this name,
// and so nothing happens (it fails silently).
writeMessage2(  );

// Declare (define) the writeMessage2(  ) function as an anonymous function literal
writeMessage2 = function (  ) {
  trace("Hello, friend.");
};

// However, the function is available from lines of code after it has been declared.
writeMessage2(  );

However, there are several reasons to use function literals:

  • You can assign a function literal to a global variable so that the function can be accessed from any timeline.

  • Function literals offer a convenient, compact, and intuitive way to define methods for objects.

  • Function literals can be treated like other variables, in that they can be passed to other functions or have their values reassigned.

Here, we assign a function literal as a property of the _global object:

_global.launchBookExamples = function (  ) {
  getURL("http://www.person13.com/ascb", "_blank");
};

Therefore, from anywhere on any timeline, you can execute the function by simply using its name. For example, you might attach this script to a button:

myButton.onRelease = function (  ) {
  launchBookExamples(  );
};

Here, we define the function as a method of a movie clip (where onEnterFrame( ) is a special, built-in method for movie clips that you need to define before it can be used):

myClip_mc.onEnterFrame = function (  ) {
  trace("Hooray for methods!");
};

Of course, you can define custom methods as well by simply assigning the function literal to a new property of the object:

myClip_mc.myCustomMethod = function (  ) {
  trace("Hooray for methods!");
};

It is also worth noting that you can set one method equal to another method. This technique is often used to assign the same actions to a movie clip or button for two different events. You can define an anonymous function and assign it to one of the event handler methods, and then simply assign one event handler method to the other. A common example of this is when you want to define the same actions for when a user releases a button or movie clip and when they release outside:

// Define an onRelease(  ) method for a movie clip.
myMovieClip.onRelease = function (  ) {
  trace("Hooray for methods!");
};

// Assign the same method definition to the onReleaseOutside(  ) method as well.
myMovieClip.onReleaseOutside = myMovieClip.onRelease;

Functions can also be passed as data. You can conveniently pass a function literal to another function that requires a function as one of its arguments, such as setInterval( ) or the Array.sort( ) method:

// Set an interval that calls a function that increments a variable, i, and displays
// the value.
setInterval(function (  ) {trace(++i);}, 1000);

Functions are subject to the same scope limitations as variables. Timeline functions are accessible only while the timeline on which they are defined exists. A timeline function can be a named function or an anonymous function assigned to a timeline variable. Additionally, timeline functions can be called only by using the proper target path. When you access the function from the same timeline, you do not need to include the target path, but when you want to access the function from another timeline, you need to make sure you provide the correct path.

// Explicitly invoke a function that is defined on the main timeline.
_root.myFunction(  );

If your function is used within the same timeline only, you don't need to worry about scope issues. However, if you intend to use the function throughout many timelines, two solutions are generally employed:

  • Define the function as a global function. If you make a function a global function, you can call it by name from any timeline in the movie without having to worry about scope:

    _global.myFunction = function (  ) {
  trace("Global functions are fun!");
};

  • Define the function as a (static) method of a global class. All of the built-in classes are global by default, and if you define a class with a global constructor (see Recipe 12.5), then even your custom classes can be global. This technique is really a variation on the first, but with the advantage that classes offer you a way of organizing your functions in a potentially meaningful way (for example, the built-in Math class organizes many mathematical functions).


Recipe 1.9 Generalizing a Function to Enhance Reusability

1.9.1 Problem

You want to perform slight variations of an action without having to duplicate multiple lines of code to accommodate the minor differences.

1.9.2 Solution

Add parameters to your function to make it flexible enough to perform slightly different actions when it is invoked rather than performing exactly the same action or producing the same result each time.

Define the parameters that account for the variability in what you want the function to do:

function myParamsFunction (param1, param2, param3) {
  trace("The average is " + (param1 + param2 + param3)/3);
}

If you don't know the exact number of parameters the function will receive, use the built-in arguments array to handle a variable number of parameters.

1.9.3 Discussion

A function that doesn't accept parameters generally produces the same result each time it is invoked. But you often need to perform almost exactly the same actions as an existing function, but with minor variations. Duplicating the entire function and then making minor changes to the second version is a bad idea in most cases. Usually, it makes your code harder to maintain and understand. More importantly, you'll usually find that you need not only two variations but many variations of the function. It can be a nightmare to maintain five or six variations of what should ideally be wrapped into a single function. The trick is to create a single function that can accept different values to operate on.

For example, if you have an average( ) function, you want to specify arbitrary values to be averaged each time it is invoked, instead of having it always average the same two numbers. You can accomplish this goal using parameters.

The most common way to work with parameters is to list them within the parentheses in the function declaration. The parameter names should be separated by commas, and when you invoke the function you should pass it a comma-delimited list of arguments that correspond to the parameters it expects.

The terms "parameters" and "arguments" are often used interchangeably to refer to the variables defined in the function declaration or the values that are passed to a function when it is invoked.

Here is a simple example of a function declaration using parameters and a function invocation in which arguments are passed during the function call:

// Define the function such that it expects two parameters: a and b.
function average (a, b) {
  trace("The average is " + (a + b)/2);
}

// When you invoke the function, pass it two arguments, such as 6 and 12, that
// correspond to the a and b parameters.
// This call to average(  ) displays: "The average is 9"
average(6, 12);

Parameters work in exactly the same way with function literals as they do with named functions:

average = function (a, b) {
  trace("The average is: " + (a + b)/2);
};

In most situations it is best to declare the parameters that the function should expect. However, there are some scenarios in which the number of parameters is unknown. For example, if you want the average( ) function to average any number of values, you can use the built-in arguments array, which is available within any function's body. All the parameters that are passed to a function are automatically placed into that function's arguments array.

// There is no need to specify the parameters
// to accept when using the arguments array.
function average (  ) {
  var result = 0;

  // Loop through each of the elements of the arguments array
  // and add that value to result.
  for (var i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  // Then divide by the total number of arguments.
  trace("The average is " + result/arguments.length);
}

// You can invoke average(  ) with any number of parameters.
// In this case, the function will display: "The average is 7.5".
average (3, 6, 9, 12);

Technically, arguments is an object with additional properties beyond that of a basic array. However, while arguments is a special kind of array, you can still work with it in the same ways that you would a regular array.


Recipe 1.10 Exiting a Function

1.10.1 Problem

You want to exit a function.

1.10.2 Solution

Functions terminate automatically after the last statement within the function executes. Use a return statement to exit a function before reaching its end.

1.10.3 Discussion

The return statement exits the current function, and the ActionScript interpreter continues the execution of the script that initially invoked the function. Any statements within the function body that follow a return statement are ignored.

function myFunction (  ) {
  return;
  trace("Never called");
}

myFunction(  );
// Execution continues here after returning from the myFuction(  ) invocation.

In the preceding example, the return statement causes the function to terminate before performing any actions, so it is not a very useful function. More commonly, you will use a return statement to exit a function under certain conditions. This example exits the function if the password is wrong:

function checkPassword (password) {

  // If password is not "SimonSays", exit the function.
  if (password != "SimonSays") {
    return;
  }

  // Otherwise, perform the rest of the actions.
  gotoAndStop ("TreasureMap");
}

// This function call uses the wrong password, and so the function exits.
checkPassword("MotherMayI");

// This function uses the correct password, and so the function jumps to the
// TreasureMap frame.
checkPassword("SimonSays");


Recipe 1.11 Obtaining the Result of a Function

1.11.1 Problem

You want to perform a function and return the results to the script that invoked the function.

1.11.2 Solution

Use a return statement that specifies the value to return.

1.11.3 Discussion

The return statement, when used without any parameters, simply terminates a function. Technically, return returns the value undefined to the caller if no value is specified. Likewise, if there is no return statement, the function returns undefined when it terminates. But any value specified after the return keyword is returned to script that invoked the function. Usually, the returned value is stored in a variable for later use:

function average (a, b) {
  // Return the average of a and b.
  return (a + b)/2;
}

var playerScore ;
// Call the average(  ) function and store the result in a variable.
playerScore = average(6, 12);
// Use the result in some way.
trace("The player's average score is " + playerScore);

You can use the return value of a function, without storing it in a variable, by passing it as a parameter to another function:

trace("The player's average score is " + average(6, 12));

Note, however, that if you do nothing with the return value of the function, the result is effectively lost. For example, this statement has no detectable benefit because the result is never displayed or used in any way:

average(6, 12);


Recipe 1.12 Avoiding Conflicting Variables

1.12.1 Problem

You want to make sure that variables within a function do not interfere with variables in other functions or within the timeline in which the function is defined.

1.12.2 Solution

Use the var keyword to declare local variables.

1.12.3 Discussion

Generally, you should declare variables used within functions as local variables. Local variables are known only within the function. Therefore, they do not conflict with variables of the same name in other functions or within the timelines in which the functions are defined. To make a variable local, declare it with the var keyword. Parameters are automatically treated as local variables, so you do not need to include the var keyword when declaring parameters for a function.

function localVarsFunction (param1, param2) {
  var myVar;
  myVar = "Local variables are fun.";
}

Or, more succinctly, you can write:

function localVarsFunction (param1, param2) {
  var myVar = "Local variables are fun.";
}

Variables declared without the var keyword are implicitly scoped to the timeline on which they reside (note that unlike some languages, ActionScript doesn't require you to declare a variable before assigning it a value for the first time). In this case, myVar is a timeline variable, not a local variable, even though it is declared within a function:

function timelineVarsFunction (  ) {
  myVar = "Timeline variables are fun but not usually a good choice in functions.";
}

To declare a global variable, attach it as a property to the _global object, as follows:

_global.companyName = "Person13";

Once declared, a global variable can be accessed from anywhere in the movie by simply using its name, as follows:

trace ("Welcome to the " + companyName + "web site.");

However, a local variable of the same name will override the global variable:

function localVarsFunction (  ) {
  var companyName = "Macromedia";
  // This displays "Welcome to the Macromedia web site."
  trace ("Welcome to the " + companyName + "web site.");

  // To access the global variable of the same name, precede it with _global.
  // This displays "Welcome to the Person 13 web site."
  trace ("Welcome to the " + _global.companyName + "web site.");
}

For this reason, make sure that you always prefix a global variable reference with _global when you want to set its value. Otherwise, Flash will create a new local variable with the same name, which can potentially cause problems.


Recipe 1.13 Reusing and Organizing Code in Multiple Movies

1.13.1 Problem

You want to reuse code that you've created for one project in another Flash movie. Or you want to write your ActionScript code in an external text editor.

1.13.2 Solution

Place your ActionScript code in external .as files and use the #include directive to add them to your Flash movies:

// Adds all the code within MyActionScriptFile.as to your Flash movie.
#include "MyActionScriptFile.as"

1.13.3 Discussion

Use the #include directive to incorporate code from external text files into your Flash movie during compilation from a .fla file to a .swf file. When you export a .swf file, Flash replaces the #include directive with the contents of the specified file. The external file must be a text file with valid ActionScript code in it. By convention the file should be named with the .as extension, though it is not absolutely necessary:

#include "ASutils.as"

Notice that the #include directive is not followed by a semicolon. Adding a semicolon causes an error.

Additionally, Flash must be able to locate the file when you export the movie. Therefore, you should place the file in a location relative to where the Flash document is saved. For example, the previous example looks for a file named ASutils.as in the same directory as the .fla document. You can also place the file in a subdirectory of the directory in which the Flash document is saved:

// Look for a file named ASutils.as in a subdirectory named myASFiles.
#include "myASFiles/ASutils.as"

You can also place the ActionScript files in the Flash installation's Include directory. And, in fact, this is recommended for all ActionScript files that you anticipate you might use in multiple movies. If Flash cannot find a file with the specified name relative to the .fla file, it looks in the Configuration\Include subdirectory of the directory in which Flash is installed. For example, on Windows-based computers, the default Include folder is located in C:\ProgramFiles\Macromedia\Flash MX\Configuration\Include.

If Flash is unable to find the external file in the folder specified by the #include directive (by default, the same directory as the .fla file) and is unable to find the file within the Flash Include directory, it displays an error message.

You should not upload your .as files along with your .swf file. The contents of the external text file are added to the .swf file when it is exported. Because the contents are not loaded into the .swf file dynamically at runtime, you must reexport the .swf file if the external .as file changes. Remember that future changes to the .as file will affect any movie that includes it the next time the movie is reexported. To prevent future changes in an external file from affecting a given movie, you can copy and paste the external code into the particular .fla and remove the original #include statement.

There is one additional consideration when working with external ActionScript files: it is good practice to add at least one blank line to the end of each .as file. This is because when Flash includes the code from the external file, it can sometimes combine the last line of the external file with the next line of code within the Flash document. This can sometimes cause errors if the last line in the external file contains code. But if the last line is blank, you can avoid these sorts of problems.

Note also that an included file can include another file (a nested #include). But paths inside an included file are relative to the original .fla file, not relative to the parent file location. Developers typically use a package-style reference incorporating their domain name or a project name as a directory for included files to avoid name collisions with other libraries. For example:

#include "com/person13/utilities/ASutils.as"


Chapter 10. Dates and Times

    Introduction

    Recipe 10.1. Finding the Current Date and Time

    Recipe 10.2. Retrieving the Day or Month Name

    Recipe 10.3. Formatting the Date and Time

    Recipe 10.4. Formatting Milliseconds as Minutes and Seconds

    Recipe 10.5. Converting Between DMYHMSM and Epoch Milliseconds

    Recipe 10.6. Calculating Elapsed Time or Intervals Between Dates

    Recipe 10.7. Parsing a Date from a String

    Recipe 10.8. Creating Timers and Clocks


Introduction

Dates and times are important to many ActionScript applications, particularly when you develop more robust applications that offer users services. Date and time values are important for determining the amount of time that has elapsed for timed operations, for determining whether a user's trial membership is active, and for storing transaction dates, to name but a few scenarios.

ActionScript stores dates and times internally as Epoch milliseconds, which are the number of milliseconds that have elapsed since the Epoch, namely midnight, January 1, 1970 Coordinated Universal Time (UTC). For our purposes, UTC is essentially equivalent to the more familiar Greenwich Mean Time (GMT). (See http://aa.usno.navy.mil/faq/docs/UT.html to learn about the subtle distinctions.) Many programming languages store dates in terms of the Epoch (often in seconds instead of milliseconds); therefore, you can readily work with date and time values that have been imported from other sources (and vice versa).

In addition, the Date class allows you to set and get date and time values in terms of years, months, days, and so on, using methods such as getFullYear( ), setFullYear( ), getMonth( ), and setMonth( ). These methods are for your convenience, but the values are stored internally as Epoch milliseconds.


Recipe 10.1 Finding the Current Date and Time

10.1.1 Problem

You want to know the current date and time.

10.1.2 Solution

Create a new date using the Date( ) constructor with no parameters. Alternatively, use a CGI script or any other server-side language to return the server time, and create a new Date object from that value.

10.1.3 Discussion

The date and time that ActionScript can calculate on its own is based on the client computer's date and time settings. Therefore, if the user's computer has the incorrect time, so will the Flash movie. With that caveat in mind, you can retrieve the current client-side date and time by creating a new Date object using the Date( ) constructor with no parameters, as follows:

// Create a new Date object.
today = new Date(  );

// Displays client-side date and time
trace(today);

Avoid the temptation to name your date variable date. Unlike in JavaScript, ActionScript identifiers (including variable and class names) are case-insensitive. Date is the name of the constructor function for the Date class; therefore, naming a variable date disables the Date constructor.

If an active Internet connection is available, the Flash movie can try to retrieve the date and time from a server. This technique can ensure more accurate dates and times. Although the server's time settings might be inaccurate, at least the time will be consistent for all client movies.

The basic process when reading the time from a server is as follows:

  1. Create a CGI script on the web server that outputs a name/value pair, in which the value is the number of seconds since midnight January 1, 1970 (the Epoch).

  2. Within the Flash movie, use a LoadVars object (or the loadVariables( ) method) to load the Epoch seconds.

  3. Convert the loaded seconds from a string to a number, multiply by 1000, and construct a new Date object by passing the value to the Date( ) constructor.

PHP is a scripting language that can be found on a large number of web hosts. It is quite simple to create a PHP page to output the current time and date as the number of seconds since the Epoch. All you need to do is create a PHP document with the following content and upload it to the server:

now=<?php echo time(  );?>

If you don't have PHP on your server, or if you are simply more comfortable with Perl (another language that is almost universally available on web servers), then here is a Perl script that outputs the number of seconds since the Epoch:

#!/usr/local/bin/perl
print "Content-type:text/plain\n\n";
print "now=";
print time;

There are a few tips to keep in mind when setting up this script on your server:

  • The first line indicates where the Perl interpreter can be found. The value given in the example is fairly universal. However, contact your web server administrator if you encounter problems.

  • Many servers disable the remote execution of scripts without particular file extensions. The .cgi extension is commonly allowed. Try naming the script getDate.cgi.

  • Most web servers limit CGI access to specific directories. Normally, these directories are found in the account root directory (or within the web root of the account), and are named either cgi or cgi-bin. Make sure you save the script in the correct directory.

  • On Unix servers, your CGI script must have its permissions set to 755. Most FTP programs allow you to change permissions. If you are working from a shell script, use the following command:

    chmod 755 filename

Within the Flash document, you need to load the time value from the server. The best way to accomplish this is to use a LoadVars object. The LoadVars class was introduced in Flash MX. For compatibility with earlier Flash Player versions, use the MovieClip.loadVariables( ) method instead.

If you pass the Date( ) constructor a single value, ActionScript interprets it as the number of milliseconds since the Epoch and creates a new Date object that corresponds to that value. Therefore, you must multiply the value returned by the script (which is in seconds) by 1,000.

// Create a LoadVars object.
lv = new LoadVars(  );

// Use the load(  ) method to call the CGI script on the server. If using PHP, this
// should point to the appropriate PHP page, such as getDate.php, instead.
lv.load("http://www.person13.com/cgi-bin/getDate.cgi");

// onLoad(  ) is invoked automatically when the server returns a response.
lv.onLoad = function (  ) {

  // Create a Date object by passing (seconds * 1000) to the Date(  ) constructor. The
  // value returned by the server is this.nameOfVariable, in this case, this.now.
  // Convert the string value to a number before multiplying it by 1000.
  var serverDate = new Date(Number(this.now) * 1000);

  // Display the date and time returned by the server (in this case, with time zone
  // offsets for the user's computer settings).
  trace(serverDate);
};

The date is always stored in ActionScript as the milliseconds since the Epoch, but it is always displayed with proper offsets based on the user's local time zone setting (unless you specifically use the UTC methods). So if the user's computer has the incorrect time zone setting, the display might be incorrect. However, the actual date (as stored in Epoch milliseconds) is still correct.

10.1.4 See Also

If your server's time is not reliable or accurate enough for your needs, there are many existing date and time servers on the Internet from which you can retrieve reasonably accurate date and time information. For example, see http://tycho.usno.navy.mil. For details on synchronizing time via the Network Time Protocol, see http://www.ntp.org and Recipe 10.7.


Recipe 10.2 Retrieving the Day or Month Name

10.2.1 Problem

You want to retrieve the name of the day or the month.

10.2.2 Solution

Create arrays that contain the string values for the names of the days of the week and the names of the months of the year. Use the numeric day and month to extract the string values from the arrays.

10.2.3 Discussion

The ActionScript Date class provides the getDay( ) and getMonth( ) methods, which return integer values representing the day of the week (from 0 to 6) and the month of the year (from 0 to 11). However, you may want the name of the day or month instead of its zero-relative number. To address this, create arrays containing the names of the days and months. The best way to do this is to define these arrays as properties of the Date class and store this information in a Date.as file that you can include in other projects:

// Create days and months arrays as properties of the Date class.
Date.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
             "Friday", "Saturday"];
Date.months = ["January", "February", "March", "April", "May", "June", "July",
               "August", "September", "October", "November", "December"];

// Create a Date object for December 1, 2002, which is a Sunday.
myDate = new Date(2002, 11, 1);

// Displays: Sunday
trace(Date.days[myDate.getDay(  )]);

// Displays: December
trace(Date.months[myDate.getMonth(  )]);

10.2.4 See Also

Recipe 10.3


Recipe 10.3 Formatting the Date and Time

10.3.1 Problem

You want to display a formatted date and/or time value.

10.3.2 Solution

Use Date.toString( ), or create a custom Date.format( ) method that returns the date and time as a string in the requested format.

10.3.3 Discussion

The Date.toString( ) method returns a user-friendly string version of the target Date object. For example:

// Displays: Mon May 26 11:32:46 GMT-0400 2003
trace(new Date().toString(  ));

Because ActionScript automatically invokes the toString( ) method on any object used in a string context, you can obtain the same result even if you omit toString( ), as in the following example:

// Also displays: Mon May 26 11:32:46 GMT-0400 2003
trace(new Date(  ));

You can rewrite the Date.toString( ) method to return a different date format:

Date.prototype.toString = function (  ) {
  return "Milliseconds since the Epoch: " + this.getTime(  );
};
// Both display: Milliseconds since the Epoch: 1053963542360
trace (new Date().toString(  ));
trace (new Date(  ));

However, a better approach is to implement a flexible, custom formatting method that accepts a parameter specifying the desired date format. Fortunately, there is a standard implementation for date formatting in languages such as Java, which you can imitate. Table 10-1 shows the symbols you should use in creating the formatting string that is passed to the custom format( ) method.

Table 10-1. Date and time symbols

Symbol

Meaning

Example

y

Year

2002

M

Month in year

December or 12 (depends on context: MM means 12 ; MMM means December)

d

Day in month

01

h

Hour in A.M./P.M. (1-12)

12

H

Hour in day (0-23)

21

m

Minute in hour

24

s

Second in minute

33

S

Millisecond in second

960

E

Day in week

Sunday or Sun (depends on context: E means Sunday; EEE means Sun)

a

A.M./P.M.

AM or PM

The custom format( ) method should accept a string parameter indicating the date/time format. It should use a switch statement to return the value in the designated format. The Date.days and Date.months arrays (see Recipe 10.2) must be accessible for the following example code to work properly. (You should include this code in your Date.as file along with the Date.days and Date.months arrays.)

Date.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
             "Friday", "Saturday"];
Date.months = ["January", "February", "March", "April", "May", "June", "July",
               "August", "September", "October", "November", "December"];

// This custom helper method converts a number value to a two-digit string so that
// single-digit values are formatted correctly (e.g., 5 becomes 05).
Date.toTens = function (val) {
  if (val < 10) {
    return "0" + val;
  } else {
    return String(val);
  }
};

// Converts the integer time to a string representation. The 24-hour clock (where
// hours is from 0 to 23) in converted to a 12-hour clock with AM/PM indictor.
Date.toTwelveHour = function (hour, min) {
  var amPm = "AM";
  if (hour > 12) {
    hour = hour - 12;
    amPm = "PM";
  } else if (hour == 0) {
    hour = 12;
  }
  return Date.toTens(hour) + ":" + Date.toTens(min) + " " + amPm;
};

Date.prototype.format = function (format) {

  // Create local variables with the date's values.
  var day       = this.getDay(  );
  var monthDate = this.getDate(  );
  var month     = this.getMonth(  );
  var year      = this.getFullYear(  );
  var hour      = this.getHours(  );
  var min       = this.getMinutes(  );
  var sec       = this.getSeconds(  );
  var millis    = this.getMilliseconds(  );

  // Return a string with the date and/or time in the requested format, such as
  // "MM-dd-yyyy". You may want to add more cases to implement the remaining formats
  // within Table 10-1.
  switch (format) {
    case "MM-dd-yyyy":
      return Date.toTens(month + 1) + "-" + Date.toTens(monthDate) + "-" + year;
    case "MM/dd/yyyy":
      return Date.toTens(month + 1) + "/" + Date.toTens(monthDate) + "/" + year;
    case "dd-MM-yyyy":
      return Date.toTens(monthDate) + "-" + Date.toTens(month + 1) + "-" + year;
    case "dd/MM/yyyy":
      return Date.toTens(monthDate) + "/" + Date.toTens(month + 1) + "/" + year;
    case "hh:mm a":
      return Date.toTwelveHour(hour, min);
    case "EEE, MMM dd, yyyy":
      return Date.days[day].substr(0, 3) + ", " + Date.months[month] + " " +
             Date.toTens(monthDate) + ", " + year;
    case "E, MMM dd, yyyy":
      return Date.days[day] + ", " + Date.months[month] + " " +
             Date.toTens(monthDate) + ", " + year;
  }
};

Here are a few examples of the format( ) method being used:

// Create a date for December 1, 2002, which is a Sunday. The time is 06:09PM.
myDate = new Date(2002, 11, 1, 18, 9);

trace(myDate.format("MM-dd-yyyy"));   // Displays: 12-01-2002
trace(myDate.format("MM/dd/yyyy"));   // Displays: 12/01/2002
trace(myDate.format("dd-MM-yyyy"));   // Displays: 01-12-2002
trace(myDate.format("dd/MM/yyyy"));   // Displays: 01/12/2002
trace(myDate.format("hh:mm a"));      // Displays: 06:09 PM
// Displays: Sun, December 01, 2002
trace(myDate.format("EEE, MMM dd, yyyy"));
// Displays: Sunday, December 01, 2002 06:09 PM
trace(myDate.format("E, MMM dd, yyyy") + " " + myDate.format("hh:mm a"));

10.3.4 See Also

It is possible to enhance the format( ) method by adding more cases to the switch statement. This is left to you as an exercise. You may find the information at http://www.php.net/manual/en/function.date.php useful as a reference. Also refer to Recipe 10.4.


Recipe 10.4 Formatting Milliseconds as Minutes and Seconds

10.4.1 Problem

You want to display milliseconds in minutes and seconds (mm:ss) format.

10.4.2 Solution

Create and use a custom, static method, Date.formatMilliseconds( ).

10.4.3 Discussion

Many values in ActionScript are given in milliseconds. For example, sound lengths are given in milliseconds. However, in most cases, you want to format the value as minutes and seconds when displaying it to the user. You can accomplish this with a short custom method that we'll add to the Date class.

Here's our static method—a static method is called directly from the class, rather than on an object instance—that converts milliseconds to the format mm:ss. Add this code to your Date.as file for easy inclusion in other projects.

Date.formatMilliseconds = function (millis) {

  // Determine the minutes and seconds portions of the time.
  var seconds = millis / 1000;
  var m = Math.floor(seconds/60);
  var s = Math.round(seconds - (m * 60));

  // Add leading zeros to one-digit numbers.
  if (m < 10) {
    m = "0" + m;
  }
  if (s < 10) {
    s = "0" + s;
  }
  return m + ":" + s;
};

Here's an example of how you can use this method:

#include "Date.as"

// Assuming mySound_sound is a Sound object, get its duration in milliseconds.
dur = mySound_sound.duration;

// Create a text field to display the value.
this.createTextField("displayTime_txt", 1, 0, 0, 100, 20);

// Display the time value formatted as mm:ss.
displayTime_txt.text = Date.formatMilliseconds(dur);

10.4.4 See Also

Recipe 10.3 and Recipe 10.5


Recipe 10.5 Converting Between DMYHMSM and Epoch Milliseconds

10.5.1 Problem

You want to convert between DMYHMSM format (days, months, years, hours, minutes, seconds, milliseconds) and Epoch milliseconds.

10.5.2 Solution

Use the getTime( ) and setTime( ) methods.

10.5.3 Discussion

Most of us are more comfortable thinking of dates and times in terms of components such as hours, days, and years than working with Epoch milliseconds or seconds. For example, it is generally more meaningful to humans to discuss the time and date 3:55 A.M., Friday, October 13, 1978 than to discuss the corresponding Epoch milliseconds value of 277124100000. However, languages such as ActionScript store times in Epoch milliseconds (or Epoch seconds) format. Therefore, it is important to be able to convert between different formats when displaying dates and times to users or when sharing dates between applications that use different formats.

When constructing a date in ActionScript, you can use the DMYHMSM approach, as follows:

// Construct a date for 3:55 AM, Friday, October 13, 1978.
myDate = new Date(1978, 9, 13, 3, 55, 0, 0);

ActionScript automatically performs the conversion and stores the date as the corresponding Epoch milliseconds value. To retrieve that value, call the getTime( ) method from the Date object:

// For Pacific Standard Time, displays: 277124100000
// The output may vary depending on your time zone.
trace(myDate.getTime(  ));

You can pass the Epoch seconds value returned by getTime( ) to another application (such as a CGI script) or use it to perform date mathematics (see Recipe 10.6).

On the other hand, you may want to set a date using the Epoch milliseconds. For example, in Recipe 10.1, the CGI script returns the current server time to Flash in Epoch seconds (which needs to be converted to milliseconds by multiplying by 1,000). Also, when performing date mathematics you may want to set a date according to Epoch milliseconds. You have two options for setting a date according to Epoch milliseconds. One choice is to pass the milliseconds value to the Date constructor as the only parameter, and the other is to pass the milliseconds value to the setTime( ) value of an existing date. Both techniques are effectively the same. The only reason to use the setTime( ) technique rather than the Date constructor is if you have assigned any custom properties to an existing date that would be lost by calling the constructor.

// Construct a new Date object for 3:55 AM, Friday, October 13, 1978. Here, we use
// the value displayed in the Output window from the preceding example.
myDate = new Date(277124100000);

// Displays: Fri Oct 13 03:55:00 GMT-0700 1978 (timezone offset may vary)
trace(myDate);

// Create a Date object.
myDate = new Date(  );

// Assign a custom property to the Date object.
myDate.label = "This is a special day!";

// Use the setTime(  ) method to set the date to 3:55 AM, Friday, October 13, 1978.
// This doesn't overwrite the label property, as calling the Date constructor would.
myDate.setTime(277124100000);

// Displays: Fri Oct 13 03:55:00 GMT-0700 1978 (timezone offset may vary)
trace(myDate);

10.5.4 See Also

After assigning a value to a date using the setTime( ) method, you can use getDate( ), getMonth( ), getYear( ), getHours( ), getSeconds( ), getMinutes( ), and getMilliseconds( ) methods to retrieve the individual DMYHMSM values in the local time. To retrieve the UTC date and time, use getUTCDate( ), getUTCMonth( ), getUTCHours( ), getUTCSeconds( ), getUTCMinutes( ), and getUTCMilliseconds( ) instead. See Recipe 10.6.


Recipe 10.6 Calculating Elapsed Time or Intervals Between Dates

10.6.1 Problem

You want to calculate an elapsed time, elapsed date, or relative time.

10.6.2 Solution

For simple elapsed time, add and subtract from the Epoch milliseconds or the value returned by getTimer( ). For more complex conversions, create custom Date.doMath( ) and Date.elapsedTime( ) methods.

10.6.3 Discussion

For simple conversions such as adding or subtracting an hour, day, or week to or from a date, simply add or subtract from the date's Epoch milliseconds value. For this purpose, note that a second is 1,000 milliseconds, a minute is 60,000 milliseconds, an hour is 3,600,000 milliseconds, a week is 604,800,000 milliseconds, and so on. Unless you have a spectacular gift for remembering these conversion values, storing them as constants of the Date class is the easiest option. You can add the following constants to your Date.as file for convenience:

// There are 1000 milliseconds in a second, 60 seconds in a minute, 60 minutes in an
// hour, 24 hours in a day, and 7 days in a week.
Date.SEC  = 1000;
Date.MIN  = Date.SEC * 60;
Date.HOUR = Date.MIN * 60;
Date.DAY  = Date.HOUR * 24;
Date.WEEK = Date.DAY * 7;

You can use the Date.getTime( ) method to retrieve a date's current value in Epoch milliseconds, and you can set the new value using the Date.setTime( ) method. The following example adds one day to a given Date object.

#include "Date.as"
myDate = new Date(1978, 9, 13, 3, 55, 0, 0);

// Displays: Fri Oct 13 03:55:00 GMT-0700 1978
trace(myDate);

// Add one day to the previous date by setting the new date/time to the original
// date/time plus Date.DAY (the number of milliseconds in a day).
myDate.setTime(myDate.getTime(  ) + Date.DAY);

// Displays: Sat Oct 14 03:55:00 GMT-0700 1978
trace(myDate);

Here are some more examples of simple conversions using Date.getTime( ), Date.setTime( ), and the aforementioned constants:

#include "Date.as"
myDate = new Date(1978, 9, 13, 3, 55, 0, 0);

// Subtract one week from the date.
// Displays : Fri Oct 6 03:55:00 GMT-0700 1978 (timezone offset may vary)
myDate.setTime(myDate.getTime(  ) - Date.WEEK);
trace(myDate);

// Add one week and one day to the date.
// Displays: Sat Oct 14 03:55:00 GMT-0700 1978 (timezone offset may vary)
myDate.setTime(myDate.getTime(  ) + Date.WEEK + Date.DAY);
trace(myDate);

// Subtract 3 hours and 55 minutes from the date.
// Displays: Sat Oct 14 00:00:00 GMT-0700 1978 (timezone offset may vary)
myDate.setTime(myDate.getTime(  ) - (3 * Date.HOUR) - (55 * Date.MIN));
trace(myDate);

You'll often want to calculate an elapsed time to create a timer for a game or other activity. Calculating the elapsed time is simply a matter of recording the time during initialization and then later comparing it to the current time at some later point during execution. For example, to calculate how long a movie has been running, you can use the following code (note that the new Date( ) constructor always returns the current time):

// Record the starting time.
var startingTime = new Date(  );
// Create a text field to display the current time.
this.createTextField("timer_txt", 1, 100, 100, 50, 20);

// Check the elapsed time during each tick of the frame rate.
this.onEnterFrame = function (  ) {
  // Determine the elapsed time.
  var elapsedTime = new Date().getTime() - startingTime.getTime(  );
  // Convert from milliseconds to seconds and round to the nearest second.
  this.timer_txt.text = Math.round(elapsedTime / 1000);
};

The global getTimer( ) function, not to be confused with the Date.getTime( ) method, returns the number of milliseconds since the Player started running. Therefore, by checking its value at successive times, it can also be used to determine the elapsed time. This alternative to the preceding example uses the global getTimer( ) function in place of new Date( ):

var startingTime = getTimer(  );
this.createTextField("timer_txt", 1, 100, 100, 50, 20);

this.onEnterFrame = function (  ) {
  // Determine the elapsed time.
  var elapsedTime = getTimer(  ) - startingTime;
  // Convert from milliseconds to seconds and round to the nearest second.
  this.timer_txt.text = Math.round(elapsedTime / 1000);
};

Note that the following code is not necessarily an acceptable alternative to the preceding example. The global getTimer( ) function returns the number of milliseconds since the Player started running, not since the current movie started running. In this example, the timer is not reset when a new movie loads:

this.createTextField("timer_txt", 1, 100, 100, 50, 20);
this.onEnterFrame = function (  ) {
  // Convert from milliseconds to seconds and round to the nearest second.
  this.timer_txt.text = Math.round(getTimer(  )/ 1000);
};

We can adapt the earlier example to create a countdown timer. This example jumps to the frame labeled "OutOfTime" after 60 seconds:

// Record the starting time.
var startingTime = getTimer(  );
// Create a text field to display the current time.
this.createTextField("timer_txt", 1, 100, 100, 50, 20);
// Count down from 60 seconds
var maxTime = 60

// Check the elapsed time during each tick of the frame rate.
this.onEnterFrame = function (  ) {
  // Determine the elapsed time.
  var elapsedTime = getTimer(  ) - startingTime;
  // Convert from milliseconds to seconds and round to the nearest second.
  elapsedTime = Math.round(elapsedTime / 1000);
  if (elapsedTime >= maxTime) {
    this.timer_txt.text = "0";
    gotoAndPlay("OutOfTime");
  } else {
    this.timer_txt.text = maxTime - elapsedTime;
  }
};

Let's return to our earlier example in which we calculated elapsed times using Date objects. When it comes to adding and subtracting years and months from dates, we cannot rely on constants. This is because the number of milliseconds in a month varies with the number of days in the month, and leap years have more milliseconds than other years. However, the Date class handles wrap-around calculations transparently when using the getter and setter methods. The most effective way to handle date math is to create a Date.doMath( ) method that performs the calculations for you. Such a method should take up to seven numeric parameters, each of which can be positive or negative:

years

The number of years to add to the date

months

The number of months to add to the date

days

The number of days to add to the date

hours

The number of hours to add to the date

minutes

The number of minutes to add to the date

seconds

The number of seconds to add to the date

milliseconds

The number of milliseconds to add to the date

Here is the custom Date.doMath( ) method, which you can add to your Date.as file for easy inclusion in other projects:

Date.prototype.doMath = function (years, months, days, hours, minutes,
                                  seconds, milliseconds) {

  // Perform conversions on a copy so as not to alter the original date.
  var d = new Date(this.getTime(  ));

  // Add the specified intervals to the original date.
  d.setYear(d.getFullYear(  ) + years);
  d.setMonth(d.getMonth(  ) + months);
  d.setDate(d.getDate(  ) + days);
  d.setHours(d.getHours(  ) + hours);
  d.setMinutes(d.getMinutes(  ) + minutes);
  d.setSeconds(d.getSeconds(  ) + seconds);
  d.setMilliseconds(d.getMilliseconds(  ) + milliseconds);

  // Return the new date value.
  return d;
};

// Example usage:
#include "Date.as"
myDate = new Date(1978, 9, 13, 3, 55, 0, 0);

// Add 1 year and subtract 3 hours and 55 minutes.
// Displays: Sat Oct 13 00:00:00 GMT-0700 1979
trace(myDate.doMath(1, 0, 0, -3, -55));

// You can also add 365 days and subtract 235 minutes to arrive at the same date as
// the previous statement. Displays: Sat Oct 13 00:00:00 GMT-0700 1979
trace(myDate.doMath(0, 0, 365, 0, -235));

// Add 24 years. Displays: Sun Oct 13 03:55:00 GMT-0700 2002
trace(myDate.doMath(24));

The preceding example demonstrates how to create a new Date object based on an elapsed time from an existing Date object. However, you may want to calculate the elapsed time between two existing Date objects, which is not as trivial as you might assume. You might try subtracting the return value of the getTime( ) method of one Date object from another, but this doesn't offer a general solution for calculating the elapsed time between two Date objects. Although the operation yields the number of milliseconds between the two dates, the result isn't convenient to manipulate when the times are not within the same day. Manually converting the number of milliseconds to a number of years, months, and days is difficult due to the varying number of days per month, leap year, etc. Furthermore, handling negative elapsed times can be cumbersome.

Therefore, it is easiest to create a custom Date.elapsedTime( ) method that returns a Date object representing an elapsed time. This allows us to use the Date class's built-in methods to calculate the number of years, months, and days between two Date objects. There are several caveats, however. Although ActionScript internally stores dates relative to the Epoch time (midnight on January 1, 1970), most Date class methods return absolute values, not values relative to the Epoch. For example, the year for a date in 1971 is returned as 1971, not 1. For our elapsed-time object to be optimally useful, we must define custom elapsedYears( ) and elapsedDays( ) methods to return values that are relative to the Epoch time. (The month, hour, minute, second, and millisecond of the Epoch time are all 0, so there is no need for custom methods to return relative offsets for these values.)

Our custom elapsedTime( ) method presented here creates a new Date object that represents the elapsed time between the Date object on which it is invoked and another Date object. If no secondary Date object is specified, the elapsed time is calculated relative to the current time ("now"). The elapsed time is always stored as a positive number, regardless of which Date object represents a later time. You should add the elapsedTime( ), elapsedYears( ), and elapsedDays( ) methods to your Date.as file for easy inclusion in other projects.

// Calculate the elapsed time (as an absolute value) between a Date object and a
// specified date.
Date.prototype.elapsedTime = function (t1) {
  // Calculate the elapsed time relative to the specified Date object.
  if (t1 == undefined) {
    // Calculate the elapsed time from "now" if no object is specified.
    t1 = new Date(  );
  } else {
    // Make a copy so as not to alter the original.
    t1 = new Date(t1.getTime(  ));
  }
  // Use the original Date object's time as one endpoint. Make a copy so as not to
  // alter the original.
  var t2 = new Date(this.getTime(  ));

  // Ensure that the elapsed time is always calculated as a positive value.
  if (t1 < t2) {
   temp = t1;
   t1 = t2;
   t2 = temp;
  }
  // Return the elapsed time as a new Date object.
  var t = new Date(t1.getTime() - t2.getTime(  ));
  return t;
};

The elapsedYears( ) and elapsedDays( ) methods return the year and day relative to the Epoch time.

Date.prototype.elapsedYears = function (  ) {
  return this.getUTCFullYear(  ) - 1970;
};

Date.prototype.elapsedDays = function (  ) {
  return this.getUTCDate(  ) - 1;
};

Here are some examples of how to use the custom methods. Note that the built-in Date class methods can be used to retrieve the elapsed months, hours, minutes, seconds, and milliseconds, but the custom elapsedYears( ) and elapsedDays( ) methods must be used to retrieve the elapsed years and days.

#include "Date.as"
// Calculate someone's age based on his birthday.
birthday = new Date(1966, 3, 17);
age = birthday.elapsedTime(  );
trace("Elapsed Years "        + age.elapsedYears(  ));
trace("Elapsed Months "       + age.getUTCMonth(  ));
trace("Elapsed Days "         + age.elapsedDays(  ));
trace("Elapsed Hours "        + age.getUTCHours(  ));
trace("Elapsed Minutes "      + age.getUTCMinutes(  ));
trace("Elapsed Seconds "      + age.getUTCSeconds(  ));
trace("Elapsed Milliseconds " + age.getUTCMilliseconds(  ));

// Calculate an interval between two dates.
firstDay = new Date(1901, 0, 1, 0, 0, 0, 0);
lastDay  = new Date(2000, 11, 31, 23, 59, 59, 999)
century = firstDay .elapsedTime(lastDay);
trace("Elapsed Years "        + century.elapsedYears(  ));
trace("Elapsed Months "       + century.getUTCMonth(  ));
trace("Elapsed Days "         + century.elapsedDays(  ));
trace("Elapsed Hours "        + century.getUTCHours(  ));
trace("Elapsed Minutes "      + century.getUTCMinutes(  ));
trace("Elapsed Seconds "      + century.getUTCSeconds(  ));
trace("Elapsed Milliseconds " + century.getUTCMilliseconds(  ));

Naturally, there are many other potential ways to implement the elapsed date/time functionality. We have chosen the simplest for illustration purposes. For a more elegant and time-consuming solution, you could implement an elapsedDate class as a subclass of the Date class. The custom class could override the built-in getUTCFullYear( ), getUTCDate( ), getYear( ), setUTCDate( ), setUTCFullYear( ), and setYear( ) to get and set values relative to the Epoch instead of absolute values. Such an implementation is left as an exercise to the reader.

10.6.4 See Also

Recipe 10.8


Recipe 10.7 Parsing a Date from a String

10.7.1 Problem

You want to create a Date object from a string.

10.7.2 Solution

Create a custom parseDate( ) method that parses the individual date components (year, month, etc.) from the string using regular expressions and then assembles a new date.

10.7.3 Discussion

ActionScript does not provide any native methods for parsing a string into a Date object. This limitation does not necessarily pose a problem. For example, Flash Remoting allows you to return native Date objects from other applications. Even if you are not working with Flash Remoting, you can pass values between Flash and other applications using Epoch seconds/milliseconds. However, if you need to parse a string into a date, you should use a custom parseDate( ) method. This method takes the string value as a parameter, parses out each of the date's parts (the year, hour, etc.), and then returns a new Date object. Add the following code to your Date.as file for easy inclusion in other projects. The following code uses the custom RegExp class. See Recipe 9.6.

// You must include the third-party RegExp.as file from Recipe 9.6. One way to ensure
// this is to add #include "RegExp.as" to the Date.as file in which you also add the
// parseDate(  ) method.
#include "RegExp.as"

// This method also relies on the Date.months array from Recipe 10.2.
Date.parseDate = function (dateStr) {

  // Create local variables to hold the year, month, date of month, hour, minute, and
  // second. Assume that there are no milliseconds in the date string.
  var year, month, monthDate, hour, minute, second;

  // Use a regular expression to test whether the string uses ActionScript's date
  // string format. Other languages and applications may use the same format. For
  // example: Thu Dec 5 06:36:03 GMT-0800 2002
  var re = new RegExp(
    "[a-zA-Z]{3} [a-zA-Z]{3} [0-9]{1,2} [0-9]{2}:[0-9]{2}:[0-9]{2} .* [0-9]{4}",
    "g");
  var match = re.exec(dateStr);

  // If the date string matches the pattern, parse the date from it and return a new
  // Date object with the extracted value.
  if (match != null) {

    // Split the match into an array of strings. Split it on the spaces so the
    // elements will be day, month, date of month, time, timezone, year.
    var dateAr = match[0].split(" ");

    // Set the month to the second element of the array. This is the abbreviated name
    // of the month, but we want the number of the month (from 0 to 11), so loop
    // through the Date.months array until you find the matching element, and set
    // month to that index.
    month = dateAr[1];
    for (var i = 0; i < Date.months.length; i++) {
      if (Date.months[i].indexOf(month) != -1) {
        month = i;
        break;
      }
    }

    // Convert the monthDate and year from the array from strings to numbers.
    monthDate = Number(dateAr[2]);
    year = Number(dateAr[dateAr.length - 1]);

    // Extract the hour, minute, and second from the time element of the array.
    var timeVals = dateAr[3].split(":");
    hour   = Number(timeVals[0]);
    minute = Number(timeVals[1]);
    second = Number(timeVals[2]);

    // If the array has six elements, there is a timezone offset included (some date
    // strings in this format omit the timezone offset).
    if (dateAr.length == 6) {
      var timezone = dateAr[4];

      // Multiply the offset (in hours) by 60 to get minutes.
      var offset = 60 * Number(timezone.substr(3, 5))/100;

      // Calculate the timezone difference between the client's computer and the
      // offset extracted from the date string.
      var offsetDiff = offset + new Date().getTimezoneOffset(  );

      // Add the timezone offset, in minutes. If the date string and the client
      // computer are in the same timezone, the difference is 0.
      minute += offsetDiff;
    }

    // Return the new date.
    return new Date(year, month, monthDate, hour, minute, second);
  }

  // If the date string didn't match the standard date string format, test whether it
  // includes either MM-dd-yy(yy) or MM/dd/yy(yy).
  re = new RegExp("[0-9]{2}(/|-)[0-9]{2}(/|-)[0-9]{2,}", "g");
  match = re.exec(dateStr);
  if (match != null) {
    // Get the month, date, and year from the match. First, use the forward slash as
    // the delimiter. If that returns an array of only one element, use the dash
    // delimiter instead.
    var mdy = match[0].split("/");
        if (mdy.length == 1) {
          mdy = match[0].split("-");
        }

    // Extract the month number and day-of-month values from the date string.
    month = Number(mdy[0]) - 1;
    monthDate = Number(mdy[1]);

    // If the year value is two characters, then we must add the century to it.
    if (mdy[2].length == 2) {
      twoDigitYear = Number(mdy[2]);
      // Assumes that years less than 50 are in the 21st century
      year = (twoDigitYear < 50) ? twoDigitYear + 2000 : twoDigitYear + 1900;
    } else {
      // Extract the four-digit year
      year = mdy[2];
    }
  }

  // Check whether the string includes a time value of the form of h(h):mm(:ss).
  re = new RegExp("[0-9]{1,2}:[0-9]{2}(:[0-9]{2,})?", "g");
  match = re.exec(dateStr);
  if (match != null) {
    // If the length is 4, the time is given as h:mm. If so, then the length of the
    // first part of the time (hours) is only one character. Otherwise, it is two
    // characters in length.
    var firstLength = 2;
    if (match[0].length == 4) {
      firstLength = 1;
    }

    // Extract the hour and minute parts from the date string. If the length of the
    // match is greater than five, assume that it includes seconds.
    hour = Number(dateStr.substr(match.index, firstLength));
    minute = Number(dateStr.substr(match.index + firstLength + 1, 2));
    if (match[0].length > 5) {
      second = Number(dateStr.substr(match.index + firstLength + 4, 2));
    }
  }

  // Return the new date.
  return new Date(year, month, monthDate, hour, minute, second);
};

Here are a few examples that show how you can use the parseDate( ) method to create a new Date object from a string. As with many of the examples in this chapter, the actual output will vary depending on your time zone settings.

#include "Date.as"

// Displays: Sat Oct 9 00:00:00 GMT-0700 1982
trace(Date.parseDate("10/09/1982"));

// Displays: Fri Oct 13 03:55:00 GMT-0700 1978
trace(Date.parseDate("It was 3:55 on 10-13-78"));

// Displays: Tue Dec 3 06:36:02 GMT-0800 2002
trace(Date.parseDate("Fri Dec 2 14:36:02 GMT+0800 2002"));


Recipe 10.8 Creating Timers and Clocks

10.8.1 Problem

You want to create a timer or perform actions at set intervals.

10.8.2 Solution

Use the setInterval( ) function.

10.8.3 Discussion

This recipe explains how to perform actions at set intervals and create a clock showing the absolute time. Refer to Recipe 10.6 for details on creating timers that show the elapsed time.

The setInterval( ) function, added in Flash MX, lets you set up a timer that calls a function or a method at a specific time interval. The function returns a reference to the interval so that you can abort the action in the future. You can choose from several variations depending on how you want to use setInterval( ). If you want to call a function at a specific interval without passing it any parameters, you can call setInterval( ) with a reference to the function and the number of milliseconds between function invocations.

// This example uses the custom format(  ) method, so it requires the Date.as file.
#include "Date.as"

// This function is called by setInterval(  ), and it displays the current time.
function displayTime(  ) {
  var d = new Date(  );
  trace(d.format("hh:mm a"));
}

// This example invokes displayTime every 60,000 milliseconds (once per minute).
dtInterval = setInterval(displayTime, 60000);

The setInterval( ) function invokes the function or method at approximately the specified interval. The interval between calls is dependent on the processor of the client computer and is not exact or consistent.

You can also use setInterval( ) to pass parameters to the called function. When you pass additional parameters to the setInterval( ) function, those values are passed along to the function that is called at the interval. You should note, however, that the parameters sent to the function cannot be dynamically updated with each call. The setInterval( ) function calls the function multiple times, but setInterval( ) itself is called only once. Therefore, the values of the parameters that are passed to the function are evaluated only once as well:

// Create a function that displays the value passed to it.
function displayValue (val) {
  trace(val);
}

// Create an interval such that displayValue(  ) is called every five seconds and is
// passed a parameter. Notice that even though the parameter is Math.random(  ) (a
// method that generates a random value between 0 and 1), the same value is always
// passed to displayValue(  ) because Math.random(  ) is evaluated only once.
dvInterval = setInterval(displayValue, 5000, Math.random(  ));

You can also use setInterval( ) to call methods of an object, in which case you must pass it at least three parameters. The first parameter is a reference to the object, the second parameter is the string name of the method, and the third parameter is the number of milliseconds for the interval.

// Include the DrawingMethods.as file from Chapter 4 for its drawTriangle(  ) method.
#include "DrawingMethods.as"

// Create a movie clip object and draw a triangle in it.
_root.createEmptyMovieClip("triangle", 1);
triangle.lineStyle(1, 0x000000, 100);
triangle.drawTriangle(300, 500, 60);

// Add a method to the triangle that moves the
// movie clip 10 pixels in the x and y directions.
triangle.move = function (  ) {
  this._x += 10;
  this._y += 10;
};

// Call the move(  ) method of the triangle movie clip every two seconds.
trInterval = setInterval(triangle, "move", 2000);

You can pass parameters to an object's method via setInterval( ) by listing the parameters after the milliseconds parameter in the setInterval( ) call:

// Include the DrawingMethods.as file from Chapter 4 for its drawTriangle(  ) method.
#include "DrawingMethods.as"

_root.createEmptyMovieClip("triangle", 1);
triangle.lineStyle(1, 0x000000, 100);
triangle.drawTriangle(300, 500, 60);

// Modify the move(  ) method to accept parameters.
triangle.move = function (x, y) {
  this._x += x;
  this._y += y;
};

// Call the move(  ) method every two seconds and pass it the values 10 and 10.
trInterval = setInterval(triangle, "move", 2000, 10, 10);

The following example uses setInterval( ) to create a clock movie clip in which the display is updated once per second:

// Include the DrawingMethods.as file from Chapter 4 for its drawCircle(  ) method.
#include "DrawingMethods.as"

// Create the clock_mc movie clip on _root and place it so it is visible on stage.
// This movie clip contains the hands and face movie clips.
_root.createEmptyMovieClip("clock_mc", 1);
clock_mc._x = 200;
clock_mc._y = 200;

// Add a face_mc movie clip to clock_mc and draw a circle in it.
clock_mc.createEmptyMovieClip("face_mc", 1);
clock_mc.face_mc.lineStyle(1, 0x000000, 100);
clock_mc.face_mc.drawCircle(100);

// Add the hour, minute, and second hands to clock_mc. Draw a line in each of them.
clock_mc.createEmptyMovieClip("hourHand_mc", 2);
clock_mc.hourHand_mc.lineStyle(3, 0x000000, 100);
clock_mc.hourHand_mc.lineTo(50, 0);
clock_mc.createEmptyMovieClip("minHand_mc", 3);
clock_mc.minHand_mc.lineStyle(3, 0x000000, 100);
clock_mc.minHand_mc.lineTo(90, 0);
clock_mc.createEmptyMovieClip("secHand_mc", 4);
clock_mc.secHand_mc.lineStyle(1, 0x000000, 100);
clock_mc.secHand_mc.lineTo(90, 0);

// Create a method for the clock_mc movie clip that updates the clock display based
// on the client computer's current time.
clock_mc.updateDisplay = function (  ) {

  // Extract the hour, minute, and second from the current time.
  var d = new Date(  );
  var hour = d.getHours(  );
  var min = d.getMinutes(  );
  var sec = d.getSeconds(  );

  // Set the rotation of the hands according to the hour, min, and sec values, The
  // _rotation property is in degrees, so calculate each value by finding the
  // percentage of the hour, min, or sec relative to a full rotation around the clock
  // (and multiply by 360 to get the degrees). Also, subtract 90 degrees from each
  // value to correctly offset the rotation from 12 o'clock rather than from the 3
  // o'clock position.
  this.hourHand_mc._rotation = (((hour + min/60)/12) * 360) - 90;
  this.minHand_mc._rotation = (((min + sec/60)/60) * 360) - 90;
  this.secHand_mc._rotation = ((sec/60) * 360) - 90;
};

// Call the updateDisplay(  ) method of the clock_mc movie clip once per second.
clockInterval = setInterval(clock_mc, "updateDisplay", 1000);

10.8.4 See Also

It is left as an exercise for you to add a digital display to the preceding clock example using the techniques shown in Recipe 10.6 and Recipe 10.3. Also refer to Recipe 1.7.


Introduction

Forms are the primary means through which applications accept user input. They are used for a wide variety of purposes, such as gathering user information, performing searches, and placing orders for products and services. But whatever the purpose of a specific form, they all share several common features:

  • A form is composed of input text fields, menus, checkboxes, radio buttons, push buttons, and possibly other custom control types such as sliders, dials, calendars, and even images.

  • A form submits data to be processed further, often by a server-side script.

  • Many forms validate their data before submitting it for processing to ensure that all necessary information has been entered properly.

  • A form may be limited to one page (screen) or be spread across several pages, depending on its length and appropriate user considerations.

ActionScript provides text fields as well as standard UI components for creating the elements of a form, but it does not provide a native Form class to manage the user input process or the form as a whole. However, this chapter includes recipes that enable you to create and utilize forms effectively in your Flash movies. These recipes extend the functionality of existing UI components, define a Form class, and define a MultiPageForm class. You should place all of this code in a Form.as file and save it to your Flash Include directory so that you can reuse the code easily in your applications.


Recipe 11.1 Adding UI Components at Runtime

11.1.1 Problem

You want to add UI components such as combo boxes, list boxes, and push buttons to a movie at runtime.

11.1.2 Solution

Add the component to the Library at authoring time and use attachMovie( ) to create a runtime instance.

11.1.3 Discussion

You can use attachMovie( ) to add UI components to your movies at runtime, just as you would any other movie clip. However, you must first add the components to the movie's Library. To do so, you must drag a component from the Components panel to the Stage during authoring, which also places a copy of the component in your Library under the heading "Flash UI Components." Flash automatically copies over any subcomponents that are needed; for example, dragging a list box to the Stage copies both the ListBox component and the ScrollBar component, on which list boxes rely, to the Library.

You can optionally delete the component instances from the Stage, but the components remain accessible in the Library. The component symbols are automatically set for export, and you should leave the linkage identifier names at their defaults, as listed in Table 11-1.

Table 11-1. UI component linkage identifiers

Component

Linkage identifier

List box

FListBoxSymbol

Combo box

FComboBoxSymbol

Checkbox

FCheckBoxSymbol

Radio button

FRadioButtonSymbol

Push button

FPushButtonSymbol

Scrollbar

FScrollBarSymbol

Scroll pane

FScrollPaneSymbol

Message box

FMessageBoxSymbol

Once a component symbol is within a movie's Library, the procedure for adding instances at runtime with attachMovie( ) is exactly the same as with any other movie clip. For example, this code creates a new movie clip instance of the CheckBox UI component and names it myCheckBox_ch:

_root.attachMovie("FCheckBoxSymbol", "myCheckBox_ch", 1);

11.1.4 See Also

Recipe 7.1, Recipe 7.19, Recipe 11.3, Recipe 11.7, Recipe 11.10, and Recipe 11.15


Recipe 11.10 Adding Checkboxes to a Form

11.10.1 Problem

You want to add checkboxes to your form.

11.10.2 Solution

Use checkbox UI components.

11.10.3 Discussion

You should use checkboxes in situations in which you want a user to be able to select multiple options from a list, but you do not wish to use a list box. Or, alternatively, you should use a checkbox in any situation that calls for a simple yes/no or true/false answer. Checkboxes function similarly to radio buttons, except for a few important distinctions:

  • You cannot set a data value for a checkbox. Checkboxes have only two possible values: true (checked) or false (unchecked).

  • Checkboxes do not have a natively available group as radio buttons do.

  • Multiple checkboxes should not be used for mutually exclusive choices.

You can set the label of a checkbox with the setLabel( ) method, and you can set the placement of the label (right or left) using the setLabelPlacement( ) method, just as with radio buttons.

The fact that checkboxes don't provide native support for grouping, as radio buttons do, is not necessarily a problem. However, by adding grouping functionality to checkboxes (and coding the checkbox group class to inherit from the radio button group class), you can easily leverage the resizing and positioning code from Recipe 11.7 and Recipe 11.8 for radio button groups to work with checkboxes.

You can create a custom checkbox group class as follows:

  1. Create a FCheckBoxGroupClass constructor.

  2. Designate FCheckBoxGroupClass to inherit from FRadioButtonGroupClass.

  3. Define setGroupName( ) and addToRadioGroup( ) methods for FCheckBoxClass (the class for checkboxes). These methods can be copied from FRadioButtonClass and modified slightly.

The following code uses variables and properties with "radio" in their names instead of "checkbox." Do not be confused by this. This is merely because the checkbox group class inherits from the radio button group class, and therefore the references are to radio buttons even though in truth they are checkboxes.

Here is our custom setGroupName( ) code for the FCheckBoxGroupClass class:

// FCheckBoxGroupClass should be defined globally just as the other UI component
// classes. Set the class to inherit from FRadioButtonGroupClass. This means that all
// the functionality of FRadioButtonGroupClass is accessible to the new class.
_global.FCheckBoxGroupClass = function (  ) {};
FCheckBoxGroupClass.prototype = new FRadioButtonGroupClass(  );

// Define a setGroupName(  ) method for checkboxes, which is the
// same as the one for radio buttons.
FCheckBoxClass.prototype.setGroupName = function (groupName) {
  for (var i = 0; i < this._parent[this.groupName].radioInstances.length; i++) {
    if (this._parent[this.groupName].radioInstances[i] == this) {
      delete this._parent[this.groupName].radioInstances[i];
    }
  }
  this.groupName = groupName;
  this.addToRadioGroup(  );
};

// Define addToRadioGroup(  ) for checkboxes. This method is called from
// setGroupName(  ). It is the same method as the addToRadioGroup(  ) method for radio
// buttons, except it creates a new instance of FCheckBoxGroupClass instead of
// FRadioButtonGroupClass.
FCheckBoxClass.prototype.addToRadioGroup = function (  ) {
  if (this._parent[this.groupName] == undefined) {
    this._parent[this.groupName] = new FCheckBoxGroupClass(  );
  }
  this._parent[this.groupName].addRadioInstance(this);
};

Once you have defined the preceding code, and added it to Forms.as, you can assign checkboxes to checkbox groups as you would assign radio buttons to radio button groups:

#include "Forms.as"
myCheckBox0_ch.setGroupName("myCheckBoxGroup");

The methods of radio button groups are then available to checkbox groups. Though most of the methods are not applicable, the custom setPositions( ) and adjustWidth( ) methods from Recipe 11.7 and Recipe 11.8 are:

// You can call the adjustWidth(  ) and setPositions(  ) methods from checkbox groups.
myCheckBoxGroup.adjustWidth(  );
myCheckBoxGroup.setPositions(100, 100, 2);

Another useful feature of checkbox groups is that you can also call the setLabelPlacement( ) method from the group, as you can from a radio button group!

// Set the label placement for all the checkboxes in the group to the left.
myCheckBoxGroup.setLabelPlacement("left");


Recipe 11.11 Getting Checkbox Values

11.11.1 Problem

You want to determine the values (true or false) of checkboxes.

11.11.2 Solution

Use the getValue( ) method for each checkbox, or create a custom getValues( ) method for the checkbox group.

11.11.3 Discussion

The getValue( ) method returns true if a checkbox is checked and false if the checkbox is unchecked:

trace(myCheckBox0_ch.getValue(  ));

If you have created a checkbox group (see Recipe 11.10), you can add a custom method, getValues( ), that returns an array of the values for each checkbox in the group. The elements of the returned array contain three properties:

name

The name of the checkbox instance

label

The label value of the checkbox

value

The value of the checkbox (true or false)

Here is our custom getValues( ) method, which you should add to your Form.as file for easy inclusion in future projects:

// Create the new method, getValues(  ), for the FCheckBoxGroupClass.
FCheckBoxGroupClass.prototype.getValues = function (  ) {

  // The dataAr array is populated with the objects for each checkbox in the group.
  var dataAr = new Array(  );

  // Create two local variables for use in the for statement.
  var cb, obj;

  // Loop through every checkbox in the group.
  for (var i = 0; i < this.radioInstances.length; i++) {

    // cb refers to the current checkbox.
    cb = this.radioInstances[i];

    // For each checkbox, create an object with name, label, and value properties.
    obj = new Object(  );
    obj.name = cb._name;
    obj.label = cb.getLabel(  );
    obj.value = cb.getValue(  );

    // Add the object to the array.
    dataAr.push(obj);
  }

  // Return the array of values.
  return dataAr;
};

11.11.4 See Also

Recipe 11.9 and Recipe 11.10


Recipe 11.12 Assembling an Advanced Form

11.12.1 Problem

You want to put together a form to which you can add advanced features such as one-step submission, validation, and multipage forms.

11.12.2 Solution

Create a custom Form class.

11.12.3 Discussion

In HTML, forms are created within the structure of a FORM element. Because HTML forms provide a container into which all the form elements can be added, it is possible to add more advanced functionality to them. ActionScript does not provide an analogous structure, however. Therefore, Flash offers nothing that inherently holds multiple elements on a page together into a true form. If you want to add advanced features to your forms, you must create a Form class to act as the container for all the form elements.

The Form class's primary function is to keep track of the otherwise disparate elements (text fields, list boxes, etc.) that you want to be part of a single form. To accomplish this, your Form class needs to have an array property to which elements can be added. In the example code, this array property is named formElements. Next, the Form class also needs a method, addElement( ), by which elements can be added to forms.

Here is the basic implementation of our Form class:

// Define the Form constructor as a global class, as with other UI components.
_global.Form = function (  ) {

  // Initialize the formElements array to store references to all the elements.
  this.formElements = new Array(  );
};

// The addElement(  ) method adds an element to the formElements array.
Form.prototype.addElement = function (element) {
  this.formElements.push(element);
};

The Form class serves as the foundation of all the advanced form features in the recipes that follow. Store it in a Form.as file and include it in any projects that require advanced form management.


Recipe 11.13 Submitting a Form

11.13.1 Problem

You want to submit a form to a URL, such as a CGI script.

11.13.2 Solution

Use a LoadVars object with properties and values corresponding to each element of the form to be submitted. Or, alternatively, write a submitToURL( ) method for the custom Form class to handle this process dynamically for any form.

11.13.3 Discussion

Here are the steps for submitting form data to a URL using LoadVars:

  1. Create a LoadVars object.

  2. Add a property to the LoadVars object for each form element. The property name should be the name of the form element. For list boxes, combo boxes, text fields, and checkboxes, the property name is the value of the element's instance name (programmatically, this is the _name property value). For radio buttons, the name of the property should be the group name. The value of each property should be the value of the element as retrieved using the recipes in this chapter.

  3. Call the send( ) method of the LoadVars object to send all the properties and values to a specified URL.

Here is a code snippet that uses this process with just a few form elements:

lv = new LoadVars(  );
lv.myListBox_lb       = myListBox_lb.getSelectedIndex(  ).data;
lv.myCheckBox0_ch     = myCheckBox0_ch.getValue(  );
lv.myRadioButtonGroup = myRadioButtonGroup.getValue(  );
lv.send("http://www.yourdomain.com/cgi/getFormData.cgi");

The preceding process is not too taxing, as long as there are only a few simple elements in the form. However, as you add more elements to the form, the process becomes more burdensome and error-prone. You can simplify the process by creating a submitToURL( ) method for the Form class (see Recipe 11.12) and using it for all your form submission needs.

The submitToURL( ) method shown in the following example accepts a single parameter: the URL to which to submit the form data. The submitToURL( ) method (shown later) also relies on another custom method, getValues( ), which populates an associative array with the form element names and their values, and then returns that array.

Form.prototype.getValues = function (  ) {

  // Create the associative array to hold the form element names and values.
  var obj = new Array(  );
  var values, value, elem;

  // Loop through all the form elements in the form.
  for (var i = 0; i < this.formElements.length; i++) {
    elem = this.formElements[i];

    // Process each form element, as appropriate to its type. The instanceof operator
    // indicates whether the element is of the specified class.
    if (elem instanceof TextField) {
      // Store each text field's value in the array
      // with the text field name as the key.
      value = elem.text;
      obj[elem._name] = value;
    }
    else if (elem instanceof FCheckBoxClass) {
      // Get the value of the checkbox, and assign it to an element of the
      // associative array using the checkbox's name as the key.
      value = elem.getValue(  );
      obj[elem._name] = value;
    }
    else if (elem instanceof FCheckBoxGroupClass) {
      // Get the values within the checkbox group (requires custom checkbox group
      // class (see Recipe 11.11).
      values = elem.getValues(  );

      // Store each checkbox value in the array, where the key is the name property
      // of the element returned by the checkbox group.
      for (var j = 0; j < values.length; j++) {
        obj[values[i].name] = values[i].value;
      }
    }
    else if (elem instanceof FRadioButtonGroupClass) {
      // Store the active radio button's value (obtained from getValue(  )), where the
      // key is the radio button group name.
      value = elem.getValue(  );
      obj[elem.getGroupName(  )] = value;
    }
    else if (elem instanceof FComboBoxClass) {
      // For a combo box, retrieve the data property of the object returned by the
      // getSelectedItem(  ) method, unless it is undefined, in which case use the
      // label property.
      value = (elem.getSelectedItem(  ).data == undefined) ?
                 elem.getSelectedItem().label: elem.getSelectedItem(  ).data;

      // If value is an object (and not a primitive string), this means that the data
      // property was assigned a reference to an object. In that case, assign to
      // value the value property from the data object. (See Recipe 12.3 regarding
      // assigning data objects.)
      if (value instanceof Object) {
        value = value.value;
      }

     // Store the value, using a key that is the name of the combo box.
     obj[elem._name] = value;
    }
    else if (elem instanceof FListBoxClass) {

      // Retrieve the values from the list box.
      values = elem.getSelectedItems(  );

      // Create an element whose key is the list box name. Assign to this element a
      // new array filled with the values from the list box (potentially multiple).
      obj[elem._name] = new Array(  );

      // For each selected item in the list box, add its value to the array. The
      // logic for getting each value is the same as used earlier for combo boxes.
      for (var j = 0; j < values.length; j++) {
        value = (values[j].data == undefined) ? values[j].label : values[j].data;
        if (value instanceof Object) {
          value = value.value;
        }
        obj[elem._name].push(value);
      }
    }
  }

  // Return the associative array containing all the form values.
  return obj;
};

// The submitToURL(  ) method should be called from your Form object to submit the form
// data to a URL.
Form.prototype.submitToURL = function (url) {

  // Create a new LoadVars object for sending the form data to the URL.
  var lv = new LoadVars(  );

  // Get the form values by calling the getValues(  ) method (defined earlier).
  var vals = this.getValues(  );

  // Add each form element to the LoadVars object so they will be submitted.
  for (var item in vals) {
    lv[item] = vals[item];
  }

  // Send the data to the server script at the specified URL.
  lv.send(url);

};

11.13.4 See Also

Recipe 11.12


Recipe 11.14 Validating Form Input

11.14.1 Problem

You want to validate user input before allowing a form to be submitted for processing.

11.14.2 Solution

Write a custom validate( ) method for the Form class that checks each form element.

11.14.3 Discussion

ActionScript does not provide a native means by which to validate form input. However, you can add a small amount of code to the custom Form class, with which you can then quickly add validation code to any form. You should add two methods to the custom Form class and add one line of code to the existing constructor.

One method, setValidator( ), should be used to set the validators for any elements requiring validation. The method should take two parameters: the name of the form element as a string and the validator.

The validator can have one of three types of values:

true

If the Boolean true is specified, the element must contain a value. Use this for mandatory form elements.

"email"

If the string "email" is specified, the element's value must be in the format of a valid email address.

Regular expression

If a regular expression is specified, it defines the pattern that the element value must match.

The setValidator( ) method adds new elements to an associative array in which the key is the element name and the value is the validator. You should, therefore, initialize the associative array in the constructor method.

The validate( ) method performs the validation based on the validators that have been specified using setValidator( ). The validate( ) method should loop through the elements of the form and, if a validator has been assigned for the element, verify that the element value meets the criteria. The validate( ) method returns the name of the first element that fails the validation test. Otherwise, the method returns true, indicating that the form validated successfully. The following is a partial listing of our enhanced Form class, which should be stored in a Form.as file. Everything in this listing should be added to the existing contents of Form.as, with one exception. The constructor method shown here should replace the existing constructor code.

// Make sure to include RegExp.as (see Recipe 9.6) in the Form.as file. The
// validation methods require it.
#include "RegExp.as"

_global.Form = function (  ) {
  this.formElements = new Array(  );

  // Our updated constructor creates the validators associative array.
  this.validators = new Object(  );
};

// The setValidator(  ) method adds elements to the validators associative array.
Form.prototype.setValidator = function (elementName, validator) {
  this.validators[elementName] = validator;
};

// The validate(  ) method attempts to validate the form.
Form.prototype.validate = function (  ) {

  // Retrieve the form's values using getValues(  ).
  var values = this.getValues(  );
  var valid, re;

  // Loop through all the values.
  for (var item in values) {

    // Get the validator for the current form element.
    valid = this.validators[item];

    // Validate the element based on its validator (true, "email", or a reg exp).
    // Remember that the condition (valid) is the same as (valid == true).
    if (valid) {
      // If the validator is true, check whether the element has some value.
      if ( (values[item] == undefined) || (values[item] == null)
           || (values[item] == "")) {
        // If no valid value exists, return this item as an error.
        return item;
      }
    } else if (valid == "email") {
      // If the validator is "email", make sure it is an email address of the form
      // something@somewhere.topleveldomain.
      re = new RegExp("^([\\w\-\\.]+)@(([\\w\\-]+\\.)+[\\w\\-]+)$");
      if (!re.test(values[item])) {
        // If it doesn't match an email pattern, return the item as an error.
        return item;
      }
    } else if (valid != undefined) {
      // If the validator is not true or "email", assume it's a regular expression
      // string. Create a regular expression from the string and test for a match.
      re = new RegExp(valid);
      if (!re.test(values[item])) {
        // If it doesn't match the reg exp, return the item as an error.
        return item;
      }
    }
  }
  // Return true to indicate successful validation
  return true;
};

You can use the validation methods in a form before submitting it. If the validate( ) method returns true, then the form is ready to submit; otherwise, you can direct the user to correct the errors on the form. The following is an example of its use. Note that this example uses a trace( ) statement to display the error in the Output window. However, the Output window is available only when testing during authoring. For more details on how to effectively alert the user with messages when the Flash Player is running in a browser, see Recipe 11.15.

#include "Form.as"

// Create two text fields and a push button.
_root.createTextField("name_txt", 1, 100, 100, 100, 20);
_root.createTextField("email_txt", 2, 100, 130, 100, 20);
_root.attachMovie("FPushButtonSymbol", "btn_pb", 3, {_x: 100, _y: 160});

// Set the text fields so that they have a border and allow input.
name_txt.border  = true;
name_txt.type    = "input";
email_txt.border = true;
email_txt.type   = "input";

// Create the form.
myForm = new Form(  );

// Add the text fields to the form and set validators for them. The name_txt field
// must contain some text, and email_txt must have a valid email format.
myForm.addElement(name_txt);
myForm.setValidator("name_txt", true);
myForm.addElement(email_txt);
myForm.setValidator("email_txt", "email");

btn_pb.onRelease = function (  ) {

  // Call the validate(  ) method of the form.
  var valid = _root.myForm.validate(  );

  // If the form validates, submit the form. Otherwise, display the name of the form
  // element that needs to be corrected.
  if (valid) {
    // Submit form.
  } else {
    trace(valid);
  }
};

11.14.4 See Also

Recipe 9.6, Recipe 11.12, and Recipe 11.15


Recipe 11.15 Alerting Users to Validation Errors

11.15.1 Problem

You want to alert the user when there is a validation error in the form data.

11.15.2 Solution

Use a message box.

11.15.3 Discussion

The MessageBox component is the perfect way to alert users when there is a form validation error. The MessageBox component can be found in Flash UI Components Set 2, which is available for free from the Macromedia Flash Exchange (http://www.macromedia.com/exchange/flash). You can add a message box programmatically using attachMovie( ), as you can with any other component. You only need to make sure that the component symbol is included in the Flash document's Library, as described in Recipe 11.1. The linkage identifier for the symbol is FMessageBoxSymbol.

this.attachMovie("FMessageBoxSymbol", "myMessageBox", 1);

Normally, you don't want a message box to be visible when the movie starts. Instead, you want to make the message box visible only if and when there is something about which to alert the user. Therefore, you should initialize a message box by setting its _visible property to false. You can do this either after attaching the movie clip or during the attachMovie( ) invocation by using an initialization object, as follows:

this.attachMovie("FMessageBoxSymbol", "myMessageBox", 1, {_visible: false});

Additionally, when you attach a message box programmatically, you must specify the buttons to include in the message box using the setButtons( ) method. The method expects an array of labels for the buttons. Typically, a message box has only one button—an OK button.

myMessageBox.setButtons(["OK"]);

You can programmatically set the message that the user sees with the setMessage( ) method:

myMessageBox.setMessage("Hello, friend.");

The following is an example of a message box used in conjunction with form validation. To test this code, you need to add both the PushButton and MessageBox component symbols to the movie's Library. You also need to make sure that your Form.as file includes all the necessary functionality. If in doubt, download the completed Form.as file from http://www.person13.com/ascb.

#include "Form.as"

// Create a text field and a push button.
_root.createTextField("email_txt", 1, 100, 130, 100, 20);
_root.attachMovie("FPushButtonSymbol", "btn_pb", 2, {_x: 100, _y: 160});

// Create the message box. Initialize it with _visible set to false.
// Also, create an OK button.
_root.attachMovie("FMessageBoxSymbol", "messageBox", 3,
                    {_x: 200, y: 200, _visible: false});
messageBox.setButtons(["OK"]);

// Set the text field so that it has a border and allows input.
email_txt.border = true;
email_txt.type   = "input";

// Create the form.
myForm = new Form(  );

// email_txt must have a valid email format.
myForm.addElement(email_txt);
myForm.setValidator("email_txt", "email");

btn_pb.onRelease = function (  ) {

  // Call the validate(  ) method of the form.
  var valid = _root.myForm.validate(  );

  // If the form validates, submit it. Otherwise, display the name of the problematic
  // form element in the message box.
  if (valid) {
    // Submit form.
  } else {
    _root.messageBox.setMessage("You must fill out a valid entry for " + valid);
    _root.messageBox._visible = true;

  }
};

11.15.4 See Also

Recipe 11.14


Appendix A. Unicode Escape Sequences for Latin 1 Characters

Table A-1 lists the characters in the Latin 1 character repertoire, with Unicode equivalents in the range of U+0000 to U+00FF (that is, C0 Controls, Basic Latin, C1 Controls, and Latin 1 Supplemental). It is reproduced from Appendix B of ActionScript for Flash MX: The Definitive Guide with the gracious permission of the author, Colin Moock.

The table's first column (labeled Dec) lists each character's code point in decimal (the standard ASCII or Latin 1 value), the second column provides the Unicode escape sequence for the character, and the third column describes or shows the character itself.

Table A-1. ISO 8859-1 (Latin 1) characters and Unicode mappings

Dec

Unicode

Description

Dec

Unicode

Description

0

\u0000

[null]

15

\u000f

[shift in]

1

\u0001

[start of heading]

16

\u0010

[data link escape]

2

\u0002

[start of text]

17

\u0011

[device control one]

3

\u0003

[end of text]

18

\u0012

[device control two]

4

\u0004

[end of transmission]

19

\u0013

[device control three]

5

\u0005

[enquiry]

20

\u0014

[device control four]

6

\u0006

[acknowledge]

21

\u0015

[negative acknowledge]

7

\u0007

[bell]

22

\u0016

[synchronous idle]

8

\u0008

[backspace]

23

\u0017

[end of transmission block]

9

\u0009

[horizontal tabulation]

24

\u0018

[cancel]

10

\u000a

[line feed]

25

\u0019

[end of medium]

11

\u000b

[vertical tabulation]

26

\u001a

[substitute]

12

\u000c

[form feed]

27

\u001b

[escape]

13

\u000d

[carriage feed]

28

\u001c

[file separator]

14

\u000e

[shift out]

29

\u001d

[group separator]

30

\u001e

[record separator]

66

\u0042

B

31

\u001f

[unit separator]

67

\u0043

C

32

\u0020

[space]

68

\u0044

D

33

\u0021

!

69

\u0045

E

34

\u0022

" (straight quotes)

70

\u0046

F

35

\u0023

#

71

\u0047

G

36

\u0024

$

72

\u0048

H

37

\u0025

%

73

\u0049

I

38

\u0026

&

74

\u004a

J

39

\u0027

' (straight apostrophe)

75

\u004b

K

40

\u0028

(

76

\u004c

L

41

\u0029

)

77

\u004d

M

42

\u002a

*

78

\u004e

N

43

\u002b

+

79

\u004f

O

44

\u002c

, (comma)

80

\u0050

P

45

\u002d

- (minus)

81

\u0051

Q

46

\u002e

. (period)

82

\u0052

R

47

\u002f

/

83

\u0053

S

48

\u0030

0

84

\u0054

T

49

\u0031

1

85

\u0055

U

50

\u0032

2

86

\u0056

V

51

\u0033

3

87

\u0057

W

52

\u0034

4

88

\u0058

X

53

\u0035

5

89

\u0059

Y

54

\u0036

6

90

\u005a

Z

55

\u0037

7

91

\u005b

[

56

\u0038

8

92

\u005c

\

57

\u0039

9

93

\u005d

]

58

\u003a

:

94

\u005e

^

59

\u003b

;

95

\u005f

_ (underscore)

60

\u003c

<

96

\u0060

` (accent grave)

61

\u003d

=

97

\u0061

a

62

\u003e

>

98

\u0062

b

63

\u003f

?

99

\u0063

c

64

\u0040

@

100

\u0064

d

65

\u0041

A

101

\u0065

e

102

\u0066

f

138

\u008a

control chr

103

\u0067

g

139

\u008b

control chr

104

\u0068

h

140

\u008c

control chr

105

\u0069

i

141

\u008d

control chr

106

\u006a

j

142

\u008e

control chr

107

\u006b

k

143

\u008f

control chr

108

\u006c

l

144

\u0090

control chr

109

\u006d

m

145

\u0091

control chr

110

\u006e

n

146

\u0092

control chr

111

\u006f

o

147

\u0093

control chr

112

\u0070

p

148

\u0094

control chr

113

\u0071

q

149

\u0095

control chr

114

\u0072

r

150

\u0096

control chr

115

\u0073

s

151

\u0097

control chr

116

\u0074

t

152

\u0098

control chr

117

\u0075

u

153

\u0099

control chr

118

\u0076

v

154

\u009a

control chr

119

\u0077

w

155

\u009b

control chr

120

\u0078

x

156

\u009c

control chr

121

\u0079

y

157

\u009d

control chr

122

\u007a

z

158

\u009e

control chr

123

\u007b

{

159

\u009f

control chr

124

\u007c

|

160

\u00a0

[no break space]

125

\u007d

}

161

\u00a1

¡

126

\u007e

~

162

\u00a2

¢

127

\u007f

[delete]

163

\u00a3

£

128

\u0080

control chr

164

\u00a4

¤

129

\u0081

control chr

165

\u00a5

¥

130

\u0082

control chr

166

\u00a6

131

\u0083

control chr

167

\u00a7

§

132

\u0084

control chr

168

\u00a8

¨

133

\u0085

control chr

169

\u00a9

©

134

\u0086

control chr

170

\u00aa

ª

135

\u0087

control chr

171

\u00ab

«

136

\u0088

control chr

172

\u00ac

¬

137

\u0089

control chr

173

\u00ad

-

174

\u00ae

®

210

\u00d2

Ò

175

\u00af

¯

211

\u00d3

Ó

176

\u00b0

°

212

\u00d4

Ô

177

\u00b1

±

213

\u00d5

Õ

178

\u00b2

2

214

\u00d6

Ö

179

\u00b3

3

215

\u00d7

x

180

\u00b4

´

216

\u00d8

Ø

181

\u00b5

µ

217

\u00d9

Ù

182

\u00b6

¶

218

\u00da

Ú

183

\u00b7

·

210

\u00d2

Ò

184

\u00b8

¸

219

\u00db

û

185

\u00b9

1

220

\u00dc

Ü

186

\u00ba

º

221

\u00dd

Ý

187

\u00bb

»

222

\u00de

188

\u00bc

1/4

223

\u00df

ß

189

\u00bd

1/2

224

\u00e0

à

190

\u00be

3/4

225

\u00e1

á

191

\u00bf

¿

226

\u00e2

â

192

\u00c0

À

227

\u00e3

ã

193

\u00c1

Á

228

\u00e4

ä

194

\u00c2

Â

229

\u00e5

å

195

\u00c3

Ã

230

\u00e6

æ

196

\u00c4

Ä

231

\u00e7

ç

197

\u00c5

Å

232

\u00e8

è

198

\u00c6

Æ

233

\u00e9

é

199

\u00c7

Ç

234

\u00ea

ê

200

\u00c8

È

235

\u00eb

ë

201

\u00c9

É

236

\u00ec

ì

202

\u00ca

Ê

237

\u00ed

í

203

\u00cb

Ë

238

\u00ee

î

204

\u00cc

Ì

239

\u00ef

ï

205

\u00cd

Í

240

\u00f0

206

\u00ce

Î

241

\u00f1

ñ

207

\u00cf

Ï

242

\u00f2

ò

208

\u00d0

243

\u00f3

ó

209

\u00d1

Ñ

244

\u00f4

ô

245

\u00f5

õ

250

\u00fa

ú

246

\u00f6

ö

251

\u00fb

Û

247

\u00f7

÷

252

\u00fc

ü

248

\u00f8

253

\u00fd

249

\u00f9

ù

254

\u00fe