Web DevCenter    
 Published on Web DevCenter (http://www.oreillynet.com/javascript/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: ActionScript Cookbook

Cooking with ActionScript

Related Reading

Actionscript Cookbook
Solutions and Examples for Flash MX Developers
By Joey Lott

by Joey Lott

Editor's note: We asked Joey Lott, the author of ActionScript Cookbook, to select some recipes from his book to showcase here on the Web DevCenter. He came up with quite a diverse slate. Today, you'll find recipes that deal with currency formatting, how to use a unique depth when creating a new movie clip, and how to perform actions at set intervals as well as create a clock showing the absolute time. Next Friday, we'll conclude this series with recipes on pausing and resuming a sound, saving a local shared object, and searching XML. This diversity of offerings reflects the sheer volume of solutions to common ActionScript problems that you'll find in Joey's book.

Recipe 5.6: Formatting Currency Amounts

Problem

You want to format a number as currency, such as dollars.

Solution

Create a custom Math.currencyFormat( ) method.

Discussion

Unlike some other languages, such as ColdFusion, ActionScript does not have a built-in function for formatting numbers as currency amounts. That's the bad news. The good news is that it is not too difficult to create a custom method to format numbers as currency amounts.

Our custom Math.currencyFormat( ) method accepts up to seven parameters:

Here is our custom Math.currencyFormat( ) method. The method converts a numeric parameter into a currency-formatted string. Include this method within Math.as along with the custom roundDecPl( ), numberFormat( ), and zeroFill( ) methods in this chapter, on which this example relies.

Math.currencyFormat = function (num, decimalPl, currencySymbol, thousandsDelim,
                      decimalDelim, truncate, spaceFill) {

  // Default to two decimal places, a dollar sign ($), a comma for thousands, 
  // and a period for the decimal point. We implemented the defaults using 
  // the conditional operator. Compare with Recipe 5.5.
  decimalPl      = (decimalPl == undefined)      ? 2   : decimalPl;
  currencySymbol = (currencySymbol == undefined) ? "$" : currencySymbol;
  thousandsDelim = (thousandsDelim == undefined) ? "," : thousandsDelim;
  decimalDelim   = (decimalDelim == undefined)   ? "." : decimalDelim;

  // Split the number into the whole and decimal (fractional) portions.
  var parts = String(num).split(".");

  // Truncate or round the decimal portion, as directed.
  if (truncate) {
    parts[1] = Number(parts[1]) * Math.pow(10, -(decimalPl - 1));
    parts[1] = String(Math.floor(parts[1]));
  } else {
    // Requires the roundDecPl(  ) method defined in Recipe 5.3
    parts[1] = Math.roundDecPl(Number("." + parts[1]), decimalPl);
    parts[1] = String(parts[1]).split(".")[1];
  }

  // Ensure that the decimal portion has the number of digits indicated. 
  // Requires the zeroFill(  ) method defined in Recipe 5.4.
  parts[1] = Math.zeroFill(parts[1], decimalPl, true);
  
  // If necessary, use the numberFormat(  ) method from Recipe 5.5 to 
  // format the number with the proper thousands delimiter and leading spaces.
  if (thousandsDelim != "" || spaceFill != undefined) {
    parts[0] = Math.numberFormat(parts[0], thousandsDelim, "",
                spaceFill - decimalPl - currencySymbol.length);
  }

  // Add a currency symbol and use String.join(  ) to merge the whole (dollar)
  // and decimal (cents) portions using the designated decimal delimiter.
  return currencySymbol + parts.join(decimalDelim);
};

Here are a few examples of Math.currencyFormat ( ) in action:

trace(Math.currencyFormat(1.2));                    // Displays: $1.20
trace(Math.currencyFormat(.3));                     // Displays: $0.30
trace(Math.currencyFormat(1234567));                // Displays: $1,234,567.00
trace(Math.currencyFormat(12.34, 2, "\u20AC"));     // Displays: €12.34 (euros)
trace(Math.currencyFormat(12.34, 2, "\u00a3"));     // Displays: £12.34 (pounds)
trace(Math.currencyFormat(12.34, 2, "\u00a5"));     // Displays: ¥12.34 (yen)
trace(Math.currencyFormat(1.2, 2, "", ".", ","));   // Displays: 1,20
trace(Math.currencyFormat(1234, 2, "", ".", ","));  // Displays: 1.234,00

See Also

Recipe 5.3 and Recipe 5.5. See Appendix A for creating special characters, including the euro, yen, and British pound symbols. To align currency amounts in text fields, use a monospaced font and set the field's format to right justification using the TextFormat.align property.

Recipe 7.11: Getting Unique Depths

Problem

You want to easily ensure that you always get a unique depth when creating a new movie clip using createEmptyMovieClip( ), attachMovie( ), or duplicateMovieClip( ).

Solution

Create and use a custom MovieClip.getNewDepth( ) method.

Discussion

Only one movie clip can exist at each depth within a parent clip, so if you specify an existing depth when using createEmptyMovieClip( ), attachMovie( ), or duplicateMovieClip( ), the movie clip already on that depth is overwritten. Unless you want to overwrite an existing movie clip, you must always use a unique depth when using these methods.

ActionScript does not provide native support for generating a unique movie clip depth. You must keep track of all the used depths yourself. When you add only a few movie clips programmatically, this does not pose a problem; however, if you programmatically generate many movie clips, it becomes difficult to track which depths are already used. Fortunately, you can solve this problem easily with a few lines of code.

You can add a getNewDepth( ) method to the MovieClip class so that every movie clip inherits it, as shown in the following code block. The process is not complex. If the movie clip does not yet have a custom currentDepth property defined, we define it and initialize it to the value of 1. The value 1 is used because that is the first depth you want to assign to any programmatically generated nested movie clip in most cases. Once the method ensures that the currentDepth property exists, it returns that property's value and increments it by 1 so that the next time the method is called, a new depth value is returned.

MovieClip.prototype.getNewDepth = function (  ) {
  // If no currentDepth is defined, initialize it to 1.
  if (this.currentDepth == undefined) {
    this.currentDepth = 1;
  }
  // Return the new depth and increment it by 1 for next time.
  return this.currentDepth++;
};

Here is an example of the getNewDepth( ) method being used:

// This assumes our custom getNewDepth(  ) method is defined in MovieClip.as.
#include "MovieClip.as"

// Create two new movie clips in _root and assign them unique depths using the
// getNewDepth(  ) method.
_root.createEmptyMovieClip("circle_mc", _root.getNewDepth(  ));
_root.createEmptyMovieClip("square_mc", _root.getNewDepth(  ));

The getNewDepth( ) method defaults the currentDepth property to a value of 1 when it is initialized. There are some cases in which you want to use depths starting below 1. For example, movie clips placed on the Stage at authoring time begin with a depth of -16383. You can programmatically create movie clips that appear below manually created movie clips if you assign them a depth less than -16383. You can still use the getNewDepth( ) method in these cases by assigning a value to the movie clip's currentDepth property before invoking getNewDepth( ).

// Include MovieClip.as> from this chapter and DrawingMethods.as from Chapter 4.
#include "MovieClip.as"
#include "DrawingMethods.as"

// Set the currentDepth property so that the next programmatically created movie clip
// can be made to appear below any manually created instance.
_root.currentDepth = -16384;

// Create a movie clip using getNewDepth(  ) to retrieve a depth value. The value 
// -16384 is used. Be aware that the value is then incremented to -16383, which is
// the depth of the first manually created instance.
_root.createEmptyMovieClip("circle_mc", _root.getNewDepth(  ));
circle_mc.lineStyle(1, 0x000000, 0);
circle_mc.beginFill(0, 100);
circle_mc.drawCircle(100, 100, 100);
circle_mc.endFill(  );

When you use this technique of setting the currentDepth property, be aware that it can overwrite manually created movie clips if you are not careful. For example, in the preceding code block, the depth of the circle_mc movie clip is -16384. The first manually created movie clip always has a depth of -16383; therefore, if you use getNewDepth( ) again and it assigns a depth of -16383, the new programmatic clip overwrites the first manually created clip in your movie. You can solve this dilemma by again setting the value of currentDepth. For example, the following code block programmatically creates a movie clip below any manually created instances. It then sets the currentDepth to 1 so that future programmatically created instances are placed above all manually created instances.

#include "MovieClip.as"

_root.currentDepth = -16384;
_root.createEmptyMovieClip("circle_mc", _root.getNewDepth(  ));
_root.currentDepth = 1;

See Also

Recipe 7.16

Recipe 10.8: Creating Timers and Clocks

Problem

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

Solution

Use the setInterval( ) function.

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);

TIP: 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);

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.

Joey Lott is a founding partner of The Morphic Group. He has written many books on Flex and Flash-related technologies, including Programming Flex 3, ActionScript 3 Cookbook, Adobe AIR in Action, and Advanced ActionScript 3 with Design Patterns.


Return to the Web DevCenter.

Copyright © 2009 O'Reilly Media, Inc.