0% found this document useful (0 votes)
9 views790 pages

ActionScript Cookbook: Practical Recipes

Uploaded by

Victor
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views790 pages

ActionScript Cookbook: Practical Recipes

Uploaded by

Victor
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Strona 1 z 1

[ Team LiB ]

• Table of Contents
• Index
• Reviews
• Reader Reviews
• Errata
ActionScript Cookbook

By Joey Lott

Publisher: O'Reilly
Pub Date: July 2003
ISBN: 0-596-00490-7
Pages: 896

ActionScript's sheer volume of capabilities can be daunting. The ActionScript Cookbook breaks it
all down into tasks that are relevant, practical, and insightful. On top of hundreds of atomic
recipes, it offers seven full chapters of larger sample applications. Appealing to all levels of
ActionScript coders, this book offers concrete solutions to the most common ActionScript needs
and problems. The ActionScript Cookbook is for people who want to hit the ground running.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 8

[ Team LiB ]

• Table of Contents
• Index
• Reviews
• Reader Reviews
• Errata
ActionScript Cookbook

By Joey Lott

Publisher: O'Reilly
Pub Date: July 2003
ISBN: 0-596-00490-7
Pages: 896

Copyright
Preface
What's Not in This Book
Flash Naming Conventions
The Code
Typographical Conventions
We'd Like to Hear from You
Acknowledgments
Part I: Local Recipes
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
Chapter 2. Runtime Environment
Introduction

[Link] 2004-02-06
Strona 2 z 8

Recipe 2.1. Detecting the Player Version


Recipe 2.2. Detecting the Operating System
Recipe 2.3. Checking the System Language
Recipe 2.4. Detecting Display Settings
Recipe 2.5. Scaling the Movie
Recipe 2.6. Changing the Alignment
Recipe 2.7. Detecting the Device's Audio Capabilities
Recipe 2.8. Prompting the User to Change Player Settings
Recipe 2.9. Hiding the Flash Player's Menu Items
Recipe 2.10. Enhancing Standalone Projectors
Recipe 2.11. Setting the Dimensions of a Projector
Recipe 2.12. Specifying Where on Screen a Projector Opens
Chapter 3. Color
Introduction
Recipe 3.1. Setting the Color of a Movie Clip
Recipe 3.2. Specifying RGB Values
Recipe 3.3. Decoding an RGB Value
Recipe 3.4. Setting the RGB Relative to Its Current Value
Recipe 3.5. Tinting a Movie Clip's Color
Recipe 3.6. Setting a Movie Clip's Transparency
Recipe 3.7. Transforming a Movie Clip's Current Color
Recipe 3.8. Restoring a Movie Clip's Original Color
Recipe 3.9. Controlling a Movie Clip's Color with Sliders
Chapter 4. Drawing and Masking
Introduction
Recipe 4.1. Drawing a Line
Recipe 4.2. Drawing a Curve
Recipe 4.3. Drawing a Rectangle
Recipe 4.4. Drawing a Rounded Rectangle
Recipe 4.5. Drawing a Circle
Recipe 4.6. Drawing an Ellipse
Recipe 4.7. Drawing a Triangle
Recipe 4.8. Drawing Regular Polygons
Recipe 4.9. Filling a Shape with a Solid or Translucent Color
Recipe 4.10. Filling a Shape with a Gradient
Recipe 4.11. Filling a Shape with a Complex Gradient
Recipe 4.12. Scripting Masks
Chapter 5. Numbers and Math
Introduction
Recipe 5.1. Representing Numbers in Different Bases
Recipe 5.2. Converting Between Different Number Systems
Recipe 5.3. Rounding Numbers
Recipe 5.4. Inserting Leading or Trailing Zeros
Recipe 5.5. Formatting Numbers for Display
Recipe 5.6. Formatting Currency Amounts
Recipe 5.7. Generating a Random Number
Recipe 5.8. Simulating a Coin Flip
Recipe 5.9. Simulating Dice
Recipe 5.10. Simulating Playing Cards
Recipe 5.11. Generating a Unique Number
Recipe 5.12. Converting Angle Measurements
Recipe 5.13. Calculating the Distance Between Two Points
Recipe 5.14. Determining Points Along a Circle
Recipe 5.15. Converting Between Units of Measurement

[Link] 2004-02-06
Strona 3 z 8

Recipe 5.16. Calculating Asset Appreciation (Future Value)


Recipe 5.17. Calculating Retirement Savings
Recipe 5.18. Calculating the Loan (Mortgage) You Can Afford
Recipe 5.19. Calculating Loan Amortization or Annuities
Chapter 6. Arrays
Introduction
Recipe 6.1. Adding Elements to the Start or End of an Array
Recipe 6.2. Looping Through an Array
Recipe 6.3. Searching for Matching Elements in an Array
Recipe 6.4. Removing Elements
Recipe 6.5. Inserting Elements in the Middle of an Array
Recipe 6.6. Converting a String to an Array
Recipe 6.7. Converting an Array to a String
Recipe 6.8. Creating a Separate Copy of an Array
Recipe 6.9. Storing Complex or Multidimensional Data
Recipe 6.10. Sorting or Reversing an Array
Recipe 6.11. Implementing a Custom Sort
Recipe 6.12. Creating an Associative Array
Recipe 6.13. Reading Elements of an Associative Array
Chapter 7. Movie Clips
Introduction
Recipe 7.1. Referring to Movie Clips via ActionScript
Recipe 7.2. Targeting Movie Clips with Dynamic Names
Recipe 7.3. Affecting Playback
Recipe 7.4. Reversing Playback
Recipe 7.5. Using Movie Clips as Buttons
Recipe 7.6. Defining Hit Areas for Movie Clips
Recipe 7.7. Checking for Mouseover
Recipe 7.8. Performing Repeated Actions on Movie Clips
Recipe 7.9. Fading a Movie Clip
Recipe 7.10. Discovering Nested Movie Clips
Recipe 7.11. Getting Unique Depths
Recipe 7.12. Getting a Movie Clip's Boundaries
Recipe 7.13. Creating a Draggable Movie Clip
Recipe 7.14. Creating a Custom Mouse Pointer
Recipe 7.15. Checking for Overlapping Movie Clips (Performing Hit Tests)
Recipe 7.16. Changing Stacking Order
Recipe 7.17. Converting Between Coordinate Spaces
Recipe 7.18. Duplicating Movie Clips
Recipe 7.19. Adding Movie Clips from the Library with ActionScript
Chapter 8. Text
Introduction
Recipe 8.1. Referring to a Text Field via ActionScript
Recipe 8.2. Creating a Text Field
Recipe 8.3. Creating an Outline Around a Text Field
Recipe 8.4. Creating a Background for a Text Field
Recipe 8.5. Making a User Input Field
Recipe 8.6. Making a Password Input Field
Recipe 8.7. Filtering Text Input
Recipe 8.8. Restricting the Maximum Field Length
Recipe 8.9. Displaying Dynamic Text at Runtime
Recipe 8.10. Displaying HTML-Formatted Text
Recipe 8.11. Condensing Whitespace
Recipe 8.12. Sizing Text Fields to Fit Contents

[Link] 2004-02-06
Strona 4 z 8

Recipe 8.13. Scrolling Text with the ScrollBar Component


Recipe 8.14. Scrolling Text Programmatically
Recipe 8.15. Responding to Scroll Events
Recipe 8.16. Formatting Existing Text
Recipe 8.17. Formatting User-Input Text
Recipe 8.18. Formatting a Portion of a Text Field
Recipe 8.19. Setting a Text Field's Font
Recipe 8.20. Embedding Fonts
Recipe 8.21. Creating Text That Can Be Rotated
Recipe 8.22. Displaying Unicode Text
Recipe 8.23. Assigning Focus to a Text Field
Recipe 8.24. Selecting Text with ActionScript
Recipe 8.25. Setting the Insertion Point in a Text Field
Recipe 8.26. Responding When Text Is Selected or Deselected
Recipe 8.27. Responding to User Text Entry
Recipe 8.28. Adding a Hyperlink to Text
Chapter 9. Strings
Introduction
Recipe 9.1. Joining Strings
Recipe 9.2. Using Quotes and Apostrophes in Strings
Recipe 9.3. Inserting Special Whitespace Characters
Recipe 9.4. Searching for a Substring
Recipe 9.5. Extracting a Substring
Recipe 9.6. Matching Patterns with Regular Expressions
Recipe 9.7. Looking for a Pattern Match
Recipe 9.8. Parsing a String into Words
Recipe 9.9. Removing and Replacing Characters
Recipe 9.10. Processing One Character at a Time
Recipe 9.11. Converting Case
Recipe 9.12. Trimming Whitespace
Recipe 9.13. Reversing a String by Word or by Letter
Recipe 9.14. Converting Between Strings and Unicode or ASCII
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
Chapter 11. Forms
Introduction
Recipe 11.1. Adding UI Components at Runtime
Recipe 11.2. Positioning Form Elements
Recipe 11.3. Adding Menus to a Form
Recipe 11.4. Making Dependent Menus
Recipe 11.5. Resizing Menus to Fit Their Contents
Recipe 11.6. Detecting the Selected Menu Items
Recipe 11.7. Adding Radio Buttons to a Group
Recipe 11.8. Aligning Radio Buttons Automatically
Recipe 11.9. Getting the Selected Radio Button Value
Recipe 11.10. Adding Checkboxes to a Form

[Link] 2004-02-06
Strona 5 z 8

Recipe 11.11. Getting Checkbox Values


Recipe 11.12. Assembling an Advanced Form
Recipe 11.13. Submitting a Form
Recipe 11.14. Validating Form Input
Recipe 11.15. Alerting Users to Validation Errors
Recipe 11.16. Making a Multipage Form
Recipe 11.17. Submitting a Multipage Form
Recipe 11.18. Validating a Multipage Form
Recipe 11.19. Transmitting Data Securely
Recipe 11.20. Prepopulating a Form
Recipe 11.21. Customizing the Tab Order
Recipe 11.22. Using Tables to Arrange Form Elements
Recipe 11.23. Creating Auto-Complete Text Fields
Recipe 11.24. Customizing a Component's Appearance
Recipe 11.25. Customizing All Components' Appearances
Chapter 12. Objects and Custom Components
Introduction
Recipe 12.1. Using Methods and Properties of Built-in Objects
Recipe 12.2. Creating an Instance of a Class
Recipe 12.3. Adding Properties to an Object Instance
Recipe 12.4. Adding Custom Methods to an Object Instance
Recipe 12.5. Creating a Custom Class
Recipe 12.6. Creating Smart Getter/Setter Properties
Recipe 12.7. Defining Read-Only Properties
Recipe 12.8. Creating Subclasses
Recipe 12.9. Implementing Subclass Versions of Superclass Methods
Recipe 12.10. Listening for Events
Recipe 12.11. Adding Listeners to Custom Classes
Recipe 12.12. Creating a Component That Subclasses MovieClip
Recipe 12.13. Program: Color Selector Component
Chapter 13. Programming Sound
Introduction
Recipe 13.1. Creating an Object to Control Sound
Recipe 13.2. Attaching Sounds at Runtime
Recipe 13.3. Playing and Stopping a Sound
Recipe 13.4. Getting Playback Time
Recipe 13.5. Looping a Sound
Recipe 13.6. Setting In and Out Points
Recipe 13.7. Pausing and Resuming a Sound
Recipe 13.8. Performing Actions When a Sound Ends
Recipe 13.9. Queuing Sounds
Recipe 13.10. Adding Sounds to Buttons and UI Components
Recipe 13.11. Setting the Volume of a Sound
Recipe 13.12. Controlling the Panning of a Sound
Recipe 13.13. Creating Advanced Stereo Panning Effects
Recipe 13.14. Fading In a Sound
Recipe 13.15. Fading Out a Sound
Recipe 13.16. Program: A Sound Controller Component

Part II: Remote Recipes


Chapter 14. FlashCom Server
Introduction
Recipe 14.1. Creating a New FlashCom Application
Recipe 14.2. Connecting to the FlashCom Server
Recipe 14.3. Adding a Video Object at Runtime

[Link] 2004-02-06
Strona 6 z 8

Recipe 14.4. Capturing and Displaying Video from a Web Cam


Recipe 14.5. Capturing and Playing Audio from a Microphone
Recipe 14.6. Controlling FlashCom Audio
Recipe 14.7. Subscribing to Audio/Video Content
Recipe 14.8. Creating Playlists
Recipe 14.9. Recording and Publishing Video and Audio
Recipe 14.10. Publishing Live Content
Recipe 14.11. Pausing and Resuming a Net Stream
Recipe 14.12. Fast-Forwarding and Rewinding a Net Stream
Recipe 14.13. Seeking Relative to the Total Stream Length
Recipe 14.14. Implementing Server-Side ActionScript
Recipe 14.15. Tracking Clients Connected to the Application
Recipe 14.16. Invoking Server-Side Functions from the Client Movie
Recipe 14.17. Invoking Client-Side Functions from the Server
Chapter 15. Loading Assets
Introduction
Recipe 15.1. Loading an External SWF
Recipe 15.2. Loading an External SWF from a Trusting Domain
Recipe 15.3. Loading an External JPEG Image
Recipe 15.4. Loading an External Image (All Formats)
Recipe 15.5. Loading an External MP3 Sound
Recipe 15.6. Loading Remote Content by Proxy
Recipe 15.7. Determining if an Asset Is Loaded
Recipe 15.8. Getting the Percentage of an Asset That Has Loaded
Recipe 15.9. Monitoring Load Progress Using a Progress Bar Component
Recipe 15.10. Monitoring Load Progress Without a Progress Bar Component
Recipe 15.11. Performing Actions When the Asset Is Loaded
Recipe 15.12. Hiding the Graphics and Text for a Progress Bar
Chapter 16. Storing Persistent Information
Introduction
Recipe 16.1. Storing and Retrieving Locally Persistent Information
Recipe 16.2. Adding Data to a Client-Side Shared Object
Recipe 16.3. Reading Values from a Client-Side Shared Object
Recipe 16.4. Saving a Local Shared Object
Recipe 16.5. Sharing Information Between Movies Within the Same Domain
Recipe 16.6. Storing Persistent Data on the Server
Recipe 16.7. Saving Remote Shared Object Data
Recipe 16.8. Checking for Updates to Remote Shared Objects
Recipe 16.9. Reading Values from a Server-Side Shared Object
Recipe 16.10. Adding Data to a Server-Side Shared Object
Chapter 17. Communicating with Other Movies
Introduction
Recipe 17.1. Communicating with Other Movieson the Same Computer
Recipe 17.2. Sending Data Using Local Connections
Recipe 17.3. Validating Receipt of Communication Over a Local Connection
Recipe 17.4. Accepting Communications from Other Domains
Recipe 17.5. Communicating Between Movies on Different Computers
Recipe 17.6. Broadcasting Data to Remote Shared Object Clients
Chapter 18. Sending and Loading Variables
Introduction
Recipe 18.1. Loading Variables from a Text File
Recipe 18.2. Loading Variables from a Server-Side Script
Recipe 18.3. Checking Load Progress
Recipe 18.4. Sending Data to a Server-Side Script

[Link] 2004-02-06
Strona 7 z 8

Recipe 18.5. Sending Variables and Handling a Returned Result


Chapter 19. XML
Introduction
Recipe 19.1. Understanding XML Structure (Reading and Writing XML)
Recipe 19.2. Creating an XML Object
Recipe 19.3. Adding Elements to an XML Object
Recipe 19.4. Adding Text Nodes to an XML Object
Recipe 19.5. Creating an XML Object from an Array
Recipe 19.6. Adding Attributes to an XML Element
Recipe 19.7. Reading Elements in an XML Tree
Recipe 19.8. Finding Elements by Name
Recipe 19.9. Reading Text Nodes and Their Values
Recipe 19.10. Reading an Element's Attributes
Recipe 19.11. Loading XML
Recipe 19.12. Removing Extra Whitespace from XML Objects
Recipe 19.13. Sending XML
Recipe 19.14. Sending XML Data and Receiving a Response
Recipe 19.15. Searching XML
Recipe 19.16. Using XML Data to Initialize a Movie
Chapter 20. Flash Remoting
Introduction
Recipe 20.1. Establishing a Connection via Flash Remoting
Recipe 20.2. Configuring Flash Remoting for ColdFusion
Recipe 20.3. Configuring Flash Remoting for .NET
Recipe 20.4. Configuring Flash Remoting for J2EE
Recipe 20.5. Configuring Flash Remoting for PHP or Perl
Recipe 20.6. Invoking a Remote Function on a Service
Recipe 20.7. Handling Flash Remoting Results
Recipe 20.8. Distinguishing Among Results from Multiple Calls to a Single Service
Recipe 20.9. Calling [Link] Functions from Flash
Recipe 20.10. Calling ColdFusion Functions from Flash
Recipe 20.11. Passing Named Parameters to ColdFusion Component Methods
Recipe 20.12. Passing Complex Parameters to ColdFusion Component Methods
Recipe 20.13. Calling Java or JSP Functions from Flash
Recipe 20.14. Transmitting Custom Datatypes to a Flash Remoting Back End
Recipe 20.15. Receiving Typed Objects with ColdFusion
Recipe 20.16. Receiving Typed Objects with [Link]
Recipe 20.17. Receiving Typed Objects with Java
Recipe 20.18. Returning Typed Objects from ColdFusion
Recipe 20.19. Returning Typed Objects from [Link]
Recipe 20.20. Returning Typed Objects from Java
Recipe 20.21. Writing Server-Side Functions in ActionScript
Recipe 20.22. Querying a Database with Server-Side ActionScript
Recipe 20.23. Making HTTP Requests with Server-Side ActionScript
Recipe 20.24. Consuming Web Services with Flash Remoting for .NET or ColdFusion
Chapter 21. Recordsets
Introduction
Recipe 21.1. Creating Recordsets
Recipe 21.2. Reading Recordsets
Recipe 21.3. Filtering Recordsets
Recipe 21.4. Sorting Recordsets by a Single Column
Recipe 21.5. Populating Menu Components
Recipe 21.6. Using Recordsets with DataGrids

Part III: Applications

[Link] 2004-02-06
Strona 8 z 8

Chapter 22. Building a Flash Paint Application


Section 22.1. Planning the Application
Section 22.2. Building the Components
Section 22.3. Assembling the Flash Paint Application
Section 22.4. Using Flash Paint
Chapter 23. Creating a Simple Animation in Stages
Section 23.1. Stage One
Section 23.2. Stage Two
Section 23.3. Stage Three
Section 23.4. Stage Four
Section 23.5. Stage Five
Section 23.6. Conclusion
Chapter 24. Video Chat/Message Center
Section 24.1. Developing the Application Overview
Section 24.2. Creating the Server-Side Application
Section 24.3. Creating the Calling Client
Section 24.4. Creating the Administrator Client
Section 24.5. Putting It All Together
Chapter 25. Image Viewer/Slideshow
Section 25.1. Planning the Application Design
Section 25.2. Beginning the Application
Section 25.3. Creating the Components
Section 25.4. Putting Together the Application
Section 25.5. Wrapping It Up
Chapter 26. Creating an MP3 Jukebox
Section 26.1. Creating an Application Overview
Section 26.2. Developing the MP3 Selectors
Section 26.3. Developing the Jukebox Application
Section 26.4. Wrapping It Up
Chapter 27. A Personalized My Page Application
Section 27.1. Formulating the Application Overview
Section 27.2. Creating the Framework
Section 27.3. Creating Service Modules
Section 27.4. Putting It All Together
Section 27.5. Extending the Framework
Section 27.6. Wrapping It Up
Chapter 28. A Scheduler Program
Section 28.1. Designing the Application Structure
Section 28.2. Making the Components
Section 28.3. Putting the Application Together
Section 28.4. Making the Scheduler Application Available Online
Section 28.5. Using LoadVars and XML
Section 28.6. Wrapping It Up

Appendix A. Unicode Escape Sequences for Latin 1 Characters


Colophon
Index
[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 1

[ Team LiB ]

Copyright
Copyright © 2003 O'Reilly & Associates, Inc.

Printed in the United States of America.

Published by O'Reilly & Associates, Inc., 1005 Gravenstein Highway North, Sebastopol, CA
95472.

O'Reilly & Associates books may be purchased for educational, business, or sales promotional
use. Online editions are also available for most titles ([Link] For more
information, contact our corporate/institutional sales department: (800) 998-9938 or
corporate@[Link].

Nutshell Handbook, the Nutshell Handbook logo, and the O'Reilly logo are registered
trademarks of O'Reilly & Associates, Inc. Many of the designations used by manufacturers and
sellers to distinguish their products are claimed as trademarks. Where those designations
appear in this book, and O'Reilly & Associates, Inc. was aware of a trademark claim, the
designations have been printed in caps or initial caps. The association between the image of a
crab-eating opossum and the topic of ActionScript is a trademark of O'Reilly & Associates, Inc.

While every precaution has been taken in the preparation of this book, the publisher and author
assume no responsibility for errors or omissions, or for damages resulting from the use of the
information contained herein.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 6

[ Team LiB ]

Preface
The ActionScript Cookbook is written with all levels of ActionScript developers in mind—people
like you, who want practical solutions to common problems. Keep this book next to your
computer to tackle programming challenges such as how to use ActionScript to draw a circle or
load images at runtime, or work with Flash Communication Server (FlashCom) and Flash
Remoting. This book is filled with exciting yet accessible and practical examples, solutions, and
insights into the situations that Flash and ActionScript developers are sure to encounter.

The book is divided into three parts. The first two parts follow the classic O'Reilly Cookbook
series format, in which each recipe presents the problem, the solution, and a discussion of the
solution. You can quickly locate the recipe that most closely matches your situation and get the
solution without having to read the whole book to understand the underlying code. The
Discussion sections of each recipe offer a deeper analysis of how the solution works and
possible design choices and ramifications. So you get the best of both worlds—quick and easy
access to the answers you want and deeper insights into the nature of both the problem and
the solution. The ActionScript Cookbook helps you develop your understanding of concepts by
applying them in real situations.

Part I contains "local recipes," which address problems that involve client-side ActionScript only
(scripting within the Flash movie). These recipes cover working with color, movie clips, text,
forms, and so on.

Part II contains "remote recipes," which involve some kind of server interaction. For example,
Part II includes recipes on working with FlashCom applications; loading external assets such as
MP3s, SWFs, and JPEGs; using XML with your Flash movies; and using Flash Remoting.

Part III diverges somewhat from the classic O'Reilly Cookbook recipe approach, but I'm
particularly excited about this part of the book. Each of the chapters in Part III show you, step
by step, how to create complete applications that incorporate many of the recipes from Part I
and Part II. These chapters help you understand how to use many of the techniques in the
context of a complete application.
[ Team LiB ]
[ Team LiB ]

What's Not in This Book


This book contains a lot of information on a wide range of topics. It covers the gamut of client-
side ActionScript, plus recipes involving FlashCom, Flash Remoting, ColdFusion, PHP, [Link],
and more. Every recipe is presented in the context of an applied solution or example. While the
book discusses some minimal theory, it is not intended as an introduction to any of these
subjects. If you need more theory and introductory information on ActionScript, you should
check out ActionScript for Flash MX: The Definitive Guide, Second Edition by Colin Moock
(O'Reilly).

Although this book is aimed at a broad spectrum of developers, it doesn't cover the absolute
basic ActionScript familiar to most casual scripters, such as the gotoAndStop( ) command. For
such basic ActionScript, consult the recipes in the Flash Cookbook, the companion volume to
this book due out in late 2003. Prior to publication, sample ActionScript-related chapters from
the Flash Cookbook are available at [Link] For additional Flash and
ActionScript resources, see [Link] and [Link]
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 2 z 6

Flash Naming Conventions


With the introduction of the MX family of products, including Flash MX, Macromedia abandoned
a standard numeric versioning system for its Flash authoring tool. The Flash Player, however, is
still versioned numerically. Table P-1 describes the naming conventions for Flash used in this
book.

Table P-1. Flash naming conventions used in this book


Name Meaning
Flash MX The Flash MX authoring tool (as opposed to the Flash Player).
The Flash Player, version 6. The Flash Player is a browser plugin for major
web browsers on Windows, Macintosh, and Linux. The platform-specific
Flash Player 6
versions of the plugin are referred to collectively as "Flash Player 6," except
where noted.
Flash Player The Flash Player, specifically the release specified by x and y, as in Flash
x.0.y.0 Player [Link].
Short name for "Flash Player 6," used where the distinction between Flash
Flash 6
MX (the authoring tool) and Flash Player 6 (the browser plugin) is irrelevant.
Flash 5 The Flash 5 authoring tool, which came before Flash MX (as opposed to the
authoring tool Flash Player).
Flash Player 5 The Flash Player, version 5.
Short name for "Flash Player 5," used where the distinction between Flash 5
Flash 5
(the authoring tool) and Flash Player 5 (the browser plugin) is irrelevant.
Flash 2, Flash
Versions of the Flash Player prior to version 5.
3, and Flash 4
Standalone A version of the Flash Player that runs directly off the local system rather
Player than as a web browser plugin or ActiveX control.
A self-sufficient executable that includes both a .swf file and a Standalone
Projector Player. Projectors can be built for either the Macintosh or Windows operating
system using Flash's File Publish feature.

[ Team LiB ]
[ Team LiB ]

The Code
This book contains a lot of ActionScript (or more colloquially, code). Many recipes offer
suggested custom classes and custom methods for built-in ActionScript classes that I have
found to be invaluable. I trust you will find them useful as well. I also recommend that you
organize these classes and methods into ActionScript library files that you can save to your
Flash Include directory so you can include them in your Flash movies with relative ease. (The
Include folder is a special subdirectory under the Flash installation directory in which Flash
automatically looks for included files. You may need to create this subdirectory manually.) You
can skip or ignore these custom classes and methods if you don't think they are relevant to the
kind of applications you build. Alternatively, you can enter the code from this book into your
own ActionScript libraries; going through the code line by line will deepen your understanding
of how it works.

[Link] 2004-02-06
Strona 3 z 6

To save time or avoid errors when hand-entering the code, you can
download the complete ActionScript libraries from
[Link]

In addition to the ActionScript libraries, there are also some other important code examples
within the recipes, such as custom components and server-side scripts ([Link], CFML, etc.).
The corresponding Flash documents and scripts are also available for download at the same
web site. The complete applications from Part III are available for download as well. To
successfully use the Flash applications from Part III, you must also have downloaded the
necessary ActionScript libraries from Part I and Part II.
[ Team LiB ]
[ Team LiB ]

Typographical Conventions
To indicate the various syntactic components of ActionScript, this book uses the following
conventions:

Menu options

Menu options are shown using the character, such as File Open.

Constant width

Indicates code samples, clip instance names, frame labels, property names, and variable
names. Variable names often end with the standard suffixes that activate code-hinting
(such as _mc for variables that refer to movie clip instances). Although using these
suffixes is considered the best practice, for brevity or clarity, the preferred suffixes have
sometimes been omitted.

Italic

Indicates function names, method names, class names, layer names, symbol linkage
identifiers, URLs, filenames, and file suffixes such as .swf. In addition to being italicized,
method and function names are also followed by parentheses, such as duplicateMovieClip
( ).

Constant width bold

Indicates text that you must enter verbatim when following a step-by-step procedure,
although it is sometimes used within code examples for emphasis, such as to highlight an
important line of code in a larger example.

Constant width italic

Indicates code that you must replace with an appropriate value (e.g., your name here).
Constant width italic is also used to emphasize variable, property, method, and
function names referenced in comments within code examples.

When referring to properties and methods of objects and classes, I use these conventions:

z Class-level (static) properties are shown with the both the class name and property in
constant width because they should both be entered verbatim. For example,
[Link] or [Link].

[Link] 2004-02-06
Strona 4 z 6

z Instance-level properties are shown with the class or object instance in constant width
italic because it should be replaced by a specific instance. The property itself is shown
in constant width and should be entered as shown. For example, [Link].

z Method and function names, and the class or object to which they pertain, are always
shown in italics and followed by parentheses, as in [Link]( ). Refer
to the online help or ActionScript for Flash MX: The Definitive Guide to know whether to
include the class name literally (i.e., if it is a so-called static method), as in
[Link]( ), or replace it with an instance name, such as
ball_mc.duplicateMovieClip( ).

z For brevity, I often omit the class name when discussing a property or method of a class.
For example, if discussing the htmlText property of the TextField class, when I say "set
the htmlText property," you should infer from context that I mean, "set the
someField_txt.htmlText property, where someField_txt is the identifier for your
particular text field."

z In some cases, an object property will contain a reference to a method or callback


handler. It wasn't always clear whether I should have used constant width to indicate
that it is a property (albeit one storing a method name) or italics and parentheses to
indicate it is a method (albeit one stored in a property). If the line between a property
referring to a method versus the method itself is sometimes blurred, forgive me. To
constantly harp on the technical difference would have made the text considerably less
accessible and readable.

Pay special attention to notes and warnings set apart from the text with the following icons:

This is a tip. It contains useful information about the topic at hand, often
highlighting important concepts or best practices.

This is a warning. It helps you solve and avoid annoying problems or


warns you of impending doom. Ignore at your own peril.

[ Team LiB ]
[ Team LiB ]

We'd Like to Hear from You


We at O'Reilly have tested and verified the information in this book to the best of our ability,
but you may find that features have changed (or even that we have made mistakes!). Please let
us know about any errors you find, as well as your suggestions for future editions, by writing
to:

O'Reilly & Associates, Inc.


1005 Gravenstein Highway North
Sebastopol, CA 95472
(800) 998-9938 (in the United States or Canada)
(707) 829-0515 (international/local)
(707) 829-0104 (fax)

We have a web page for the book, where we list errata, examples, or any additional
information. You can access this page at:

[Link]

[Link] 2004-02-06
Strona 5 z 6

To comment or ask technical questions about this book, send email to:

bookquestions@[Link]

For more information about our books, conferences, software, Resource Centers, and the
O'Reilly Network, see our web site at:

[Link]

[ Team LiB ]
[ Team LiB ]

Acknowledgments
While there is only one name that goes on the cover, this book was not produced by one person
alone. There are many, many acknowledgments I would like to make to all the people who have
made this possibility a reality.

First of all, thanks go to Bruce Epstein—editor extraordinaire and (I suspect) superhero as well.
Bruce's patience with my seemingly endless emails, his faith, and his support have proved
invaluable. And he has helped to transform my words into something akin to prose.

I would also like to thank all the people at O'Reilly who have helped out with all their talents.
There is so much more that goes into a book than simply writing some words, and I am grateful
for all the people who have contributed in their many ways. My thanks to Matt Hutchinson,
Claire Cloutier, Glenn Bisignani, Rob Romano, Johnna Dinse, Reg Aubry, Sarah Sherman, Emma
Colby, David Futato, Andrew Savikas, and everyone in the marketing, sales, tools, design, and
production departments.

I am grateful to Tim O'Reilly for maintaining a vision for a publishing company that is dedicated
to such high quality. It shows at every step along the way, and it is reflected in the end
product. It is truly a pleasure and honor to work with O'Reilly.

I would like to acknowledge my agent, David Fugate, of Waterside Productions. David always
makes sure that things are working well for everyone involved. He is absolutely great to work
with, and his enthusiasm and dedication make a difference.

I would like to thank Robert Reinhardt, my friend and sometimes co-author, for all that he has
done for me and all that he continues to do. Robert is always there to help me test out new
ideas and to offer his encouragement.

Also, I would like to thank Colin Moock for indirectly providing the opportunity to write this book
by paving the way with ActionScript for Flash MX: The Definitive Guide. I would also like to
thank him for graciously allowing me to adapt or reproduce several tables from his well-
respected book, which was a frequent companion during the writing and reviewing process.

I would like to thank the Macromedians who develop and improve Flash, without whom there
would be nothing to write about. And on a related note, thanks go to the entire Flash
community for embracing Flash and ActionScript and for creating the demand for a book like
this one.

This book has been transformed from its earlier stages into what you are about to read with the
much appreciated assistance of technical reviewers who were kind enough to lend their time
and expertise. Specifically, I would like to thank Sham Bhangal, Gareth Downes-Powell, and
Devon O'Dell for their feedback for the entire manuscript. Thanks also to Brian Lesser for his

[Link] 2004-02-06
Strona 6 z 6

comments on the recipes relating to FlashCom and Pavils Jurjans for his careful review of the
recipes on regular expressions. Thanks are also due to John Viega and Andy Oram for reviewing
the encryption/security recipe. Last, but not least, I would like to thank Gareth and Devon for
providing assistance with PHP scripts, Arun Bhalla for his assistance with Perl scripts, and Peter
deHaan for his assistance with ColdFusion scripts. I would also like to thank other technical
reviewers who offered feedback on one or more chapters, including Kathryn Aaker, Beau
Ambur, Jeffrey Bardzell, Eric Bell, David Humphreys, Chafic Kazoun, Danny Kodicek, Tom Muck,
Samuel Neff, Jesse Warden, and Edoardo Zubler. I apologize if I accidentally omitted anyone
from the list.

Thanks are definitely due to the many readers, to the developers on numerous mailing lists,
and to all the people who post to various newsgroups and user forums. All the questions and
answers and feedback are more beneficial than you can know.

Certainly not least of all, I would sincerely like to thank all the wonderful people in my life who
love, support, and encourage me.

And without a doubt I acknowledge the oneness, the great mystery that makes all things
possible.

—Joey Lott
Los Angeles, California
May 2003
[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 1

[ Team LiB ]

Part I: Local Recipes


Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Chapter 13
[ Team LiB ]

mk:@MSITStore:D:\SHARED\OReilly%20-%20ActionScript%20Cookbook%20-%... 2004-02-06
Strona 1 z 30

[ Team LiB ]

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


[ Team LiB ]
[ Team LiB ]

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 Actions), as shown in Figure 1-1.

Figure 1-1. The Actions panel in Expert Mode

[Link] 2004-02-06
Strona 2 z 30

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.

[Link] 2004-02-06
Strona 3 z 30

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.

[Link] 2004-02-06
Strona 4 z 30

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,

[Link] 2004-02-06
Strona 5 z 30

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 ]
[ 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 operator

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)—th
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 sid
of the equation.

Although the following may look strange to those who remember basic algebra, it is very commo
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. F
// example, if myNum was 3, this statement sets it to 9.
myNum = myNum + 6;

[Link] 2004-02-06
Strona 6 z 30

However, when performing mathematical operations, it is often more convenient to use one of th
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 followin
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 th
operand, they are called postfix operators. The prefix and postfix operators modify the operand i
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

[Link] 2004-02-06
Strona 7 z 30

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 th
frame rate:

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

[ Team LiB ]
[ Team LiB ]

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

[Link] 2004-02-06
Strona 8 z 30

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.");
}
}

[Link] 2004-02-06
Strona 9 z 30

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:

[Link] 2004-02-06
Strona 10 z 30

// 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

[ Team LiB ]
[ Team LiB ]

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 particula
action or group of actions. To execute an action under certain circumstances, use one of ActionS
conditional statements: if, switch, or the ternary conditional operator (? :).

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

First, we'll look at the if statement. Of the conditional statements in ActionScript, the if statemen
the most important to understand. In its most basic form, an if statement includes the keyword
followed by the test expression whose truthfulness you want to evaluate to determine which acti
actions to execute. The test expression must be in parentheses, and the statement(s) to be exec
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 whe
the user answered a quiz question correctly (here, animalName is a variable assumed to contain
user's answer). Note that the double equals sign (==) is used to test whether two items are equa
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.");
}

[Link] 2004-02-06
Strona 11 z 30

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
browser, so it is intended for testing purposes only. We use the gotoAndStop( ) command to jum
different frame (presumably one that displays an appropriate message) depending on whether th
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 sk
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 i
// "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 animal
is "turtle" and another to check if it is "dove")? The example would work as intended, but it woul
less efficient. Using the else if statement guarantees that if animalName is "turtle", we don't both
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.");

[Link] 2004-02-06
Strona 12 z 30

}
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
most likely use a switch statement instead; generally, switch statements are more legible and su
than the comparable if statement. Where performance is critical, some ActionScripters prefer to
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 compose
the following parts:

The case or default keyword

Each case must begin with a case keyword. The exception is the default case (analog
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 express
the two values are equal, the code in the case body is executed. The default case (th
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 performe


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 fol
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
}

[Link] 2004-02-06
Strona 13 z 30

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

var animalName = "dove";

/* In the following switch statement, the first trace( ) statement does not execu
because animalName is not equal to "turtle". But both the second and third tra
statements execute, because once the "dove" case tests true, all subsequent co
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 state
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
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 s
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.");

[Link] 2004-02-06
Strona 14 z 30

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 thre
operands, as opposed to the one or two operands required by unary and binary operators. The fi
operand of the conditional operator is a conditional expression that evaluates to true or false. T
second operand is the value to assign to the variable if the condition is true, and the third operan
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
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 fa

Here is an example in which salutation defaults to "Hello" if no greeting was specified; otherw
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!
[ Team LiB ]
[ Team LiB ]

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.

[Link] 2004-02-06
Strona 15 z 30

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 conditio
are both true, use the logical AND operator (&&), as follows (see Chapter 10 for details on workin
with dates):

// Check if today is April 17th.


now = new Date( );
if ([Link]() == 17 && [Link]( ) == 3) {
trace ("Happy Birthday, Bruce!");
}

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

// Check if today is April 17th.


if (([Link]() == 17) && ([Link]( ) == 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 (([Link]() == 0) || ([Link]( ) == 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 conditio

// Check to see if a movie clip is visible. If so, display a message. This condit
// 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 mess
// 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 operato

[Link] 2004-02-06
Strona 16 z 30

// Check to see if the name is neither Bruce nor Joey. (This could also be rewrit
// 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 un
the first half of the expression is true. If the first half is false, the overall expression is always fal
so it would be inefficient to bother evaluating the second half. Likewise, ActionScript does not bo
to evaluate the second half of a logical OR statement unless the first half of the expression is fals
If the first half is true, the overall expression is always true.

[ Team LiB ]
[ Team LiB ]

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

[Link] 2004-02-06
Strona 17 z 30

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:

[Link] 2004-02-06
Strona 18 z 30

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

[Link] 2004-02-06
Strona 19 z 30

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.
[ Team LiB ]
[ Team LiB ]

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.
[ Team LiB ]
[ Team LiB ]

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

[Link] 2004-02-06
Strona 20 z 30

The setInterval( ) function allows you to specify an interval (in milliseconds) at which your Flash
will invoke a function. Use setInterval( ) to perform a particular action over time but not necessa
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, i
accuracy and granularity depend on computer playback performance in relation to other tasks be
demanded of the processor. There are two implications to this:

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

z 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
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 se


// Assign setInterval( )'s return value to the variable myIntervalID for later u
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
the variation of the setInterval( ) function in which you pass it three parameters—a reference to
object, the name of the function (as a string), and the interval in milliseconds— instead of just tw

// Create a simple object using the Object constructor.


obj = new Object( );

// Assign a method named myMethod to an object, obj.


[Link] = function ( ) {
trace("[Link]( ) has been called");
};

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

Whichever variation of the setInterval( ) function you use, any additional parameters that you pa
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);
}

[Link] 2004-02-06
Strona 21 z 30

// Use setInterval( ) to call displayValue( ) once per minute. The third parame
// 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( ) func
are evaluated only at the time the interval is initialized. So the same parameter values are alway
passed to a function or method that is called via setInterval( ):

obj = new Object( );

[Link] = 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
// traceAnimalName( ), because myAnimalName was "cub" when setInterval( ) was f
// called.
myAnimalName = "puppy";

One of the neat things you can do with setInterval( ) is create animations that are independent o
movie's frame rate. Remember that the onEnterFrame( ) method executes at the same interval a
frame rate, so using that technique ties you to the movie's properties. But with setInterval( ) yo
call a function or method at any interval you want. Here is an example in which two intervals are
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 t
// 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 th
// 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);

[ Team LiB ]
[ Team LiB ]

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

[Link] 2004-02-06
Strona 22 z 30

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 man
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 deta
when using it) and easier to maintain (because you can make changes in one place rather than i
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 afte
the call to the function.

For example, even though the writeMessage( ) function is not declared until after it is invoked, th
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 nam
// and so nothing happens (it fails silently).

[Link] 2004-02-06
Strona 23 z 30

writeMessage2( );

// Declare (define) the writeMessage2( ) function as an anonymous function liter


writeMessage2 = function ( ) {
trace("Hello, friend.");
};

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

However, there are several reasons to use function literals:

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

z Function literals offer a convenient, compact, and intuitive way to define methods for objec

z 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("[Link] "_blank");
};

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

[Link] = function ( ) {
launchBookExamples( );
};

Here, we define the function as a method of a movie clip (where onEnterFrame( ) is a special, bu
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 ne
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 simp
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.


[Link] = function ( ) {
trace("Hooray for methods!");
};

[Link] 2004-02-06
Strona 24 z 30

// Assign the same method definition to the onReleaseOutside( ) method as well.


[Link] = [Link];

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 [Link]
method:

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

Functions are subject to the same scope limitations as variables. Timeline functions are accessib
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 function
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 issue
However, if you intend to use the function throughout many timelines, two solutions are general
employed:

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

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

z Define the function as a (static) method of a global class. All of the built-in classes are glob
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).
[ Team LiB ]
[ Team LiB ]

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:

[Link] 2004-02-06
Strona 25 z 30

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.

[Link] 2004-02-06
Strona 26 z 30

// 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 < [Link]; i++) {
result += arguments[i];
}
// Then divide by the total number of arguments.
trace("The average is " + result/[Link]);
}

// 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.
[ Team LiB ]
[ Team LiB ]

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

[Link] 2004-02-06
Strona 27 z 30

// 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");
[ Team LiB ]
[ Team LiB ]

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

[Link] 2004-02-06
Strona 28 z 30

[ Team LiB ]
[ Team LiB ]

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 loca
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 ( ) {

[Link] 2004-02-06
Strona 29 z 30

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.
[ Team LiB ]
[ Team LiB ]

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 [Link] to your Flash movie.


#include "[Link]"

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 "[Link]"

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 [Link] 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 [Link] in a subdirectory named myASFiles.


#include "myASFiles/[Link]"

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

[Link] 2004-02-06
Strona 30 z 30

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/[Link]"
[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 16

[ Team LiB ]

Chapter 2. Runtime Environment

Introduction

Recipe 2.1. Detecting the Player Version

Recipe 2.2. Detecting the Operating System

Recipe 2.3. Checking the System Language

Recipe 2.4. Detecting Display Settings

Recipe 2.5. Scaling the Movie

Recipe 2.6. Changing the Alignment

Recipe 2.7. Detecting the Device's Audio Capabilities

Recipe 2.8. Prompting the User to Change Player Settings

Recipe 2.9. Hiding the Flash Player's Menu Items

Recipe 2.10. Enhancing Standalone Projectors

Recipe 2.11. Setting the Dimensions of a Projector

Recipe 2.12. Specifying Where on Screen a Projector Opens


[ Team LiB ]
[ Team LiB ]

Introduction
Flash Player 6 offers a relatively large amount of information about and control over the
runtime environment. ActionScript's [Link] object returns information about the
Player and the computer on which it is running, such as the operating system, language, and
audio capabilities. The System object also allows you to control some elements of the Player
such as the right-click menu under Windows (Ctrl-click on the Macintosh) and the Settings
dialog box. The Stage object controls the scaling and alignment of the movie within the Player.
[ Team LiB ]
[ Team LiB ]

Recipe 2.1 Detecting the Player Version


2.1.1 Problem
You want to know the user's Flash Player version.

2.1.2 Solution

[Link] 2004-02-06
Strona 2 z 16

Use a plugin-detection script that runs in the browser before loading the Flash Player, or use
ActionScript ([Link], getVersion( ), or $version) within the Flash Playe
as supported by the minimum Player version you expect to encounter.

2.1.3 Discussion
There are two broad categories of Flash Player version detection: you can attempt to detect the
Player version with a browser-based script before the Player loads, or you can use ActionScript
within the Flash Player to check the version that is currently running.

The problem with the first method is that it doesn't work on all platforms and with all browsers, a
explained in part at
[Link]

Therefore, you should guard against a "false negative" in your detection approach; even if you fa
detect the Flash Player plugin, you should give the user the option of telling you that he has the
plugin installed and would like to view your Flash content (i.e., don't force visitors to the
Macromedia Flash installation page just because you can't detect their Player version).

There is a wealth of existing documentation on detecting the presence and version of the Flash
Player plugin with a browser script, so refer to the following URLs for explanations and examples

Macromedia Technote 14526, "How to detect the presence of the Flash Player," is a good starting
point:

[Link]

See also the Player-detection discussion in the Flash Deployment Kit:

[Link]

The standard third-party browser-based Flash plugin-detection tool is the Moock Flash Player
Inspector:

[Link]

Testing the Flash Player version from ActionScript can be problematic because the user might no
have the Player installed at all (in which case your movie and its ActionScript code are never
executed), or the installed version may not support the latest ActionScript techniques. Therefore
is typical to use the lowest common denominator for checking Flash Player versions (you can use
the more modern techniques if you are sure the user will have at least a known minimum versio
Naturally, if your movie is running in the Standalone Player, you can't use the browser-based plu
detection scripts, but you can build the executable with whatever version of Flash you choose.

For maximum compatibility, use the _level0.$version property, which returns a string of the fo

OS MajorVersion,MinorVersion,Build,Patch

The $version property is supported in Flash Player [Link] and later. For example, the followin
returned string indicates the Windows Player version 6.0, build number 40.0:

WIN 6,0,40,0

You can extract the operating system, major version, and minor version using the [Link]( )
method, making it easier to work with the results:

[Link] 2004-02-06
Strona 3 z 16

// Split $version into an array with two elements using a space as the delimiter.
// This creates an array with two elements, such as "WIN" and "6,0,40,0".
playerParts = _root.$[Link](" ");

// Store the OS name in playerOS.


playerOS = playerParts[0];

// Split the remaining version string using a comma as the delimiter. This create
// array with four elements, such as "6", "0", "40", and "0".
playerVersion = playerParts[1].split(",");

// Convert the major and minor version, the build, and the patch into numbers and
// store them for later use.
playerMajorMinor = Number(playerVersion[0] + "." + playerVersion[1]);
playerBuild = Number(playerVersion[2]);
playerPatch = Number(playerVersion[3]);

You can use the version number to make a decision, such as sending the user to an error page t
says, "This site requires Flash 5 or later":

if (playerMajorMinor < 5.0) {


getURL ("[Link]
} else {
getURL ("[Link]
}

Why check the Player version from within Flash at all? Why not just publish a Flash 5 .swf file if y
site requires the Flash 5 Player? In some browsers, such as Internet Explorer for Windows, the
browser will automatically attempt to load the later plugin if the currently installed plugin is too o
But by using a Flash-based detection script, you can detect the plugin more accurately than you
in some browsers. Furthermore, you can customize the user experience to make it more user-
friendly. Therefore, if you expect visitors to have older versions of the plugin, you should publish
Flash 4 .swf file that acts as a gatekeeper to Flash content requiring newer versions of the plugin
That is, the Flash 4 .swf should detect the version and then branch to another .swf file containing
code implemented in the more modern version you are supporting.

Flash 5 added support for the getVersion( ) global function, which returns the same version
information string as $version. Use $version to detect the Player version unless you are sure th
the user has at least Flash 5 installed. Likewise, Flash 6 supports the more modern
[Link] property, which again returns the same string as $version. The
advantage of $version is, of course, that it is supported in older versions of the Player. And sinc
the purpose in most cases is to detect the correct Player version, it doesn't generally make sense
use a technique that is not available in older versions of the Player. The exception is if you want
detect only versions and revisions since the Flash 5 and Flash 6 Players, respectively.

Put the following code in a [Link] file and include it (using the #include directive) to more
readily perform version checking in your movies. This code will work with Flash 5 or later.

// Create a System object if it doesn't exist already.


if (System == undefined) {
System = new Object( );
}

// Create a [Link] object if it doesn't exist already.


if ([Link] == undefined) {
[Link] = new Object( );
}

// The extractPlayer( ) method extracts the OS and major and minor versions of th
// Player and saves them as properties of the [Link] object.
[Link] = function ( ) {

[Link] 2004-02-06
Strona 4 z 16

var playerParts = _level0.$[Link](" ");


[Link] = playerParts[0];
var playerVersion = playerParts[1].split(",");
[Link] = Number(playerVersion[0] + "." + playerVersion[1]);
[Link] = Number(playerVersion[2]);
[Link] = Number(playerVersion[3]);
}

// The following methods return the playerOS, playerMajorMinor, playerBuild, and


// playerPatch. If necessary, extractPlayer( ) is called first.
[Link] = function ( ) {
if ([Link] == undefined) {
[Link]( );
}
return [Link];
}

[Link] = function ( ) {
if ([Link] == undefined) {
[Link]( );
}
return [Link];
}

[Link] = function ( ) {
if ([Link] == undefined) {
[Link]( );
}
return [Link];
}

[Link] = function ( ) {
if ([Link] == undefined) {
[Link]( );
}
return [Link];
}

// Test each get method to see that it works.


trace([Link]( ));
trace([Link]( ));
trace([Link]( ));
trace([Link]( ));

You can use the preceding code to easily check the various portions of the version string. For
example, if your movie uses scroll pane components, you should require Player [Link] or later
because previous revisions had bugs related to scroll panes. For example:

// Display an error message if not using at least version [Link].


if ( !([Link]( ) >= 6 &&
[Link]( ) >= 40) ) {
[Link] = "You need version [Link] or later of the " +
"Flash Player in order to view this movie correctly."
}

For testing purposes, older versions of the Flash Player can be obtained from:

[Link]
[ Team LiB ]
[ Team LiB ]

Recipe 2.2 Detecting the Operating System

[Link] 2004-02-06
Strona 5 z 16

2.2.1 Problem
You want to know the operating system under which the Flash movie is being played, perhaps
to indicate which operating systems are not supported or to implement a platform-specific
feature.

2.2.2 Solution
Use the $version or [Link] property.

2.2.3 Discussion
In Recipe 2.1, we saw that the $version property string includes the operating system on
which the Player is running. The operating system can be either "MAC", "WIN", or "UNIX".

playerParts = _level0.$[Link](" ");


switch (playerParts[0]) {
case "MAC":
gotoAndStop ("WelcomeMac");
break;
case "WIN":
gotoAndStop ("WelcomeWindows");
break;
case "UNIX":
gotoAndStop ("WelcomeUnix");
}

As of Flash 6, you can use the [Link] property, which returns a string
indicating the operating system and version name. Possible values include "Windows XP",
"Windows 2000", "Windows NT", "Windows 98/Me", "Windows 95", and "Windows CE". On the
Macintosh, the string includes the version number, such as "MacOS 9.2.1" or "MacOS 10.1.4".

You can make design choices based on the operating system. For example, your movie might
load different assets depending on the user's operating system, or you may simply want to
record the operating systems of the users who view your movies for statistical analysis.

If all you care about is the general platform type instead of the specific version, you can check
just the first three letters of the string, as follows:

os = [Link](0, 3);
if (os == "Win") {
// Windows-specific code goes here.
} else if (os == "Mac") {
// Mac-specific code goes here.
} else {
// Must be Unix or Linux
}

[ Team LiB ]
[ Team LiB ]

Recipe 2.3 Checking the System Language


2.3.1 Problem

[Link] 2004-02-06
Strona 6 z 16

You want to know what language is used on the computer playing the movie.

2.3.2 Solution
Use the [Link] property.

2.3.3 Discussion
You can use the [Link] property to determine the language of the
computer that is playing the movie. The property returns a two-letter ISO-639-1 language code
(i.e., "fr" for French). Where applicable, a two-letter country code is appended, separated from t
language code with a hyphen (i.e., "en-US" for U.S. English and "en-UK" for U.K. English).

For a summary of language codes, see the following resources:

[Link]
[Link]

Here is an example of how to use the language property:

// Example output: en-US


trace([Link]);

You can use this property to dynamically load content in the appropriate language:

// Create an associative array with language codes


// for the keys and greetings for the values.
greetings = new Array( );
greetings["en"] = "Hello";
greetings["es"] = "Hola";
greetings["fr"] = "Bonjour";

// Extract the first two characters from the language code.


lang = [Link](0, 2);

// Use a default language if the language is not in the list.


if (greetings[lang] == undefined) {
lang = "en";
}

// Display the greeting in the appropriate language.


trace(greetings[lang]);

When you want to offer multiple language capabilities in your movies, you can choose from seve
different approaches. One approach, as shown in the preceding code, is to create associative arr
for all the text that appears in the movie. Another is to create static content in multiple movies (
for each language) and load those movies based on the language code. With this technique,
each .swf filename should include the language code, such as myMovie_en.swf, myMovie_es.swf
myMovie_fr.swf, etc.

// Get the language from the capabilities object.


lang = [Link](0, 2);

// Create an array of the languages you are supporting (i.e., the languages for wh
// you have created movies).
supportedLanguages = ["en", "es", "fr"];

// Set a default language in case you don't support the user's language.
useLang = "en";

[Link] 2004-02-06
Strona 7 z 16

// Loop through the supported languages to find a match to the user's language. I
// you find one, set useLang to that value and then exit the for statement.
for (var i = 0; i < [Link]; i++) {
if (supportedLanguages[i] == lang) {
useLang = lang;
break;
}
}

// Load the corresponding

movie.
_root.loadMovie("myMovie_" + useLang + ".swf");

[ Team LiB ]
[ Team LiB ]

Recipe 2.4 Detecting Display Settings


2.4.1 Problem
You want to know the display settings for the device on which the movie is being played.

2.4.2 Solution
Use the screenResolutionX and screenResolutionY properties of the [Link]
object.

2.4.3 Discussion
You should use the [Link] object to determine the display settings of the device
that is playing the movie. The screenResolutionX and screenResolutionY properties return th
display resolution in pixels.

// Example output:
// 1024
// 768
trace([Link]);
trace([Link]);

You can use these values to determine how to display a movie or even which movie to load. The
decisions are increasingly important as more handheld devices support the Flash Player. For
example, the dimensions of a cell phone screen and a typical desktop computer display are
different, so you should load different content based on the playback device:

resX = [Link];
resY = [Link];

// If the resolution is 240 x 320 or less, then load the PocketPC movie versi
// Otherwise, assume the device is a desktop computer and load the regular conten
if ( (resX <= 240) && (resY <= 320) ) {
_root.loadMovie("main_pocketPC.swf");
}
else {
_root.loadMovie("main_desktop.swf");
}

[Link] 2004-02-06
Strona 8 z 16

You can also use the screen-resolution values to center a pop-up browser window:

resX = [Link];
resY = [Link];

// Set variables for the width and height of the new browser window.
winW = 200;
winH = 200;

// Determine the x and y values in order to center the window.


winX = (resX / 2) - (winW / 2);
winY = (resY / 2) - (winH / 2);

// Create the code that, when passed to getURL( ), opens the new browser window.
jsCode = "javascript:void(newWin=[Link]('[Link] +
"'newWindow', 'width=" + winW +
", height=" + winH + "," +
"left=" + winX + ",top=" + winY + "'));";

// Call the JavaScript function using getURL( ).


_root.getURL(jsCode);

Additionally, it is worth considering using the screen-resolution values to determine whether to


scale a movie. For example, when users have their resolution set to a high value such as 1600 x
1200, some fonts may appear too small to read.
[ Team LiB ]
[ Team LiB ]

Recipe 2.5 Scaling the Movie


2.5.1 Problem
You want to control how a movie fits in the Player, including the scaling.

2.5.2 Solution
Use the [Link] property.

2.5.3 Discussion
The Flash Player defaults to a scale mode of "showAll" (except the test Player, which defaults to
"noScale"). In "showAll" mode, the Flash movie scales to fit the Player while maintaining the
movie's original aspect ratio. The result is that the movie can have borders on the sides if the
Player aspect ratio does not match the movie aspect ratio. You can set a movie to "showAll"
mode, as follows:

[Link] = "showAll";

The "noBorder" mode scales a movie to fit the Player while maintaining the original aspect ratio,
but it forces the Player to display no borders around the Stage. If the aspect ratio of the Player
does not match that of the movie, some of the movie will be cut off around the edges. You can
set a movie to "noBorder" mode, as follows:

[Link] = "noBorder";

The "exactFit" mode scales a movie to fit the Player and alters the movie's aspect ratio, if
necessary, to match that of the Player. The result is that the movie always exactly fills the

[Link] 2004-02-06
Strona 9 z 16

Player, but the elements of the movie may be distorted. For example:

[Link] = "exactFit";

In "noScale" mode, the movie is not scaled, and it maintains its original size and aspect ratio
regardless of the Stage's size. When you use the "noScale" mode, also set the movie's
alignment (see Recipe 2.6). For example:

[Link] = "noScale";

The scaleMode property's value does not prevent the user from being able to scale the movie
using the right-click/Ctrl-click menu. However, you can disable those options in the menu, as
shown in Recipe 2.9.

2.5.4 See Also


Additionally, you can affect scaling (among other things) in Standalone Projectors using a third-
party utility such as SWF Studio (see Recipe 2.10).

[ Team LiB ]
[ Team LiB ]

Recipe 2.6 Changing the Alignment


2.6.1 Problem
You want to change the alignment of the movie within the Player.

2.6.2 Solution
Use the [Link] property.

2.6.3 Discussion
Flash movies appear in the center of the Player by default. You can control the alignment of a mo
within the Player by setting the [Link] property, as shown in Table 2-1.

Table 2-1. Alignment as controlled by [Link]


Value Horizontal alignment Vertical alignment
"T" Center Top
"B" Center Bottom
"L" Left Center
"R" Right Center
"C" or " " Center Center
"LT" or "TL" Left Top
"RT" or "TR" Right Top
"LB" or "BL" Left Bottom

[Link] 2004-02-06
Strona 10 z 16

"RB" or "BR" Right Bottom

There is no "official" value to center the Stage both vertically and horizontally
in the Player. Technically, any string that doesn't match one of the other
modes will center the Stage. The value "C" is listed in Table 2-1 because it is
convenient to remember and it will work.

The following code demonstrates the effects of both the scale mode and alignment of a movie wi
the Player. This code relies on the [Link] file from Chapter 4 (specifically, the
drawRectangle( ) method). Additionally, make sure to include the PushButton component in the
movie's Library (see Recipe 11.1). Once you have added this code to a new movie's main timelin
test the movie in the Standalone Player. Experiment by scaling the Player and clicking on the diff
buttons to see the effects.

// Include [Link] from Chapter 4 for the drawRectangle( ) method.


#include "[Link]"

// Create a movie clip and draw a rectangle in it. The rectangle is 550 x 400
// dimensions of the default movie), and it is positioned so that the upper-left
// corner of the rectangle is at (0,0) within the Stage. This movie clip allows y
// to see the boundaries of the Stage when the movie is scaled.
_root.createEmptyMovieClip("background", 1);
[Link](0, 0, 0);
[Link](0xFF00FF);
[Link](550, 400, 0, 0, 275, 200);
[Link]( );

// Array of possible values for scaleMode


scaleLabels = ["noScale", "exactFit", "showAll", "noBorder"];

// Create push buttons for each scale mode.


for (var i = 0; i < [Link]; i++) {
btn = _root.attachMovie("FPushButtonSymbol", "scaleBtn" + i, (i + 2),
{_y: (i * 25)});
[Link](scaleLabels[i]);
[Link] = function ( ) {
[Link] = [Link]( );
};
}

// Array of possible alignment values


alignLabels = ["T", "B", "L", "R", "C", "LT", "RT", "LB", "RB"];

// Create push buttons for each alignment option.


for (var i = 0; i < [Link]; i++) {
btn = _root.attachMovie("FPushButtonSymbol", "scaleBtn" + (i + 4), (i + 6),
{_x: 200, _y: (i * 25)});
[Link](alignLabels[i]);
[Link] = function ( ) {
[Link] = [Link]( );
};
}

[ Team LiB ]
[ Team LiB ]

Recipe 2.7 Detecting the Device's Audio Capabilities

[Link] 2004-02-06
Strona 11 z 16

2.7.1 Problem
You want to determine the audio capabilities of the device on which the Player is running.

2.7.2 Solution
Use the hasAudio and hasMP3 properties of the [Link] object.

2.7.3 Discussion
Desktop versions of Flash Player 6 and later support MP3 playback and the ability to encode aud
from a microphone or similar device. However, Flash Players for other devices do not necessarily
support all, or possibly any, audio capabilities. The [Link] property
returns true if the Player has any audio capabilities and false otherwise. This is extremely
important for playing movies on multiple devices. If a device has no audio support, you should
avoid forcing users to download something they cannot hear (especially because audio can be qu
large):

// Load a .swf containing sound only if the Player can play audio.
if ([Link]) {
[Link]("[Link]");
} else {
[Link]("[Link]");
}

Just because a Player has audio capabilities, however, does not necessarily mean that it can play
back MP3 sounds. Therefore, if publishing MP3 content, you should test for MP3 capabilities using
the [Link].hasMP3 property. MP3 sounds are preferable, if supported, because
they offer better sound-quality-to-file-size ratios than ADCP sounds.

// If the Player can play MP3s, load an MP3 using a Sound object. Otherwise, load
// .swf containing ADCP sound into a nested movie clip.
if ([Link].hasMP3) {
mySound = new Sound(mySoundHolder);
[Link]("sound.mp3", false);
} else {
mySoundHolder("[Link]");
}

It is important to understand that the hasAudio and hasMP3 property settings are based on the
capabilities of the Player and not of the system on which the Player is running. The desktop syste
players (for Windows, Mac OS, and Linux) always return true for both properties regardless of
whether the system actually has the hardware (i.e., soundcard and speakers) to play back sound
However, players for other devices may return false if the device does not support the audio or
MP3 features.

[ Team LiB ]
[ Team LiB ]

Recipe 2.8 Prompting the User to Change Player


Settings
2.8.1 Problem
You want to open the user's Flash Player Settings dialog box to prompt her to allow greater

[Link] 2004-02-06
Strona 12 z 16

access to her local system.

2.8.2 Solution
Use the [Link]( ) method.

2.8.3 Discussion
The [Link]( ) method opens the Flash Player Settings dialog box, which includes
four tabs. The number in parentheses is the value you pass to showSettings( ) to open that
particular tab.

Privacy (0)

Allows the user to specify whether to allow Flash access to her camera and microphone.

Local Storage (1)

Allows the user to specify how local shared objects are stored, including the maximum
allowable disk usage.

Microphone (2)

Allows the user to select a microphone and adjust the volume.

Camera (3)

Allows the user to select a camera.

If you don't pass any parameters to the showSettings( ) method, it opens the Settings dialog
box to the tab that was opened the last time the Settings dialog box was used. Here, we open
the Settings dialog box to the Local Storage tab by explicitly specifying a value of 1:

// Open the Settings dialog box to the Local Storage tab.


[Link](1);

Out of courtesy, you should prompt the user to open the Settings dialog
box with a button rather than simply opening it without warning. Also,
you should alert the user beforehand as to which settings to change.

2.8.4 See Also


Recipe 16.4
[ Team LiB ]
[ Team LiB ]

Recipe 2.9 Hiding the Flash Player's Menu Items


2.9.1 Problem
You want to hide the right-click menu under Windows (Ctrl-click on the Macintosh).

2.9.2 Solution

[Link] 2004-02-06
Strona 13 z 16

You can't disable the Flash Player's pop-up menu entirely, but you can minimize the options
shown in the menu by setting the [Link] property to false.

2.9.3 Discussion
By default, the following options appear in the Flash Player's pop-up menu when the user right-
clicks in Windows (or Ctrl-clicks on the Macintosh):

Zoom In
Zoom Out
100%
Show All
Quality (Low, Medium, or High)
Settings
Print
About Flash Player 6

You can remove most of the menu options with the following line of ActionScript code, although
the Settings and About options remain in place:

[Link] = false;

Unfortunately, Flash does not provide a way to disable the menu entirely. Furthermore,
Windows users are accustomed to using right-click to display a pop-up browser menu that
allows them to open a link in a new window, for example. Such options are not available due to
the Flash pop-up menu's presence. It is possible to use a third-party utility such as SWFKit or
SWF Studio to completely disable the menu for enhanced Standalone Projectors (Windows
versions only), as shown in Recipe 2.10.

2.9.4 See Also


See Recipe 2.8 for a way to display Flash's Settings dialog box without requiring the user to
right-click (in Windows) or Ctrl-click (on Macintosh).
[ Team LiB ]
[ Team LiB ]

Recipe 2.10 Enhancing Standalone Projectors


2.10.1 Problem
You want to create an enhanced Standalone Projector with features such as borderless
playback, custom titles, no Flash menus, and so on.

2.10.2 Solution
Use a third-party tool such as SWF Studio or SWFKit to create the Projector from your
completed Flash movie.

2.10.3 Discussion
SWF Studio is a third-party utility that helps you overcome some of the limitations of regular
standalone Flash Projectors. However, SWF Studio produces Windows projectors only. Also note
that the resultant projector files are rather hefty (minimum file size is around 1.5 MB).
Therefore, projectors created with SWF Studio are more suited for distribution via CD-ROM or a
kiosk than via download.

[Link] 2004-02-06
Strona 14 z 16

SWF Studio has many features, as described on the official product site
([Link] SWFKit ([Link] offers similar feature
enhancements, but we cover only SWF Studio in detail here.

However, regardless of the Projector enhancements you want to achieve, there are several
steps necessary in any project that uses SWF Studio:

1. Create your Flash movie by exporting the .swf file from Flash.

2. Open SWF Studio. The program automatically starts with a new project opened, so you
don't need to create a new project.

3. On the left side of the application window is the Project Tree (see Figure 2-1). Locate the
Layout option at the end of the list and click on it.

4. In the right pane, click on the Layout Options button. A menu appears from which you
should select Add Movie (see Figure 2-1).

5. A new Movie is added to the project and appears as an item under the Layout option in
the Project Tree. The Movie option should be selected automatically, and the right pane
should now contain a File form field. Click on the Browse button to the right of the form
field, select the .swf file you want to add to the project, and click OK.

6. Select the enhancements you want to add to your projector (the default settings disable
the right-click menu entirely).

7. After you have chosen the settings for the Projector, click Build to export the EXE. If you
saved the project previously, SWF Studio exports the EXE with that name; otherwise, it
exports the EXE as [Link]. If you do not specify an output directory, the EXE
appears on the desktop.

Figure 2-1. SWF Studio Project Tree window

2.10.4 See Also


Third-party tools: SWF Studio ([Link] and SWFKit
([Link] See Recipe 2.9, Recipe 2.11, and Recipe 2.12 for details on several of
their benefits.

[Link] 2004-02-06
Strona 15 z 16

[ Team LiB ]
[ Team LiB ]

Recipe 2.11 Setting the Dimensions of a Projector


2.11.1 Problem
You want to specify the dimensions of a Projector.

2.11.2 Solution
Use SWF Studio to create the Projector (Windows only). Set the size settings prior to building
the project.

2.11.3 Discussion
If you are creating a Windows Projector, you can use SWF Studio to set the dimensions of the
movie. Follow the steps in Recipe 2.10, substituting the following tasks for step 6:

1. Choose the first item from the Project Tree. If you have saved your project, the item has
the same name as your project; otherwise, the item is labeled Untitled.

2. On the right, the Project Options pane appears, and the Output tab is selected by default.
You can leave the output settings at their default values.

3. Select the Window tab.

4. In the Window tab, choose one of the following options:

Full Screen

This option forces the Projector to full screen, matching the monitor resolution at
runtime.

Fixed Size

If you select this option, you must specify the dimensions (in pixels) to use for the
Projector. The default is 500 x 400.

% of Desktop

This option scales the Projector to fit a percentage of the user's desktop. Because
users can have different resolution settings on their computers, choosing a
percentage can have different results than using Fixed Size.

Match the Size of your Main Movie

This option leaves the movie's dimensions as they are.

2.11.4 See Also


See Recipe 2.10 for more information on how to use SWF Studio.

[Link] 2004-02-06
Strona 16 z 16

[ Team LiB ]
[ Team LiB ]

Recipe 2.12 Specifying Where on Screen a Projector


Opens
2.12.1 Problem
You want to specify a location on the screen where a Projector should open.

2.12.2 Solution
Use SWF Studio to create the Projector (Windows only) and specify the position settings.

2.12.3 Discussion
One of the many useful features of SWF Studio is the ability to specify where on screen the
Projector should open. Standard Flash Projectors don't offer this option, but SWF Studio can set
the percentage values in the x and y directions at which the Projector should open. To
accomplish this, complete the steps in Recipe 2.10, substituting the following for step 6:

1. Choose the first item from the Project Tree. If you have saved your project, the item has
the same name as your project; otherwise, the item is labeled Untitled.

2. On the right, the Project Options pane appears and the Output tab is selected by default.
You can leave all the output settings at their default values.

3. Select the Window tab.

4. In the Window tab locate the Size and Position portion of the settings. To the right is a
controller that allows you to adjust the position at which the movie opens on screen. The
black rectangle represents the user's screen, and the white rectangle represents the
Projector. By moving the horizontal and vertical sliders you can adjust the relative
position of the Projector.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 12

[ Team LiB ]

Chapter 3. Color
Introduction

Recipe 3.1. Setting the Color of a Movie Clip

Recipe 3.2. Specifying RGB Values

Recipe 3.3. Decoding an RGB Value

Recipe 3.4. Setting the RGB Relative to Its Current Value

Recipe 3.5. Tinting a Movie Clip's Color

Recipe 3.6. Setting a Movie Clip's Transparency

Recipe 3.7. Transforming a Movie Clip's Current Color

Recipe 3.8. Restoring a Movie Clip's Original Color

Recipe 3.9. Controlling a Movie Clip's Color with Sliders


[ Team LiB ]
[ Team LiB ]

Introduction
Color objects can control colors programmatically, which means you can create dynamically
colored elements in your movies. You can change background colors, animate colors over
gradations, and allow user control over colors (with sliders, buttons, and so on).

To work effectively with colors, you must understand how to specify color values. ActionScript
colors have four parts: red, green, blue, and alpha (or transparency). For the color
components—each ranging in value from 0 to 255 (in standard, decimal format)—higher
numbers mean brighter colors. When red, green, and blue are all 0, the resulting color is black.
When red, green, and blue are all 255, the resulting color is white. When red, green, and blue
are all equal, the resulting color is a shade of gray.

The alpha value determines the transparency: a value of 0 is completely transparent, and the
maximum value, which depends on the method or property used to set the alpha value, is fully
opaque. The maximum value for alpha is 100 when specified using the _alpha property; the
maximum alpha value is 255 when specified as part of a color transformation along with RGB
components.

For example, a pure blue, fully opaque color would have the following components:

Red: 0, Green: 0, Blue: 255, Alpha: 255

For colors in which you don't want to change the alpha (which defaults to the value set for the
object during authoring) you can represent the RGB parts as a single value ranging from 0 to
16777215, but using the hexadecimal equivalent is much more practical.

The hexadecimal representation of an ActionScript color begins with 0x and is followed by six
bytes (two bytes each for red, green, and blue), so it takes the form 0xRRGGBB. For example,

[Link] 2004-02-06
Strona 2 z 12

pure red is 0xFF0000, pure green is 0x00FF00, and pure blue is 0x0000FF. All other colors are
some combination of red, green, and blue. For example, a nice lime green is 0xA9FC9C. The
Flash Color Mixer panel (Shift + F9) shows the hexadecimal value for the selected color under
the swatch in the lower-left portion of the panel, as shown in Figure 3-1.

Figure 3-1. The Color Mixer panel

Hexadecimal format makes it easy to identify the red, green, and blue components of a color.
However, you are free to use the decimal equivalent. For example, 11140252 is the decimal
equivalent of the hexadecimal number 0xA9FC9C.

[ Team LiB ]
[ Team LiB ]

Recipe 3.1 Setting the Color of a Movie Clip


3.1.1 Problem
You want to control the color of a movie clip dynamically (at runtime, rather than during
authoring).

3.1.2 Solution
Create a Color object that targets the desired movie clip, and then use the [Link]( )
method.

3.1.3 Discussion
You can't change the color of a movie clip directly. Instead, you must first create a Color object
that targets the movie clip of interest, as follows:

my_color = new Color(myMovieClip);

You can alternatively specify the movie clip name as a string:

my_color = new Color("myMovieClip");

The target movie clip, as specified when creating a Color object, must
correspond to the name of a movie clip instance on the Stage, not the
name of the Library symbol from which the clip is derived. Therefore, you

[Link] 2004-02-06
Strona 3 z 12

must set the target clip's instance name using the Property inspector
before you can target it using a Color object.

A Color object can target a movie clip on any timeline using an absolute or relative reference,
such as:

my_color = new Color(_root.myMovieClip);


my_color = new Color(_parent.myMovieClip);

Once you have created a Color object, you can use it to control the color of the targeted movie
clip instance.

The [Link]( ) method applies a single color value to the movie clip targeted by the Color
object (as specified when the Color object was created), filling the entire movie clip shape with
a single color. The color value can be of any valid ActionScript numeric format.

The following examples both apply a solid blue color to the targeted movie clip:

my_color.setRGB(0x0000FF); // Hexadecimal
my_color.setRGB(255); // Decimal

The following examples both apply a solid lime green color to the targeted movie clip:

my_color.setRGB(0xA9FC9C); // Hexadecimal
my_color.setRGB(11140252 ); // Decimal

3.1.4 See Also


Recipe 5.2

[ Team LiB ]
[ Team LiB ]

Recipe 3.2 Specifying RGB Values


3.2.1 Problem
You want to create an RGB value to use with setRGB( ) based on the red, green, and blue part
values.

3.2.2 Solution
Use the bitshift left and bitwise OR operators.

3.2.3 Discussion
The easiest way to combine individual red, green, and blue components into a single RGB value
is to use bitwise operations. Simply shift the red value left by 16 bits and the green value left
by 8 bits, then combine them with the blue value using the bitwise OR operator, as follows:

red = 100;
green = 100;

[Link] 2004-02-06
Strona 4 z 12

blue = 100;
rgb = (red << 16) | (green << 8) | (blue);

// Create the Color object.


my_color = new Color(myMovieClip);

// Set the RGB color.


my_color.setRGB(rgb);

The bitshift left operator (<<) effectively multiplies a number by two for each bit position that
the number is shifted (this is similar to how shifting a decimal number one decimal place to the
left effectively multiplies it by 10). In this context, the bitwise OR operator (|) essentially
concatenates the shifted numbers together. Therefore, assuming that red, green, and blue are
each in the range of 0 to 255, the following:

rgb = (red << 16) | (green << 8) | (blue);

is equivalent to:

rgb = (red * [Link](2,16)) + (green * [Link](2,8)) + blue;

or:

rgb = (red * 65536) + (green * 256) + blue;

In practice, it is often easier to use [Link]( )—in which the red, green, and blue
components are specified as separate properties of a transform object—to alter the targeted
clip's color. Furthermore, setTransform( ) allows you to set the alpha value for a color, which
setRGB( ) does not.

3.2.4 See Also


Recipe 3.7

[ Team LiB ]
[ Team LiB ]

Recipe 3.3 Decoding an RGB Value


3.3.1 Problem
You want to extract the red, green, and blue components from an RGB value returned by
[Link]( ).

3.3.2 Solution
Use the bitshift right and bitwise AND operators.

3.3.3 Discussion
You can extract the red, green, and blue components from the single RGB value returned by
[Link]( ) using the bitshift right (>>) and bitwise AND (&) operators. You can extract one
or more of the colors individually as follows:

[Link] 2004-02-06
Strona 5 z 12

// Create the Color object.


my_color = new Color(myMovieClip);

// Get the current RGB color.


rgb = my_color.getRGB( );

// rgb contains an RGB color value in decimal form, such as 14501017 (rosy pink),
// which is stored internally as its hex equivalent, such as 0xDD4499.
red = (rgb >> 16);
green = (rgb >> 8) & 0xFF;
blue = rgb & 0xFF;

Although displayed as a decimal number, remember that each color is stored internally in its
hexadecimal form: 0xRRGGBB. For example, the color value 14501017 (which is rosy pink) is
stored internally as 0xDD4499. In this format, it is easy to see that the red component is DD in
hex (221 in decimal), the green component is 44 in hex (68 in decimal), and the blue componen
is 99 in hex (153 in decimal).

The preceding transformation effectively separates a 24-bit value into its three 8-bit components
(the leftmost eight bits represent red, the middle eight bits represent green, and the rightmost
eight bits represent blue). The bitshift right operator is used to shift the eight bits of interest to
the rightmost position. Using the bitwise AND operator with 0xFF retains the rightmost eight bits
only, effectively masking off any unwanted bits on the left.

In practice, it is often easier to use [Link]( )—in which the red, green, and blue
components are returned as separate properties of a transform object—to determine a clip's
color. Furthermore, getTransform( ) also returns the alpha value for a color, which getRGB( )
does not.

3.3.4 See Also


Recipe 3.7
[ Team LiB ]
[ Team LiB ]

Recipe 3.4 Setting the RGB Relative to Its Current Value


3.4.1 Problem
You want to modify the RGB value of a movie clip relative to its current value.

3.4.2 Solution
Use [Link]( ) to retrieve the current value, then perform bitwise operations to modify the
value, and set the new value using [Link]( ).

3.4.3 Discussion
We saw in Recipe 3.3 how to retrieve the current RGB value of a movie clip using [Link](
and then use bitwise operations to extract the red, blue, and green components. To perform a
relative color adjustment, simply modify the individual color components and reapply the new co
using [Link]( ). For example, you could brighten a movie clip's color by increasing the red
green, and blue components by a certain amount:

// Create the Color object.


my_color = new Color(myMovieClip);

[Link] 2004-02-06
Strona 6 z 12

// Retrieve the current RGB setting.


rgb = my_color.getRGB( );
red = (rgb >> 16);
green = (rgb >> 8) & 0xFF;
blue = rgb & 0xFF;

// Brighten the colors by increasing their magnitude. This assumes that the red,
// green, and blue values are no more than 245 prior to the operation.
red += 10;
green += 10;
blue += 10;

// Combine the components into a single RGB value and apply it with [Link](
rgb = (red << 16) | (green << 8) | blue;
my_color.setRGB(rgb);
[ Team LiB ]
[ Team LiB ]

Recipe 3.5 Tinting a Movie Clip's Color


3.5.1 Problem
You want to modify the color tint of a movie clip (as opposed to applying a single color to the
whole shape).

3.5.2 Solution
Use the setTransform( ) method of the Color object that targets the movie clip.

3.5.3 Discussion
Using [Link]( ) to fill a movie clip with a solid color overrides any color contrast within
the movie clip. To apply a tint to a movie clip, use [Link]( ) instead.

Flash records the color values set for a movie clip during authoring. A single clip might contain
hundreds of colors. However, setRGB( ) applies the same RGB value to every color region within
a movie clip. On the other hand, setTransform( ) can modify colors relative to their original
values. For example, consider a movie clip that contains a JPEG with hundreds of colors. Using
setRGB( ) applies one color to the whole movie clip, resulting in a solid-colored rectangle. But
with setTransform( ), you can adjust the red, green, and blue levels of each original color,
effectively tinting the image without losing the initial contrast.

The setTransform( ) method accepts a single parameter: a transform object that includes the
eight properties shown in Table 3-1.

Table 3-1. Properties of a color transform object


Property Range Description
ra -100 to 100 Red percentage transformation
rb -255 to 255 Red offset
ga -100 to 100 Green percentage transformation
gb -255 to 255 Green offset
ba -100 to 100 Blue percentage transformation
bb -255 to 255 Blue offset

[Link] 2004-02-06
Strona 7 z 12

aa -100 to 100 Alpha percentage transformation


ab -255 to 255 Alpha offset

The values in Table 3-1 are used to transform the existing color values within the movie clip
using the following formulas:

red = originalRed * (ra/100) + rb


green = originalGreen * (ga/100) + gb
blue = originalBlue * (ba/100) + bb
alpha = originalAlpha * (aa/100) + ab

If any of the resulting values (red, green, blue, or alpha) are outside of the 0 to 255 range, it
becomes more difficult to predict the resulting effect on the color. Therefore, it is generally wise
to specify property values such that the resulting color remains in the valid range.

You can create the transform object by creating a generic instance of the base Object class and
adding properties to it, as follows:

myColorTransform = new Object( );


[Link] = 100;
[Link] = 50;
[Link] = 100;
[Link] = 50;
[Link] = 100;
[Link] = 50;
[Link] = 100;
[Link] = 0;

Or you can create an equivalent transform object using object literal notation:

myColorTransform = {ra: 100, rb: 50, ga: 100, gb: 50,


ba: 100, bb: 50, aa: 100, ab: 0};

Any properties that are omitted from the preceding transform object are given a value of 0.

After defining a transform object, apply it to the targeted movie clip via [Link]( ),
as follows:

my_color = new Color(myMovieClip);


my_color.setTranform(myColorTransform);

The values involved in transforming a movie clip's colors can sometimes appear a bit daunting
and confusing at first. Familiarize yourself with how these values work together to produce a
single color transformation by experimenting with the advanced color settings in the Flash MX
authoring tool. The advanced settings correspond to the properties that make up a transform
object, and they have the same effect—albeit at authoring time instead of runtime. You can
access the advanced color settings at authoring time by following these steps:

1. Select a movie clip instance on the Stage.

2. Open the Property inspector (Window Properties).

3. Select Advanced from the Color dropdown list in the Property inspector.

4. The Settings button appears to the right of the Color dropdown list. Click the Settings
button to open the Advanced Effects dialog box.

[Link] 2004-02-06
Strona 8 z 12

[ Team LiB ]
[ Team LiB ]

Recipe 3.6 Setting a Movie Clip's Transparency


3.6.1 Problem
You want to adjust a movie clip's transparency.

3.6.2 Solution
Set the _alpha property for the movie clip instance, or set the aa and ab properties of the clip's
transform object when using [Link]( ).

3.6.3 Discussion
You can set a movie clip's alpha value (transparency) anywhere from completely transparent
(0) to fully opaque (100). The most direct, and generally the most common, way to adjust a
movie clip's transparency is by setting the movie clip's _alpha property. You can assign the
_alpha property any numeric value, but only values in the range of 0 to 100 will effect a
predictable visible change. This sets the clip to be partially transparent:

myMovieClip._alpha = 63;

For more information on how you can animate the _alpha property over time to create fade
effects, see Recipe 7.9.

In addition, you can use a Color object to modify a movie clip's transparency. Use a Color
object's setTransform( ) method (see Recipe 3.5) to assign color transformations to a movie
clip. Among the properties of a color transform object are the aa (alpha offset) and ab (alpha
percentage) properties. Therefore, you can use these properties to assign new transparency
settings to a movie clip:

// Get the current transform object and modify the aa and ab properties.
// See Recipe 3.7 for details on modifying a transform object's properties.
my_color = new Color(circle_mc);
transformObj = my_color.getTransform( );
[Link] = 63;
[Link] = 0;
my_color.setTransform(transformObj);

Modifying a movie clip's alpha using [Link]( ) requires much more code than
simply setting the movie clip's _alpha property. However, if you are already setting a movie
clip's color values using [Link]( ), it is convenient to set the alpha at the same
time using this technique.

Note that all changes made to the MovieClip._alpha property are reflected in the transform
object returned by [Link]( ). Similarly, changes made via [Link]( )
are reflected in the MovieClip._alpha property.

3.6.4 See Also


Recipe 7.9 for important information on modifying the _alpha property over time

[Link] 2004-02-06
Strona 9 z 12

[ Team LiB ]
[ Team LiB ]

Recipe 3.7 Transforming a Movie Clip's Current Color


3.7.1 Problem
You want to modify a movie clip's color relative to the current color transformation, instead of
relative to the author-time color values.

3.7.2 Solution
Use the getTransform( ) and setTransform( ) methods of the Color object that targets the movie
clip.

3.7.3 Discussion
The [Link]( ) method returns the transform object last applied to the targeted mov
clip. For example, if you had previously applied a transform object that set the movie clip's alpha
percentage to 42, getTransform( ) would return an object with the following values:

{ra: 100, rb: 0, ga: 100, gb: 0, ba: 100, bb: 0, aa: 42, ab: 0}

The transform object reflects changes made to a movie clip's color either at authoring time or at
runtime. If you modify the color values using the Property inspector at authoring time, those val
are indicated in the movie clip's transform object. You can make changes to a movie clip's color
runtime by using the setTransform( ) or setRGB( ) methods of its Color object, and you can adju
the movie clip's _alpha property separately. All runtime changes—not just the changes made us
setTransform( )—are reflected in the transform object. If you have not applied any color changes
runtime or authoring time then getTransform( ) returns the following value (a neutral transform
object):

{ra: 100, rb: 0, ga: 100, gb: 0, ba: 100, bb: 0, aa: 100, ab: 0}

You can modify the properties of the transform object returned by getTransform( ) and then app
the modifications using setTransform( ):

// Create the Color object.


my_color = new Color(myMovieClip);

// Get the transform object.


myTransformObject = my_color.getTransform( );

// Set the green percentage of all colors within the movie clip to 50% of the cur
// value.
[Link] = 50;

// Apply the transform object.


my_color.setTransform(myTransformObject);

The preceding example retains the previously applied transform values, with the exception of ga
which is set to 50. You can instead increment or decrement the properties relative to their curren
values:

// Get the transform object.


myTransformObject = my_color.getTransform( );

[Link] 2004-02-06
Strona 10 z 12

// Increment the red, green, and blue offsets by 10 to brighten the object's colo
[Link] += 10;
[Link] += 10;
[Link] += 10;

// Set the transform object.


my_color.setTransform(myTransformObject);

Transformations applied with setTransform( ) occur relative to the colors in


the original movie clip symbol, independent of any previous transformations.
In other words, the transformations are not cumulative. We simulated a
cumulative transformation by basing the new transformation on the previous
values, as returned by getTransform( ).

[ Team LiB ]
[ Team LiB ]

Recipe 3.8 Restoring a Movie Clip's Original Color


3.8.1 Problem
You want to restore the original symbol's color values to a movie clip.

3.8.2 Solution
Reset the transformation object using the setTransform( ) method of the Color object that target
the movie clip.

3.8.3 Discussion
No matter what changes you made to a movie clip's color using setTransform( ) and setRGB( ) (o
made at authoring time), you can restore the original color values using a reset transform object
(one with 100 for the percentages and 0 for the offsets):

resetTransform = {ra: 100, rb: 0, ga: 100, gb: 0, ba: 100, bb: 0, aa: 100, ab: 0}

When this reset transform object is passed to the setTransform( ) method, the colors in the
targeted movie clip are set to those of the Library symbol on which the clip is based:

// Create the Color object.


my_color = new Color(myMovieClip);

// Apply a unity transformation.


resetTransform = {ra: 100, rb: 0, ga: 100, gb: 0, ba: 100, bb: 0, aa: 100, ab: 0}
my_color.setTransform(resetTransform);
[ Team LiB ]
[ Team LiB ]

Recipe 3.9 Controlling a Movie Clip's Color with Sliders


This recipe presents a full application that creates sliders for the red, green, blue, and alpha valu
that control a movie clip's color:

[Link] 2004-02-06
Strona 11 z 12

1. Create a new Flash document and save it.

2. On the main timeline, rename the default layer as movieClips and create a new layer name
actions.

3. Create a movie clip symbol and draw a circle in it. The circle should be approximately 120
pixels.

4. Return to the main timeline and create an instance of the circle movie clip on the Stage on
movieClips layer. Place the instance on the left side of the Stage. Name the instance circl
using the Property inspector.

5. Open the Components panel (Window Components) and drag four instances of the
ScrollBar component onto the Stage on the movieClips layer. Name these instances red_sb
green_sb, blue_sb, and alpha_sb. Line them up horizontally on the right side of the Stage

6. Select the keyframe of the actions layer and open the Actions panel.

7. Add the following code to the Actions panel and test the movie (Control Test Movie). T
scrollbars are automatically colorized to indicate the color components they control. Moving
thumb sliders on the scrollbars adjusts the circle's color.

// Define a function that will initialize the scrollbar instances as sliders


// control the color values.
function initSliders ( ) {

// First, set the scroll properties of each of the scrollbars. For the red
// green, and blue scrollbars, the values should range from 0 to 255. Use
// pageSize of 120 for the color sliders to create a proportional thumb ba
// The alpha range is from 0 to 100, and so the pageSize should be 47 to c
// a thumb bar that is proportional with the other sliders.
red_sb.setScrollProperties (120, 0, 255);
green_sb.setScrollProperties(120, 0, 255);
blue_sb.setScrollProperties (120, 0, 255);
alpha_sb.setScrollProperties(47, 0, 100);

// Colorize the sliders themselves. Make the red_sb slider red and, simila
// make green_sb green and blue_sb blue. Make the alpha_sb slider white.
red_sb.setStyleProperty ("face", 0xFF0000);
green_sb.setStyleProperty("face", 0x00FF00);
blue_sb.setStyleProperty ("face", 0x0000FF);
alpha_sb.setStyleProperty("face", 0xFFFFFF);

// Set the initial position for the color sliders. alpha_sb remains at 100
red_sb.setScrollPosition (127);
green_sb.setScrollPosition(127);
blue_sb.setScrollPosition (127);
}

function initColor ( ) {
// Store a new Color object in a property of circle_mc.
my_color = new Color(circle_mc);
circle_mc.col = my_color;

// Store references to the four scrollbars as properties of circle_mc.


circle_mc.red = red_sb;
circle_mc.green = green_sb;
circle_mc.blue = blue_sb;
circle_mc.alpha = alpha_sb;
}

[Link] 2004-02-06
Strona 12 z 12

// Initialize the sliders and the Color object.


initSliders( );
initColor( );

// Update the color of the circle_mc movie clip based on the slider position
circle_mc.onEnterFrame = function ( ) {
// Retrieve the current position of the color and alpha sliders.
var r = 255 - [Link]( );
var g = 255 - [Link]( );
var b = 255 - [Link]( );
var a = 100 - [Link]( );

// Set up the transformation object properties to set circle_mc's color.


transformObj = new Object( );
[Link] = 0;
[Link] = r;
[Link] = 0;
[Link] = g;
[Link] = 0;
[Link] = b;
[Link] = a;
[Link] = 0;
[Link](transformObj);
}
[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 26

[ Team LiB ]

Chapter 4. Drawing and Masking

Introduction

Recipe 4.1. Drawing a Line

Recipe 4.2. Drawing a Curve

Recipe 4.3. Drawing a Rectangle

Recipe 4.4. Drawing a Rounded Rectangle

Recipe 4.5. Drawing a Circle

Recipe 4.6. Drawing an Ellipse

Recipe 4.7. Drawing a Triangle

Recipe 4.8. Drawing Regular Polygons

Recipe 4.9. Filling a Shape with a Solid or Translucent Color

Recipe 4.10. Filling a Shape with a Gradient

Recipe 4.11. Filling a Shape with a Complex Gradient

Recipe 4.12. Scripting Masks


[ Team LiB ]
[ Team LiB ]

Introduction
The ActionScript Drawing API, introduced in Flash MX, allows you to draw inside movie clips at
runtime. The handful of primitive Drawing API methods let you draw almost any imaginable
shape, as demonstrated in this chapter's recipes. The Drawing API uses a pen metaphor, akin to
a CAD line plotter, in which an imaginary pen is moved around the drawing canvas. The pen's
stroke attributes can be customized, and the pen can draw lines or curves. Shapes can be empty
or filled. Note that the coordinates are relative to the movie clip's registration point. Use the
MovieClip._x and MovieClip._y properties to reposition the entire clip on the Stage.

The Drawing API is a subset of the methods of the MovieClip class, and therefore most of the
recipes in this chapter are implemented as custom methods of the MovieClip class. You should
add these custom methods to an ActionScript file named [Link] (or download the
final version from the URL cited in the Preface) and include it in your own projects.

All paths and shapes drawn with the Drawing API are drawn inside the movie clip from which the
methods are invoked. For this reason the createEmptyMovieClip( ) method is often used in
conjunction with any runtime drawing. The createEmptyMovieClip( ) method does just what its
name implies—it creates a new, empty movie clip nested within the movie clip on which it is
invoked:

[Link] 2004-02-06
Strona 2 z 26

// Create a new movie clip named myMovieClip_mc with a depth of 1 inside of _root
_root.createEmptyMovieClip("myMovieClip_mc", 1);

You can specify the pen style with the MovieClip class's lineStyle( ) method and draw lines and
curves with the lineTo( ) and curveTo( ) methods. The pen can be repositioned without drawing
anything by calling the moveTo( ) method.

Many of these recipes use the custom [Link]( ) method from Recipe 5.12 to convert
angles from degrees to radians. Your [Link] file should include [Link] from
Chapter 5 by using this statement on the first line:

#include "[Link]"

Also see Recipe 5.13 and Recipe 5.14 for an explanation of how to calculate X and Y coordinates
using trigonometry, which is called for in some of this chapter's recipes.
[ Team LiB ]
[ Team LiB ]

Recipe 4.1 Drawing a Line


4.1.1 Problem
You want to draw a line using ActionScript.

4.1.2 Solution
Use the lineStyle( ) method to specify the thickness, color, and transparency of the line. Then
use the lineTo( ) method to draw a line from the current pen position to a destination point.

4.1.3 Discussion
Before you can draw anything using ActionScript, you need to tell Flash what kind of line style
to use. The line style consists of the line's thickness, color, and alpha value (opacity), and you
can set these values using the lineStyle( ) method on the movie clip in which you wish to draw.
The line's thickness should be specified in pixels, but any value less than 1 draws lines with
hairline thickness. Color values should be specified as numbers, as explained in the Introduction
to Chapter 3. The alpha value should be anywhere from 0 (completely transparent) to 100
(completely opaque).

// Set a 1-pixel, blue, completely opaque line style.


myMovieClip_mc.lineStyle(1, 0x0000FF, 100);

The most basic type of drawing that you can do with ActionScript is drawing a straight line.
Flash uses the current pen position as the starting point, so you need to provide only the
coordinates of the destination point. Use the [Link]( ) method to create a line from
the current pen position to the specified destination point:

// Draws a line from the current pen position to (100,100) within the coordinate
// system of myMovieClip_mc.
myMovieClip_mc.lineTo(100, 100);

When you use ActionScript methods to draw, all the lines and fills are drawn within the movie
clip from which the methods are invoked. For example, in the preceding code, the line is drawn
within myMovieClip_mc. Additionally, the line style must be defined for each movie clip. If you
define a line style for myMovieClip_mc, but then you try to use the lineTo( ) method with
another movie clip instance, Flash does not draw anything.

[Link] 2004-02-06
Strona 3 z 26

As mentioned previously, when you use the Drawing API methods such as lineTo( ), Flash
draws the line beginning at the current pen position. If you have not otherwise moved the pen
(by calling a lineTo( ), curveTo( ), or moveTo( ) method), the pen is positioned at the origin of
the movie clip's coordinate system, point (0,0). The origin is the clip's registration point, which
resides at the clip's center by default. You can move the pen without drawing a line by using
the moveTo( ) method. The moveTo( ) method simply relocates the pen to the coordinate you
specify.

// Move the pen in myMovieClip_mc to (300,30).


myMovieClip_mc.moveTo(300, 30);

The moveTo( ) method is important in situations in which you want to begin drawing from a
point other than the movie clip's center or draw lines or shapes without necessarily connecting
all the lines:

// Create an empty movie clip to act as the drawing canvas.


_root.createEmptyMovieClip("myMovieClip_mc", 1);

// Set a 1-pixel, black, completely opaque line style.


myMovieClip_mc.lineStyle(1, 0x000000, 100);

// Draw a dashed line using a series of lines


and spaces.
myMovieClip_mc.moveTo( 0, 0);
myMovieClip_mc.lineTo(10, 0);
myMovieClip_mc.moveTo(15, 0);
myMovieClip_mc.lineTo(25, 0);
myMovieClip_mc.moveTo(30, 0);
myMovieClip_mc.lineTo(40, 0);
myMovieClip_mc.moveTo(45, 0);
myMovieClip_mc.lineTo(55, 0);

4.1.4 See Also


Recipe 7.17

[ Team LiB ]
[ Team LiB ]

Recipe 4.2 Drawing a Curve


4.2.1 Problem
You want to draw a curve using ActionScript.

4.2.2 Solution
Set a line style with lineStyle( ) and then use the curveTo( ) method.

4.2.3 Discussion
When drawing lines or curves with ActionScript, the first thing you must do is define the line
style, as shown in Recipe 4.1.

Once you have set a line style, you can draw a curve using the curveTo( ) method. The curveTo
( ) method draws an approximation of a Bézier curve (though optimized for performance),
which requires three points: a starting point, a control point, and a destination point. The

[Link] 2004-02-06
Strona 4 z 26

starting point is always determined by the current pen position. The destination point is simply
the point on the canvas to which you wish to draw. The control point is the point that
determines the shape of the curve; it is calculated by determining where the tangents to the
curve at the starting and destination points intersect. The control point is not actually on the
curve, as shown in Figure 4-1.

Figure 4-1. The control point of a curve

This simple example draws a curve:

// Draw a curved line in myMovieClip_mc with a control point at (0,100) and a


// destination point of (100,100).
[Link]("myMovieClip_mc", 1);
myMovieClip_mc.lineStyle(1, 0x000000, 100);
myMovieClip_mc.curveTo(0, 100, 100, 100);

[ Team LiB ]
[ Team LiB ]

Recipe 4.3 Drawing a Rectangle


4.3.1 Problem
You want to draw a rectangle at runtime.

4.3.2 Solution
Create a custom [Link]( ) method using the Drawing API and invoke it
on a movie clip.

4.3.3 Discussion
To draw a simple rectangle, specify the stroke's attributes using the lineStyle( ) method and
then draw four lines using the lineTo( ) method:

// Create rectangle_mc with a depth of 1 on the main timeline.


_root.createEmptyMovieClip("rectangle_mc", 1);

// Specify a one-pixel, solid, black line.


rectangle_mc.lineStyle(1, 0x000000, 100);

// Draw four lines to form the perimeter of the rectangle.


rectangle_mc.lineTo(100, 0);
rectangle_mc.lineTo(100, 50);
rectangle_mc.lineTo( 0, 50);
rectangle_mc.lineTo( 0, 0);

[Link] 2004-02-06
Strona 5 z 26

Thus, drawing a simple rectangle is no huge feat. To draw multiple rectangles with various
dimensions, you should create a custom drawSimpleRectangle( ) method for the MovieClip
class, as follows:

// Define the custom method on [Link] so that it's available to all


// movie clip instances.
[Link] = function (width, height) {
[Link](width, 0);
[Link](width, height);
[Link](0, height);
[Link](0, 0);
}

// Invoke the custom method like this.


_root.createEmptyMovieClip("rectangle_mc", 1);
rectangle_mc.lineStyle(1, 0x000000, 100);
rectangle_mc.drawSimpleRectangle(100, 50);

The dimensions of the rectangle are 102 x 52 pixels due to the line thickness. Reduce the
dimensions by two pixels in each direction to create a rectangle whose outside dimensions
match the intended size.

4.3.4 See Also


See Recipe 4.4 for an enhanced rectangle-drawing routine that adds several features, such as
optional rounded corners, offset, and rotation.

[ Team LiB ]
[ Team LiB ]

Recipe 4.4 Drawing a Rounded Rectangle


4.4.1 Problem
You want to draw a rectangle with rounded corners, an offset, or rotation.

4.4.2 Solution
Create a custom [Link]( ) method using

the Drawing API and invoke it on a movie clip.

4.4.3 Discussion
The drawSimpleRectangle( ) method from Recipe 4.3 is, as the name suggests, quite simple.

Let's create a more complex version that also:

z Draws a rectangle with a specified angle of rotation

z Let's you specify the rectangle center's coordinates

z Can draw a rectangle with rounded corners

[Link] 2004-02-06
Strona 6 z 26

The drawRectangle( ) method accepts six parameters:

width

The width of the rectangle in pixels

height

The height of the rectangle in pixels

round

The radius (in pixels) of the arc that is used to round the corners. If the value is undefined
the corners remain square.

rotation

The clockwise rotation to apply to the rectangle in degrees. If undefined, the rectangle is
rotated.

The x coordinate of the center point for the rectangle. If undefined, the rectangle is cente
x = 0.

The y coordinate of the center point for the rectangle. If undefined, the rectangle is cente
y = 0.

Here is our enhanced drawRectangle( ) method, defined on [Link], so it's availa


all movie clip instances:

// Include the custom Math library from Chapter 5 to access [Link]( ).


#include "[Link]"

[Link] = function (width, height, round, rotation, x,


// Make sure the rectangle is at least as wide and tall as the rounded corners.
if (width < (round * 2)) {
width = round * 2;
}
if (height < (round * 2)) {
height = round * 2;
}

// Convert the rotation from degrees to radians.


rotation = [Link](rotation);

// Calculate the distance from the rectangle's center to one of the corners (or
// where the corner would be in rounded-cornered rectangles). See the line labe
// r in Figure 4-2.
var r = [Link]([Link](width/2, 2) + [Link](height/2, 2));

// Calculate the distance from the rectangle's center to the upper edge of the
// bottom-right rounded corner. See the line labeled rx in Figure 4-2. When rou
// is 0, rx is equal to r.
var rx = [Link]([Link](width/2, 2) + [Link]((height/2) - round, 2));

[Link] 2004-02-06
Strona 7 z 26

// Calculate the distance from the rectangle's center to the lower edge of the
// bottom-right rounded corner. See the line labeled ry in Figure 4-2. When rou
// is 0, ry is equal to r.
var ry = [Link]([Link]((width/2) - round, 2) + [Link](height/2, 2));

// Calculate angles. r1Angle is the angle between the X axis that runs through
// center of the rectangle and the line rx. r2Angle is the angle between rx and
// r3Angle is the angle between r and ry. And r4Angle is the angle between ry an
// the Y axis that runs through the center of the rectangle.
var r1Angle = [Link]( ((height/2) - round) /( width/2) );
var r2Angle = [Link]( (height/2) / (width/2) ) - r1Angle;
var r4Angle = [Link]( ((width/2) - round) / (height/2) );
var r3Angle = ([Link]/2) - r1Angle - r2Angle - r4Angle;

// Calculate the distance of the control point from the


// arc center for the rounded corners.
var ctrlDist = [Link](2 * [Link](round, 2));

// Declare the local variables used to calculate the control point.


var ctrlX, ctrlY;

// Calculate where to begin drawing the first side segment and then draw it.
rotation += r1Angle + r2Angle + r3Angle;
var x1 = x + ry * [Link](rotation);
var y1 = y + ry * [Link](rotation);
[Link](x1, y1);
rotation += 2 * r4Angle;
x1 = x + ry * [Link](rotation);
y1 = y + ry * [Link](rotation);
[Link](x1, y1);

// Set rotation to the starting point for the next side segment and calculate th
// and y coordinates.
rotation += r3Angle + r2Angle;
x1 = x + rx * [Link](rotation);
y1 = y + rx * [Link](rotation);

// If the corners are rounded, calculate the control point for the corner's curv
// and draw it.
if (round > 0) {
ctrlX = x + r * [Link](rotation - r2Angle);
ctrlY = y + r * [Link](rotation - r2Angle);
[Link](ctrlX, ctrlY, x1, y1);
}

// Calculate the end point of the second side segment and draw the line.
rotation += 2 * r1Angle;
x1 = x + rx * [Link](rotation);
y1 = y + rx * [Link](rotation);
[Link](x1, y1);

// Calculate the next line segment's starting point.


rotation += r2Angle + r3Angle;
x1 = x + ry * [Link](rotation);
y1 = y + ry * [Link](rotation);

// Draw the rounded corner, if applicable.


if (round > 0) {
ctrlX = x + r * [Link](rotation - r3Angle);
ctrlY = y + r * [Link](rotation - r3Angle);
[Link](ctrlX, ctrlY, x1, y1);
}

// Calculate the end point of the third segment and draw the line.

[Link] 2004-02-06
Strona 8 z 26

rotation += 2 * r4Angle;
x1 = x + ry * [Link](rotation);
y1 = y + ry * [Link](rotation);
[Link](x1, y1);

// Calculate the starting point of the next segment.


rotation += r3Angle + r2Angle;
x1 = x + rx * [Link](rotation);
y1 = y + rx * [Link](rotation);

// If applicable, draw the rounded corner.


if (round > 0) {
ctrlX = x + r * [Link](rotation - r2Angle);
ctrlY = y + r * [Link](rotation - r2Angle);
[Link](ctrlX, ctrlY, x1, y1);
}

// Calculate the end point for the fourth segment and draw it.
rotation += 2 * r1Angle;
x1 = x + rx * [Link](rotation);
y1 = y + rx * [Link](rotation);
[Link](x1, y1);

// Calculate the end point for the next corner arc and, if applicable, draw it.
rotation += r3Angle + r2Angle;
x1 = x + ry * [Link](rotation);
y1 = y + ry * [Link](rotation);
if (round > 0) {
ctrlX = x + r * [Link](rotation - r3Angle);
ctrlY = y + r * [Link](rotation - r3Angle);
[Link](ctrlX, ctrlY, x1, y1);
}
}

Figure 4-2 shows the geometry when drawing the rounded corners for the rectangle.

Figure 4-2. A conceptual illustration of drawing a rounded rectangle

The preceding example will be clearer with a closer examination.

The ActionScript trigonometric methods require angles measured in radians. Therefore, wheneve
specify an angle in degrees (which is generally easier for humans), you must convert the units to
radians before passing them to ActionScript's trigonometric methods. In this case, we convert th
rotation parameter from degrees to radians using the [Link]( ) method from Recipe 5

rotation = [Link](rotation);

The length of the three imaginary lines used for drawing the rounded corners, as shown in Figure
are calculated using the Pythagorean theorem, as discussed in Recipe 5.13. In our example, thes
distances are:

var r = [Link]([Link](width/2, 2) + [Link](height/2, 2));

[Link] 2004-02-06
Strona 9 z 26

var rx = [Link]([Link](width/2, 2) + [Link]((height/2) - round, 2));


var ry = [Link]([Link]((width/2) - round, 2) + [Link](height/2, 2));

Next, we must calculate the angles formed between the axes and the lines r, rx, and ry. These
are used to determine the x and y coordinates of the starting and ending side segments. If you k
the lengths of the sides of a right triangle, you can determine the angles that they form. Because
axes and the lines r, rx, and ry can be formed into right triangles you can determine the angles
lines form using the tangent and arctangent. The tangent in a right triangle is defined as the rati
the side opposite the angle to the side adjacent to the angle. The arctangent is the inverse of the
tangent function, so we use the following to determine the angles:

var r1Angle = [Link](((height/2) - round)/(width/2));


var r2Angle = [Link]((height/2)/(width/2)) - r1Angle;
var r4Angle = [Link](((width/2) - round)/(height/2));
var r3Angle = ([Link]/2) - r1Angle - r2Angle - r4Angle;

The corners are each composed of a single curve that is a semicircle. To determine the distance
between the semicircle's center point and the control point used to draw that curve, again use th
Pythagorean theorem:

var ctrlDist = [Link](2 * [Link](round, 2));

The first thing you want to do when you draw the rectangle is to move the imaginary pen to a st
point on the rectangle without actually drawing a line. In this example, the calculated starting po
at the right end of the bottom segment (of an unrotated rectangle). If you know the distance bet
two points and the angle (the opposite angle formed by an imaginary right triangle with the know
being the hypotenuse), you can calculate the x and y coordinates of the destination point using
trigonometric functions. The x coordinate is determined by the distance times the cosine of the a
The y coordinate is determined by the distance times the sine of the angle. In this example the x
coordinates (x1 and y1) are also offset by the x and y parameters to draw a rectangle whose cen
not at (0, 0):

rotation += r1Angle + r2Angle + r3Angle;


var x1 = x + ry * [Link](rotation);
var y1 = y + ry * [Link](rotation);
[Link](x1, y1);

The remainder of the example follows the same pattern: draw a line, draw a rounded corner (if
applicable), and then move to the next side segment. The new coordinates for each segment are
calculated using the same process as described previously. Once you have defined and included
drawRectangle( ) method in your Flash document, you can quickly draw a rectangle within any m
clip instance. Don't forget that you still need to define the line style (see Recipe 4.1) before Flash
actually draw anything.

// Create a new movie clip into which to draw the rectangle.


[Link]("rectangle_mc", 1);

// Define a 1-pixel, black, solid line style.


rectangle_mc.lineStyle(1, 0x000000, 100);

// Draw a rectangle with dimensions of 100 x 200. The rectangle has rounded c
// with radii of 10, and it is rotated 45 degrees clockwise.
rectangle_mc.drawRectangle(100, 200, 10, 45);

You can draw a square by using the drawRectangle( ) method with equal height and width value

[Link]("square_mc", 1);
square_mc.lineStyle(1, 0x000000, 100);
square_mc.drawRectangle(100, 100);

[Link] 2004-02-06
Strona 10 z 26

You can draw filled rectangles by invoking beginFill( ) or beginGradientFill( ) before drawRectang
and invoking endFill( ) after drawRectangle( ):

[Link]("filledRectangle_mc", 1);
filledRectangle_mc.lineStyle(1, 0x000000, 100); // Define a black, 1-pixel bord
filledRectangle_mc.beginFill(0x0000FF); // Define a solid blue fill.
filledRectangle_mc.drawRectangle(100, 200);
filledRectangle_mc.endFill( );

4.4.4 See Also


Recipe 4.3, Recipe 4.8, Recipe 4.9, Recipe 5.12, Recipe 5.13, and Recipe 5.14
[ Team LiB ]
[ Team LiB ]

Recipe 4.5 Drawing a Circle


4.5.1 Problem
You want to draw a circle at runtime.

4.5.2 Solution
Create a custom [Link]( ) method using the Drawing API and invoke it on a movie
clip.

4.5.3 Discussion
You can create a circle in ActionScript with eight curves. Fewer curves results in a distorted circle
and too many curves hinders performance. Let's create a custom method of the MovieClip class f
drawing circles. This method, drawCircle( ), allows for three parameters:

radius

The radius of the circle

The x coordinate of the circle's center point. If undefined, the circle is centered at x = 0.

The y coordinate of the circle's center point. If undefined, the circle is centered at y = 0.

Define the custom drawCircle( ) method on [Link] to make it available to all mo


clip instances:

[Link] = function (radius, x, y) {


// The angle of each of the eight segments is 45 degrees (360 divided by 8), wh
// equals π/4 radians.
var angleDelta = [Link] / 4;

// Find the distance from the circle's center to the control points for the curv
var ctrlDist = radius/[Link](angleDelta/2);

[Link] 2004-02-06
Strona 11 z 26

// Initialize the angle to 0 and define local variables that are used for the
// control and ending points.
var angle = 0;
var rx, ry, ax, ay;

// Move to the starting point, one radius to the right of the circle's center.
[Link](x + radius, y);

// Repeat eight times to create eight segments.


for (var i = 0; i < 8; i++) {

// Increment the angle by angleDelta (π/4) to create the whole circle (2π).
angle += angleDelta;

// The control points are derived using sine and cosine.


rx = x + [Link](angle-(angleDelta/2))*(ctrlDist);
ry = y + [Link](angle-(angleDelta/2))*(ctrlDist);

// The anchor points (end points of the curve) can be found similarly to the
// control points.
ax = x + [Link](angle)*radius;
ay = y + [Link](angle)*radius;

// Draw the segment.


[Link](rx, ry, ax, ay);
}
}

How the drawCircle( ) method functions is better understood with a little explanation.

The distance of the control point for each segment from the circle's center is found using a
trigonometric formula that states that the cosine of an angle is equal to the adjacent side over th
hypotenuse. In the case of the circle, the angle that bisects a segment (thus also intersecting its
control point) is π/8 (angleDelta/2). The distance to the control point from the center of the circ
forms the hypotenuse of the right triangle, as you can see in Figure 4-3.

var ctrlDist = radius/[Link](angleDelta/2);

Figure 4-3. Calculating a point approximating a circular path

Basic trigonometric formulas can be used to find the x and y coordinates along the circle's
circumference given the angle and the hypotenuse. For the control point, the hypotenuse value i
ctrlDist, and the angle is angle - angleDelta/2, since this angle bisects the segment. The anc
point is found using the value of angle, which is calculated to be the angle that intersects the
anchor point, and the circle's radius (since the anchor point should always be on the circle's
circumference). Thus, it follows:

rx = x + [Link](angle-(angleDelta/2))*(ctrlDist);
ry = y + [Link](angle-(angleDelta/2))*(ctrlDist);
ax = x + [Link](angle)*radius;
ay = y + [Link](angle)*radius;

[Link] 2004-02-06
Strona 12 z 26

Once you have defined the drawCircle( ) method and included it in your Flash document, you can
quickly draw a circle with just a few lines of code. Remember that you still need to define a line s
before Flash will draw anything.

// Create a movie clip instance in which you will draw the circle.
[Link]("circle_mc", 1);

// Define a 1-pixel, black, solid line style.


circle_mc.lineStyle(1, 0x000000, 100);

// Draw a circle of radius 100, centered at (50,75).


circle_mc.drawCircle(100, 50, 75);
// Draw a circle of radius 65, centered at (0,0).
circle_mc.drawCircle(65);

You can fill a circle by invoking beginFill( ) or beginGradientFill( ) before drawCircle( ) and invoki
endFill( ) after drawCircle( ):

[Link]("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100); // Use a 1-pixel, black, solid border.
circle_mc.beginFill(0x0000FF); // Use a solid blue fill.
circle_mc.drawCircle(100);
circle_mc.endFill( );

[ Team LiB ]
[ Team LiB ]

Recipe 4.6 Drawing an Ellipse


4.6.1 Problem
You want to draw an ellipse (oval) at runtime.

4.6.2 Solution
Create a custom [Link]( ) method using the Drawing API and invoke it on a movi
clip.

4.6.3 Discussion
You can create a method of the MovieClip class to draw an ellipse that is very similar to the
drawCircle( ) method in Recipe 4.5. In fact, the drawCircle( ) method is merely a degenerate
version of drawEllipse( ), in which the radii in the x and y directions are the same.

The custom drawEllipse( ) method accepts four parameters:

xRadius

The radius of the ellipse in the x direction (major axis).

yRadius

The radius of the ellipse in the y direction (minor axis).

[Link] 2004-02-06
Strona 13 z 26

The x coordinate of the center of the ellipse.

The y coordinate of the center of the ellipse.

Refer to Recipe 4.5 for those aspects of the drawEllipse( ) method that are not commented on
here.

[Link] = function (xRadius, yRadius, x, y) {


var angleDelta = [Link] / 4;

// While the circle has only one distance to the control point for each segment
// the ellipse has two distances: one that corresponds to xRadius and another th
// corresponds to yRadius.
var xCtrlDist = xRadius/[Link](angleDelta/2);
var yCtrlDist = yRadius/[Link](angleDelta/2);
var rx, ry, ax, ay;
[Link](x + xRadius, y);
for (var i = 0; i < 8; i++) {
angle += angleDelta;
rx = x + [Link](angle-(angleDelta/2))*(xCtrlDist);
ry = y + [Link](angle-(angleDelta/2))*(yCtrlDist);
ax = x + [Link](angle)*xRadius;
ay = y + [Link](angle)*yRadius;
[Link](rx, ry, ax, ay);
}
}

Once you have defined and included the drawEllipse( ) method in your Flash document, you can
draw an ellipse rather easily. Use the drawEllipse( ) method the same way you used the drawCir
( ) method in Recipe 4.5 but provide both x and y radii instead of just a single radius. Remembe
that you still need to define the line style before you call the drawEllipse( ) method.

// Create an ellipse with minor and major axes of 100 and 200, respectively.
[Link]("ellipse", 1);
[Link](1, 0x000000, 100); // Use a one-pixel, black, solid border
[Link](100, 200);

Having defined drawEllipse( ), we can rewrite the drawCircle( ) method, as follows:

[Link] = function (radius, x, y) {


// Call drawEllipse( ) with the same radius for both x and y.
[Link] (radius, radius, x, y);
}

4.6.4 See Also


Recipe 4.5
[ Team LiB ]
[ Team LiB ]

Recipe 4.7 Drawing a Triangle


4.7.1 Problem
You want to draw a triangle at runtime.

[Link] 2004-02-06
Strona 14 z 26

4.7.2 Solution
Create a custom [Link]( ) method using the Drawing API and invoke it on a mov
clip.

4.7.3 Discussion
You can determine and plot the vertices of a triangle given the lengths of two sides and the angl
between them. This is a better approach than specifying the lengths of the three sides because
knowing the lengths of two sides and the angle between them always determines a triangle,
whereas three arbitrary sides may not fit together to make a triangle.

The custom drawTriangle( ) method accepts six parameters:

ab

The length of the side formed between points a and b, as shown in Figure 4-4.

ac

The length of the side formed between points a and c, as shown in Figure 4-4.

angle

The angle (in degrees) between sides ab and ac.

rotation

The rotation of the triangle in degrees. If 0 or undefined, side ac parallels the x axis.

The x coordinate of the centroid (the center point) of the triangle.

The y coordinate of the centroid of the triangle.

Figure 4-4. The parameters used in the drawTriangle( ) method

Define the custom drawTriangle( ) method on [Link] to make it available to all


movie clip instances:

// Include the custom Math library from Chapter 5 to access [Link]( ).


#include "[Link]"

[Link] = function (ab, ac, angle, rotation, x, y) {

// Convert the angle between the sides from degrees to radians.


angle = [Link](angle);

[Link] 2004-02-06
Strona 15 z 26

// Convert the rotation of the triangle from degrees to radians.


rotation = [Link](rotation);

// Calculate the coordinates of points b and c.


var bx = [Link](angle - rotation) * ab;
var by = [Link](angle - rotation) * ab;
var cx = [Link](-rotation) * ac;
var cy = [Link](-rotation) * ac;

// Calculate the centroid's coordinates.


var centroidX = (cx + bx)/3 - x;
var centroidY = (cy + by)/3 - y;

// Move to point a, then draw line ac, then line cb, and finally ba (ab).
[Link](-centroidX, -centroidY);
[Link](cx - centroidX, cy - centroidY);
[Link](bx - centroidX, by - centroidY);
[Link](-centroidX, -centroidY);
}

There are a few points about this method that bear further discussion.

Point a will always be the point of rotation, so you don't need to calculate it's coordinates. Howev
points b and c need to be calculated (using basic trigonometric ratios). We define point b at the e
of line ab at an angle of angle in an unrotated triangle. To factor in rotation we subtract rotatio
from angle. We define point c to be at the end of line ac. In an unrotated triangle, point c is on
same x axis as point a, but to factor in rotation you should subtract rotation from the angle
between line ac (and itself, which is, of course, 0). This leads us to:

var bx = [Link](angle - rotation) * ab;


var by = [Link](angle - rotation) * ab;
var cx = [Link](-rotation) * ac;
var cy = [Link](-rotation) * ac;

The x coordinate of the centroid of a triangle is calculated by adding together the x coordinates o
the vertices and dividing by three. The y coordinate is found in an analogous manner. In our
drawTriangle( ) method, the coordinates of point a are always (0, 0), so it doesn't factor into the
equation. We subtract the x and y inputs from the centroid coordinates to account for any user-
defined offset:

var centroidX = (cx + bx)/3 - x;


var centroidY = (cy + by)/3 - y;

Here is an example of how to use the drawTriangle( ) method. Notice that you still have to define
the line style before invoking the drawTriangle( ) method.

// Draw a triangle with sides of 100 and 200 pixels and an angle of 30 degrees.
[Link]("triangle_mc", 1);
triangle_mc.lineStyle(1, 0x000000, 100); // Use a one-pixel, black, solid bo
triangle_mc.drawTriangle(100, 200, 30);

4.7.4 See Also


You can draw an isosceles triangle using the drawRegularPolygon( ) method of Recipe 4.8 (and
specifying a shape with three sides).

[ Team LiB ]

[Link] 2004-02-06
Strona 16 z 26

[ Team LiB ]

Recipe 4.8 Drawing Regular Polygons


4.8.1 Problem
You want to draw a regular polygon (a polygon in which all sides are equal length) at runtime.

4.8.2 Solution
Create a custom [Link]( ) method using the Drawing API and invoke it o
movie clip.

4.8.3 Discussion
You can create a method to draw a regular polygon using basic trigonometric ratios to determine
necessary angles and coordinates of the segments.

The drawRegularPolygon( ) accepts five parameters:

sides

The number of sides in the polygon

length

The length of each side in pixels

rotation

The number of degrees by which the polygon should be rotated

The x coordinate of the center of the polygon

The y coordinate of the center of the polygon

Define the custom drawRegularPolygon( ) method on [Link] to make it available


all movie clip instances:

// Include the custom Math library from Chapter 5 to access [Link]( ).


#include "[Link]"

[Link] = function (sides, length, rotation, x, y)

// Convert rotation from degrees to radians.


rotation = [Link](rotation);

// The angle formed between the segments from the polygon's center as shown in
// Figure 4-5. Since the total angle in the center is 360 degrees (2π radians),
// each segment's angle is 2π divided by the number of sides.
var angle = (2 * [Link]) / sides;

[Link] 2004-02-06
Strona 17 z 26

// Calculate the length of the radius that circumscribes the polygon (which is
// the distance from the center to any of the vertices).
var radius = (length/2)/[Link](angle/2);

// The starting point of the polygon is calculated using trigonometry, where ra


// is the hypotenuse and rotation is the angle.
var px = ([Link](rotation) * radius) + x;
var py = ([Link](rotation) * radius) + y;

// Move to the starting point without yet drawing a line.


[Link](px, py);

// Draw each side. Calculate the vertex coordinates using the same trigonometri
// ratios used to calculate px and py earlier.
for (var i = 1; i <= sides; i++) {
px = ([Link]((angle * i) + rotation) * radius) + x;
py = ([Link]((angle * i) + rotation) * radius) + y;
[Link](px, py);
}
}

Figure 4-5 shows the angle used when calculating how to draw a regular polygon.

Figure 4-5. Drawing a regular polygon

Once you have defined the drawRegularPolygon( ) method and included it in your Flash documen
you can quickly draw regular polygons with any number of sides (with a minimum of three sides
course, for it to be a valid polygon.) Remember, as with all the other drawing methods in this
chapter, you must define a line style prior to invoking the drawRegularPolygon( ) method.

// Create the movie clip into which to draw the polygon.


[Link]("polygon_mc", 1);

// Use a 1-pixel, black, solid line style for the border.


polygon_mc.lineStyle(1, 0x000000, 100);

// Draw a regular nonagon (a nine-sided polygon) with sides of length 30.


polygon_mc.drawRegularPolygon(9, 30);

As with all the custom methods in this chapter that define shapes, you can create a filled polygon
invoking beginFill( ) or beginGradientFill( ) before drawRegularPolygon( ) and invoking endFill( )
after drawRegularPolygon( ).

[Link]("polygon_mc", 1);
polygon_mc.lineStyle(1, 0x000000, 100); // Define a black, 1-pixel border.
polygon_mc.beginFill(0x00FF00); // Define a solid green fill.
polygon_mc.drawRegularPolygon(9, 30)
polygon_mc.endFill( );

[ Team LiB ]

[Link] 2004-02-06
Strona 18 z 26

[ Team LiB ]

Recipe 4.9 Filling a Shape with a Solid or Translucent


Color
4.9.1 Problem
You want to draw a shape and fill it with a solid or translucent color at runtime.

4.9.2 Solution
Use the beginFill( ) and endFill( ) methods to initiate and close a shape drawn at runtime.

4.9.3 Discussion
To draw a filled shape, call beginFill( ) prior to any other drawing methods, including the custom
methods you have defined such as drawCircle( ) and drawPolygon( ). Invoke endFill( ) after calli
other drawing methods to create the shape.

You cannot apply a fill to an existing shape drawn at authoring time or


runtime. You must invoke beginFill( ) before drawing the shape to be filled.

This example creates a solid blue circle with a radius of 100 pixels:

_root.createEmptyMovieClip("shape_mc", 1);

// Tell ActionScript to begin a solid, blue fill.


shape_mc.beginFill(0x0000FF, 100);

// Invoke a custom drawing method, such as drawCircle( ), or invoke lineTo( ) o


// curveTo( ) multiple times to create a closed shape.
shape_mc.drawCircle(100);

// Call endFill( ) to close the shape after other drawing methods have been call
shape_mc.endFill( );

The beginFill( ) method requires two parameters:

fillColor

The RGB value to use for the fill

alpha

The value between 0 (transparent) and 100 (opaque) that controls the opacity

To create a translucent, filled shape, specify an alpha less than 100. If alpha is 0, the shape wil
appear unfilled. Don't forget to define a line style if you want the outline to be visible.

The endFill( ) method does not require any parameters. It simply ends the fill initiated with
beginFill( ) or beginGradientFill( ). To avoid unexpected results, ensure that the pen returns to th
starting point to complete the shape before invoking endFill( ).

[Link] 2004-02-06
Strona 19 z 26

4.9.4 See Also


Recipe 3.2 and Recipe 4.10

[ Team LiB ]
[ Team LiB ]

Recipe 4.10 Filling a Shape with a Gradient


4.10.1 Problem
You want to draw a shape and fill it with a gradient at runtime.

4.10.2 Solution
Use the beginGradientFill( ) and endFill( ) methods to initiate and close a shape drawn at runtim

4.10.3 Discussion
In a gradient fill, there is a graded change in colors. Flash supports linear gradients, in which one
color fades into the next from left to right. Flash also supports radial gradients, in which the colo
radiate out from a center point. You can initiate a gradient-filled shape using beginGradientFill( )
the same way you initiate a solid-filled shape with beginFill( ). The difference is that the call to
beginGradientFill( ) requires a more complex set of parameters:

gradientType

Either "linear" for a linear gradient, or "radial" for a radial gradient.

colors

An array of RGB values for the colors to use in the gradient. They are displayed in the
gradient from left to right in a linear gradient, or from the center outward in a radial
gradient.

alphas

An array of alpha values that correspond to the colors in the colors parameter array.

ratios

An array whose elements are numbers corresponding to the colors and alphas elements.
The values in the ratios array indicate the point within the gradient at which each color is
pure. The range of values for the ratios should be from 0 (leftmost point in a linear fill, or
innermost point in a radial fill) to 255 (rightmost or outermost).

matrix

An object with the following properties:

matrixType

This value should always be "box".

[Link] 2004-02-06
Strona 20 z 26

The x coordinate of the bottom-left corner of the gradient.

The y coordinate of the bottom-left corner of the gradient.

width

The width of the gradient in pixels.

height

The height of the gradient in pixels.

The rotation of the gradient in radians (not degrees).

Here is an example that uses a linear gradient to fill a rectangle:

// Include the drawing methods, which are needed for the drawRectangle( ) method
#include "[Link]"

// Define the width and height of the rectangle to be drawn and filled.
rectWidth = 100;
rectHeight = 200;

// Create an empty clip into which we will draw the shape.


_root.createEmptyMovieClip("shape_mc", 1);
shape_mc.lineStyle(3, 0, 100);

// Create a colors array with RGB values for blue, green, and red.
colors = [0x0000FF, 0x00FF00, 0xFF0000];

// Create an alphas array in which the colors are 100% opaque.


alphas = [100, 100, 100];

// Create a ratios array where pure blue is at the left edge of the gradient, pur
// green is in the center, and pure red at the right edge.
ratios = [0, 127.5, 255];

// Create the matrix object. Set the x and y coordinates so that the bottom-left
// corner of the gradient lines up with the bottom-left corner of the rectangle.
// the width and height of the gradient to match the rectangle.
matrix = {matrixType: "box", x: -rectWidth/2, y: -rectHeight/2, w: rectWidth,
h: rectHeight, r:0};

// Call beginGradientFill( ) so that the rectangle will be


// filled with a linear gradient.
shape_mc.beginGradientFill("linear", colors, alphas, ratios, matrix);

// Draw the rectangle with rounded corners (requires [Link]).


shape_mc.drawRectangle(rectHeight, rectWidth, 10);

// End the fill.


shape_mc.endFill( );

Note that the endFill( ) method is used to end a drawing operation begun with either beginFill( )

[Link] 2004-02-06
Strona 21 z 26

beginGradientFill( ).

Here is an example of a radial, gradient fill used to fill an ellipse:

// Include the drawing methods, which are needed for the drawEllipse( ) method.
#include "[Link]"

// Define the width and height of the ellipse to be drawn and filled.
ellipseWidth = 100;
ellipseHeight = 200;

_root.createEmptyMovieClip("shape_mc", 1);
shape_mc.lineStyle(3, 0x000000, 100);

// Create colors, alphas, and ratios arrays for white and black, both 100% opaque
// Pure white starts in the center and grades into pure black at the outside edge
colors = [0xFFFFFF, 0x000000];
alphas = [100, 100];
ratios = [0, 255];

// Define the matrix object.


matrix = {matrixType: "box", x: -ellipseWidth/2, y: -ellipseHeight/2,
w: ellipseWidth, h: ellipseHeight, r:0};

// Begin the radial fill.


shape_mc.beginGradientFill("radial", colors, alphas, ratios, matrix);

// Draw the ellipse (requires [Link]).


shape_mc.drawEllipse(ellipseWidth/2, ellipseHeight/2);

// End the fill.


shape_mc.endFill( );

4.10.4 See Also


See Recipe 4.4 and Recipe 4.6 for implementations of the custom drawRectangle( ) and drawEllip
( ) methods. See Recipe 4.11 for details on creating a custom gradient fill.

[ Team LiB ]
[ Team LiB ]

Recipe 4.11 Filling a Shape with a Complex Gradient


4.11.1 Problem
You want to draw a gradient-filled shape at runtime, but the box-style gradient matrix doesn't of
sufficient control.

4.11.2 Solution
Use the beginGradientFill( ) and endFill( ) methods in conjunction with a TransformMatrix object

4.11.3 Discussion
In Recipe 4.10, we used a box-style gradient to apply a simple fill pattern. However, you can att
precise control over the gradient by passing a transformation matrix as the matrix parameter se
the beginGradientFill( ) method.

[Link] 2004-02-06
Strona 22 z 26

ActionScript uses a 3 x 3 transformation matrix to apply a custom gradient. Working directly with
transformation matrix can be a bit more complicated than it is worth, so you should take advant
the TransformMatrix class, which makes this process much easier. You need to include
[Link] in any Flash document in which you wish to work with a transform matrix:

#include "[Link]"

The [Link] file is not part of the standard Flash installation, but it is included on the
CD-ROM under Goodies\Macromedia\OtherSamples\[Link]. You should copy the file
the CD-ROM to Flash's Include directory (FlashIntallation\Configuration\Include).

First, create a TransformMatrix object using the standard constructor method:

matrix = new TransformMatrix( );

Thereafter, you can call any of the five available methods:

translate(x, y)

The translate( ) method offsets the bottom-left corner (for linear gradients) or the center (
radial gradients) of the gradient. By default, the gradient is positioned at (0,0). The two
parameters specify the number of pixels by which to translate the gradient in the x and y
directions.

scale(width, height)

The scale( ) method scales the gradient to the specified width and height.

rotate(degrees)

The rotate( ) method rotates the gradient clockwise by the specified number of degrees (n
radians).

skew(degrees)

The skew( ) method skews the gradient by the number of degrees specified, creating a
stretching effect.

shear(x, y)

The shear( ) method shears the gradient. The two parameters specify the number of pixels
which to shear the gradient in the x and y directions.

Unlike the standard 3 x 3 transformation matrix, the TransformMatrix class uses a gradient that
1 pixels until you scale it. Therefore, when working with a transformation matrix, you should use
scale( ) method, either by itself or in conjunction with the other methods, to ensure that the gra
is appropriately sized.

This example applies the translate( ) method with the scale( ) method:

// Include the drawing methods, which are needed for the drawRectangle( ) method
#include "[Link]"

// Include the file that implements the TransformMatrix class.


#include "[Link]"

// Create an empty movie clip for drawing.

[Link] 2004-02-06
Strona 23 z 26

_root.createEmptyMovieClip("shape_mc", 1);

// Create the colors, alphas, and ratios arrays just as you would when working wi
// box-style gradient.
colors = [0x0000FF, 0x00FF00, 0xFF0000];
alphas = [100, 100, 100];
ratios = [0, 127.5, 255];

// Create a new TransformMatrix object.


matrix = new TransformMatrix( );

// Scale the gradient to 100 x 200 pixels and offset it 20 pixels to the righ
[Link](100, 200);
[Link](20, 0);

// Use a 1-pixel, black, solid border.


shape_mc.lineStyle(1, 0x000000, 100);

// Call beginGradientFill( ) just as you would when working with a box-style gra
// except the matrix parameter is a TransformMatrix object.
shape_mc.beginGradientFill("linear", colors, alphas, ratios, matrix);

// Draw a rectangle (requires [Link]).


shape_mc.drawRectangle(100, 200);
shape_mc.endFill( );

You can achieve animation effects by repeatedly redrawing the same shape and applying differen
transformations to the gradient fill:

// Include the drawing methods, which are needed for the drawCircle( ) method.
#include "[Link]"
// Include the file that implements the TransformMatrix class.
#include "[Link]"

_root.createEmptyMovieClip("shape_mc", 1);

// Call the following actions repeatedly by placing them within the shape_mc clip
// onEnterFrame( ) method.
shape_mc.onEnterFrame = function ( ) {

// Create the colors, alphas, and ratios arrays as usual.


var colors = [0x0000FF, 0x00FF00, 0xFF0000];
var alphas = [100, 100, 100];
var ratios = [0, 127.5, 255];

// Create the TransformMatrix object.


var matrix = new TransformMatrix( );

// Increment a custom scaleFactor property and pass that value to the transform
// matrix's scale( ) method. This will scale the gradient in the x and y direc
// uniformly, increasing over time.
var sf = [Link]++;
[Link](sf, sf);

// Clear the previously drawn shape. Otherwise, Flash will get bogged down tryin
// to keep track of all the old shapes.
[Link]( );

// Draw a circle with a gradient fill. Each time this circle is redrawn the
// gradient will be larger.
[Link](1, 0x000000, 100); // Use a one-pixel, black, solid border
[Link]("radial", colors, alphas, ratios, matrix);
[Link](100); // Requires [Link]
[Link]( );

[Link] 2004-02-06
Strona 24 z 26

4.11.4 See Also


Recipe 4.4, Recipe 4.5, and Recipe 4.10
[ Team LiB ]
[ Team LiB ]

Recipe 4.12 Scripting Masks


4.12.1 Problem
You want to create a mask at runtime.

4.12.2 Solution
Use the Drawing API to create a shape and then use [Link]( ) to apply the mask.

4.12.3 Discussion
Masks can be used to create unique shapes or visual effects. For example, you can use masks to
create wipes and transitions or interesting animations in which only the masked portion of the ar
is visible at a given time. You can even create masks that change shape over time, and use them
mask bitmapped graphics (in movie clips).

You can use any movie clip as a mask of another movie clip using the setMask( ) method. The se
( ) method is called from the movie clip to be masked, and you should pass it a reference to the
clip that acts as the mask:

[Link](maskMovieClip);

In most cases, masks are simple shapes, such as rectangles or circles. You do not need to use th
Drawing API to draw the mask movie clip, but it is recommended that you do so unless the mask
an unusual shape.

First, here is an example in which a mask follows the mouse. The mask is assigned to a movie cl
containing a loaded image, so the effect is that the user can see only the portion of the image ov
which he has positioned the mouse.

// Include the drawing methods, which are needed for the drawCircle( ) method.
#include "[Link]"

// Create a movie clip and a nested movie clip for loading an image. See Recipe 1
// for more information on the need for creating nested movie clips when loading
// external JPEGs.
_root.createEmptyMovieClip("image_mc", 1);
_root.image_mc.createEmptyMovieClip("imageHolder_mc", 1);

// Load the image into the movie clip. You can use this URL if you want, but it w
// work only while you are using the test or standalone players. See Recipe 15.3
// more information on loading JPEGs.
image_mc.imageHolder_mc.loadMovie("[Link]

// Draw the masking movie clip.


_root.createEmptyMovieClip("mask_mc", 2);
mask_mc.lineStyle(3, 0x000000, 0);
mask_mc.beginFill(0, 100);

[Link] 2004-02-06
Strona 25 z 26

mask_mc.drawCircle(60);
mask_mc.endFill( );

// Call the setMask( ) method on the masked movie clip and pass it the masking m
// clip as a parameter.
image_mc.setMask(mask_mc);

// Call the startDrag( ) method of the masking movie clip so that the mask can b
// moved with the cursor.
mask_mc.startDrag(true);

Next, here is an example in which a mask is used to create a wipe transition between two loaded
images.

#include "[Link]"

// Create a movie clip and a nested movie clip and load the first image into it.
_root.createEmptyMovieClip("image0_mc", 1);
_root.image0_mc.createEmptyMovieClip("imageHolder_mc", 1);
image0_mc.imageHolder_mc.loadMovie("[Link]

// Create another movie clip and nested movie clip and load the second image into
// Both image0_mc and image1_mc are created at (0,0). This means that they will
// overlap. This is what we want.
_root.createEmptyMovieClip("image1_mc", 2);
_root.image1_mc.createEmptyMovieClip("imageHolder_mc", 1);
image1_mc.imageHolder_mc.loadMovie("[Link]

// Draw the masking movie clip. The dimensions of the images are 640 x 480 (i
// load the images using the URLs provided) and so the mask should be a rectangle
// with the same dimensions.
_root.createEmptyMovieClip("mask_mc", 3);
mask_mc.lineStyle(3, 0x000000, 0);
mask_mc.beginFill(0, 100);
mask_mc.drawRectangle(640, 480);
mask_mc.endFill( );

// Position the mask so that it is off to the left side of the Stage.
mask_mc._x = -320;
mask_mc._y = 240;

// Call the setMask( ) method to set mask_mc as the mask for image1_mc. This cau
// image0_mc to display initially, even though it is below image1_mc.
image1_mc.setMask(mask_mc);

// Define an event handler method for image0_mc so that the mask movie clip moves
// when the user clicks on image0_mc.
image0_mc.onRelease = function ( ) {

// Use an onEnterFrame( ) event handler method to move the mask. This assumes
// have the default frames per second setting of 12.
_root.mask_mc.onEnterFrame = function ( ) {

// Move the mask to the right by 12 pixels.


this._x += 12;

// If the mask is fully masking the image, then delete the onEnterFrame( ) m
if (this._x >= 320) {
this._x = 320;
delete [Link];
}
}
}

[Link] 2004-02-06
Strona 26 z 26

If you use the URLs provided in this example, then the images that are loaded
have dimensions of 640 x 480. Therefore, you might need to increase the
dimensions of your movie to see the full images. If you use your own images,
they must be of the same resolution for the effect to work as described.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 37

[ Team LiB ]

Chapter 5. Numbers and Math


Introduction

Recipe 5.1. Representing Numbers in Different Bases

Recipe 5.2. Converting Between Different Number Systems

Recipe 5.3. Rounding Numbers

Recipe 5.4. Inserting Leading or Trailing Zeros

Recipe 5.5. Formatting Numbers for Display

Recipe 5.6. Formatting Currency Amounts

Recipe 5.7. Generating a Random Number

Recipe 5.8. Simulating a Coin Flip

Recipe 5.9. Simulating Dice

Recipe 5.10. Simulating Playing Cards

Recipe 5.11. Generating a Unique Number

Recipe 5.12. Converting Angle Measurements

Recipe 5.13. Calculating the Distance Between Two Points

Recipe 5.14. Determining Points Along a Circle

Recipe 5.15. Converting Between Units of Measurement

Recipe 5.16. Calculating Asset Appreciation (Future Value)

Recipe 5.17. Calculating Retirement Savings

Recipe 5.18. Calculating the Loan (Mortgage) You Can Afford

Recipe 5.19. Calculating Loan Amortization or Annuities


[ Team LiB ]
[ Team LiB ]

Introduction
Although numbers aren't always in the spotlight, don't overlook their power and importance in
your code. Numbers come in all shapes and sizes—from binary to decimal to hexadecimal. Each
type of representation has its own particular niche in which it is most valuable. For example,
hexadecimal numbers are often used to represent RGB color values because they make it easy
to discern each of the three color components. See Recipe 5.2 to learn how to convert between
different number bases.

[Link] 2004-02-06
Strona 2 z 37

Closely related to numbers is the subject of mathematics. Without mathematical operations,


your Flash movies would be rather dull. Simple operations such as addition and subtraction are
essential to even the most basic ActionScript applications, and more advanced math, such as
random-number generation and trigonometric calculations, is equally essential to advanced
applications.

The Math object supports methods and properties useful in a range of mathematical operations.
In the recipes throughout this chapter, you can discover how to extend the functionality of the
Math object to perform more advanced operations, such as financial calculations. See
[Link] for the final version of the [Link] file used throughout this
chapter.

[ Team LiB ]
[ Team LiB ]

Recipe 5.1 Representing Numbers in Different Bases


5.1.1 Problem
You want to specify a value as a binary, octal, or hexadecimal number.

5.1.2 Solution
Hexadecimal literals start with 0X or 0x (where the first character is a zero, not an "oh"), and
octal literals start with 0 (again, zero, not "oh"). Binary numbers can't be represented directly,
but you can specify their octal or hexadecimal equivalent.

5.1.3 Discussion
You can represent numbers in ActionScript using whichever format is most convenient, such as
decimal or hexadecimal notation. For example, if you set the value of the
MovieClip._rotation property, it is most convenient to use a decimal number:

myMovieClip._rotation = 180;

On the other hand, hexadecimal numbers are useful for specifying RGB colors. For example,
you can set the RGB value for a Color object in hexadecimal notation (in this example,
0xF612AB is a hex number representing a shade of pink):

myColor = new Color(myMovieClip);


[Link](0xF612AB);

Any numeric literal starting with 0X or 0x is presumed to be a hexadecimal number (a.k.a. hex
or base-16). Allowable digits in a hexadecimal number are 0-9 and A-F (both uppercase and
lowercase letters are allowed).

Any numeric literal starting with 0, but not 0x or 0X, is presumed to be an octal number (a.k.a.
base-8). Allowable digits in an octal number are 0-7. For example, 0777 is an octal number.
Most developers don't ever use octal numbers in ActionScript.

The only digits allowed in binary numbers (a.k.a. base-2) are 0 and 1. Although you can't
specify a binary number directly, you can specify its hexadecimal equivalent. Four binary digits
(bits) are equivalent to a single hex digit. For example, 1111 in binary is equal to F in hex (15
in decimal). The number 11111111 in binary is equal to FF in hex (255 in decimal). Binary

[Link] 2004-02-06
Strona 3 z 37

numbers (or rather their hexadecimal equivalents) are most commonly used with ActionScript's
bitwise operators (&, |, ^, >>, <<, and >>>).

5.1.4 See Also


Recipe 3.2, Recipe 3.3, Recipe 3.4, and Recipe 5.2
[ Team LiB ]
[ Team LiB ]

Recipe 5.2 Converting Between Different Number


Systems
5.2.1 Problem
You want to convert a number between different bases (decimal, binary, hexadecimal, etc.).

5.2.2 Solution
Use the parseInt( ) function with the radix parameter (the radix is the number's base) to
convert a string to a decimal representation. Use the [Link]( ) method with the radix
parameter to convert a decimal number to a string representation of the value in another base.

5.2.3 Discussion
No matter how you set a number value in ActionScript, the result is always retrieved as a
decimal (base-10) number.

// Create a Color object.


myColor = new Color(this);

// Set the RGB value as a hexadecimal.


[Link](0xF612AB);

// This displays the value as decimal: 16126635


trace([Link]( ));

However, if you want to output a value in a different base, you can use [Link](radix)
to convert any number value to a string representing that number in the specified base.

These two examples convert numeric literals to Number objects and output the string
representations in base-2 (binary) and base-16 (hexadecimal) format:

// The radix is 2, so output as binary.


trace(new Number(51).toString(2)); // Displays: "110011"
// The radix is 16, so output as hex.
trace(new Number(25).toString(16)); // Displays: "19"

This example assigns a primitive number to a variable and calls the toString( ) method to
output the value in hexadecimal:

myNum = 164;
trace([Link](16)); // Displays: "A4"

Note that the results from these examples are not numeric literals, but rather strings, such as
"110011", "19", and "A4".

[Link] 2004-02-06
Strona 4 z 37

This example sets the RGB value of a Color object, then calls toString( ) on the result to display
the value as a hexadecimal (as it had been input, although the alpha digits are converted to
lowercase, and the result is a string, not a number):

myColor = new Color(this);


[Link](0xF612AB);
trace([Link]( ).toString(16)); // Displays: "f612ab"

The valid range for the radix parameter of the toString( ) method is from 2 to 36. If you call
toString( ) with no radix parameter or an invalid value, decimal format (base-10) is assumed.

You can achieve the inverse of the toString( ) process using the parseInt( ) function with the
radix parameter. The parseInt( ) function takes a string value and returns a number. This is
useful if you want to work with inputs of bases other than 10.

These examples parse the numbers from the string in base-2 (binary), base-16 (hexadecimal),
and base-10, respectively. Note that the result is always a decimal.

trace(parseInt("110011", 2)); // Displays: 51


trace(parseInt("19", 16)); // Displays: 25
trace(parseInt("17", 10)); // Displays: 17

If omitted, the radix is assumed to be 10, unless the string starts with 0X or 0, in which case
hexadecimal or octal is assumed:

trace(parseInt("0x12")); // The radix is implicitly 16. Displays: 18


trace(parseInt("017")); // The radix is implicitly 8. Displays: 15

An explicit radix overrides an implicit one. In the next example, the result is 0, not 12. When
the number is treated as base-10, conversion stops when a nonnumeric character—the x—is
encountered.

// The number is treated as a decimal, not a hexadecimal number.


trace(parseInt("0x12", 10)); // Displays: 0 (not 12 or 18)

Here, although the leading zero doesn't prevent the remaining digits from being interpreted, it
is treated as a decimal number, not an octal number:

// The number is treated as a decimal, not an octal number.


trace(parseInt("017", 10)); // Displays: 17 (not 15)

Due to a deviation from the ECMA-262 standard, ActionScript gets confused by the "0x" that
prefixes a hexadecimal number if you specify an explicit radix of 16. Therefore, if you specify
an explicit radix, don't prefix the number with "0x". For example, although the following should,
according to the ECMA-262 standard, return the decimal equivalent of 0xA9FC9C, it returns 0
instead:

trace (parseInt("0xA9FC9C", 16)); // Displays: 0

Either of these will work as expected:

trace (parseInt("0xA9FC9C")); // Displays: 11140252 (implicit radix)


trace (parseInt("A9FC9C", 16)); // Displays: 11140252 (explicit radix)

Conversely, don't forget to include either "0x" or an explicit radix. The following interprets the
string as a decimal and returns NaN (Not-a-Number) because "A" can't be converted to an
integer:

[Link] 2004-02-06
Strona 5 z 37

trace(parseInt("A9FC9C")); // NaN

[ Team LiB ]
[ Team LiB ]

Recipe 5.3 Rounding Numbers


5.3.1 Problem
You want to round a number to the nearest integer, decimal place, or interval (such as to the
nearest multiple of 5).

5.3.2 Solution
Use [Link]( ) to round a number to the nearest integer. Use [Link]( ) and [Link]( )
to round a number down or up, respectively. Create a custom roundTo( ) method to round a
number to a specified number of decimal places or to a specified multiple.

5.3.3 Discussion
There are numerous reasons to round numbers. For example, when displaying the results of a
calculation, you might display only the intended precision. Because all arithmetic in ActionScript
is performed with floating-point numbers, some calculations result in unexpected floating-point
numbers that must be rounded. For example, the result of a calculation may be 3.9999999 in
practice even though it should be 4.0 in theory.

The [Link]( ) method returns the nearest integer value of any parameter passed to it:

trace([Link](204.499)); // Displays: 204


trace([Link](401.5)); // Displays: 402

The [Link]( ) method rounds down, and the [Link]( ) method rounds up:

trace([Link](204.99)); // Displays: 204


trace([Link](401.01)); // Displays: 402

To round a number to the nearest decimal place:

1. Decide the number of decimal places to which you want to round. For example, if you
want to round 90.337 to 90.34, then you will round to two decimal places, which means
that you will round to the nearest .01.

2. Divide the input value by the number chosen in Step 1 (in this case, .01).

3. Use [Link]( ) to round the calculated value from Step 2 to the nearest integer.

4. Multiply the result of Step 3 by the same value that you used to divide in Step 2.

For example, to round 90.337 to two decimal places, you could use:

trace ([Link](90.337 / .01) * .01); // Displays: 90.34

You can use the identical math to round a number to the nearest multiple of an integer. For
example, this rounds 92.5 to the nearest multiple of 5:

[Link] 2004-02-06
Strona 6 z 37

trace ([Link](92.5 / 5) * 5); // Displays: 95

As another example, this rounds 92.5 to the nearest multiple of 10:

trace ([Link](92.5 / 10) * 10); // Displays: 90

You can create a custom [Link]( ) method that encapsulates this functionality. The
custom method takes two parameters:

num

The number to round.

roundToInterval

The interval to which to round num. For example, if you want to round to the nearest
tenth, use 0.1 as the interval. Or, to round to the nearest multiple of six, use 6.

Here is our custom roundTo( ) method, which is attached directly to the Math object, so it is
available throughout the entire movie. You can add this custom method to a [Link] file for
easy inclusion in other projects.

[Link] = function (num, roundToInterval) {


// roundToInterval defaults to 1 (round to the nearest integer).
if (roundToInterval == undefined) {
roundToInterval = 1;
}
// Return the result.
return [Link](num / roundToInterval) * roundToInterval;
};

Here is an example of how to use the [Link]( ) method, assuming it is stored in an


external file named [Link]:

#include "[Link]"
trace([Link]([Link])); // Displays: 3
trace([Link]([Link], .01)); // Displays: 3.14
trace([Link]([Link], .0001)); // Displays: 3.1416
trace([Link](123.456, 1)); // Displays: 123
trace([Link](123.456, 6)); // Displays: 126
trace([Link](123.456, .01)); // Displays: 123.46

You might prefer a custom [Link]( ) method that rounds a number to a specified
number of decimal places. Our custom method takes two parameters:

num

The number to round

decPl

The number of decimal places to which to round num

Here is our custom roundDecPl( ) method, which is attached directly to the Math object, so it is
available throughout the entire movie:

[Link] = function (num, decPl) {

[Link] 2004-02-06
Strona 7 z 37

// decPl defaults to 0 (round to the nearest integer).


if (decPl == undefined) {
decPl = 0;
}
// Calculate the value used to multiply the original number,
// which is 10 raised to the power of decPl.
var multiplier = [Link](10, decPl);
// Return the result.
return [Link](num * multiplier ) / multiplier;
};

Here is an example of how to use [Link]( ), assuming it is stored in an external file


named [Link]:

#include "[Link]"
trace([Link]([Link])); // Displays: 3
trace([Link]([Link], 2)); // Displays: 3.14
trace([Link]([Link], 4)); // Displays: 3.1416

[ Team LiB ]
[ Team LiB ]

Recipe 5.4 Inserting Leading or Trailing Zeros


5.4.1 Problem
You want to add leading or trailing zeros to a number to display it as a string.

5.4.2 Solution
Create a custom [Link]( ) method.

5.4.3 Discussion
You might need to format numbers with leading or trailing zeros for display purposes, such as
when displaying times or dates. For example, you would want to format 6 hours and 3 minutes
as 6:03 or 06:03, not 6:3. And when you display monetary values, you often want to ensure
that two digits appear after the decimal place so that 23 dollars and 40 cents is formatted as
$24.40, not $23.4.

To add leading zeros, follow these steps:

1. Convert the number into a string using the String( ) function.

2. Determine the number of zeros to add by subtracting the string length from the number
of places you want in the resulting value.

3. Use a for loop to prepend the necessary number of zeros (as strings) to the number.

To add trailing zeros, follow the same steps, but append the zeros instead of prepending them.

You can define a custom method, [Link]( ), to format a number with leading or trailing
zeros and invoke it whenever it is needed.

The [Link]( ) method accepts up to three parameters (it returns a string):

num

[Link] 2004-02-06
Strona 8 z 37

The number you want to format.

places

The number of total digits that should be filled in the resulting, formatted numeric string.

trailing

If true, then the zeros are appended to the string. Otherwise, the zeros are prepended.

Here's the custom zeroFill( ) method that you can add to your [Link] file for easy inclusion in
any Flash movie:

[Link] = function (num, places, trailing) {

// Convert the number to a string.


var filledVal = String(num);

// Get the length of the string.


var len = [Link];

// Use a for statement to add the necessary number of characters.


for (var i = 0; i < (places - len); i++) {
// If trailing is true, append the zeros; otherwise, prepend them.
if (trailing) {
filledVal += "0";
} else {
filledVal = "0" + filledVal;
}
}
// Return the string.
return filledVal;
};

Here is an example that uses zeroFill( ) to display the binary representation of a number. The
first trace( ) statement simply displays the value returned by [Link]( ). The second
trace( ) statement displays the zero-filled version showing all eight bits of the number instead
of just six.

#include "[Link]"
val = 42;
trace([Link](2)); // Displays: "101010"
trace([Link]([Link](2), 8)); // Displays: "00101010"

5.4.4 See Also


Recipe 5.2 and Recipe 5.6

[ Team LiB ]
[ Team LiB ]

Recipe 5.5 Formatting Numbers for Display


5.5.1 Problem
You want to format a number with a custom decimal delimiter, thousands delimiters, and/or
leading spaces.

[Link] 2004-02-06
Strona 9 z 37

5.5.2 Solution
Create a custom [Link]( ) method.

5.5.3 Discussion
ActionScript does not include a built-in way to format numbers as strings, so you need to create
a custom method to handle this for you. Furthermore, the delimiter used to indicate decimals
and thousands varies by country. For example, the number represented as 1,234.56 in the
United States is written as 1.234,56 in many countries. Using some basic string, array, and
mathematical methods, we can create a custom method to format numbers with a configurable
decimal delimiter (i.e., 123.4 or 123,4), configurable thousands delimiters (i.e., 1,234 or 1.234),
and optional leading spaces (to align values when using a monospace font).

Our custom [Link]( ) method accepts four parameters:

num

The number you want to format

thousandsDelim

The value to use for delimiting the thousands, millions, billions, etc. places

decimalDelim

The value to use for delimiting the decimal places

spaceFill

Specifies the number of total spaces the formatted string should occupy (optional)

Here is our method definition, which can be added to [Link] for easy inclusion in your projects:

[Link] = function (num, thousandsDelim, decimalDelim, spaceFill) {


// Default to a comma for thousands and a period for decimals.
if (thousandsDelim == undefined) {thousandsDelim = ",";}
if (decimalDelim == undefined) {decimalDelim= ".";}

// Convert the number to a string and split it at the decimal point.


parts = String(num).split(".");

// Take the whole number portion and store it as an array of single characters.
// This makes it easier to insert the thousands delimiters, as needed.
partOneAr = parts[0].split("");

// Reverse the array so we can process the characters right to left.


[Link]( );

// Insert the thousands delimiter after every third character.


for (var i = 0, counter = 0; i < [Link]; i++) {
counter++;
if (counter > 3) {
counter = 0;
[Link](i, 0, thousandsDelim);
}
}

[Link] 2004-02-06
Strona 10 z 37

// Reverse the array again so that it is back in the original order.


[Link]( );

// Create the formatted string using decimalDelim, if necessary.


var val = [Link]("");
if (parts[1] != undefined) {
val += decimalDelim + parts[1];
}

// If spaceFill is defined, add the necessary number of leading spaces.


if (spaceFill != undefined) {
// Store the original length before adding spaces.
var origLength = [Link];
for (var i = 0; i < spaceFill - origLength; i++) {
val = " " + val;
}
}

// Return the value.


return val;
};

Note that in the preceding example, we store the length of the string prior to prepending it with
spaces. If we checked the length within the loop, as follows, the length itself would change for
each iteration (because it would count the spaces already added):

// This leads to the wrong result!


for (var i = 0; i < spaceFill - [Link]; i++) {
val = " " + val;
}

Here is another way the loop could be implemented. This time, we intentionally re-test the
length of the string with each loop iteration. Note that the loop's initialization and update
expressions are empty; only the loop's conditional test is used.

for (; [Link] < spaceFill; ) {


val = " " + val;
}

Here are a few examples of how to use numberFormat( ):

trace([Link](1234.2)); // Displays: "1,234.2"


trace([Link](1234.2, ".", ",")); // Displays: "1.234,2"
// Note the extra leading spaces in the result of the following example.
trace([Link](1234.2, ",", ".", 10)); // Displays: " 1,234.2"

5.5.4 See Also


Recipe 5.3 and Recipe 5.4 can be used to ensure that a certain number of digits are displayed
past the decimal point. Then, if you are using a monospaced font, aligning numbers is simply a
matter of setting the text field's format to right justification using the [Link]
property. Also refer to Recipe 5.6.

[ Team LiB ]
[ Team LiB ]

Recipe 5.6 Formatting Currency Amounts

[Link] 2004-02-06
Strona 11 z 37

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

5.6.2 Solution
Create a custom [Link]( ) method.

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

Our custom [Link]( ) method accepts up to seven parameters:

num

The number to format.

decimalPl

The number of decimal places in the formatted number.

currencySymbol

The symbol, such as a dollar sign ($), to use.

thousandsDelim

The characters used to delimit thousands, millions, etc.

decimalDelim

The characters used to delimit the fractional portion from the whole number.

truncate

If true, truncate the number to the specified number of decimal places; otherwise, round t
number.

spaceFill

The number of spaces the entire formatted string should occupy.

Here is our custom [Link]( ) method. The method converts a numeric parameter
currency-formatted string. Include this method within [Link] along with the custom roundDecP
numberFormat( ), and zeroFill( ) methods in this chapter, on which this example relies.

[Link] = function (num, decimalPl, currencySymbol, thousandsDelim,


decimalDelim, truncate, spaceFill) {

// Default to two decimal places, a dollar sign ($), a comma for thousands, and
// period for the decimal point. We implemented the defaults using the condition
// operator. Compare with Recipe 5.5.

[Link] 2004-02-06
Strona 12 z 37

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]) * [Link](10, -(decimalPl - 1));
parts[1] = String([Link](parts[1]));
} else {
// Requires the roundDecPl( ) method defined in Recipe 5.3
parts[1] = [Link](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] = [Link](parts[1], decimalPl, true);

// If necessary, use the numberFormat( ) method from Recipe 5.5 to format the n
// with the proper thousands delimiter and leading spaces.
if (thousandsDelim != "" || spaceFill != undefined) {
parts[0] = [Link](parts[0], thousandsDelim, "",
spaceFill - decimalPl - [Link]);
}

// Add a currency symbol and use [Link]( ) to merge the whole (dollar) an
// decimal (cents) portions using the designated decimal delimiter.
return currencySymbol + [Link](decimalDelim);
};

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

trace([Link](1.2)); // Displays: $1.20


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

5.6.4 See Also


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

[ Team LiB ]
[ Team LiB ]

Recipe 5.7 Generating a Random Number


5.7.1 Problem
You want to generate a random number within a certain range.

[Link] 2004-02-06
Strona 13 z 37

5.7.2 Solution
Use [Link]( ) as the basis for a custom randRange( ) method.

5.7.3 Discussion
You can use the [Link]( ) method to generate a random floating-point number from 0 to
0.999999999. In most cases, however, programs call for a random integer, not a random floatin
point number. Furthermore, you may want a random value within a specific range. If you do wan
a random floating-point number, you will ordinarily want to specify its precision (the number of
decimal places).

Here is the basic process for generating random numbers of this type:

1. Calculate the range difference and then add 1. For example, if you want to generate rando
numbers between 4 and 10, the range is 10-4 (which equals 6). To ensure an equal
distribution, add 1 to this range.

2. If you want the random number to include decimal places, multiply the range by the correc
multiple of 10 prior to adding 1. For example, to create random numbers with one decimal
place, multiply the range by 10. To create random numbers with two decimal places, multi
the range by 100, etc.

3. Multiply the value from step 2 by the value returned by [Link]( ). This will give you
value between 0 and the value from step 2 (with decimal places).

4. Use the [Link]( ) method to get the integer part of the value from step 3.

5. Divide the result from step 4 by the same multiple of 10 that you used in step 2. For
example, if you originally multiplied by 10, divide by 10. This step converts the result to th
correct number of decimal places.

6. Add the value from step 5 to the smallest value in the range. This offsets the value to be
within the specified range.

A custom [Link]( ) method encapsulates the process so that you can generate random
numbers while blissfully ignoring the details.

The custom randRange( ) method should take up to three parameters:

minNum

The smallest value in the range.

maxNum

The largest value in the range.

decPl

The number of decimal places to which to round the random number. If or omitted, the
number is returned as an integer.

Here is our custom randRange( ) method, which is attached directly to the Math object, so it is
available throughout the entire movie. It assumes that minNum is always less than maxNum. You c
add this custom method to the [Link] file for easy inclusion in other projects.

[Link] 2004-02-06
Strona 14 z 37

[Link] = function(minNum, maxNum, decPl) {

// Default to zero decimal places (generate a random integer).


decPl = (decPl == undefined) ? 0 : decPl;

// Calculate the range, multiply by 10 to the power of the number of decimal


// places, and add 1. We'll call this "delta".
var rangeDiff = (maxNum - minNum) * [Link](10, decPl) + 1;

// Multiplying delta by [Link]( ) generates a value from 0 to 0.999 * del


var randVal = [Link]( ) * rangeDiff;

// Truncate the value to the integer part.


randVal = [Link](randVal);

// Restore the proper number of decimal places.


randVal /= [Link](10, decPl);

// Add minNum as an offset to generate a random number in the correct range.


randVal += minNum;

// Return the random value. Use the custom roundTo( ) method from Recipe 5.3 t
// ensure that the result is rounded to the proper number of decimal places.
return [Link](randVal, [Link](10, -decPl));
};

Here is an example of how to use the randRange( ) method, assuming it has been stored in an
external ActionScript text file named [Link]:

#include "[Link]"

// Generate a random integer from 5 to 10, inclusive.


trace([Link](5, 10));

// Generate a random number, rounded to two decimal places from -5 to 5.


trace([Link](-5, 5, 2));

You can use randRange( ) when you don't want an even distribution from 0 to the maximum valu
in the range. Here is an example of how to use randRange( ) to generate a random alpha value f
a movie clip in the range of 75 to 100. It allows a clip to have a random alpha while avoiding clip
that are too transparent.

myMovieClip._alpha = [Link](75, 100);

Random numbers generated by traditional programming languages are not truly random. Rather
they are derived from a pseudorandom algorithm fed by an initial random seed. ActionScript doe
not allow you to specify the random seed (Flash presumably uses an essentially random starting
point, such as the time in milliseconds). Don't confuse a random number with a unique number
because random numbers are not guaranteed to be unique.

5.7.4 See Also


Recipe 5.3 and Recipe 5.11

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 15 z 37

Recipe 5.8 Simulating a Coin Flip


5.8.1 Problem
You want to simulate a coin flipping or other Boolean (true/false) event in which there is a 50%
of either outcome.

5.8.2 Solution
Use the randRange( ) method to generate an integer that is either 0 or 1 and then correlate each
possible answer with one of the desired results.

5.8.3 Discussion
You can use the randRange( ) method from Recipe 5.7 to generate a random integer in the spec
range. To relate this result to an event that has two possible states, such as a coin flip (heads or
or a Boolean condition (true or false), treat each random integer as representing one of the poss
states. By convention, programmers use 0 to represent one state (such as "off") and 1 to repres
the opposite state (such as "on"), although you can use 1 and 2 if you prefer.

For example, here is how you could simulate a coin flip:

#include "[Link]"

function coinFlip ( ) {
flip = [Link](0, 1);
if (flip == 0) {
return "heads";
} else {
return "tails";
}
}

// Example usage:
trace ("The result of the coin flip was " + coinFlip( ));

Here, we write a function that tests our coinFlip( ) routine to see if it is reasonably even-handed.
you expect a perfect 50/50 distribution regardless of the number of coin flips? Test it and see.

#include "[Link]"

function testCoinFlip (numFlips) {


// We'll count how many of each result occurs. Initialize them to 0.
var heads = 0;
var tails = 0;

// Repeat the process numFlips times and keep tabs on the results.
for (var i = 0; i < numFlips; i++) {
flip = [Link](0, 1);
if (flip == 0) {
heads++;
} else {
tails++;
}
}
// Display the results. Will it be 50/50?
trace ("The result of " + numFlips + " coin flips was:");
trace ("Heads: " + heads);
trace ("Tails: " + tails);

[Link] 2004-02-06
Strona 16 z 37

// Example usage:
testCoinFlip(10);
testCoinFlip(100);
testCoinFlip(1000);

Applying a similar principle, here is a function that returns a random "yes" or "no" reply:

function randomReply ( ) {
reply = [Link](0, 1);
if (reply == 0) {
return "yes";
} else {
return "no";
}
}

We can use a shortcut when creating a function that generates a Boolean (true or false) value. H
we take advantage of the fact that the Boolean( ) method will convert the number 0 to false an
number 1 to true. In this case, using a range from 1 to 2 won't work because both 1 and 2 conv
the Boolean value true.

function trueOrFalse ( ) {
return Boolean([Link](0, 1));
}

If you are testing the value of your random number, be sure to save the result
in a variable (and test the saved result) rather than generating a new random
number each time you perform the test.

The following is wrong because it generates independent random numbers in the dependent else
clauses. In some cases, none of the conditions will be true and the function will return undefine

function guessABC ( ) {
if ([Link](1, 3) == 1) {
return "A";
} else if ([Link](1, 3) == 2) {
return "B";
} else if ([Link](1, 3) == 3) {
return "C";
}
// We may arrive here unintentionally, due to our buggy implementation . . . in
// case undefined is returned.
}

This is the correct way to accomplish our goal:

function guessABC ( ) {
// Store the random number.
guess = [Link](1, 3);
// Now test it as many times as necessary.
if (guess == 1) {
return "A";
} else if (guess == 2) {
return "B";
} else if (guess == 3) {
return "C";
}
}

[Link] 2004-02-06
Strona 17 z 37

[ Team LiB ]
[ Team LiB ]

Recipe 5.9 Simulating Dice


5.9.1 Problem
You want to mimic rolling dice.

5.9.2 Solution
Use the randRange( ) method to generate random numbers in the desired range.

5.9.3 Discussion
You can use the randRange( ) method from Recipe 5.7 to generate random integer values to sim
rolling a die or dice in your Flash movies. Mimicking the rolling of dice is an important feature in
games you might create using ActionScript, and the randRange( ) method makes your job easy.

// Include [Link] to have access to [Link]( ).


#include "[Link]"

// Calling [Link]( ) with min = 1 and max = 6 generates


// a random number as though you are rolling a six-sided die.
die1 = [Link](1, 6);

// Add the results of two "rolls" together to simulate a pair of six-sided dice.
dice = [Link](1, 6) + [Link](1, 6);

[Link](1, 12) does not correctly simulate a pair of six-sided dice


because the results must be between 2 and 12, not 1 and 12. Does
[Link](2, 12) give the correct result? No, it does not!
[Link](2, 12) results in a smooth distribution of numbers from 2 to
12, whereas in games played with two dice, 7 is much more common than 2 or
12. Therefore, you must simulate each die separately and then add the results
together. Furthermore, in many games, such as backgammon, game play
depends on the individual value of each die, not simply the total of both dice, so
you'll want to keep these results separate.

It is not uncommon to want to generate a random number and then store it for later use. If you
to reuse an existing random number, be sure to save the result rather than generating a new ran
number, as per the warning in Recipe 5.8. Note the difference in the following two scenarios. In
first scenario, dice will always be the sum of die1 plus die2:

die1 = [Link](1, 6);


die2 = [Link](1, 6);
dice = die1 + die2;

In the second scenario, there is no relation between the value of dice and the earlier random va
stored in die1 and die2. In other words, even if die1 and die2 add up to 7, dice stores a comp
different value between 2 and 12:

die1 = [Link](1, 6);


die2 = [Link](1, 6);
dice = [Link](1, 6) + [Link](1, 6);

[Link] 2004-02-06
Strona 18 z 37

You can call [Link]( ) with any range to simulate a multisided die. Here, we call it with
range from 1 to 15 to generate a random number as though the user is rolling a 15-sided die, as
be found in a role-playing game such as Dungeons and Dragons:

myRoll = [Link](1, 15);

Here is an example that uses the drawing methods to simulate a single 15-sided die. It uses the
randRange( ) method to generate random values when the user clicks on the die_mc movie clip.

// Include both [Link] and [Link] (from Chapter 4) to access the meth
// used in this example.
#include "[Link]"
#include "[Link]"

// Create a die_mc clip on _root. Within that clip, create a shape_mc clip and dr
// filled (white fill, black outline) square using drawRectangle( ).
_root.createEmptyMovieClip("die_mc", 1);
die_mc.createEmptyMovieClip("shape_mc", 1);
die_mc.shape_mc.lineStyle(1, 0x000000, 100);
die_mc.shape_mc.beginFill(0xFFFFFF, 100);
die_mc.shape_mc.drawRectangle(50, 50);

// Create a text field within die_mc and set the font size to 24.
die_mc.createTextField("num", 2, 0, 0, 0, 0);
die_mc.[Link] = true;
tf = new TextFormat( );
[Link] = 24;
die_mc.[Link](tf);

// Move die_mc so that it is visible on stage.


die_mc._x = 200;
die_mc._y = 200;

// Create a method that generates random numbers and sets


// the text field of the die to display those values.
die_mc.onToss = function ( ) {

// Generate a random number from 1 to 15 to mimic a 15-sided die.


var randNum = [Link](1, 15);

// Display the value in the text field and center the text field on the die.
[Link] = randNum;
[Link]._x = -([Link]._width/2);
[Link]._y = -([Link]._height/2);

// Count to 20 and set the onEnterFrame( ) method to null. This animates the d
// 21 frames and then stops until it is clicked again.
[Link]++;
if ([Link] > 20) {
[Link] = 0;
[Link] = null;
}
};

// When the user clicks on the die_mc movie clip, set onEnterFrame to the onToss(
// method so it is called at the frame rate until 20 frames have animated. After
// which, onEnterFrame( ) becomes null and nothing more happens until the user c
// on it again.
die_mc.onRelease = function ( ) {
[Link] = [Link];
};

[ Team LiB ]

[Link] 2004-02-06
Strona 19 z 37

[ Team LiB ]

Recipe 5.10 Simulating Playing Cards


5.10.1 Problem
You want to use ActionScript to deal cards for a card game using a standard 52-card deck (witho
jokers).

5.10.2 Solution
Create a custom Cards class that uses the randRange( ) method to deal the cards.

5.10.3 Discussion
To work with playing cards within your Flash movies, you should create a custom Cards class. Th
Cards class should have an array property that contains all the cards in the deck and a deal( ) m
that deals the cards to a specified number of players. Additionally, you should define a supportin
named CardPlayer. The CardPlayer class represents each player in a card game, including their h
of cards. Let's take a look at the example code first:

// Include the [Link] file on whose methods this recipe relies.


#include "[Link]"

// When a new card player is created by way of its constructor, pass it a referen
// to the card deck and give it a unique player ID.
function CardPlayer (deck, id) {
[Link] = null;
[Link] = deck;
[Link] = id;
}

// The setHand( ) method sets the cards in a player's hand.


[Link] = function (hand) {
[Link] = hand;
};

// The getHand( ) method returns the cards in a player's hand.


[Link] = function ( ) {
return [Link];
};

// The draw( ) method deals the specified number of cards to the player.
[Link] = function (num) {
[Link]([Link], num);
};

// The discard( ) method discards the cards specified


// by their indexes in the player's hand.
[Link] = function (cardsArIds) {
[Link]( );
for (var i = 0; i < [Link]; i++) {
[Link](cardsArIds[i] - i, 1);
}
};

// The Cards constructor creates a deck of cards.


function Cards ( ) {

// Create a local array that contains the names of the four suits.

[Link] 2004-02-06
Strona 20 z 37

var suits = ["Hearts", "Diamonds", "Spades", "Clubs"];

// The cards property is an array that is populated with all the cards.
[Link] = new Array( );

// Specify the names of the cards for stuffing into the cards array later.
var cardNames = ["2", "3", "4", "5", "6", "7", "8", "9", "10",
"J", "Q", "K", "A"];

// Create a 52-card array. Each element is an object that contains properties f


// the card's integer value (for sorting purposes), card name, suit name, and
// display name. The display name combines the card's name and suit in a single
// string for display to the user.
for (var i = 0; i < [Link]; i++) {
// For each suit, add 13 cards.
for (var j = 0; j < 13; j++) {
[Link](
{val: j,
name: cardNames[j],
suit: suits[i],
display: cardNames[j] + " " + suits[i]});
}
}
}

// The deal( ) method needs to know the number of players in the game and the num
// of cards to deal each player. If the cardsPerPlayer parameter is undefined, th
// it deals all the cards.
[Link] = function (numOfPlayers, cardsPerPlayer) {

// Create an array, players, that holds the cards dealt to each player.
var players = new Array( );

// The players array contains CardPlayer objects. Each card player is given a
// reference to this deck of cards, as well as a unique player ID.
for (var i = 0; i < numOfPlayers; i++) {
[Link](new CardPlayer(this, i));
}

// Make a copy of the deck of cards from which we'll deal. This way, we can alw
// recreate a fresh deck from the original cards property.
[Link] = [Link]( );

// If a cardsPerPlayer value was passed in, deal that number of cards. Otherwis
// divide the number of cards (52) by the number of players.
var cardsEach = cardsPerPlayer;
if (cardsPerPlayer == undefined) {
cardsEach = [Link]([Link] / numOfPlayers);
}

// Deal the specified number of cards to each player.


var rand, hand;
for (var i = 0; i < numOfPlayers; i++) {

hand = new Array( );

// Deal a random card to each player. Remove that card from the
// CardsToDeal array so that it cannot be dealt again.
for (var j = 0; j < cardsEach; j++) {
rand = [Link](0, [Link] - 1);
[Link]([Link][rand]);
[Link](rand, 1);
}

[Link] 2004-02-06
Strona 21 z 37

// Use [Link]( ) to sort a player's hand and use setHand( ) to ass


// to the card player object.
players[i].setHand([Link]([Link]( )));
}

// Return the players array.


return players;
};

// The [Link]( ) method is called from [Link]( ) to draw the speci


// number of cards from the deck and add them to the player's hand.
[Link] = function (player, numToDraw) {

// Get the player's current hand.


var hand = players[player].getHand( );

// Add the specified number of cards to the hand.


for (var i = 0; i < numToDraw; i++) {
rand = [Link](0, [Link] - 1);
[Link]([Link][rand]);
[Link](rand, 1);
}

// Sort the hand and reassign it to the player object.


var orderedHand = [Link](hand);
players[player].setHand(orderedHand);
};

// Used by sort( ) in the orderHand( ) method to sort the cards by suit and ran
[Link] = function(a, b) {
if ([Link] > [Link]) {
return 1;
} else if ([Link] < [Link]) {
return -1;
} else {
return (Number([Link]) > Number([Link]))
}
};

// This method sorts an array of cards by calling the sort( ) method on that arr
// with a sorter method of [Link]( ).
[Link] = function(hand) {
[Link]([Link]);
return hand;
};

Now let's look more carefully at some of the pieces of the CardPlayer and Cards classes and wha
do.

First, we define the CardPlayer class constructor. Each CardPlayer object contains three propertie
hand (which is an array of cards), a reference to the deck from which the game is being played (
instance of the Card class), and a player ID that is unique within the game (i.e., each player is
identified by a number, such as 0, 1, 2, or 3). The CardPlayer class defines getter and setter me
for the hand property and draw( ) and discard( ) methods to obtain new cards and remove old on

The first part of the Card class is the constructor method, which creates an array of all possible c
There are many ways to approach this. The example uses nested for statements to create a 52-c
array (the loop executes 13 times for each of the 4 suits). First, it creates an array specifying the
names of the four suits and another array specifying the names of each card. Then, it populates
cards array by creating 13 cards for each suit. The push( ) method appends a new object for ea
card, which becomes an element of the cards array.

[Link] 2004-02-06
Strona 22 z 37

The deal( ) method creates an array of card players. It returns an array of CardPlayer objects (o
each player in the game). The number of players is determined by the numOfPlayers parameter
allowing for flexibility in how the cards are dealt. The code "deals" the cards by randomly selectin
element (card) from the cardsToDeal array and adding it to a player's hand. It is important that
selected element is removed from cardsToDeal, as shown using splice( ) in this example, so tha
not dealt twice. Next, each player's hand is sorted using the orderHand( ) method, and then eac
player's hand's contents are set for each player's CardPlayer object.

The orderHand( ) and sorter( ) methods work together to sort a user's hand by suit and then by
The orderHand( ) method calls the sort( ) method on the array, with sorter( ) as the custom sort
function.

Additionally, the Card class defines a draw( ) method, which is useful for games such as poker in
players can draw additional cards. The draw( ) method draws a specified number of cards from t
deck and adds them to a player's hand.

This example code tests the Cards class:

// First, create a new Cards object.


c = new Cards( );

// Call the deal( ) method, specifying four players and six cards per player.
players = [Link](4, 6);

// For each player . . .


for (var i = 0; i < [Link]; i++) {

// Display the player number.


trace("player " + i);

// Display the player's cards.


for (var j = 0; j < players[i].getHand( ).length; j++) {
trace(" " + players[i].getHand( )[j].display);
}
}

// Now, for the first player, discard the first and third cards from the hand and
// draw two new cards.
players[0].discard([0, 2]);
players[0].draw(2);

// Now display the players' hands again. Notice that the first player has two
// different cards while the rest of the players have the same hands as before.
for (var i = 0; i < [Link]; i++) {

// Display the player number.


trace("player " + i);
// Display the player's cards.
for (var j = 0; j < players[i].getHand( ).length; j++) {
trace(" " + players[i].getHand( )[j].display);
}
}

Note that our code draws cards from the deck randomly rather than from the "top." This is neces
because we added the cards in rank and suit order, so drawing from the top would not give a
randomized deal. An alternative solution is to randomize (shuffle) the cards when creating the ar
and then deal the cards in order from the deck. In that case, the programmer should be careful t
shuffle the cards again each time a new deck of cards is created from the master copy (rather th
shuffling the master copy, which would result in all games being played with the same deck of ca

Also note that our example doesn't deal gracefully with games in which you might run out of car

[Link] 2004-02-06
Strona 23 z 37

assumes that 52 cards is sufficient for the current hand and that a new deck will be created for e
subsequent hand. Enhancing the example is left to you as an exercise.
[ Team LiB ]
[ Team LiB ]

Recipe 5.11 Generating a Unique Number


5.11.1 Problem
You want to generate a unique number to append to a URL to prevent the browser from retrievin
the asset from the local cache.

5.11.2 Solution
Use the number of milliseconds elapsed since January 1, 1970, as returned by Date( ).getTime(

5.11.3 Discussion
Unique numbers are most commonly used to generate a unique URL (to prevent an asset from b
retrieved from the local cache). That is, by appending a unique number to the end of a URL, it is
unlike any previous URL; therefore, the browser obtains the data from the remote server instead
the local cache.

Assuming you need a unique number less frequently than once per millisecond, the number of
milliseconds returned by Date( ).getTime( ) is essentially unique (at least during the execution o
your movie).

Note that you do not want to use the milliseconds of an existing Date object because its time val
doesn't automatically increase as your movie runs. Instead, generate a new Date object
representing "now" each time you need to extract a unique number.

Here, we define our custom [Link]( ) method to encapsulate the code to generate th
unique number. By default, the method automatically generates a new value each time it is calle
However, if you pass it a useCached parameter of true, the method returns the same ID that it
generated the last time it was called. This can be useful if you want to create a single ID for a m
and access it multiple times.

[Link] = function (useCached) {

// If the unique ID has already been created and if useCached is true, return th
// previously generated value.
if ([Link] != undefined && useCached) {
return [Link];
}

var now = new Date( ); // Generate a Date object that represents "now
[Link] = [Link]( ); // Get the elapsed milliseconds since Jan 1, 1
return [Link];
};

Note that you shouldn't use the [Link]( ) method to generate unique numbers
because it returns the milliseconds of a Date object (i.e., the fractional remainder of the number
seconds), which is always in the range of 0 to 999.

As already noted, the getUniqueID( ) method generates a unique value only if at least one
millisecond elapses between invocations. If you call it more frequently, it returns the same value
twice.

[Link] 2004-02-06
Strona 24 z 37

// Each of these calls returns the same value because they are all called within
// millisecond of each other.
timeOne = [Link]( );
timeTwo = [Link]( );
timeThree = [Link]( );
trace(timeOne);
trace(timeTwo);
trace(timeThree);

[ Team LiB ]
[ Team LiB ]

Recipe 5.12 Converting Angle Measurements


5.12.1 Problem
You want to work with angle values in ActionScript, but you must convert to the proper units.

5.12.2 Solution
Create custom degToRad( ) and radToDeg( ) methods.

5.12.3 Discussion
The _rotation property of a movie clip object is measured in degrees. Every other angle
measurement in ActionScript, however, uses radians, not degrees. This can be a problem in two
ways. First of all, if you want to set the _rotation property based on the output of one of
ActionScript's trigonometric methods, you must convert the value from radians to degrees.
Second, humans generally prefer to work in degrees, which we must convert to radians before
feeding to any of the trigonometric methods. Fortunately, the conversion between radians and
degrees is simple. You should add the following degToRad( ) and radToDeg( ) methods to your
[Link] file for converting from degrees to radians and vice versa. Note that they are attached
directly to the top-level Math object, making them available throughout your movie.

// Convert degrees to radians by multiplying by π and dividing by 180.


[Link] = function(deg){
return ([Link] * deg) / 180;
};

// Convert radians to degrees by multiplying by 180 and dividing by π.


[Link] = function(rad){
return (rad * 180) / [Link];
};

This following code demonstrates how the methods work:

trace([Link](90)); // Displays: 1.5707963267949 (which is π/2)


trace([Link]([Link])); // Displays: 180

These two methods are invaluable when you want to use the trigonometric methods:

// Use degToRad( ) to convert degrees to radians before passing the value to


// [Link]( ) (which expects radians).
trace([Link]([Link](36)));

[Link] 2004-02-06
Strona 25 z 37

// Get the angle (in radians) for a cosine of .75 using the inverse cosine.
angle = [Link](.75);
// Set MovieClip._rotation to the degree equivalent of the angle in radians.
myMovieClip._rotation = [Link](angle);

5.12.4 See Also


Recipe 5.15
[ Team LiB ]
[ Team LiB ]

Recipe 5.13 Calculating the Distance Between Two


Points
5.13.1 Problem
You want to calculate the distance between two points.

5.13.2 Solution
Create custom [Link]( ) method.

5.13.3 Discussion
You can calculate the distance (in a straight line) from any two points by using the Pythagorean
theorem. The Pythagorean theorem states that in any right triangle (a triangle in which one of
the angles is 90 degrees), the length of the hypotenuse (the long side) is equal to the square
root of the sum of the squares of the two other sides (referred to as the legs of the triangle).
The Pythagorean theorem is usually written as:

a2 + b 2 = c 2

You can use this formula to calculate the distance between any two points, where a is the
difference between the points' x coordinates, b is the difference between their y coordinates,
and c (the distance to be determined) equals the square root of (a2 + b2). In ActionScript, this
is written as:

var c = [Link]([Link](a, 2) + [Link](b, 2));

How do you calculate the distance between two points using a right triangle? While it might not
seem immediately obvious, you can form an imaginary right triangle using any two points in the
Flash coordinate system, as shown in Figure 5-1.

Figure 5-1. The hypotenuse of a right triangle is drawn between two


points to calculate the distance between the points

[Link] 2004-02-06
Strona 26 z 37

The hypotenuse of the imaginary triangle is formed by the line connecting the two points. The
legs of the triangle are formed by lines extending horizontally and vertically from the two
points. You can find the lengths of the legs by finding the differences between the x and y
coordinates. The length of leg a is determined by the difference in the points' x coordinates, and
the length of leg b is determined by the difference in the points' y coordinates. Once you know
the lengths of legs a and b, you can use the Pythagorean theorem to calculate the length of the
hypotenuse, c, which represents the distance between the points (our original quarry).

It is convenient to encapsulate this calculation in a method that you can reuse. The custom
[Link]( ) method we define here accepts the x and y coordinates of the two points as
its four parameters:

[Link] = function (x0, y0, x1, y1) {

// Calculate the lengths of the legs of the right triangle.


var dx = x1 - x0;
var dy = y1 - y0;

// Find the sum of the squares of the legs of the triangle.


var sqr = [Link](dx, 2) + [Link](dy, 2);

// Return the square root of the sqr value.


return ([Link](sqr));
};

Here is an example of the [Link]( ) method being used to calculate the distance
between two points at (300,400) and (0,0):

trace([Link](300, 400, 0, 0)); // Displays: 500

5.13.4 See Also


Recipe 4.7

[ Team LiB ]
[ Team LiB ]

Recipe 5.14 Determining Points Along a Circle


5.14.1 Problem
You want to calculate the coordinates of a point along a circle given the circle's radius and the
sweep angle.

5.14.2 Solution
Use the [Link]( ) and [Link]( ) methods to calculate the coordinates using basic
trigonometric ratios.

5.14.3 Discussion
Finding the coordinates of a point along a circle is easy with some trigonometry. So let's look at
the formulas you can use within your ActionScript code and the theory behind them.

[Link] 2004-02-06
Strona 27 z 37

Given any point on the Stage—a point we'll call p0, with coordinates (x0, y0)—plus a distance an
the angle from the horizontal, you can find the coordinates of another point—which we'll call p1,
with coordinates (x1, y1)—using some basic trigonometric ratios. The angle is formed between a
conceptual line from p0 to p1 and a line parallel to the X axis, as shown in Figure 5-2. The
opposite side (O) is the side furthest away from the angle. The adjacent side (A) is the side that
forms the angle with the help of the hypotenuse (H).

Figure 5-2. The angle, adjacent side, opposite side, and hypotenuse of a
right triangle

If you know the distance between two points and the angle to the horizontal, as shown in Figure
5-2, you can calculate the x and y coordinates of the destination point using trigonometric
functions. The trigonometric sine of the angle is equal to the ratio of the opposite side over the
hypotenuse:

sine(angle) = opposite/hypotenuse

Solving for the opposite side's length, this can be written as:

opposite = sine(angle) * hypotenuse

You can see from Figure 5-2 that the opposite side represents the change in the y direction.

The trigonometric cosine of the angle is equal to the ratio of the adjacent side over the
hypotenuse:

cosine(angle) = adjacent/hypotenuse

Solving for the adjacent side's length, this can be written as:

adjacent = cosine(angle) * hypotenuse

You can see from Figure 5-2 that the adjacent side represents the change in the x direction.

Because the lengths of the opposite and adjacent sides yield the changes in the x and y
directions, by adding the original x and y coordinates to these values you can calculate the
coordinates of the new point.

So how does this help in determining a point along a circle's perimeter? Figure 5-3, which shows
our familiar triangle inscribed within a circle, emphasizes the equivalency: the triangle's
hypotenuse (H) equates to the circle's radius, and the triangle's angle equates to the sweep ang
to the point of interest along the circle's perimeter.

Figure 5-3. Using trigonometry to determine a point along a circle's


perimeter

[Link] 2004-02-06
Strona 28 z 37

Therefore, the x coordinate of a point along the circle's perimeter is determined by the radius
times the cosine of the angle. The y coordinate is determined by the radius times the sine of the
angle. Here is the ActionScript code for finding the coordinates of p1 when the circle's radius and
center point (p0) are known:

x1 = x0 + ([Link](angle) * radius);
y1 = y0 + ([Link](angle) * radius);

Therefore, these formulas can be used to determine any point along a circle's perimeter, given th
circle's center point and radius. By changing the angle over time, you can trace the path of a
circle. Here are a few examples:

// Include [Link] from Chapter 4.


#include "[Link]"
#include "[Link]"

// Create a square_mc movie clip.


_root.createEmptyMovieClip("square_mc", 1);
square_mc.lineStyle(1, 0x000000, 100);
square_mc.drawRectangle(10, 10);

// Move the square in a circular path with a radius of 50. The larger the value b
// which angle is incremented, the faster the movement.
square_mc.onEnterFrame = function ( ) {
this._x = [Link]([Link]([Link])) * 50;
this._y = [Link]([Link]([Link])) * 50;
[Link] += 5;
};

// Create another square movie clip.


_root.createEmptyMovieClip("square2_mc", 2);
square2_mc.lineStyle(1, 0x000000, 100);
square2_mc.drawRectangle(10, 10);

// Make the square spiral outward in 30-degree increments, increasing the radius b
// two pixels per frame.
[Link] = function ( ) {
this._x += [Link]([Link](30)) * 2;
this._y += [Link]([Link](30)) * 2;
};

5.14.4 See Also


Recipe 4.5 and Recipe 4.6

[ Team LiB ]
[ Team LiB ]

Recipe 5.15 Converting Between Units of Measurement

[Link] 2004-02-06
Strona 29 z 37

5.15.1 Problem
You want to convert between Fahrenheit and Centigrade, pounds and kilograms, or other units o
measurement.

5.15.2 Solution
Write a custom method to perform the conversion and add it to the Math class.

5.15.3 Discussion
There are various systems of measurement used throughout the world. For example, temperatur
is commonly measured with both the Fahrenheit and Centigrade scales, and weight is commonly
measured using both pounds and kilograms. For these reasons, you may need to convert from o
unit of measurement to another.

Each of these conversions has its own algorithm. For example, to convert from Centigrade to
Fahrenheit, you should multiply by 9/5 and then add 32 (to convert from Fahrenheit to Centigrad
subtract 32 and multiply by 5/9). Likewise, you can multiply by 2.2 to convert pounds to kilogram
and you can divide by 2.2 to convert kilograms to pounds. We also saw in Recipe 5.12 how to
convert angles from degrees to radians and vice versa.

Here is a temperature converter. The method takes three parameters: the name of the units from
which you are converting, the name of the units to which you are converting, and the value to
convert. In this example code, we define conversions between Fahrenheit and Centigrade ("F" is
interpreted to mean "Fahrenheit"; "C" and "Celsius" are recognized alternatives to "Centigrade")

[Link] = function (fMeasure, tMeasure, val) {


// Convert all names to lowercase to match any capitalization.
fMeasure = [Link]( );
tMeasure = [Link]( );

if ( (fMeasure == "centigrade" || fMeasure == "celsius" || fMeasure == "c")


&& (tMeasure == "fahrenheit" || tMeasure == "f") ) {
// Convert Centigrade to Fahrenheit.
return (val * 9/5) + 32;
} else if ( (fMeasure == "fahrenheit" || fMeasure == "f") &&
(tMeasure == "centigrade" || tMeasure == "celsius" || tMeasure == "c") ) {
// Convert Fahrenheit to Centigrade.
return (val - 32) * 5/9;
} else {
trace ("Invalid conversion type from " + fMeasure + " to " + tMeasure);
return NaN;
}
};

Here are examples of how to use this function:

trace([Link] ("Centigrade", "Fahrenheit", 0)); // Displays: 32


trace([Link] ("c", "f", 0)); // Displays: 32
trace([Link] ("fahrenheit", "centigrade", 212)); // Displays: 10
trace([Link] ("fahrenheit", "celsius", 212)); // Displays: 10

You could modify the preceding function to also support degrees Kelvin. However, if you support
more than two units of measure, the number of possible permutations (and the number of requir
else if clauses) multiplies rapidly. In such a case, you can convert all values to an interim unit of
measurement to reduce the number of transformations required. For example, you can convert a
temperatures to and from Centigrade, as follows:

[Link] 2004-02-06
Strona 30 z 37

[Link] = function (fMeasure, val) {


fMeasure = [Link]( );
if (fMeasure == "kelvin" || fMeasure == "k") {
return (val - 273.15);
} else if ( fMeasure == "fahrenheit" || fMeasure == "f" ) {
return (val - 32) * 5/9;
} else if (fMeasure == "centigrade" || fMeasure == "celsius" || fMeasure == "c"
return val;
} else {
return NaN;
}
};

[Link] = function (tMeasure, val) {


tMeasure = [Link]( );
if (tMeasure == "kelvin" || tMeasure == "k") {
return (val + 273.15);
} else if ( tMeasure == "fahrenheit" || tMeasure == "f" ) {
return (val * 9/5) + 32;
} else if (tMeasure == "centigrade" || tMeasure == "celsius" || tMeasure == "c"
return val;
} else {
return NaN;
}
};

This allows our [Link]( ) method to be simplified, as follows:

[Link] = function (fMeasure, tMeasure, val) {


var centigradeVal = [Link] (fMeasure, val);
return [Link] (tMeasure, centigradeVal );
};

Here are examples of how to use this function:

trace([Link] ("centigrade", "Kelvin", 0)); // Displays: 273.15


trace([Link] ("k", "f", 0)); // Displays: -459.6
trace([Link] ("fahrenheit", "kelvin", 212)); // Displays: 373.15
trace([Link] ("K", "celsius", 0)); // Displays: -273.1

Or, if you prefer, you could write single-purpose functions, such as [Link]( ),
[Link]( ), and [Link]( ), that simply accept the value to convert.

Here is another function that converts between pounds and kilograms using the same structure
shown earlier:

[Link] = function (fMeasure, tMeasure, val) {


if (fMeasure == "pounds" && tMeasure == "kilograms") {
return val / 2.2;
} else if (fMeasure == "kilograms" && tMeasure == "pounds") {
return val * 2.2;
} else {
return "invalid conversion type";
}
};

Here are some examples of its use:

trace([Link] ("pounds", "kilograms", 0)); // Displays: 0


trace([Link] ("kilograms", "pounds", 100)); // Displays: 220

[Link] 2004-02-06
Strona 31 z 37

You can add support for conversion to other units of weight by inserting additional else if
statements following the same pattern. Or you can use the technique demonstrated for
temperature, in which all values are first converted to a common unit.

5.15.4 See Also


For an example of using a hardcoded function to perform a single type of unit conversion, refer t
Recipe 5.12, which includes a function to convert angles from degrees to radians and another
function that does the opposite.
[ Team LiB ]
[ Team LiB ]

Recipe 5.16 Calculating Asset Appreciation (Future Value


5.16.1 Problem
You want to know how much something will be worth in the future, given its value today and an
expected interest rate, i (for example, calculating the amount of money you can accumulate in a
savings account given an initial deposit).

5.16.2 Solution
You must calculate the appreciation of an asset over time at the assumed interest rate. This is
often referred to as the future value of an asset. For a given interest rate, i, an asset appreciates
by a factor of (1 + i) for each period (such as a year).

5.16.3 Discussion
For the purposes of illustration, let's calculate the future value of an asset using the brute-force
method.

If you deposit $1 in a bank account that earns 5% interest per year, one year from now, you wil
have earned 5 cents in interest. The total account, including principal and interest, will be worth
$1.05. The math for this is:

FV = PV * (1 + i);

where FV is the future value (the amount of money you'll have next year, sometimes called FV1)
PV is the present value (the amount of money you deposited initially, sometimes called FV0), an
is the interest rate (expressed as a decimal, such as 0.05).

So if you deposit $100 at a 5% interest rate, the future value at the end of one year is $105, wh
is determined as follows:

FV1 = 100 * (1 + .05);

Typically, interest is compounded over time, so at the end of the second year, the value is:

FV2 = FV1 * (1 + i);

Here is a function that calculates the future value by brute force for an arbitrary number of
periods. We name the parameter that represents the interest rate interest (instead of i) so as n
to mistake it for an index variable (and we'll use periods as our loop variable to avoid the revers
confusion):

[Link] 2004-02-06
Strona 32 z 37

[Link] = function (interest, n, PV) {


// PV = initial deposit (present value)
// interest = periodic interest rate
// n = number of periods

// Start with the future value equal to the present value.


FV = PV;

// Now compound it over n periods at an interest rate of interest.


for (var periods = 1; periods <= n; periods++) {
FV = FV * (1 + interest);
}
return FV;
};
// Example usage:
// How much will $500 appreciate in 10 years at a 5% rate?
trace ("It will be worth: " + [Link](.05, 10, 500));

You can use the custom CurrencyFormat( ) method from Recipe 5.6 to format the result for
display.

It doesn't matter whether the interest is compounded daily, weekly, monthly, or annually, provid
that the specified interest rate is correct for the number of periods. For example, if compounding
monthly, be sure to multiply the number of years by 12 and divide the annual interest rate by 12

// How much will $500 appreciate in 10 years at a 5% rate with monthly compoundin
trace ("It will be worth: " + [Link](.05/12, 10*12, 500));

So the brute-force method appears to work, but it has two drawbacks. One drawback is that it ca
be slow if you are doing a lot of calculations. The other problem is that it can't be used to calcula
so-called continuous compounding.

Let's revisit this formula for the value of the account at the end of the second year:

FV2 = FV1 * (1 + i);

Substituting PV * (1 + i) for FV1, we get:

FV2 = (PV * (1 + i)) * (1 + i);

This can also be written as:

FV2 = PV * ((1 + i) * (1 + i));

The last portion is (1 + i) squared, so the formula can be written as:

FV2 = PV * [Link]((1 + i), 2);

where 2 is the number of periods.

Extrapolating, you can guess that the future value after n periods would be:

FVN = PV * [Link]((1 + i), n);

So we can speed up our calculation by using the following implementation:

[Link] = function (interest, n, PV) {


// PV = initial deposit (present value)
// interest = periodic interest rate

[Link] 2004-02-06
Strona 33 z 37

// n = number of payment periods


multiplier = [Link] ((1 + interest), n);
return (PV * multiplier);
};

You'll see that you get the same result as with our earlier brute-force approach:

// How much will $500 appreciate in 10 years at a 5% rate with monthly compoundin
trace ("It will be worth: " + [Link](.05/12, 10*12, 500));

but our new code will be much faster for a large number of periods (when n is large).

However, to perform continuous compounding, the number of periods, n, becomes infinite.


Although the derivation for the formula is beyond the scope of this book, continuous compoundin
can be implemented as follows:

[Link] = function (interest, n, PV) {


// PV = initial deposit (present value)
// interest = periodic interest rate
// n = number of payment periods
multiplier = [Link] (Math.E, n*interest);
return (PV * multiplier);
};
// How much will $500 appreciate in 10 years at a 5% rate with continuous
// compounding?
trace ("It will be worth: " + [Link] (.05, 10, 500));

The above calculations assume an interest rate, i, which may not be known or even predictable i
many scenarios. For example, you can't predict the exact return of the stock market over any
given period (although you can make educated guesses based on historic data if the time period
long enough). So, typically, you should allow the user to specify an interest rate and perform the
calculations under various scenarios.

Remember to represent the interest rate as a decimal, such as 0.01. If you


specify an interest rate of 1.0, it is equivalent to 100% interest! And be sure
to specify the correct interest rate for the chosen number of periods (such
as the monthly interest rate rather than the annual interest rate).

If you are calculating the future value of a fixed investment (such as a bond or certificate of
deposit), you would know the fixed rate it returns. However, you might not be able to predict the
rate of inflation. Everyone knows that inflation reduces the effective value of money in the future
To calculate the so-called real rate of interest, subtract the rate of inflation from the stated intere
rate. For example, if your bond earns 5%, but you expect inflation to be 4%, you can use the
above examples with an interest rate of 1% to calculate how much your money will be worth in t
future relative to today's dollars.

5.16.4 See Also


Recipe 5.6, Recipe 5.17, Recipe 5.18, and Recipe 5.19. For the exact derivation of continuous
compounding and other financial formulas, see any competent college-level financial accounting
applied mathematics book. You can find much of the interesting math online by Googling for
"continuous compounding derivation." For example, see
[Link]

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 34 z 37

Recipe 5.17 Calculating Retirement Savings


5.17.1 Problem
You want to calculate how much money you'll have at retirement, assuming you'll receive a
series of equal periodic payments.

5.17.2 Solution
Calculate the future value of a series of periodic payments using the custom [Link]( )
function.

5.17.3 Discussion
Suppose you are implementing a retirement calculator. People commonly deposit a certain
amount of money each month into their retirement account. Typically, they want to know how
much money they'll accumulate in the account at some time in the future. There are two
components to the accumulation of funds: the interest earned each period, plus the periodic
payment that is added. The total value also includes a third component: the principal in the
account at the beginning of the calculation.

Here is an enhanced version of the futureValue( ) function presented in Recipe 5.16. It


calculates the future value based on an initial payment plus periodic payments. We calculate
the final amount by adding together the appreciation of the principal and the appreciation of the
periodic payments. If the periodic payment is 0, the calculation reduces to the equation from
Recipe 5.16 (in which there is principal and interest only). If there is no initial deposit, you can
use 0 for the PV parameter. You can add this to your [Link] file to use it in other projects.

[Link] = function (interest, n, PMT, PV) {


// PV = initial deposit (present value)
// interest = periodic interest rate
// n = number of payment periods
// PMT = periodic payment
// FV = future value

// Financial equations for a series of payments compounded over n periods:


// multiplier = [Link] ((1 + interest ), n)
// FV of principle = PV * [Link] ((1 + interest ), n)
// = PV * multiplier
// FV of payments = PMT * (([Link] ((1 + interest ), n) - 1) / interest)
// = PMT * ((multiplier-1)/interest)

multiplier = [Link] ((1 + interest), n);


// Add FV of principal and FV of payments together to get total value.
FV = PV * multiplier + PMT * ( (multiplier-1) / interest);

return FV;
};

You can use the custom [Link]( ) method as follows:

// How much money will I have in 30 years if it appreciates at 6%, starting with
// $50,000 and paying $400/month?
trace ([Link](.06/12, 30*12, 400, 50000));

Again, you can consult a financial textbook for the derivation of the preceding formulas. You
can confirm their accuracy by testing them against the value derived from the brute-force
method:

[Link] 2004-02-06
Strona 35 z 37

[Link] = function (interest, n, PMT, PV) {


FV = PV;
for (var periods = interest; periods <= n; periods++) {
FV = FV * (1 + interest) + PMT;
}
return FV;
};

You can and should confirm your calculations against the values returned
by a commercial spreadsheet or actuarial table. For example, our custom
[Link]( ) function returns the same value, as the one obtained in
Microsoft Excel using the Insert Function Financial FV
formula.

5.17.4 See Also


Recipe 5.6, Recipe 5.16, Recipe 5.18, and Recipe 5.19
[ Team LiB ]
[ Team LiB ]

Recipe 5.18 Calculating the Loan (Mortgage) You Can


Afford
5.18.1 Problem
You want to calculate how large a loan you can get assuming you make a given monthly
payment.

5.18.2 Solution
Calculate the present value of a series of equal future periodic payments using the custom
[Link]( ) function.

5.18.3 Discussion
Suppose you are a bank with a big pile of money and you want to lend it to someone to earn
some interest. The borrower might come to you and say, "I can pay you back $1,000 per month
for 30 years. How much money will you loan me?" The answer requires you to calculate how
much the money that you will receive at various times in the future (as a series of periodic
payments) is worth today.

The following custom [Link]( ) function returns the same value as the one obtained in
Microsoft Excel using the Insert Function Financial PV formula. You can add this to
your [Link] file for easy inclusion in other projects.

[Link] = function (i, n, PMT) {


// i = periodic interest rate
// n = number of payment periods
// PMT = periodic payment

// Present value compounded over n periods:


// PV = PMT * ( (multiplier-1)/(i*multiplier))
// where multiplier = [Link] ((1 + i), n)

multiplier = [Link] ((1 + i), n);

[Link] 2004-02-06
Strona 36 z 37

PV = PMT * (multiplier-1) / (i*multiplier);


return PV;
};

You can use the custom [Link]( ) method as follows:

// How much will the bank loan me if I can pay $1000/month for 30 years (6% rate)?
trace ([Link](.06/12, 30*12, 1000));

5.18.4 See Also


Recipe 5.6, Recipe 5.16, Recipe 5.17, and Recipe 5.19. You can consult a financial textbook for
the derivation of the preceding formula. It is essentially based on the reverse of the process
used in Recipe 5.16 to calculate the future value. .

[ Team LiB ]
[ Team LiB ]

Recipe 5.19 Calculating Loan Amortization or Annuities


5.19.1 Problem
You want to determine the monthly payment it takes to pay off a loan (i.e., amortize the loan).
Alternatively, you want to know how much money you can withdraw from a retirement account
on a periodic basis.

5.19.2 Solution
Calculate the payments using the custom [Link]( ) function.

5.19.3 Discussion
Suppose you want to borrow $100,000 from a bank, and you need to know the required
monthly payment to pay back the loan. The answer requires the bank to calculate how much
money is needed to pay off the principal and the interest that accrues before the loan is finally
paid off in full. The calculation is equivalent to the one needed to calculate how much money
you can take out monthly from a retirement account. However, in the case of a so-called
annuity, you start with the lump sum and extract a series of periodic payments. The math still
answers the question, "Given an initial pile of money and the prevailing interest rate, how much
money can I take out (or must I pay back) each month before the balance is zero?"

The following custom [Link]( ) function returns the same value as the one obtained in
Microsoft Excel using the Insert Function Financial PMT formula. You can add this
to your [Link] file for easy inclusion in other projects.

[Link] = function (i, n, PV) {


// PV = initial savings deposit or loan amount (present value)
// i = periodic interest rate
// n = number of payment periods
// PMT = periodic payment

// Calculate the periodic payment needed to amortize (pay back) a loan.


multiplier = [Link]((1 + i), n);

return (PV * i * multiplier / (multiplier - 1));


};

[Link] 2004-02-06
Strona 37 z 37

// Example usage:
// What monthly payment pays back a $100,000 mortgage (30-year fixed at 6%)?
trace("Your monthly payment is " + [Link] (0.06/12, 30*12, 100000));

5.19.4 See Also


Recipe 5.6, Recipe 5.16, Recipe 5.17, and Recipe 5.18. You can consult a financial textbook for
a discussion of annuities and loan amortization or see online resources such as
[Link]
[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 20

[ Team LiB ]

Chapter 6. Arrays
Introduction

Recipe 6.1. Adding Elements to the Start or End of an Array

Recipe 6.2. Looping Through an Array

Recipe 6.3. Searching for Matching Elements in an Array

Recipe 6.4. Removing Elements

Recipe 6.5. Inserting Elements in the Middle of an Array

Recipe 6.6. Converting a String to an Array

Recipe 6.7. Converting an Array to a String

Recipe 6.8. Creating a Separate Copy of an Array

Recipe 6.9. Storing Complex or Multidimensional Data

Recipe 6.10. Sorting or Reversing an Array

Recipe 6.11. Implementing a Custom Sort

Recipe 6.12. Creating an Associative Array

Recipe 6.13. Reading Elements of an Associative Array


[ Team LiB ]
[ Team LiB ]

Introduction
Arrays are essential to successful ActionScript programming. At its most basic, an array can be
thought of as a series of variables that can be referred to using a single, common name, which is
often easier than having separate variable names for each datum. And what's more, the data in
array is ordered and enumeratable, providing a convenient way to organize, sort, and access the
values.

Arrays provide a way of grouping together, organizing, and processing related data elements. Th
concept of an array should not be foreign to you. If fact, we use the concept of arrays in everyda
life all the time. A simple grocery list or to-do list can be thought of as an array. Your address bo
is an array containing people's names and addresses. Libraries keep track of books using an
indexing system whereby each book becomes, conceptually, an element in a library's array. You'
ordinarily use arrays to hold a series of related items, such as playing cards in a deck, as seen in
Recipe 5.10.

In ActionScript, there are two kinds of arrays: integer-indexed and associative. Unless otherwise
specified, the term "array" usually refers to an integer-indexed array. Both types of arrays group
together related data, but they use different means of accessing the data. An integer-indexed ar
uses integers (numbers) as unique identifiers for each element in the array. Such arrays are
ordered by index (i.e., element number), starting from 0. Each element occupies a numbered slo

[Link] 2004-02-06
Strona 2 z 20

in the array. Integer-indexed arrays are ideal for sets of data that you want to work with in
sequential order. Associative arrays use string keys to access each value. You can read more abo
associative arrays in Recipe 6.12.

Integer-indexed arrays are the focus of the majority of the recipes in this chapter, and so you
should know how to create an array first. There are two ways to construct a new array in
ActionScript: with the constructor function or as an array literal. All arrays are members of the
Array class. You can use the Array( ) constructor function to instantiate new array objects in one
three ways:

myArray = new Array( ); // Create an empty array.


myArray = new Array(numElems); // Create an array with numElems undefined elemen
myArray = new Array(elem0,...elemn); // Create an array with specified elements.

The array literal notation also creates a new array but without having to use the constructor
function. Literal notation is convenient for specifying elements at the time of creation, such as:

myArray = ["a", "b", "c"];

Some methods of the Array class modify the existing array on which the method is called, and
others return a new array (offering an indirect way to create arrays).

You can retrieve and set array elements using the array-access operator (square brackets) and t
index of the element you wish to get or set. For example:

// Set the fifth element of the array to "apples" (array indexes start at 0).
myArray[4] = "apples";

// Display the fifth element in the Output window.


trace(myArray[4]); // Displays: apples

ActionScript doesn't care what kind of values you store in arrays. You can store strings, numbers
Booleans, or any kind of objects. And, unlike stricter programming languages, you can even stor
different datatypes in a single array. For example, this array stores a string, an integer, a Boolea
and an object:

myArray = ["a", 2, true, new Object( )];

Unlike many languages, ActionScript doesn't require you to specify the number of elements in an
array when it is declared (a.k.a. dimensioned or allocated).
[ Team LiB ]
[ Team LiB ]

Recipe 6.1 Adding Elements to the Start or End of an Arra


6.1.1 Problem
You want to add elements to an existing array.

6.1.2 Solution
Use the push( ) method to append elements to an array; use the unshift( ) method to insert
elements at the beginning of an array.

6.1.3 Discussion

[Link] 2004-02-06
Strona 3 z 20

You can append elements to an existing array using the [Link]( ) method, passing it one or
more values to be appended:

myArray = new Array( );


[Link]("val 1", "val 2");

You can also append a single element using the array's length property as the index. Because
ActionScript array indexes are zero-relative (meaning that the first index is 0, not 1), the last
element is at an index of [Link] - 1.

myArray[[Link]] = "val 3";

If you try to set an element with an index that does not yet exist, the array is extended to includ
the necessary number of elements automatically (in which case intervening elements are initializ
to undefined). For example, after executing the following statements, myArray contains the
elements ["a", "b", "c", undefined, undefined, "f"]:

myArray = ["a", "b", "c"];


myArray[5] = "f";

Appending elements onto an array is common when you want to build an array incrementally or
when you want to store the history of a user's actions for the purpose of implementing a Back
button or history feature.

To add elements to the beginning of an array, use the unshift( ) method, which shifts the existin
elements by one index position, and inserts the new element at index 0:

// Create an array with four elements: "a", "b", "c", and "d".
myArray = new Array( );
[Link]("a", "b", "c", "d");

// Add "z" to the beginning of the array. This shifts all the other elements so th
// the value "a" moves from index 0 to index 1, etc.
[Link]("z");

// Display the results by looping through the elements. See Recipe 6.2.
for (var i = 0; i < [Link]; i++) {
trace(myArray[i]);
}

Should you add elements to the beginning or the end of an array? That depends on your goal. It
also depends on how you intend to access or remove the elements at a later time. For example,
you want to access items in last-in-first-out (LIFO) order, you might use [Link]( ) to add
elements to an array and [Link]( ) to remove the elements in reverse order.
[ Team LiB ]
[ Team LiB ]

Recipe 6.2 Looping Through an Array


6.2.1 Problem
You want to access or process all of the elements of an array in sequential order.

6.2.2 Solution
Use a for loop that increments an index variable from 0 to [Link] - 1. Use the index to
access each element in turn.

[Link] 2004-02-06
Strona 4 z 20

6.2.3 Discussion
To access the values stored in the elements of an array, loop through the array's elements
using a for loop. Because the first index of an array is 0, the index variable in the for statement
should start at 0. The last index of an array is always 1 less than the length property of that
array. Within the for statement, you can use the loop index variable within square brackets to
access array elements. For example:

myArray = ["a", "b", "c"];


for (var i = 0; i < [Link]; i++) {
// Display the elements in the Output window.
trace("Element " + i + ": " + myArray[i]);
}

The looping index variable (i in the example code) should range from 0 to one less than the
value of the length property. Remember that the last index of an array is always one less than
its length.

Alternatively, you can also use a for statement that loops backward from [Link] - 1 to
0, decrementing by one each time. Looping backward is useful when you want to find the last
matching element rather than the first (see Recipe 6.3). For example:

myArray = ["a", "b", "c"];


for (var i = [Link] - 1; i >= 0; i--){
// Display the elements in the Output window in reverse order.
trace("Element " + i + ": " + myArray[i]);
}

There are many examples in which you might want to loop through all the elements of an array.
For example, by looping through an array containing references to movie clips, you can perform
a particular action on each of the movie clips:

for (var i = 0; i < [Link]; i++){


// Move each movie clip one pixel to the right.
myArray[i]._x++;
}

You can improve the performance of a loop by storing the array's length in a variable rather
than computing it during each loop iteration. For example:

var len = [Link];


for (var i = 0; i < len; i++){
// Move each movie clip one pixel to the right.
myArray[i]._x++;
}

This works as expected only if the length of the array does not change during the loop, which
assumes that elements are not being added or removed.

6.2.4 See Also


See Recipe 5.5 for some correct and incorrect ways to loop through characters in a string. See
Recipe 6.13 for details on enumerating elements of an associative array.

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 5 z 20

Recipe 6.3 Searching for Matching Elements in an Array


6.3.1 Problem
You want to find the first element in an array that matches a specified value.

6.3.2 Solution
Use a for statement to loop through an array, and use a break statement once a match has been
found.

6.3.3 Discussion
When you are searching for the first element in an array that matches a specified value, you sho
use a for statement, as shown in Recipe 6.2, but with the addition of a break statement to exit t
loop once the match has been found.

A break statement used within a for statement causes the loop to exit once it is encountered. No
that the break statement is generally placed within an if statement, so it is reached only when a
certain condition is met. The importance of the break statement when searching for the first
matching element is two-fold. First of all, you should not needlessly loop through the remaining
elements of an array once the match has been found, since it would waste processing time. In th
following example, the break statement exits the loop after the second iteration, saving six more
needless iterations. Imagine the savings if there were a thousand more elements. Furthermore,
break statement is vital when searching for the first match because it ensures that only the first
element is matched and that subsequent matches are ignored. If the break statement is omitted
the following example, all matching elements are displayed, as opposed to the first one only.

// Create an array with eight elements.


myArray = ["a", "b", "c", "d", "a", "b", "c", "d"];

// Specify what we want to search for.


searchString = "b";

// Use a for statement to loop through, potentially, all the elements of the arra
for (var i = 0; i < [Link]; i++) {

// Check whether the current element matches the search value.


if (myArray[i] == searchString) {

// Do something with the matching element, if necessary. In this example, disp


// a message in the Output window for testing purposes.
trace("Element with index " + i + " found to match " + searchString);

// Include a break statement to exit the for loop once a match has been found
break;
}
}

You can also search for the last matching element of an array by reversing the order in which the
statement loops through the array. Initialize the index variable to [Link] - 1 and loop un
reaches 0 by decrementing the index variable, as follows.

myArray = ["a", "b", "c", "d", "a", "b", "c", "d"];

searchString = "b";

[Link] 2004-02-06
Strona 6 z 20

// Loop backward through the array. In this example, we'll find the "b" at index
for (var i = [Link] - 1; i >= 0; i--) {
if (myArray[i] == searchString) {
trace("Element with index " + i + " found to match " + searchString);
break;
}
}

6.3.4 See Also


Recipe 6.10
[ Team LiB ]
[ Team LiB ]

Recipe 6.4 Removing Elements


6.4.1 Problem
You want to remove an element or elements from an array and shift any remaining elements to
the vacant indexes.

6.4.2 Solution
Use the splice( ) method to remove elements in the middle of the array. You can use pop( ) to
remove the last element or shift( ) to remove the first element.

6.4.3 Discussion
You can remove elements from an array starting at a specified index using the splice( ) method.
When using splice( ) to delete elements, you should pass it two parameters:

start

The index of the array from which to start deleting elements.

deleteCount

The number of elements to delete. If this value is undefined, all the elements from start
the end of the array are deleted:

myArray = ["a", "b", "c", "d"];

// Remove one element from myArray starting at index 1.


[Link](1, 1);

// Display the results. The array now contains three elements: "a", "c", and
for (var i = 0; i < [Link]; i++) {
trace(myArray[i]);
}

The splice( ) method also returns a new array containing the deleted elements. For example:

myArray = ["a", "b", "c", "d"];

// Remove two elements from myArray starting at index 0.


deletedElems = [Link](0, 2);

[Link] 2004-02-06
Strona 7 z 20

// Display the deleted elements: "a" and "b".


for (var i = 0; i < [Link]; i++) {
trace(deletedElems [i]);
}

If deleting a single element from the beginning or end of the array, you can use the shift( ) and p
( ) methods. The shift( ) method removes the first element of the array and returns its value. Th
pop( ) method removes the last element of the array and returns its value.

myArray = ["a", "b", "c", "d"];

// Remove the first element and display its value.


trace([Link]( ));

// Remove the last element and display its value.


trace([Link]( ));

// Display the remaining elements. The array has two elements left: "b" and "c".
for (var i = 0; i < [Link]; i++) {
trace(myArray[i]);
}
[ Team LiB ]
[ Team LiB ]

Recipe 6.5 Inserting Elements in the Middle of an Array


6.5.1 Problem
You want to insert elements in the middle of an array.

6.5.2 Solution
Use the splice( ) method.

6.5.3 Discussion
You can use the splice( ) method to insert elements as well as delete them. Any values passed
as parameters to the splice( ) method following the first and second parameters are inserted
into the array starting at the index specified by the start parameter. All existing elements
following that index are shifted to accommodate for the inserted values. If 0 is passed to the
splice( ) method for the deleteCount parameter, no elements are deleted, but the new values
are inserted:

myArray = ["a" ,"b", "c", "d"];

// Insert three string values--"one", "two", and "three"--starting at index 1.


[Link](1, 0, "one", "two", "three");

// myArray now contains seven elements: "a", "one", "two", "three", "b", "c", and
// "d".
for (var i = 0; i < [Link]; i++) {
trace(myArray[i]);
}

You can also delete elements and insert new elements at the same time:

myArray = ["a" ,"b", "c", "d"];

[Link] 2004-02-06
Strona 8 z 20

// Remove two elements and insert three more into myArray starting at index 1.
[Link](1, 2, "one", "two", "three");

// myArray now contains five elements: "a", "one", "two", "three", and "d".
for (var i = 0; i < [Link]; i++) {
trace(myArray[i]);
}
[ Team LiB ]
[ Team LiB ]

Recipe 6.6 Converting a String to an Array


6.6.1 Problem
You have a list of values as a string and you want to parse it into an array of separate
elements.

6.6.2 Solution
Use the [Link]( ) method.

6.6.3 Discussion
You can split any list of values (a string) into an array using the split( ) method of the String
class. The list must be delimited by a uniform substring. For example, the list
"Susan,Robert,Paula" is one in which the elements of the list are delimited by commas.

The split( ) method takes up to two parameters:

delimiter

The substring that is used to delimit the elements of the list. If undefined, the entire list
is placed into the first element of the new array.

limit

The maximum number of elements to place into the new array. If undefined, all the
elements of the list are placed into the new array.

You can use a space as the delimiter to split a string into an array of words:

myList = "Peter Piper picked a peck of pickled peppers";

// Split the string using the space as the delimiter. This puts
// each word into an element of the new array, myArray.
myArray = [Link](" ");

The split( ) method can be extremely useful when values are loaded into Flash using
loadVariables( ) or a LoadVars object. For example, a list of names might be retrieved from the
server as a string:

names=Michael,Peter,Linda,Gerome,Catherine

You can make it easier to use the names by parsing them into an array using the split( )
method:

[Link] 2004-02-06
Strona 9 z 20

namesArray = [Link](",");

6.6.4 See Also


Recipe 6.7

[ Team LiB ]
[ Team LiB ]

Recipe 6.7 Converting an Array to a String


6.7.1 Problem
You want to convert an array to a string.

6.7.2 Solution
Use the join( ) method.

6.7.3 Discussion
ActionScript provides you with a built-in way to quickly convert arrays to strings (assuming, of
course, that the array elements themselves are either strings or another datatype that
ActionScript can automatically cast to a string) using the join( ) method. You should provide the
join( ) method with a parameter that tells Flash which delimiter to use to join the elements:

myArray = ["a", "b", "c"];


trace([Link]("|")); // Displays: a|b|c

If you don't provide a delimiter, Flash uses a comma by default:

myArray = ["a", "b", "c"];


trace([Link]( )); // Displays: a,b,c

The toString( ) method does the same thing as calling the join( ) method
either with no parameters or with the comma as the parameter. And, in
fact, if you try to use an array in a situation in which a string is required,
Flash will automatically call the toString( ) method:

myArray = ["a", "b", "c"];


trace(myArray); // Displays: a,b,c

6.7.4 See Also


Recipe 6.6

[ Team LiB ]
[ Team LiB ]

Recipe 6.8 Creating a Separate Copy of an Array

[Link] 2004-02-06
Strona 10 z 20

6.8.1 Problem
You want to copy an array, but y ou want the duplicate to be dissociated from the original array.

6.8.2 Solution
Use the concat( ) method or the slice( ) method.

6.8.3 Discussion
Because arrays are a composite datatype, they are copied and compared differently from primitiv
data. A variable that holds an array doesn't truly contain all of the array's data. Instead, the vari
simply points to the place in the computer's memory where the array's data resides. This makes
sense from an optimization standpoint. Primitive data tends to be small, such as a single numbe
a short string. But composite data, such as an array, can be very large. It would be very inefficie
to copy an entire array every time you wanted to perform an operation on it or pass it to a funct
Therefore, when you try to copy an array, ActionScript doesn't necessarily make a separate copy
the array's data. A simple example illustrates this.

First, let's look at how primitive data is copied from the variable myNumber to another variable,
myOtherNumber:

// Assign the number 5 to a variable.


var myNumber = 5;
// Copy myNumber's value to another variable, myOtherNumber.
var myOtherNumber = myNumber;
// Change myNumber's value.
myNumber = 29;

trace(myNumber); // Displays: 29
trace(myOtherNumber); // Displays: 5

When the copy is made, the contents of myNumber are copied to myOtherNumber. After the copy i
made, subsequent changes to myNumber have no effect on myOtherNumber because primitive dat
said to be copied by value.

Now, let's look at an analogous operation with arrays, but note the substantial difference from th
preceding example. Data is copied from the variable myArray to the variable myOtherArray, but
two variables remain joined! When the value of myArray changes, the changes are reflected in
myOtherArray!

// Assign elements of an array.


var myArray = ["a", "b", "c"];
// Copy myArray to another variable, myOtherArray.
var myOtherArray = myArray;

// Both arrays contain the same values, as expected.


trace(myArray); // Displays: "a,b,c"
trace(myOtherArray); // Displays: "a,b,c"

// Change myArray's value.


myArray = ["d", "e", "f"];

// Surprise! Both arrays contain the new values. The old values are lost!
trace(myArray); // Displays: "d,e,f"
trace(myOtherArray); // Displays: "d,e,f" (not "a,b,c")

Is the relationship between two copies of an array a good thing or a bad thing? The answer depe

[Link] 2004-02-06
Strona 11 z 20

on what you expect and what you need to accomplish. Let's first understand what is happening,
then learn how to address it.

In the preceding example, the following line does not make a copy of myArray's contents, as it
would if myArray held a primitive datum:

var myOtherArray = myArray;

Instead, it says to Flash, "Make myOtherArray point to whatever myArray points to, even if the
contents change in the future." So the two variables myArray and myOtherArray always point to
same data in memory. If it helps, you can think of this arrangement as being similar to a file
shortcut on Windows (which is called an alias on the Macintosh). A shortcut simply points to ano
file located elsewhere. Whether you open the original file directly or access it via the shortcut, th
is only one physical file that contains the content of interest. If the file's contents change, the
shortcut still offers access to the current contents of the file. If you wanted two independent files
you'd have to duplicate the original file rather than simply create a shortcut to it.

So, is it a good thing if two arrays refer to the same data? As explained earlier, in the natural co
of things, it increases efficiency to avoid copying the contents of an array unnecessarily. But you
might wish to operate on a copy of an array so as not to alter the original. For example, in Recip
5.10, we set up a deck of cards as an array and make a copy of it before dealing. That way, whe
we want a new deck, we can easily make a new copy from the pristine original without having to
recreate it from scratch.

You can create a copy of an array that is separate from the original using concat( ):

// Assign elements of an array.


var myArray = ["a", "b", "c"];
// Create an independent copy of myArray using concat( ), which returns a new ar
var myOtherArray = [Link]( );

// Both arrays contain the same values, as expected.


trace(myArray); // Displays: "a,b,c"
trace(myOtherArray); // Displays: "a,b,c"

// Change myArray's value.


myArray = ["d", "e", "f"];

// Unlike preceding examples, the arrays are independent.


trace(myArray); // Displays: "d,e,f"
trace(myOtherArray); // Displays: "a,b,c"

In line 4 of the preceding example, you could also use slice( ) instead of concat( ), as follows:

var myOtherArray = [Link](0);

[ Team LiB ]
[ Team LiB ]

Recipe 6.9 Storing Complex or Multidimensional Data


6.9.1 Problem
You have two or more sets of related data and you want to be able to keep track of the relations
between the elements.

[Link] 2004-02-06
Strona 12 z 20

6.9.2 Solution
Use parallel arrays, an array of arrays (a multidimensional array), or an array of objects.

6.9.3 Discussion
You can create two or more parallel arrays in which the elements with the same index in each ar
are related. For example, the beginGradientFill( ) method, discussed in Recipe 4.10, uses three
parallel arrays for the colors, alphas, and ratios of the values used in the gradient. In each array
the elements with the same index correspond to one another.

To create parallel arrays, populate multiple arrays such that the elements with the same index
correspond to one another. When you use parallel arrays, you can easily retrieve related data sin
the indexes are the same across the arrays. For example:

colors = ["maroon", "beige", "blue", "gray"];


years = [1997, 2000, 1985, 1983];
makes = ["Honda", "Chrysler", "Mercedes", "Fiat"];

// Loop through the arrays. Since each array is the same length, you can use the
// length property of any of them in the for statement. Here, we use [Link]
for (var i = 0; i < [Link]; i++) {
// Outputs:
// A maroon 1997 Honda
// A beige 2000 Chrysler
// A blue 1985 Mercedes
// A gray 1983 Fiat

// Display the elements with corresponding indexes from the arrays.


trace("A " + colors[i] + " " + years[i] + " " + makes[i]);
}

Another option for working with multiple sets of data is to create a multidimensional array, which
an array of arrays (i.e., an array in which each element is another array):

// Create an array, cars, and populate it with elements that are arrays. Each elem
// array represents a car and contains three elements (make, year, and color).
cars = new Array( );
[Link](["maroon", 1997, "Honda"]);
[Link](["beige", 2000, "Chrysler"]);
[Link](["blue", 1985, "Mercedes"]);
[Link](["gray", 1983, "Fiat"]);

// Loop through the elements of the cars array.


for (var i = 0; i < [Link]; i++) {
// The output is the same as in the earlier parallel arrays example:
// A maroon 1997 Honda
// A beige 2000 Chrysler
// A blue 1985 Mercedes
// A gray 1983 Fiat

// Output each element of each subarray, cars[i]. Note the use of two successiv
// indexes in brackets, such as cars[i][0].
trace("A " + cars[i][0] + " " + cars[i][1] + " " + cars[i][2]);
}

Here is another way to view the two-dimensional cars array's contents. This displays the elemen
in a long list (the formatting isn't as nice as in the previous example, but it shows the array
structure more clearly).

[Link] 2004-02-06
Strona 13 z 20

// Loop through the elements of the cars array.


for (var i = 0; i < [Link]; i++) {
// Loop through the elements of each subarray, cars[i].
for (var j = 0; j < cars[i].length; j++) {
// Note the use of two successive indexes in brackets, cars[i][j].
trace("Element [" + i + "][" + j + "] contains: " + cars[i][j]);
}
}

In the preceding example (the array of arrays), it is hard to discern the meaning of something lik
cars[i][0] or cars[i][j]. Furthermore, if the order of elements in a subarray changes, we wo
have to modify our code (or it might erroneously display "A Honda maroon 1997" instead of "A
maroon 1997 Honda").

One alternative is to work with related data using an array of objects. This technique is similar to
working with an array of arrays, but it offers the advantage of named properties. When you use
array of arrays, you must reference each value by its numbered index. However, when you use a
array of objects, you can reference the data by property name instead of index number. We can
specify the properties of the object in any order we like, because we'll refer to them later by nam
not by number.

// Create an array, cars, and populate it with objects. Each object has a make
// property, a year property, and a color property.
cars = new Array( );
// Here, we use object literals to define three properties for each car and then
// the object literals to the main array.
[Link]({make: "Honda", year: 1997, color: "maroon"});
[Link]({make: "Chrysler", year: 2000, color: "beige"});
[Link]({make: "Mercedes", year: 1985, color: "blue"});
[Link]({make: "Fiat", year: 1983, color: "gray"});

// Loop through the cars array.


for (var i = 0; i < [Link]; i++) {
// The output is the same as in the earlier examples, but each value is referen
// by its property name, which is more programmer-friendly.
trace("A " + cars[i].color + " " + cars[i].year + " " + cars[i].make);
}

6.9.4 See Also


Recipe 6.12 covers associative arrays, in which elements are accessed by name instead of numb

[ Team LiB ]
[ Team LiB ]

Recipe 6.10 Sorting or Reversing an Array


6.10.1 Problem
You want to sort the elements of an array.

6.10.2 Solution
Use the sort( ) method. For arrays of objects, you can also use the sortOn( ) method.

6.10.3 Discussion

[Link] 2004-02-06
Strona 14 z 20

You can perform a simple sort on an array using the sort( ) method. The sort( ) method,
without any parameters, sorts the elements of an array in ascending order. Elements are sorted
according to the Unicode code points of the characters in the string (roughly alphabetical for
Western European languages). However, the sort is also case-sensitive, and it sorts numbers
"alphabetically" instead of numerically. See Recipe 6.11 for details on creating custom sorting
algorithms that are case-insensitive.

words = ["tricycle", "relative", "aardvark", "jargon"];


[Link]( );
trace(words); // Displays: aardvark,jargon,relative,tricycle

The reverse( ) method reverses the order of the elements in an array:

words = ["tricycle", "relative", "aardvark", "jargon"];


[Link]( );
trace(words); // Displays: jargon,aardvark,relative,tricycle

If you want to sort the elements of an array in descending order, use the sort( ) method
followed by the reverse( ) method:

words = ["tricycle", "relative", "aardvark", "jargon"];


[Link]( );
[Link]( );
trace(words); // Displays: tricycle,relative,jargon,aardvark

You can sort arrays of objects using the sortOn( ) method. The sortOn( ) method requires a
string parameter specifying the name of the property on which to sort the elements:

cars = new Array( );


[Link]({make: "Honda", year: 1997, color: "maroon"});
[Link]({make: "Chrysler", year: 2000, color: "beige"});
[Link]({make: "Mercedes", year: 1985, color: "blue"});
[Link]({make: "Fiat", year: 1983, color: "gray"});

// Sort the cars array according to the year property of each element.
[Link]("year");

for (var i = 0; i < [Link]; i++) {


// Outputs:
// A gray 1983 Fiat
// A blue 1985 Mercedes
// A maroon 1997 Honda
// A beige 2000 Chrysler
trace("A " + cars[i].color + " " + cars[i].year + " " + cars[i].make);
}

To sort the elements of an array of objects in descending order, call the reverse( ) method after
calling sortOn( ).

Sorted arrays can be useful in many scenarios. For example, if you want to display the
elements of an array in a UI component or a text field, you often want to list the elements in
alphabetical order.

6.10.4 See Also


The sort( ) and sortOn( ) methods make changes to the order of the original array; they do not
return a new array. See Recipe 6.8 to make a separate copy of an array on which you can
perform destructive operations. See Recipe 6.11 for details on case-insensitive sorting and
numeric sorting, as well as custom sorting in general.

[Link] 2004-02-06
Strona 15 z 20

[ Team LiB ]
[ Team LiB ]

Recipe 6.11 Implementing a Custom Sort


6.11.1 Problem
You want to sort an array in a way such that the basic sort( ) and sortOn( ) methods do not suffi
You want to sort an array in a case-insensitive manner, perform a numeric sort, or use another c
or multikey criterion.

6.11.2 Solution
Use the sort( ) method and pass it a reference to a compare function.

6.11.3 Discussion
If you want complete control over sorting criteria, use the sort( ) method with a custom compare
function (also called a sorter function). The compare function is called repeatedly by the sort( ) m
to reorder two elements of the array at a time. The compare function receives two parameters (l
call them a and b), which it should compare to determine which one should be ordered first. You
custom compare function should return a positive number, a negative number, or 0, depending o
the elements are to be sorted. If the function returns a negative number, a is ordered before b.
function returns 0, then the current order is preserved. If the function returns a positive number
ordered after b. Your compare function is called with every relevant combination of elements unt
entire array has been properly ordered. Using a custom compare function is easier than it sound
do not need to concern yourself with the details of the sorting algorithm; you simply specify the
criteria for comparing any two elements.

Here is a simple compare function that performs a case-insensitive sort:

function insensitiveSorter(a, b) {
itemOne = [Link]( );
itemTwo = [Link]( );
if (itemOne > itemTwo) {
return 1;
} else if (itemOne < itemTwo) {
return -1;
} else {
return 0
}
}

Case-insensitive sorting is useful when you have an array of values with mixed cases, because F
automatically sorts all uppercase letters before lowercase letters by default:

myArray = ["cardinal", "California", "camel", "Chicago"];


[Link]( );
trace(myArray); // Displays: California,Chicago,camel,cardinal

However, when you use the case-insensitive sort utilizing the custom sorter function as defined
previously, Flash sorts the values in alphabetical order regardless of case:

myArray = ["cardinal", "California", "camel", "Chicago"];


[Link](insensitiveSorter);
trace(myArray); // Displays: California,camel,cardinal,Chicago

[Link] 2004-02-06
Strona 16 z 20

When sorting numbers, the standard sort( ) method produces unexpected results. Numbers are s
"alphabetically" instead of numerically. After the following example, myArray is [1,12,2,3,4,43],
[1,2,3,4,12,43]:

myArray = [1, 12, 2, 3, 43, 4];


[Link]( )
trace (myArray); // Displays: 1,12,2,3,4,43 not 1,2,3,4,12,43

Here is a simple compare function that performs a numeric sort, instead of a string-based sort, e
the elements are strings, such as "1", "2", "3":

function numberSorter(a, b) {
itemOne = parseInt(a);
itemTwo = parseInt(b);
if (itemOne > itemTwo) {
return 1;
} else if (itemOne < itemTwo) {
return -1;
} else {
return 0
}
}

You can use it as follows:

myArray = [1, 12, 2, 3, 43, 4];


[Link](numberSorter);
trace (myArray); // Displays: 1,2,3,4,12,43

In the numberSorter( ) function, the difference between the two numbers yields a positive result
greater than b, and a negative result if the opposite is true. It yields 0 if they are equal. Therefo
numeric comparisons of the previous type can be simplified as follows.

function numberSorter(a, b) {
itemOne = parseInt(a);
itemTwo = parseInt(b);
return (itemOne - itemTwo);
}

You can easily modify the sort function to sort the numbers in reverse order, as follows:

function reverseSorter(a, b) {
itemOne = parseInt(a);
itemTwo = parseInt(b);
return (itemTwo - itemOne);
}

Here is a full-fledged example that sorts the cars array by make and year:

// Create an array with elements that have some matching make properties but
// different year and color properties.
cars = new Array( );
[Link]({make: "Honda", year: 1997, color: "maroon"});
[Link]({make: "Chrysler", year: 2000, color: "beige"});
[Link]({make: "Mercedes", year: 1985, color: "blue"});
[Link]({make: "Fiat", year: 1983, color: "gray"});
[Link]({make: "Honda", year: 1982, color: "white"});
[Link]({make: "Chrysler", year: 1999, color: "green"});
[Link]({make: "Mercedes", year: 2002, color: "tan"});
[Link]({make: "Fiat", year: 1981, color: "brown"});

[Link] 2004-02-06
Strona 17 z 20

// Create the custom compare function. The function is always passed two elements
// parameters. It is convenient to call them a and b.
function sorter(a, b) {

// If the make property of a is larger than (meaning it comes alphabetically af


// the make property of b, return 1 to sort a after b. If the make property of
// less than the make property of b, then return -1 to sort a before b. Otherwi
// (if [Link] and [Link] are the same), perform the comparison on the year
// property. We use [Link]( ) to ensure a case-insensitive compari
// We also convert the year to an integer and use the aforementioned shortcut f
// numeric comparison.
makeOne = [Link]( );
makeTwo = [Link]( );
if (makeOne > makeTwo ) {
return 1;
} else if (makeOne < makeTwo) {
return -1;
} else {
return (parseInt([Link]) - parseInt([Link]))
}
}

// Call the sort( ) method and pass it a reference to the sorter( ) compare fun
[Link](sorter);

// Loop through the array and output the results.


for (var i = 0; i < [Link]; i++) {
// Displays the results alphabetically by make, then by year:
// A green 1999 Chrysler
// A beige 2000 Chrysler
// A brown 1981 Fiat
// A gray 1983 Fiat
// A white 1982 Honda
// A maroon 1997 Honda
// A blue 1985 Mercedes
// A tan 2002 Mercedes

trace("A " + cars[i].color + " " + cars[i].year + " " + cars[i].make);


}

[ Team LiB ]
[ Team LiB ]

Recipe 6.12 Creating an Associative Array


6.12.1 Problem
You want to create an array that uses named elements instead of numbered indexes.

6.12.2 Solution
Create an associative array.

6.12.3 Discussion
When you are working with sets of data in which each element has a specific meaning or
importance, a typical, number-indexed array does not always suffice.

[Link] 2004-02-06
Strona 18 z 20

For example, if you are working with a set of data such as the names of members of a
committee, a number-indexed array is sufficient:

members = new Array("Franklin", "Gina", "Sindhu");

However, if each member of the committee plays a special role, a standard array offers no way
to indicate that. To address the issue, you can use an associative array (sometimes called a
hash table or simply an object). An associative array uses named elements rather than numeric
indexes. The names used to refer to elements are often called keys or properties. The keys can
give a meaningful context to the associated element value.

You can create an associative array in ActionScript using object literal notation (which will be
shown shortly) or by adding elements to an object. Despite their name, you don't use the Array
class to create associative arrays. The Array class provides methods and properties that work
with number-indexed arrays only and not with associative arrays. Associative arrays must be
instances of the Object class. Technically, since the Object class is the base class for all
ActionScript classes, all ActionScript objects are associative arrays. You can use this fact to your
advantage when working with movie clips (see Recipe 7.10). But when you are creating an
object explicitly for the purpose of forming an associative array, you should use an instance of
the generic Object class.

One way you can create an associative array is by using object literal notation. With this
technique you use curly braces ({ }) to enclose a comma-delimited list of keys and values,
which are separated by a colon (:). Here is an example:

members = {scribe: "Franklin", chairperson: "Gina", treasurer: "Sindhu"};

For readability, this can be written as:

members = {scribe: "Franklin",


chairperson: "Gina",
treasurer: "Sindhu"};

You can also create an associative array using the following multiline technique with the Object
constructor. Although the object literal notation is fine for creating small associative arrays in a
single step, you should use the Object constructor technique for creating larger associative
arrays. It improves readability and lets you add properties to an associative array by assigning
the properties (keys) on subsequent lines. For example:

members = new Object( );


[Link] = "Franklin";
[Link] = "Gina";
[Link] = "Sindhu";

Although using an Object constructor is more common, you can initialize the associative array
object using an empty object literal in place of the Object constructor:

members = {};

You can retrieve the values from an associative array in two ways. The first way is to access the
elements using property notation (with the dot operator):

trace([Link]); // Displays: Franklin

The other option for retrieving values from an associative array is using array-access notation.
To use array-access notation, reference the associative array followed by the array-access
operator ([]). Within the array-access operator, you must use the string value of the name of
the key you wish to access:

[Link] 2004-02-06
Strona 19 z 20

trace(members["scribe"]); // Displays: Franklin

Array-access notation is extremely useful in situations in which there are multiple keys with
names in a sequence. This is because you can dynamically generate the key string value,
whereas you cannot do this with property notation. For example:

members = new Object( );


members.councilperson1 = "Beatrice";
members.councilperson2 = "Danny";
members.councilperson3 = "Vladamir";

for (var i = 1; i <= 3; i++) {


trace(members["councilperson" + i];
}

You can use either the property notation or array-access notation to read or write the values of
an associative array:

members = new Object( );


members["councilperson"] = "Ruthie";
trace([Link]); // Displays: Ruthie
[Link] = "Rebecca";
trace(members["councilperson"]); // Displays: Rebecca

6.12.4 See Also


Recipe 6.13 contains more details on accessing named elements of an associative array.

[ Team LiB ]
[ Team LiB ]

Recipe 6.13 Reading Elements of an Associative Array


6.13.1 Problem
You want to enumerate the elements of an associative array.

6.13.2 Solution
Use a for . . . in statement.

6.13.3 Discussion
You can iterate through the elements of integer-indexed arrays using a for statement. However,
named elements in associative arrays cannot be accessed by a numeric index, and the order of
associative array elements is not guaranteed, regardless of the order in which the elements are
added to the array.

Fortunately, you can loop through the enumerable elements of an associative array using a for .
. . in statement. That is, a for . . . in statement iterates through all the readable properties of
the specified object. The syntax for a for . . . in statement is as follows:

for (key in object) {


// Actions
}

The for . . . in statement doesn't require an explicit update statement because the number of

[Link] 2004-02-06
Strona 20 z 20

loop iterations is determined by the number of properties in the object being examined. Note
that key is a variable name that will be used to store the property name during each iteration,
not the name of a specific property or key. On the other hand, object is the specific object
whose properties you want to read. For example:

members = new Object( );


[Link] = "Franklin";
[Link] = "Gina";
[Link] = "Sindhu";

// Use a for . . . in statement to loop through all the elements.


for (var role in members) {
// Outputs:
// treasurer: Sindhu
// chairperson: Gina
// scribe: Franklin
trace(role + ": " + members[role]);
}

When you use a for . . . in statement, you must use array-access notation (square brackets)
with the associative array. If you try to use property notation (with the dot operator) it will not
work properly. This is because the value that is assigned to the key iterator variable is the
string name of the key, not the key's identifier.

A for . . . in loop does not display all built-in properties of an object. For example, it displays
custom properties added at runtime, but it does not enumerate methods of built-in objects,
even though they are stored in object properties.

6.13.4 See Also


Recipe 6.2 and Recipe 7.10

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 41

[ Team LiB ]

Chapter 7. Movie Clips


Introduction

Recipe 7.1. Referring to Movie Clips via ActionScript

Recipe 7.2. Targeting Movie Clips with Dynamic Names

Recipe 7.3. Affecting Playback

Recipe 7.4. Reversing Playback

Recipe 7.5. Using Movie Clips as Buttons

Recipe 7.6. Defining Hit Areas for Movie Clips

Recipe 7.7. Checking for Mouseover

Recipe 7.8. Performing Repeated Actions on Movie Clips

Recipe 7.9. Fading a Movie Clip

Recipe 7.10. Discovering Nested Movie Clips

Recipe 7.11. Getting Unique Depths

Recipe 7.12. Getting a Movie Clip's Boundaries

Recipe 7.13. Creating a Draggable Movie Clip

Recipe 7.14. Creating a Custom Mouse Pointer

Recipe 7.15. Checking for Overlapping Movie Clips (Performing Hit Tests)

Recipe 7.16. Changing Stacking Order

Recipe 7.17. Converting Between Coordinate Spaces

Recipe 7.18. Duplicating Movie Clips

Recipe 7.19. Adding Movie Clips from the Library with ActionScript
[ Team LiB ]
[ Team LiB ]

Introduction
Movie clips are at the very core of ActionScript. Without movie clips there would be no visual
element to a Flash movie, so you can see why they are incredibly important.

Unlike many other object types, movie clips are never instantiated using a constructor function
(the MovieClip class constructor is used only when creating subclasses, as in Recipe 12.12).
Instead, movie clips are created in one of four ways:

[Link] 2004-02-06
Strona 2 z 41

z Manually (at authoring time)

z Attached from the Library at runtime, using attachMovie( )

z Duplicated from another movie clip at runtime, using duplicateMovieClip( )

z Created as a new, empty movie clip at runtime, using createEmptyMovieClip( )

Once a movie clip has been instantiated, it can be controlled in a variety of ways. The timelines
of movie clips can be controlled using playback methods such as play( ), stop( ), and
gotoAndPlay( ). Movie clips can be assigned actions that occur when certain events are
triggered, and movie clip attributes can be controlled such that you can affect position, rotation,
transparency, and so on.

To control a movie clip using ActionScript, you must know the target path to that instance. Each
movie clip in a movie can be targeted in one of two ways: using a relative path or an absolute
path. In relative paths there is a known relationship between the timeline from which the
instructions are being issued and the movie clip to which they are being addressed. For
example, a movie clip can address itself in a relative fashion using the keyword this:

// Instruct a movie clip to play from within its own timeline.


[Link]( );

Generally, if the this keyword is omitted, it is assumed:

// Instruct a nested movie clip to set its x position to 50.


[Link]._x = 50;

// Perform same task. In this example, this is assumed.


myNestedMovieClip._x = 50;

However, in some cases, omitting this can be problematic because there are global functions
with the same names as movie clip methods. For example, if you use the command stop( ),
Flash might think you are using the global stop( ) function rather than the [Link]( )
method. Therefore, as a best practice, you should always explicitly use this when referring to
the current timeline rather than relying on Flash to assume it.

You can also target a nested movie clip's parent using the _parent keyword.

When you use _parent, you should always use an explicit reference to
the starting point. In some cases Flash assumes this, but in other cases
it does not, leading to unexpected results. To solve the problem, specify
the movie clip you are starting from, as seen in the following examples:

// Set the rotation of a parent movie clip to 36.


this._parent._rotation = 36;

// Set the alpha of a parent clip's parent to 50.


this._parent._parent._alpha = 50;

Absolute paths do not require you to know the relationship of the timeline from which the
instructions are being issued to the targeted movie clip. Rather, you need to know only the full
path to the movie clip from the main, or root, movie clip. The main movie clip in a Flash movie
is given a special name, _root. Therefore, absolute target paths begin with _root (or _leveln
in some cases, though this is not recommended):

[Link] 2004-02-06
Strona 3 z 41

// Instruct myNestedMovieClip to play. This instruction can be issued from any


// timeline with success.
_root.[Link]( );

Using the global function loadMovieNum( ), it is possible to load .swf files


into different levels within the same movie. In these cases, each movie
can be referenced using absolute paths that begin with _level0,
_level1, _level2, etc. However, the loadMovie( ) method is the
preferred technique for loading content into your movie, and it does not
use levels (see Recipe 15.1).

[ Team LiB ]
[ Team LiB ]

Recipe 7.1 Referring to Movie Clips via ActionScript


7.1.1 Problem
You need to refer to a movie clip programmatically.

7.1.2 Solution
If you are creating the movie clip at authoring time, select the movie clip on stage and specify
an instance name for it using the Property inspector. Then you can refer to it by that name via
ActionScript.

7.1.3 Discussion
We saw in the Introduction how to refer to movie clips throughout your ActionScript code. But
what names should you substitute for myMovieClip or myNestedMovieClip in the earlier
examples to refer to your own movie clips?

The instance name you use in your code must match the name of the movie clip instance on
stage, not the name of the Library symbol from which the clip is derived. Therefore, you must
set the clip's instance name using the Property inspector before you can refer to it from
ActionScript (see Figure 1-4). For example, if you've set the instance name of a movie clip to
"circle_mc" using the Property inspector, you can control it via ActionScript:

circle_mc._xscale = 200;

7.1.4 See Also


For information on working with runtime-generated movie clips, see Recipe 7.2, Recipe 7.18,
and Recipe 7.19. Also refer to Recipe 3.1.
[ Team LiB ]
[ Team LiB ]

Recipe 7.2 Targeting Movie Clips with Dynamic Names


7.2.1 Problem
You want to target a movie clip in which the clip's name is generated dynamically.

7.2.2 Solution

[Link] 2004-02-06
Strona 4 z 41

Use array-access notation (square brackets) and string concatenation (the + operator).

7.2.3 Discussion
If you know a movie clip's name, you can target it using standard dot notation:

// Target a movie clip named myMovieClipA that is within a clip named holderClip
// is, in turn, within _root.
_root.[Link]._x = 25;

However, the situation is a little different when the movie clip's name (or any part of the path) is
explicitly known. For example, if you have the movie clip's name stored in a variable, you may m
the common mistake of trying to use the variable name within the target path:

// This will not work! It will look for a movie clip named "myVar" instead of a m
// clip with the same name as the value of myVar.
_root.[Link]._x = 25; // Wrong!

Instead, you should use array-access notation. All objects, including movie clips, can be treated
associative arrays. This means that you can access any element of a movie clip—even a nested
movie clip—using the array-access operator ([]). Note that while standard dot notation expects
movie clip reference, array-access notation expects a string or a variable that contains a string
value:

// This works! It evaluates myVar through the use of array-access notation. It


// targets the clip within holderClip that has the same name as the value of myVa
_root.holderClip[myVar]._x = 25;

Another common mistake when using array-access notation is to accidentally use a dot before th
array-access operator:

// This will not work! There should not be a dot between holderClip and the
// array-access operator.
_root.holderClip.[myVar]._x = 25; // Wrong!

Using array-access notation is extremely beneficial when you have sequentially named movie clip
that you want to target:

// These few lines of code can set the _visible property to false for 50 movie cl
// named myMovieClip0 thru myMovieClip49.
for (var i = 0; i < 50; i++) {
_root["myMovieClip" + i]._visible = false;
}

7.2.4 See Also


Recipe 7.1
[ Team LiB ]
[ Team LiB ]

Recipe 7.3 Affecting Playback


7.3.1 Problem
You want to control the playhead of a movie clip's timeline.

[Link] 2004-02-06
Strona 5 z 41

7.3.2 Solution
Use the global functions and movie clip methods that control the playhead.

7.3.3 Discussion
You can use the timeline-affecting methods, shown in Table 7-1, to control the playback of a mo
clip. These methods are most meaningful for movie clips that have more than one frame in their
timelines. Therefore, movie clips created using the createEmptyMovieClip( ) method cannot be
usefully affected by the timeline-affecting methods because they have only one frame, and there
no way to programmatically add more frames.

Table 7-1. The timeline-affecting methods


Method Description
Plays the movie clip's timeline from the current frame. If the timeline is already
play( )
playing, this has no effect.
Stops the movie clip's timeline at the current frame. If the timeline is already
stop( ) stopped, this has no effect. Flash continues to render the Stage and process
events even when a clip is stopped.
gotoAndPlay Plays the timeline from a particular frame regardless of the current frame. The
() frame can be specified as a frame number or a frame label.
gotoAndStop Moves the playhead to a particular frame and stops the play head. The frame ca
() be specified as a frame number or a frame label.
nextFrame( ) Moves the playhead to the frame after the current frame and stops the play hea
Moves the playhead to the frame just before the current frame and stops the pla
prevFrame( )
head.

There are many potential uses for each of these methods. You should use them to meet the need
of your movie. Here are some tips for how you might use each method.

You can use the play( ) method to play the timeline of an animation when the user rolls over the
movie clip:

[Link] = function ( ) {
[Link]( );
};

You can also use play( ) to play the timeline of a movie clip once a callback method, such as the
onLoad( ) method of a LoadVars object, has been invoked. This can be useful for situations in wh
you want to ensure that data has been loaded into the movie before allowing the user to see a
particular part of it. It is also useful when the movie clip depends on loaded data to work properl

// lv is a LoadVars object.
[Link] = function ( ) {
// Process the loaded data.

// Call the play( ) method of a movie clip on _root.


_root.[Link]( );
};

You can use the stop( ) method to stop the playback of a movie clip at the outset. This is useful
you want to prevent playback until certain conditions are met, such as when the user rolls over t
movie clip or when data loads into the movie, etc. Movie clips always play by default, so you hav

[Link] 2004-02-06
Strona 6 z 41

explicitly tell them to stop:

[Link]( );

By default, movie clip timelines continue to loop (playback begins again at the first frame after t
playhead reaches the last frame). If you want a timeline to play only once, you should use the st
( ) method on the last frame of the movie clip's timeline:

[Link]( );

You can use the gotoAndPlay( ) method to play a particular section of a timeline when a user
performs an action, such as clicking on a navigational button. Generally, it is not the best practic
create multiple sections of a timeline. Rather, each timeline should be a distinct unit. However, y
may have good reason in some cases to have a single timeline in which many parts of an animat
take place, and in these cases gotoAndPlay( ) is a useful method. (That said, ActionScript doesn'
mesh well with scenes, so Flash scenes should generally be avoided in ActionScript-heavy movie
Here are two examples:

// When the user clicks and releases myButtonMovieClip


// (presumed to be a navigational button in your movie),
// tell the main timeline (_root) to begin playback from frame 33.
[Link] = function ( ) {
_root.gotoAndPlay(33);
};

// This example is the same as the previous one, except it uses the frame label
// "section b" instead of a frame number. You can apply frame labels by selecting
// frame and entering a value into the Frame field of the Property inspector.
[Link] = function ( ) {
_root.gotoAndPlay("section b");
};

You can use the gotoAndStop( ) method to go to a single frame of a timeline when a user perfor
an action, such as rolling out of a movie clip. Recipe 7.5 discusses how movie clips used as butto
automatically have a gotoAndStop( ) method applied for the _up, _over, and _down labels within
movie clip, if they exist. You could make a more interesting movie clip button by also adding
_rollout and other such labels to the timeline and using gotoAndStop( ) within the appropriate
event handler methods:

[Link] = function ( ) {
[Link]("_rollout");
};

You can use the nextFrame( ) and prevFrame( ) methods to navigate back and forth along a mov
clip's timeline, such as in a slide presentation:

// This code allows a user to move back and forth along the frames of the main
// timeline. prevBtn and nextBtn are buttons or movie clips that act as buttons.
[Link] = function ( ) {
_root.prevFrame( );
};

[Link] = function ( ) {
_root.nextFrame( );
};

You can also use the _currentframe property in conjunction with gotoAndStop( ) or gotoAndPlay
to instruct the playhead to move to a frame on the timeline relative to the current frame:

[Link] = function ( ) {

[Link] 2004-02-06
Strona 7 z 41

_root.gotoAndStop(_currentframe + 5);
};

[ Team LiB ]
[ Team LiB ]

Recipe 7.4 Reversing Playback


7.4.1 Problem
You want to play a movie clip's timeline in reverse.

7.4.2 Solution
Use an onEnterFrame( ) event handler with the prevFrame( ) method.

7.4.3 Discussion
You can programmatically reverse the playback of a movie clip's timeline with ActionScript. The
onEnterFrame( ) event handler of a movie clip is called repeatedly at the same frequency as the
movie's frame rate. Therefore, if you create an onEnterFrame( ) method definition for a movie
clip that includes a call to the prevFrame( ) method, the movie clip's timeline will continually
move to the previous frame at the movie's frame rate:

[Link] = function ( ) {
[Link]( );
};

If the movie clip's playhead is already on the first frame, prevFrame( ) has no effect. Therefore,
to loop the movie clip's timeline while playing in reverse, add a conditional statement that goes
to the last frame of the movie clip if it is already on the first frame. The current frame number
is determined using the _currentframe property, and the last frame is determined using the
_totalframes property:

[Link] = function ( ) {
if (this._currentframe > 1) {
// Play the clip backwards.
[Link]( );
} else {
// Loop back to the end if we're already at the beginning.
[Link](this._totalframes);
}
};

7.4.4 See Also


Recipe 7.8
[ Team LiB ]
[ Team LiB ]

Recipe 7.5 Using Movie Clips as Buttons


7.5.1 Problem

[Link] 2004-02-06
Strona 8 z 41

You want to use a movie clip as a button.

7.5.2 Solution
Use the button event handler methods with the movie clip.

7.5.3 Discussion
Button symbols are still appropriate choices when you want to create simple, authoring-time but
with basic button states. However, in most cases, you should use movie clips as buttons instead
because movie clips can handle all the button events, and ActionScript gives you much more
programmatic control over movie clips than buttons. For example, you can attach movie clips to
timeline from the Library, programmatically control the contents of a movie clip such as text labe
and use drawing methods to draw inside movie clips.

While it is not technically improper to apply event handlers to the physical instances of movie cli
the Stage, using the event handler methods (introduced in Flash MX) offers several advantages.
of all, they enable you to keep your code centralized rather than applying the code to each insta
Furthermore, you can assign event handler methods to dynamically created movie clips as well a
manually created instances.

The button event handler methods include those shown in Table 7-2.

Table 7-2. The button event handler methods


Event handler
Description
method
onPress( ) Triggered when the user clicks on the instance.
onRelease( ) Triggered when the user releases the mouse following a click on the instan
Triggered when the user releases a mouseclick outside the instance. In ot
onReleaseOutside( ) words, the user clicked on the instance, moved the cursor off the instance
while still pressing on the mouse button, and then released the button.
onRollOver( ) Triggered when the user moves the pointer over the instance.
onRollOut( ) Triggered when the user moves the pointer off the instance.
Triggered when the user drags the pointer onto the instance while the mo
onDragOver( ) button is pressed. However, the mouse must have been pressed while init
over another button or movie clip with button handlers.
Triggered when the user drags the pointer off the instance while the mous
onDragOut( )
button is pressed.
onSetFocus( ) Triggered when the focus is set to the instance.
onKillFocus( ) Triggered when the focus leaves the instance.

Here is an example you can use to understand how the button events work with a movie clip. Yo
the drawCircle( ) method from Recipe 4.5 to draw a circle movie clip and then apply the button e
handler methods to it.

// Include the [Link] file from Chapter 4 for its drawCircle( ) metho
#include "[Link]"

// Create a circle_mc clip and draw a blue circle at the center of the Stage.
_root.createEmptyMovieClip("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100);

[Link] 2004-02-06
Strona 9 z 41

circle_mc.beginFill(0x0000FF, 100);
circle_mc.drawCircle(100, [Link]/2, [Link]/2);
circle_mc.endFill( );

// Next, apply the button event handler methods so that when each event is trigge
// a message is written to the Output window.
circle_mc.onPress = function ( ) {
trace("pressed");
};

circle_mc.onRelease = function ( ) {
trace("released");
};

circle_mc.onReleaseOutside = function ( ) {
trace("released outside");
};

circle_mc.onRollOver = function ( ) {
trace("rolled over");
};

circle_mc.onRollOut = function ( ) {
trace("rolled out");
};

circle_mc.onDragOver = function ( ) {
trace("dragged over");
};

circle_mc.onDragOut = function ( ) {
trace("dragged out");
};

circle_mc.onSetFocus = function ( ) {
trace("focus set");
};

circle_mc.onKillFocus = function ( ) {
trace("focus removed");
};

// Use the [Link]( ) method to set the focus to the circle_mc movie
// and then use it to remove the focus. This will trigger the onSetFocus( ) and
// onKillFocus( ) methods of circle_mc.
[Link](circle_mc);
[Link](null);

When you create a movie clip to use as a button, you can also apply frame labels of _up, _over,
_down to the frame of the movie clip that represents each button state. When a button event han
is applied to such a movie clip, Flash automatically goes to and stops at the frame label that mat
the current button state. You must also place a stop( ) action on the first frame; otherwise, Flash
automatically plays the timeline until one of the button states is activated. If you create an anim
sequence for each button state, add a [Link]( ) method call to the _up, _over, and _down fram
within the movie clip; otherwise, the subsequent frames will not play back.
[ Team LiB ]
[ Team LiB ]

Recipe 7.6 Defining Hit Areas for Movie Clips


7.6.1 Problem

[Link] 2004-02-06
Strona 10 z 41

You want to define a hit area for a movie clip used as a button.

7.6.2 Solution
Create a second movie clip to serve as the hit area. Then assign a reference to the hit area movi
clip to the hitArea property of the button movie clip.

7.6.3 Discussion
When you create button symbols at authoring time, you can create alternate hit areas using the
frame of the button's timeline. This can be very useful if the artwork in the button is not solid or
you want to define a hit area that is otherwise different from the shape defined by the button's
artwork.

You can also define hit areas for movie clips used as buttons. All movie clips have a built-in prop
named hitArea. If hitArea is left undefined, Flash automatically uses the movie clip's artwork a
the hit area. However, you can assign the hitArea property a reference to another movie clip to
serve as the hit area.

The following example shows how a custom hit area can be useful when dealing with odd shapes
outlines. Using code, we generate a movie clip that contains 100 small, unfilled circles that are
aligned to form a square grid. We then assign an onRelease( ) event handler method to the mov
clip instance. Testing the code reveals that the movie clip's rollover state is activated only when
mouse pointer is directly over one of the circle outlines.

// Include the [Link] file from Chapter 4 for its drawCircle( ) metho
#include "[Link]"

// Create the button movie clip. Name it myButton_mc.


[Link]("myButton_mc", 1);

// Use a for statement to create 100 small circles and align them in 10 rows of 1
// form a square grid.
for (var i = 0, x = 0, y = 0; i < 100; i++) {
myButton_mc.createEmptyMovieClip("circle_mc" + i, i);
circle_mc = myButton_mc["circle_mc" + i];
circle_mc.lineStyle(0, 0, 100);
circle_mc.drawCircle(1);
circle_mc._x = x;
circle_mc._y = y;
// Space the circles in a row by five pixels.
x += 5;

// Start the next row of circles.


if (x > 45) {
x = 0;
y += 5;
}
}
myButton_mc.onRelease = function ( ) {
trace("you clicked the button");
};

We can create a solid, filled, transparent square movie clip and overlay it on top of the square gr
composed of circles. Then, by assigning the new movie clip to the button movie clip's hitArea
property, Flash uses its shape to define the hit area for the button movie clip.

// Create the new movie clip.


[Link]("hitArea_mc", 2);

[Link] 2004-02-06
Strona 11 z 41

// Draw a square in the movie clip with the dimensions of myButton_mc.


hitArea_mc.lineStyle(1, 0, 0);
hitArea_mc.beginFill(0, 0);
hitArea_mc.drawRectangle(myButton_mc._width, myButton_mc._height);

// Position hitArea_mc so that it overlays myButton_mc.


hitArea_mc._x = myButton_mc._x + hitArea_mc._width/2;
hitArea_mc._y = myButton_mc._y + hitArea_mc._height/2;

// Assign hitArea_mc as the hit area for myButton_mc.


myButton_mc.hitArea = hitArea_mc;

7.6.4 See Also


Recipe 7.15

[ Team LiB ]
[ Team LiB ]

Recipe 7.7 Checking for Mouseover


7.7.1 Problem
You want to check whether the mouse pointer is over a movie clip.

7.7.2 Solution
Use the onRollover( ) event handler, or use the hitTest( ) method within an onEnterFrame( ) me

7.7.3 Discussion
In many cases, the onRollOver( ) and onRollOut( ) event handler methods, as shown in Recipe 7
the easiest way to respond to the mouse pointer rolling over a movie clip. However, this techniqu
when you drag one clip over another, such as when using a clip as a custom mouse pointer. Here
example to illustrate the point:

// Include the [Link] file from Chapter 4 for its drawRectangle( ) an


// drawCircle( ) methods.
#include "[Link]"

// Create a rectangle_mc movie clip on the Stage.


_root.createEmptyMovieClip("rectangle_mc", 1);
rectangle_mc.lineStyle(1, 0x000000, 100);
rectangle_mc.beginFill(0, 100);
rectangle_mc.drawRectangle(100, 200, 10);
rectangle_mc.endFill( );
rectangle_mc._x = 100;
rectangle_mc._y = 100;

// Create onRollOver( ) and onRollOut( ) methods such that when the mouse rolls
// off the movie clip, it goes to 50% transparency and back.
rectangle_mc.onRollOver = function ( ) {
this._alpha = 50;
};

rectangle_mc.onRollOut = function ( ) {

[Link] 2004-02-06
Strona 12 z 41

this._alpha = 100;
};

// Create a circle_mc movie clip.


_root.createEmptyMovieClip("circle_mc", 2);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.beginFill(0xFFFFFF, 100);
circle_mc.drawCircle(50);
circle_mc.endFill( );

// Create onPress( ) and onRelease( ) methods such


// that circle_mc is draggable when clicked.
circle_mc.onPress = function ( ) {
[Link]( );
};

circle_mc.onRelease = function ( ) {
[Link]( );
};

Testing this example reveals that mousing over the rectangle normally works just fine. But when
drag the circle over the rectangle, the rectangle_mc.onRollOver( ) event handler is not triggered

The workaround is to use an onEnterFrame( ) method instead of onRollOver( ) and onRollOut( )


check whether the mouse pointer's coordinates overlap the movie clip using hitTest( ). You shou
_root._xmouse and _root._ymouse as the x and y parameters for hitTest( ).

// Include the [Link] file from Chapter 4 for its drawRectangle( ) an


// drawCircle( ) methods.
#include "[Link]"

_root.createEmptyMovieClip("rectangle_mc", 1);
rectangle_mc.lineStyle(1, 0x000000, 100);
rectangle_mc.beginFill(0, 100);
rectangle_mc.drawRectangle(100, 200, 10);
rectangle_mc.endFill( );
rectangle_mc._x = 100;
rectangle_mc._y = 100;

// Use an onEnterFrame( ) method instead of onRollOver( ) and onRollOut( ).


rectangle_mc.onEnterFrame = function ( ) {

// Check whether the mouse pointer, at coords (_root._xmouse, _root._ymouse),


// overlaps the movie clip using the hitTest( ) method. If it overlaps, set _a
// to 50; otherwise, set _alpha to 100.
if ([Link](_root._xmouse, _root._ymouse)) {
this._alpha = 50;
} else {
this._alpha = 100;
}
};

_root.createEmptyMovieClip("circle_mc", 2);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.beginFill(0xFFFFFF, 100);
circle_mc.drawCircle(50);
circle_mc.endFill( );

circle_mc.onPress = function ( ) {
[Link]( );
};

circle_mc.onRelease = function ( ) {

[Link] 2004-02-06
Strona 13 z 41

[Link]( );
};

There is an additional issue that arises with this technique. In the preceding example code, the _
property is set to 50 continuously as long as the mouse pointer is over the movie clip, and it is s
100 continuously as long as the mouse pointer is not over the movie clip. In this example this is
particularly a problem, but in some applications it is important that the code be executed only on
mouseover and once on mouseout. You can accomplish this task by the addition of a custom pro
and a little bit of extra logic, as shown by the bold text in the following example:

rectangle_mc.onEnterFrame = function ( ) {
// Check whether the mouse pointer is over the movie clip and whether the custom
// property, mouseover, is false. If both conditions are true, perform the
// mouseover actions. Otherwise, if the mouse pointer is not over the movie clip
// and the mouseover property is true, perform the mouseout actions.
if ([Link](_root._xmouse, _root._ymouse) && ![Link]) {
trace("setting _alpha = 50");
this._alpha = 50;

// Set mouseover to true to prevent mouseover actions from executing again.


[Link] = true;
}
else if (![Link](_root._xmouse, _root._ymouse) && [Link]) {
trace("setting _alpha = 100");
this._alpha = 100;

// Set mouseover to false to prevent mouseout actions from executing again.


[Link] = false;

}
};
[ Team LiB ]
[ Team LiB ]

Recipe 7.8 Performing Repeated Actions on Movie Clips


7.8.1 Problem
You want to perform a particular action on a movie clip repeatedly.

7.8.2 Solution
Use an onEnterFrame( ) event handler method or use setInterval( ).

7.8.3 Discussion
Every movie clip's onEnterFrame( ) method is called repeatedly during a Flash movie's playback.
movie clip's onEnterFrame( ) method is defined, any actions it contains are automatically invoke
the frequency of the movie's frame rate. For example, if a movie's frame rate is set to 12 frames
second, actions defined within a movie clip's onEnterFrame( ) method are invoked 12 times per
second. Here is an example using onEnterFrame( ), which shows that it is called at approximatel
frame rate. You will notice that it is not exact. The precision to which the movie plays back at the
correct frame rate depends on the computer's processor and what else is being processed at the
same time as the Flash movie.

// Define an onEnterFrame( ) method for an existing movie clip named myMovieClip


[Link] = function ( ) {

[Link] 2004-02-06
Strona 14 z 41

// Store the number of milliseconds since the movie started to play.


var currentTime = getTimer( );

// Calculate the difference between the current time and the time of the previou
// call to onEnterFrame( ), which gives you the elapsed time between calls. Th
// first time through, prevTime is undefined.
var timeElapse = currentTime - [Link];

// Display the timeElapse times 12 (or the movie's frame rate if it is not 12).
// This will tell you how many milliseconds it takes for 12 calls to the
// onEnterFrame( ) method. The output should generally be around 1000 millisec
// which means that the method is called 12 times per second.
trace(timeElapse * 12);

// Set prevTime to the current time. prevTime needs to be a property so that it


// accessible the next time onEnterFrame( ) is called. Local variables disappe
// between calls to the method.
[Link] = currentTime;
};

There are many useful things you can do using an onEnterFrame( ) method. For example, you ca
continuously animate a movie clip's position by adjusting its _x and/or _y properties:

// At the current frame rate, increment myMovieClip's _x and _y properties. This


// animates the movie clip slowly in a diagonal to the lower-right corner and
// eventually off the Stage.
[Link] = function ( ) {
this._x++;
this._y++;
};

onEnterFrame( )'s only limitation is that it is always called at the frame rate of the movie. If you
to create intervals that differ from the movie's frame rate, use the setInterval( ) function. If the
screen updates should be more frequent than the frame rate, make sure to include a call to
updateAfterEvent( ) at the end of your interval function.

function myIntervalFunction ( ) {
myMovieClip._x++;
updateAfterEvent( );
}

myMovieClipInterval = setInterval(myIntervalFunction, 10);

7.8.4 See Also


For more details on how to use setInterval( ), see Recipe 1.7. To test a movie's frame rate, see t
time-tracker tool at [Link]
[ Team LiB ]
[ Team LiB ]

Recipe 7.9 Fading a Movie Clip


7.9.1 Problem
You want to programmatically fade a movie clip.

7.9.2 Solution
Create a fade( ) method that uses the onEnterFrame( ) event handler method and the movie clip

[Link] 2004-02-06
Strona 15 z 41

_alpha property.

7.9.3 Discussion
You can set the value of a movie clip's _alpha property to adjust its transparency. Thus, if you w
to set a movie clip's transparency to a single value, you can do so with a single statement such a

// Set the movie clip's transparency to 50%.


myMovieClip._alpha = 50;

You can repeat this process to achieve a fading effect on the same movie clip. You should do this
placing the property assignment statement within the onEnterFrame( ) method of the movie clip

// Repeatedly decrement the _alpha of the movie clip.


[Link] = function ( ) {
this._alpha--;
};

The valid range for the _alpha property of a movie clip is from 0 (completely transparent) to 100
(fully opaque). However, because ActionScript allows you to set the value outside that range, us
if statement to safeguard against inappropriate values:

// Continuously decrement _alpha, provided it is greater than 0.


[Link] = function ( ) {
this._alpha--;
if (this._alpha <= 0) {
delete [Link];
}
};

The preceding code works, but it is not completely efficient and has the potential for some minor
errors. The reason is that the working range for _alpha is from 0 to 100, but internally Flash
converts all the numbers to a range of 0 to 255. As a result, you can end up with incorrect value
using the preceding technique. You can test this for yourself by adding a trace( ) statement to th
code:

[Link] = function ( ) {
this._alpha--;
trace(this._alpha);
if (this._alpha <= 0) {
delete [Link];
}
};

Testing the example reveals that the _alpha value is not decremented by 1 each time, as you m
expect, and the final result can be negative. To solve this problem, you can use another variable
an intermediary:

[Link] = function ( ) {

// Use alphaCount as an intermediate variable. If the value is undefined,


// initialize it to 99; otherwise, decrement it by 1.
[Link] = ([Link] == undefined) ? 99 : --[Link];

// Assign the value of alphaCount to _alpha. The alphaCount value is always an


// integer, which is what we want.
this._alpha = [Link];
if (this._alpha <= 0) {
delete [Link];
}

[Link] 2004-02-06
Strona 16 z 41

};

And, of course, you can do the reverse to fade up a movie clip:

[Link] = function ( ) {

// Use alphaCount as an intermediate variable. If the value is undefined,


// initialize it to 0; otherwise, increment it by 1.
[Link] = ([Link] == undefined) ? 0 : ++[Link];

// Assign the value of alphaCount to _alpha. The alphaCount value is always an


// integer, which is what we want.
this._alpha = [Link];

// Make sure the value does not exceed 100.


if (this._alpha >= 100) {
delete [Link];
}
};

If you work with programmatically generated fades often, you should create a fade( ) method fo
MovieClip objects and add it to a [Link] file for easy inclusion in future projects. The metho
should accept two parameters:

rate

The rate at which to increment or decrement the movie clip's _alpha property. In most cas
it should be from 1 to 20. If the value 0 is used, then no changes occur to the _alpha
property.

up

A Boolean that indicates whether to fade up ( if true) or fade down ( if false).

Here is our custom fade( ) method:

[Link] = function (rate, up) {

// Create a new, nested movie clip to monitor the fade progress. This avoids
// interfering with any existing onEnterFrame( ) method applied to the movie c
[Link]("fadeMonitor_mc", [Link]( ));

// Define a new onEnterFrame( ) method for the monitor movie clip.


this.fadeMonitor_mc.onEnterFrame = function ( ) {

// Set a Boolean property, isFading, so that other methods know if the movie
// is being faded.
this._parent.isFading = true;

// Check whether the movie clip is being faded up or down.


if (up) {
// Use an intermediate property to determine the new alpha value.
[Link] = ([Link] == undefined) ? 0 :
[Link] + rate;

// If the current _alpha of the clip is under 100, assign the new value;
// otherwise, set the value to 100 and delete the onEnterFrame( ) method.
if (this._parent._alpha < 100) {
this._parent._alpha = [Link];
} else {
this._parent._alpha = 100;

[Link] 2004-02-06
Strona 17 z 41

delete [Link];

// Set isFading to false since the fade is completed.


this._parent.isFading = false;
}
} else {
// Use alphaCount as an intermediate property to determine the alpha value.
[Link] = ([Link] == undefined) ? 100 :
[Link] - rate;

// If the current _alpha is greater than 0, assign the new value; otherwise
// set the value to 0 and delete the onEnterFrame( ) method.
if (this._parent._alpha > 0) {
this._parent._alpha = [Link];
} else {
this._parent._alpha = 0;
delete [Link];

// Set isFading to false since the fade is completed.


this._parent.isFading = false;
}
}
}
};

Here is an example that uses the custom fade( ) method to fade a movie clip from total opacity t
total transparency. This example does not demonstrate the ability of the fade( ) method to
incorporate an existing onEnterFrame( ) method.

// Include [Link] from Chapter 4 for its drawCircle( ) method.


#include "[Link]"
// Include [Link] from this chapter for its custom fade( ) method.
#include "[Link]"

// Draw a circle.
_root.createEmptyMovieClip("myMovieClip", 1);
[Link](1, 0x000000, 100);
[Link](100);

// Call fade( ) with a rate of 5. This fades the movie clip down in 20 frames.
[Link](5);

When you use the fade( ) method on a movie clip where you are also defining an onEnterFrame(
method, you must define the onEnterFrame( ) method before you call fade( ). Here is an examp
how to use the fade( ) method in such a scenario:

_root.createEmptyMovieClip("myMovieClip", 1);
[Link](1, 0x000000, 100);
[Link](100);

// Set the _alpha property to 0 initially so that it can fade up from nothing.
myMovieClip._alpha = 0;

// Create an onEnterFrame( ) method that moves the movie clip.


[Link] = function ( ) {
this._x++;
this._y++;
};

// Call fade( ) to fade up. Notice that


the fade and the movement defined in the
// onEnterFrame( ) method work simultaneously. If the onEnterFrame( ) method is
// defined after the call to fade( ), the fade( ) method does not work.

[Link] 2004-02-06
Strona 18 z 41

[Link](5, true);
[ Team LiB ]
[ Team LiB ]

Recipe 7.10 Discovering Nested Movie Clips


7.10.1 Problem
You want to use ActionScript to retrieve an array of movie clip instances nested within a specific
movie clip.

7.10.2 Solution
Use a for . . . in statement to loop through all the contents of the movie clip, and if the content
is an instance of the MovieClip class (as indicated by the instanceof operator), add it to an array
of the nested movie clips.

7.10.3 Discussion
Movie clips can be nested within other movie clips. A nested clip instance is stored as a property
of its parent clip. Since movie clip instances are objects, you can access all the nested movie
clips using a for . . . in statement. However, a basic for . . . in statement lists other properties
in addition to nested movie clips. This example displays not only movie clips nested in _root,
but also any timeline variables, functions, and the built-in $version variable:

for (var item in _root) {


trace(item + ": " + _root[item]);
}

To separate nested movie clips from the rest of a clip's contents, you can use the instanceof
operator to check if an item is an instance of the MovieClip class, as follows:

// The if statement disregards everything but movie clips.


for (var item in _root) {
if (_root[item] instanceof MovieClip) {
trace(item + ": " + _root[item]);
}
}

You can also write a simple custom method for the MovieClip class that returns an array of
nested movie clips:

[Link] = function ( ) {

// Define the array to contain the movie clip references.


var mcs = new Array( );

// Loop through all the contents of the movie clip.


for (var i in this) {
// Add any nested movie clips to the array.
if (this[i] instanceof MovieClip) {
[Link](this[i]);
}
}

return mcs;
};

[Link] 2004-02-06
Strona 19 z 41

Here is an example that uses the getMovieClips( ) method to display all the movie clips within
_root:

trace(_root.getMovieClips( ));

Here is a version that recursively displays all movie clips in a tree:

[Link] = function ( ) {
// Loop through all the contents of the movie clip.
for (var i in this) {
// Add any nested movie clips to the array.
if (this[i] instanceof MovieClip) {
trace (this[i]);
// Recursively call this function to show any nested clips
this[i].showMovieClips( );
}
}
};
// Example usage:
_root.showMovieClips( );

7.10.4 See Also


Recipe 6.13

[ Team LiB ]
[ Team LiB ]

Recipe 7.11 Getting Unique Depths


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

7.11.2 Solution
Create and use a custom [Link]( ) method.

7.11.3 Discussion
Only one movie clip can exist at each depth within a parent clip, so if you specify an existing dep
when using createEmptyMovieClip( ), attachMovie( ), or duplicateMovieClip( ), the movie clip alre
on that depth is overwritten. Unless you want to overwrite an existing movie clip, you must alwa
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 programmatical
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,
shown in the following code block. The process is not complex. If the movie clip does not yet hav
custom currentDepth property defined, we define it and initialize it to the value of 1. The value

[Link] 2004-02-06
Strona 20 z 41

used because that is the first depth you want to assign to any programmatically generated neste
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.

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

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

// This assumes our custom getNewDepth( ) method is defined in [Link].


#include "[Link]"

// 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 assig
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 [Link] from this chapter and [Link] from Chapter 4.


#include "[Link]"
#include "[Link]"

// Set the currentDepth property so that the next programmatically created movie
// 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 i
// 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 overwri
manually created movie clips if you are not careful. For example, in the preceding code block, th
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, th
new programmatic clip overwrites the first manually created clip in your movie. You can solve th
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 manu
created instances.

#include "[Link]"

[Link] 2004-02-06
Strona 21 z 41

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

7.11.4 See Also


Recipe 7.16
[ Team LiB ]
[ Team LiB ]

Recipe 7.12 Getting a Movie Clip's Boundaries


7.12.1 Problem
You want to know the minimum and maximum x and y coordinates that determine the edges of
a movie clip (its bounding box).

7.12.2 Solution
Use the getBounds( ) method.

7.12.3 Discussion
You can determine the left, right, top, and bottom coordinates of a movie clip within its parent
movie clip using a combination of the _x, _y, _width, and _height properties:

left = myMovieClip._x;
right = left + myMovieClip._width;
top = myMovieClip._y;
bottom = top + myMovieClip._height;

While this technique works, it is rather intensive and inefficient when compared to the
getBounds( ) method. The getBounds( ) method returns an object with four properties:

xMin

The leftmost x coordinate of the movie clip

xMax

The rightmost x coordinate of the movie clip

yMin

The topmost y coordinate of the movie clip

yMax

The bottommost y coordinate of the movie clip

The four properties define the bounding box of the visible content within the movie clip. (Note
that y coordinates increase as you move down the screen, which is the opposite of the
Cartesian coordinate system.) When you call getBounds( ) without parameters, it returns an
object with values relative to the movie clip's coordinate system. You can also pass the method
a reference to a target coordinate system (another movie clip) for which you want the returned

[Link] 2004-02-06
Strona 22 z 41

object's values to be given:

// Include [Link] from Chapter 4 for its drawCircle( ) method.


#include "[Link]"

// Create a new movie clip and draw a circle in it.


_root.createEmptyMovieClip("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.drawCircle(100);

// Move the circle to (200,200) within _root.


circle_mc._x = 200;
circle_mc._y = 200;

// Get the bounds of the circle within circle_mc as well as relative to the _root
// coordinate space.
insideBounds = circle_mc.getBounds( );
parentBounds = circle_mc.getBounds(_root);

// Loop through the bounds objects and list the values.


trace("inside bounds:");
for (var val in insideBounds) {
trace(val + ": " + insideBounds[val]);
}

trace("parent bounds:");
for (var val in parentBounds) {
trace(val + ": " + parentBounds[val]);
}

/* Output window shows:


inside bounds:
yMax: 101
yMin: -101
xMax: 101
xMin: -101
parent bounds:
yMax: 301
yMin: 99
xMax: 301
xMin: 99
*/

7.12.4 See Also


Recipe 7.17
[ Team LiB ]
[ Team LiB ]

Recipe 7.13 Creating a Draggable Movie Clip


7.13.1 Problem
You want a user to be able to drag a movie clip with the mouse.

7.13.2 Solution
Use the startDrag( ) method.

[Link] 2004-02-06
Strona 23 z 41

7.13.3 Discussion
You can allow the user to drag any movie clip with the mouse by invoking the startDrag( ) metho
on the clip. When invoked without parameters, startDrag( ) moves the movie clip relative to the
pointer at the moment the method was invoked (the clip moves with the pointer but maintains th
same distance from the pointer). You can see this for yourself.

1. Place the following code on the first frame of the main timeline of a new Flash document:

// Include [Link] from Chapter 4 for its drawCircle( ) method.


#include "[Link]"

// Create a movie clip and draw a circle in it.


_root.createEmptyMovieClip("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.drawCircle(100);

// Call the startDrag() method from circle_mc.


circle_mc.startDrag( );

2. Move the pointer so that it appears on the right side of the Player when you test it.

3. Test the movie. Notice that the movie clip moves with the pointer but always maintains the
same distance.

The startDrag( ) method can be called with no parameters, as shown in the previous example.
Optionally, you can also specify one Boolean parameter that explicitly tells the movie clip whethe
not to snap to the mouse pointer:

_root.createEmptyMovieClip("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.drawCircle(100);

// Call the startDrag( ) method from circle_mc, passing it the value true. This w
// cause the movie clip to automatically snap to the pointer. This use of the
// startDrag( ) method can be useful for situations such as creating custom poin
// (see Recipe 7.14).
circle_mc.startDrag(true);

Additionally, you can define a rectangular area within which the movie clip can be dragged by
specifying values for the optional second through fifth parameters of the startDrag( ) method. Th
following is a complete list of possible parameters for startDrag( ):

lockCenter

If true, the movie clip snaps to the pointer; otherwise, the movie clip moves relative to its
position to the pointer when the method was called.

left

A number indicating the leftmost pixel value to which the movie clip can be dragged.

top

A number indicating the topmost pixel value to which the movie clip can be dragged.

right

[Link] 2004-02-06
Strona 24 z 41

A number indicating the rightmost pixel value to which the movie clip can be dragged.

bottom

A number indicating the bottommost pixel value to which the movie clip can be dragged.

Here is an example that demonstrates the optional constraining feature of startDrag( ):

// Create a new movie clip and draw a circle within it.


_root.createEmptyMovieClip("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.drawCircle(100);

// Create a new movie clip and draw a rectangle within it at (200,200).


_root.createEmptyMovieClip("rectangle_mc", 2);
rectangle_mc.lineStyle(1, 0x000000, 100);
rectangle_mc.drawRectangle(300, 300);
rectangle_mc._x = 200;
rectangle_mc._y = 200;

// Determine the bounding area within which the circle can be dragged. Figure it
// that the circle never leaves the boundaries of the rectangle.
rb = rectangle_mc.getBounds(_root);
l = [Link] + circle_mc._width/2;
r = [Link] - circle_mc._width/2;
t = [Link] + circle_mc._width/2;
b = [Link] - circle_mc._width/2;

// Invoke startDrag( ) on circle_mc, telling it to snap circle_mc to the pointer


// passing the bounding box limits within which circle_mc can be dragged.
circle_mc.startDrag(true, l, t, r, b);

The preceding code snippets show how to make a movie clip draggable from the beginning of
playback. Another popular place for startDrag( ) is within an onPress( ) event handler method,
where it makes a movie clip draggable as soon as it is clicked. To stop the clip from dragging wh
the user releases the mouse button, call stopDrag( ) from within the movie clip's onRelease( ) ev
handler method:

// Draw a filled circle movie clip.


_root.createEmptyMovieClip("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.beginFill(0, 100);
circle_mc.drawCircle(100);
circle_mc.endFill( );

// When the user clicks anywhere on the circle, allow it to be dragged.


circle_mc.onPress = function ( ) {
[Link]( );
};

// When the user releases the mouse button, stop the drag action. This drops the
// movie clip on the Stage wherever the user released the mouse button.
circle_mc.onRelease = function ( ) {
[Link]( );

};

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 25 z 41

Recipe 7.14 Creating a Custom Mouse Pointer


7.14.1 Problem
You want to create a custom mouse pointer in place of the usual arrow or hand cursor.

7.14.2 Solution
Simulate a mouse pointer using a movie clip. Use a movie clip with an onEnterFrame( ) method t
continually update its position to match the mouse pointer, and use [Link]( ) to hide the
standard pointer.

7.14.3 Discussion
You can effectively replace the mouse pointer with a custom movie clip using a simple, two-step
technique. First, you should hide the default arrow pointer from view with the static method,
[Link]( ). You saw in Recipe 7.13 how you can cause a movie clip to move with the pointer
the startDrag( ) method. It seems logical to use this approach to make a custom pointer movie c
follow the mouse movement on stage; however, only one movie clip can follow the mouse move
at one time using the startDrag( ) method. This limitation can cause problems if you want to use
startDrag( ) for both a custom pointer and to implement drag-and-drop functionality. To avoid th
conflict, use an onEnterFrame( ) method to continually update the custom pointer movie clip's
position on stage so that it matches the built-in _xmouse and _ymouse properties of the clip's par
as follows:

[Link] = function ( ) {
this._x = this._parent._xmouse;
this._y = this._parent._ymouse;
};

Or, if you prefer, you can use the setInterval( ) technique in place of onEnterFrame( ) (see Recip
7.8). Using setInterval( ) is advisable in situations in which the frame rate is too low to update th
custom pointer position quickly enough.

function updatePointer (pointerClip) {


pointerClip._x = pointerClip._parent._xmouse;
pointerClip._y = pointerClip._parent._ymouse;
updateAfterEvent( );
}

pointerInterval = setInterval(updatePointer, 10, mousePointer);

Follow these steps to replace the mouse pointer:

1. Create a movie clip instance to be used as the new mouse pointer. You should make sure t
depth of the movie clip is higher than any other clip so that it does not disappear behind ot
movie clips on the Stage.

2. Define an onEnterFrame( ) method or an interval function that continually sets the _x and
properties of the mouse pointer movie clip to the _xmouse and _ymouse values of its paren
movie clip.

3. Call the [Link]( ) method to hide the default pointer.

Let's create a simple working example. The following code creates a circle movie clip that uses th
custom fade( ) method (see Recipe 7.9) to continually fade the circle in and out. Then, using an
interval, Flash continually updates the position of the movie clip to follow the mouse movement.

[Link] 2004-02-06
Strona 26 z 41

to show that there is no conflict between the custom mouse pointer and draggable movie clips, w
add a draggable square movie clip as well.

// Include [Link] from Chapter 4 for its custom drawing methods. Inclu
// [Link] from this chapter for its custom fade( ) method.
#include "[Link]"
#include "[Link]"

// Create a custom pointer movie clip with a depth of 10000 to ensure that it app
// in front of all other movie clips on the Stage. In this example, draw a small
// filled circle in the movie clip.
_root.createEmptyMovieClip("mousePointer", 10000);
[Link](1, 0x000000, 100);
[Link](0, 100);
[Link](9);
[Link]( );
// Add an onEnterFrame( ) method to mousePointer that causes the movie clip to
// continually fade up and down.
[Link] = function ( ) {

// If the movie clip is not currently being faded, call fade( ).


if (![Link]) {

// Call fade( ) with a rate of 5 and with the value of property [Link]. The
// [Link] property is a custom property created for this situation. At first
// [Link] has the value undefined, which is like passing false to fade( ). A
// each call to fade( ), toggle [Link] so that it alternates between true an
// false, causing the clip to fade up and down repeatedly.
[Link](5, [Link]);
[Link] = ![Link];
}
};

// Create an interval to update the position of the custom pointer movie clip.
function updatePointer (pointerClip) {
pointerClip._x = pointerClip._parent._xmouse;
pointerClip._y = pointerClip._parent._ymouse;

// Use updateAfterEvent( ) to make sure that the screen updates often enough.
updateAfterEvent( );
}

// Set the interval on which updatePointer( ) is called.


pointerInterval = setInterval(updatePointer, 100, mousePointer);

// Hide the default pointer.


[Link]( );

// Create a square movie clip.


_root.createEmptyMovieClip("square_mc", 1);
square_mc.lineStyle(1, 0x000000, 100);
square_mc.beginFill(0, 0);
square_mc.drawRectangle(100, 100);
square_mc.endFill;

// Make the square draggable when clicked.


square_mc.onPress = function ( ) {
[Link]( );
};
square_mc.onRelease = function ( ) {
[Link]( );
};

To show the default pointer again in place of the custom pointer, follow these steps:

[Link] 2004-02-06
Strona 27 z 41

1. Remove the custom pointer movie clip using removeMovieClip( ) or hide it by setting its
_visible property to false:

mousePointer._visible = false;

2. Call the [Link]( ) method:

[Link]( );

[ Team LiB ]
[ Team LiB ]

Recipe 7.15 Checking for Overlapping Movie Clips


(Performing Hit Tests)
7.15.1 Problem
You want to find out if two movie clips are overlapping.

7.15.2 Solution
Use the hitTest( ) method. Or, for testing collisions between two complex shapes, create and uti
custom hitTestOutline( ) method.

7.15.3 Discussion
The hitTest( ) method enables you to determine one of three things:

z Whether any point within the bounding box of one movie clip overlaps any point in the bou
box of another movie clip

z Whether a specific coordinate is within the bounding box of a movie clip

z Whether a specific coordinate is within the outline of a movie clip's shape

The easiest way to test whether a movie clip overlaps another clip is to call hitTest( ) from one m
clip instance and pass it a reference to the other movie clip instance:

// Returns true if myMovieClipA overlaps myMovieClipB, and false otherwise. This


// tests whether the bounding boxes overlap. If the shapes are nonrectangular, th
// technique may not be appropriate.
[Link](myMovieClipB);

Here is a working example that draws a circle and a rectangle. Then, using the startDrag( ) meth
the code causes the circle movie clip to follow the pointer. Using an onEnterFrame( ) method in
conjunction with hitTest( ), the code continually checks whether the circle overlaps the rectangle
you test the example, you can see that there are times when the hit test returns true even thou
circle shape does not overlap the rectangle. This is because the circle's bounding box might over
with the rectangle even though its outline does not.

// Include [Link] from Chapter 4 for its custom drawing methods.


#include "[Link]"

[Link] 2004-02-06
Strona 28 z 41

// Draw a circle.
_root.createEmptyMovieClip("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.drawCircle(20);

// Draw a rectangle and position it at (200,200).


_root.createEmptyMovieClip("rectangle_mc", 2);
rectangle_mc.lineStyle(1, 0x000000, 100);
rectangle_mc.drawRectangle(100, 100);
rectangle_mc._x = 200;
rectangle_mc._y = 200;

// Tell the circle to snap to the pointer and follow it around.


circle_mc.startDrag(true);

// Use an onEnterFrame( ) method to continually check hitTest( )'s value.


circle_mc.onEnterFrame = function ( ) {
// Use trace( ) to output the value of the hit test.
trace([Link](_root.rectangle_mc));
};

Another way to use hitTest( ) is to check whether a coordinate is within the bounding box of the
clip. When you use hitTest( ) in this way, you do not need to specify another movie clip instance
the x and y coordinates against which you wish to test. Additionally, you should set the third,
shapeFlag parameter to false to let hitTest( ) know you want to perform a test on the bounding
of the movie clip and not its outline. Here is an example that checks to see if the coordinates of t
mouse pointer are within a rectangle:

// Include [Link] from Chapter 4 for its drawRectangle( ) method.


#include "[Link]"

_root.createEmptyMovieClip("rectangle_mc", 1);
rectangle_mc.lineStyle(1, 0x000000, 100);
rectangle_mc.drawRectangle(100, 100);
rectangle_mc._x = 200;
rectangle_mc._y = 200;

rectangle_mc.onEnterFrame = function ( ) {
// Perform a hit test using the _xmouse and _ymouse properties of _root. This w
// test true if the mouse pointer is over the rectangle and false otherwise.
trace([Link](_root._xmouse, _root._ymouse, false));
};

If you want to test whether coordinates are within a movie clip's shape instead of simply testing
hits within the bounding box, use the same basic technique as shown in the preceding example b
the shapeFlag parameter to true. This is great for testing to see if a coordinate is within a comp
(nonrectangular) shape. Here is an example that does just that:

// Include [Link] from Chapter 4 for its drawCircle( ) method.


#include "[Link]"

_root.createEmptyMovieClip("circle_mc", 1);
circle_mc.lineStyle(1, 0x000000, 100);
circle_mc.drawCircle(60);
circle_mc._x = 200;
circle_mc._y = 200;

circle_mc.onEnterFrame = function ( ) {
trace([Link](_root._xmouse, _root._ymouse, true));
};

[Link] 2004-02-06
Strona 29 z 41

All hit tests are performed using global coordinates—i.e., coordinates within the
_root object's coordinate system.

Notice that in the preceding example, the mouse pointer coordinates were obtained using the _x
and _ymouse properties of _root. You would not get the same results if you used the mouse poin
coordinates relative to the rectangle or circle clips. This is important to understand in situations i
which you want to test to see if a coordinate within one movie clip is overlapping another movie
In these cases you need to convert the local coordinates to global coordinates using the localToG
( ) method and test using the resulting values. The following example demonstrates this:

// Include [Link] from Chapter 4 for its drawCircle( ) method.


#include "[Link]"

// Draw a filled circle and position it at (200,200).


_root.createEmptyMovieClip("staticCircle_mc", 2);
staticCircle_mc.lineStyle(1, 0x000000, 100);
staticCircle_mc.beginFill(0xFF, 33);
staticCircle_mc.drawCircle(100);
staticCircle_mc.endFill( );
staticCircle_mc._x = 200;
staticCircle_mc._y = 200;

// Create a movie clip that will follow the mouse pointer. Inside that movie clip
// create a nested movie clip and draw a small circle in it.
_root.createEmptyMovieClip("bouncingCircleHolder", 1);
[Link]("bouncingCircle_mc", 1);
bouncingCircleHolder.bouncingCircle_mc.lineStyle(1, 0, 100);
bouncingCircleHolder.bouncingCircle_mc.drawCircle(3);

// Tell the bouncingCircle_mc movie clip to move back and forth within the
// bouncingCircleHolder movie clip.
bouncingCircleHolder.bouncingCircle_mc.velocity = 3;
bouncingCircleHolder.bouncingCircle_mc.onEnterFrame = function ( ) {

// Change the x position of bouncingCircle_mc by the velocity.


this._x += [Link];

// If the x position is outside of the range of -30 to 30, then reverse the
// velocity so that it begins moving back in the opposite direction.
if (this._x < -30 || this._x > 30) {
[Link] *= -1;
}

// Create a point object for use with the localToGlobal( ) method. Assign it th
// and y values from bouncingCircle_mc.
var pnts = {x: this._x, y: this._y};

// Use localToGlobal( ) to convert the points' object values


// to their global equivalents.
this._parent.localToGlobal(pnts);

// Output the hit test results for staticCircle_mc within the coordinates of
// bouncingCircle_mc (as they have been converted to their global equivalents).
trace(_root.staticCircle_mc.hitTest(pnts.x, pnts.y, true));
};

// Instruct the boundingCircleHolder movie clip to follow the mouse pointer.


[Link](true);

In the preceding example, you cannot get accurate results by simply performing a hit test betwe

[Link] 2004-02-06
Strona 30 z 41

staticCircle_mc and bouncingCircle_mc. This is because such a hit test would be performed
between the bounding boxes of the two movie clips, and they are each circles. Notice, however,
while this example works more or less when bouncingCircle_mc is relatively small, a larger vers
would not produce very accurate results. The reason for this is that the hit test is being performe
between staticCircle_mc and the center point of bouncingCircle_mc, not the entire
bouncingCircle_mc shape.

ActionScript does not provide a simple way to perform a hit test between two circles. However, y
can perform such a test by checking whether the centers of the circles are within a distance that
than the sum of their radii, as shown in the following example (see Recipe 5.13 for more informa

// Include [Link] from Chapter 4 for its drawCircle( ) method.


#include "[Link]"

// Create two circle movie clips: circle0 and circle1.


_root.createEmptyMovieClip("circle0", 1);
[Link](1, 0x000000, 100);
[Link](60);
circle0._x = 200;
circle0._y = 200;

_root.createEmptyMovieClip("circle1", 2);
[Link](1, 0x000000, 100);
[Link](60);
[Link](true);

// Continually check to see if the circles overlap.


[Link] = function ( ) {

// Find the difference in x and y coordinates between


// the centers of the two circles.
var dx = circle0._x - this._x;
var dy = circle0._y - this._y;

// Calculate the distance between the centers of the two circles.


var dist = [Link]([Link](dx, 2) + [Link](dy, 2));

// If the distance between the centers is less than the sum of


// the two radii, the circles overlap.
if (dist < (circle0._width + this._width)/2) {
trace("circles touching");
}
};

There is no trivial way to perform hit tests between two complex shapes in ActionScript. Howeve
can be done with a little bit of work. The premise is that while you cannot use hitTest( ) to test o
the shapes of two movie clips (and not their bounding boxes), you can mimic this by performing
shape-sensitive hit test between one of the objects and a series of movie clips that outline the sh
of the other. Traditionally, people have accomplished this by creating a series of small, circle mo
clips around the outline of one of the shapes at authoring time. However, you can do even bette
that. The following code illustrates how to create a series of small circles to outline a shape at ru
and how to use them to perform a hit test between two complex movie clips:

// Include [Link] from Chapter 4 for its drawCircle( ) method.


#include "[Link]"

// The outline( ) method creates a series of small circles within the movie clip
// outline the shape. The optional circleRadius parameter defines the radii of th
// circles. The smaller the circles, the more accurate the hit test, but it is al
// more processor-intensive. The optional show parameter allows you to show the
// circles for testing purposes.
[Link] = function (circleRadius, show) {

[Link] 2004-02-06
Strona 31 z 41

// Use a default radius of 3 pixels.


if (circleRadius == undefined) {
circleRadius = 3;
}

// Create an array for holding the references to the outline circles.


[Link] = new Array( );

// Get the coordinates of the bounding box and set the x and y variables
// accordingly. The x variable must be more than the minimum x boundary because
// otherwise the method will not be able to locate the shape.
var bounds = [Link](this);
var x = [Link] + circleRadius;
var y = [Link];

// Begin by outlining the shape from the top-left corner


// and moving to the right as x increases.
var dir = "incX";
goodToGo = true;
var pnts;
var i = 0;

// The goodToGo variable is true until the last circle is drawn.


while (goodToGo) {
i++;

// Create the new circle outline movie clip and draw a circle in it.
[Link]("outline" + i, i);
var mc = this["outline" + i];
[Link](0, 0x0000FF, 100);
[Link](circleRadius);

// Set the circle visibility to false unless show is true.


mc._visible = show ? true : false;

// Add the circle movie clip to the outlines array for use during the hit tes
[Link](mc);

// Check to see in which direction the outline is being drawn.


switch (dir) {
case "incX":

// Increment the x value by the width of one of the circles to move the n
// circle just to the right of the previous one.
x += mc._width;

// Create a point object and call localToGlobal( ) to convert the values


// the global equivalents.
pnts = {x: x, y: y};
[Link](pnts);

// If the center of the circle does not touch the shape within the movie
// clip, increment y and calculate the new global equivalents for another
// test. This moves the circle down until it touches the shape.
while (![Link](pnts.x, pnts.y, true)) {
y += mc._width;
pnts = {x: x, y: y};
[Link](pnts);
}

// If the maximum x boundary has been reached, set the new direction to b
// moving in the increasing y direction.
if (x >= [Link] - (mc._width)) {
dir = "incY";

[Link] 2004-02-06
Strona 32 z 41

// Set the coordinates of the circle movie clip.


mc._x = x;
mc._y = y;

// Reset y to the minimum y boundary so that you can move the next circle
// down from the top until it touches the shape.
y = [Link];
break;

case "incY":
// The remaining cases are much like the first, but they move in differen
// directions.
y += mc._width;
pnts = {x: x, y: y};
[Link](pnts);
while (![Link](pnts.x, pnts.y, true)) {
x -= mc._width;
pnts = {x: x, y: y};
[Link](pnts);
}
if (y >= [Link] - (mc._width)) {
dir = "decX";
}
mc._x = x;
mc._y = y;
x = [Link];
break;

case "decX":
x -= mc._width;
pnts = {x: x, y: y};
[Link](pnts);
while (![Link](pnts.x, pnts.y, true)) {
y -= mc._width;
pnts = {x: x, y: y};
[Link](pnts);
}
if (x <= [Link] + (mc._width)) {
dir = "decY";
}
mc._x = x;
mc._y = y;
y = [Link];
break;

case "decY":
y -= mc._width;
pnts = {x: x, y: y};
[Link](pnts);
while (![Link](pnts.x, pnts.y, true)) {
x += mc._width;
pnts = {x: x, y: y};
[Link](pnts);
}
if (y <= [Link] + (mc._width)) {
goodToGo = false;
}
mc._x = x;
mc._y = y;
x = [Link];
break;
}

[Link] 2004-02-06
Strona 33 z 41

}
};

// Perform a hit test between a movie clip with outline circles and another movie
// clip you specify in the parameter.
[Link] = function (mc) {

// Loop through all the elements of the outlines array.


for (var i = 0; i < [Link]; i++) {

// Create a point object and get the global equivalents.


var pnts = {x:[Link][i]._x, y:[Link][i]._y};
[Link](pnts);

// If the mc movie clip tests true for overlapping with any of the outline
// circles, then return true. Otherwise, the method returns false.
if ([Link](pnts.x, pnts.y, true)) {
return true;
}
}
return false;
};

// In this example, create two movie clips with complex shapes. Create instances
// named mc1 and mc2. This code creates an outline on mc1 (and for testing purpos
// it shows you the circles).
[Link](3, true);

// Continually perform a hit test between the two shapes. The hitTestOutline( ) m
// must be invoked from the movie clip with the outlines.
[Link] = function ( ) {
trace([Link](mc2));
};

// Tell mc2 to follow the mouse pointer.


[Link](true);

7.15.4 See Also


Recipe 7.6 and Recipe 7.17
[ Team LiB ]
[ Team LiB ]

Recipe 7.16 Changing Stacking Order


7.16.1 Problem
You want to swap the depths of two movie clips.

7.16.2 Solution
Use the swapDepths( ) method.

7.16.3 Discussion
The swapDepths( ) method swaps the depths of any two movie clips. You should call it from one
of the two movie clips and pass it a reference to the other:

[Link](myMovieClip2);

[Link] 2004-02-06
Strona 34 z 41

One way in which this method is particularly useful is in swapping the depths of two movie clips
when hitTest( ) detects that one overlaps the other. The following example uses this concept but
also illustrates the problem that can occur when trying to use this technique.

The following example shows a white circle and a black circle that overlap
and rapidly change depths. The result is a very fast blinking effect, which
may be induce seizures in readers prone to epilepsy or may induce nausea
in sensitive viewers. Please exercise caution.

The following code swaps two clips' depths when they overlap:

// Include [Link] from Chapter 4 for its drawCircle( ) method.


#include "[Link]"

// Draw two circle movie clips: one black and one white.
_root.createEmptyMovieClip("circle1", 1);
[Link](1, 0x000000, 100);
[Link](0xFFFFFF, 100);
[Link](100);
[Link]( );

_root.createEmptyMovieClip("circle2", 2);
[Link](1, 0x000000, 100);
[Link](0, 100);
[Link](100);
[Link]( );

// Set each movie clip to be on the visible part of the Stage.


circle1._x = circle2._x = 200;
circle1._y = circle2._y = 200;

// Make each movie clip draggable when clicked.


[Link] = function ( ) {
[Link]( );
};

[Link] = function ( ) {
[Link]( );
};

[Link] = function ( ) {
[Link]( );
};

[Link] = function ( ) {
[Link]( );
};

// Continually check to see if circle1 and circle2 overlap. If so, call the
// swapDepths( ) method to switch their depths.
_root.onEnterFrame = function ( ) {
if ([Link](circle2)) {
[Link](circle2);
}
};

As you can see, the depths swap back and forth over and over. You need to add more logic to th
code to instruct the movie how to perform a more intelligent swap. The basic process is that the
onEnterFrame( ) method needs to know which movie clip should remain on top, and it should
perform that swap only if the movie clip is not already on top. Here is how you can rewrite the
previous code to fix the problem (changes are shown in bold).

[Link] 2004-02-06
Strona 35 z 41

. . .
[Link] = function ( ) {
[Link]( );

// Set a dragged property in _root, so _root knows that


// circle1 is being dragged. And set a nondragged property in _root,
// so _root knows that circle2 is not being dragged.
_root.dragged = this;
_root.nondragged = _root.circle2;
};

[Link] = function ( ) {
[Link]( );
};

[Link] = function ( ) {
[Link]( );

// Set the dragged and nondragged properties as before, but with the values
// reversed from when circle1 was being dragged.
_root.dragged = this;
_root.nondragged = _root.circle1;
};

[Link] = function ( ) {
[Link]( );
};

_root.onEnterFrame = function ( ) {

// Now check to see if the two movie clips are overlapping and see if the dragg
// movie clip is below the nondragged movie

clip. If so, swap them.


if ([Link]([Link]) &&
[Link]() < [Link]( )) {
[Link]([Link]);
}
};

7.16.4 See Also


Recipe 7.11

[ Team LiB ]
[ Team LiB ]

Recipe 7.17 Converting Between Coordinate Spaces


7.17.1 Problem
You want to convert between the movie clip's coordinate space and the Stage's coordinate
space, or vice versa.

7.17.2 Solution
Create a point object and use localToGlobal( ) or globalToLocal( ).

[Link] 2004-02-06
Strona 36 z 41

7.17.3 Discussion
The most common reason for needing to convert coordinates from one coordinate space to
another is to perform hit tests (see Recipe 7.15). But regardless of why you want to perform
the coordinate conversions, you can quickly accomplish your task using the localToGlobal( ) and
globalToLocal( ) methods. Each method requires you to first create a point object. A point
object is an instance of the Object class with x and y properties set to the x and y coordinates
of the point of interest.

You can define a point object using an object literal. For example:

pnts = {x: 24, y:42};

If the point object represents a point in the global coordinate system that you want to convert
to the local equivalent, you should use the globalToLocal( ) method and invoke it from the
target movie clip:

[Link](pnts);

If you want to convert a local point to a point in the global coordinate system, you should use
the localToGlobal( ) method instead:

[Link](pnts);

7.17.4 See Also


Recipe 7.15

[ Team LiB ]
[ Team LiB ]

Recipe 7.18 Duplicating Movie Clips


7.18.1 Problem
You have an existing movie clip on the Stage and you want to create duplicates of it.

7.18.2 Solution
Use the duplicateMovieClip( ) method.

7.18.3 Discussion
You can create a duplicate of any existing movie clip (except _root) by calling the duplicateMovi
( ) method from it. The duplicate movie clip is created within the same timeline as the original, a
inherits all the settings of standard movie clip properties (such as _x, _y, _rotation, etc.) as we
onClipEvent( ) handlers from the original clip. While the duplicated movie clip does not inherit an
custom properties or any event handler methods from the original, you can use an initialization o
to copy properties to the new object (see Recipe 7.19). The duplicateMovieClip( ) method require
least two parameters: the name and depth of the new movie clip.

// This example creates a new movie clip named myDupMovieClip1 at depth 1. The new
// movie clip is a copy of myMovieClip and resides on the same timeline.
[Link]("myDupMovieClip1", 1);

[Link] 2004-02-06
Strona 37 z 41

// This example creates a movie clip named myDupMovieClip2 with a depth of 2. It


// initialized such that the _x property is set to 20.
[Link]("myDupMovieClip2", 2, {_x: 20});

When you use duplicateMovieClip( ), you often want to create slightly customized duplicates. In
words, all the duplicates share the same basic core but might be customized with various color
settings, etc. For example, you might want to create 30 duplicates of a balloon movie clip, positi
each at random points on the Stage. A basic for statement can be useful when creating the dupli
Also, though this aspect is undocumented, the method returns a reference to the duplicate movi
which can be useful in many situations.

// Use a for statement to loop 30 times.


for (var i = 0; i < 30; i++) {

// Generate random x and y coordinates.


x = [Link]( ) * 550;
y = [Link]( ) * 400;

// Create the duplicate movie clip with names balloon0, balloon1, etc. from the
// original movie clip named balloonBase_mc. Each duplicate is positioned random
// using the init object. The returned reference to the new movie clip is assign
// to the variable balloonDuplicate.
balloonDuplicate = balloonBase_mc.duplicateMovieClip("balloon" + i, i,
{_x: x, _y: y});

// Create a Color object to control the new movie clip. Assign it to a custom
// property of the new movie clip so that you can differentiate easily between
// Color objects.
[Link] = new Color(balloonDuplicate);

// Set the RGB for the new movie clip to a random number.
[Link]([Link]( ) * 255 * 255 * 255);
}

Even though the duplicateMovieClip( ) method does not apply custom properties and methods
(including event handler methods) to the new movie clip, there is a technique that you can use a
short workaround. When you use a for . . . in statement to loop through all the properties of a m
clip, only custom properties and methods show up. Therefore, you can use a for . . . in loop to cr
an initialization object to pass to the duplicateMovieClip( ) method:

// Create an initialization object with the Object constructor, then use a for .
// loop to create elements within the initialization object that correspond to th
// elements within the original movie clip.
init = new Object( );
for (var item in myMovieClip) {
init[item] = myMovieClip[item];
}

// Call duplicateMovieClip( ) to create a new movie clip that inherits the stand
// properties (as do all duplicated movie clips) and the custom properties and
// methods by way of the initialization object.
[Link]("myDupMovieClip2", 2, init);

7.18.4 See Also


Recipe 7.19

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 38 z 41

Recipe 7.19 Adding Movie Clips from the Library with


ActionScript
7.19.1 Problem
You want to add a movie clip to your movie from the library without having to create an instance
manually at authoring time.

7.19.2 Solution
Use the attachMovie( ) method.

7.19.3 Discussion
You should make a practice of using the attachMovie( ) method to add movie clips to your movie
any instances that are controlled entirely by ActionScript. The attachMovie( ) method allows you
add a movie clip using ActionScript at runtime instead of having to add the movie clip manually a
authoring time. This is advantageous for several reasons, including:

z With attachMovie( ), you can add movie clips dynamically. This is extremely important for
movies in which you need to load movie clips based on user information or data drawn from
XML file or a database.

z It is bad practice to add movie clips to the Stage at authoring time that will be controlled
exclusively by ActionScript. Don't add movie clips to your Stage at authoring time unless th
are being manually animated or you need to visually lay them out.

To use attachMovie( ), you must export the movie clip Library symbols. Library symbols are
automatically exported with the movie when you create instances of them at authoring time.
However, if a symbol is not explicitly used within the movie as a manually created instance, it is
automatically included in the .swf file (this saves space by not including unused symbols). In the
cases, you need to tell Flash to export the symbol so that you can use it from ActionScript at run
To do this, follow these steps:

1. Select a Library symbol and open the Linkage properties from the Library panel's pop-up
Options menu.

2. In the Linkage Properties dialog box, select the Export for ActionScript and Export in First
Frame checkboxes (see Figure 7-1).

3. You should also give the symbol a linkage identifier. The linkage identifier is the name that
use with the attachMovie( ) method. Give the symbol a linkage identifier that is descriptive
it is easier to remember. As a best practice, you should give your symbols linkage identifie
that end with Symbol. For example, largeCircleSymbol is a much better identifier than Sy
1.

4. Click on OK to close the Linkage Properties dialog box.

Figure 7-1. Setting a Library symbol's properties for ActionScript acce

[Link] 2004-02-06
Strona 39 z 41

Figure 7-1 shows a Library symbol with its symbol linkage identifier set to "semiCircle_symbol" i
Linkage Properties dialog box (accessible from the Library panel's Options pop-up menu).

The preceding steps explain what is perhaps the most common way of exporting movie clip symb
for use at runtime. If you are creating a movie that will be distributed on a CD-ROM or other non
Internet-based medium, or if you will require that the user download the entire movie prior to
beginning playback (i.e., a nonstreaming movie) then the preceding technique will work perfectly
you. However, because the technique exports all the movie clips on the first frame of the movie,
can cause a long initial wait for movies that are intended to stream. In these cases, you can mod
the technique slightly so that the movie clips are not exported on the first frame, but instead on
frames where they are used. To do this, follow these steps:

1. Select a Library symbol and open the Linkage properties from the Library panel's pop-up
Options menu.

2. In the Linkage Properties dialog box, select the Export for ActionScript checkbox.

3. Uncheck the Export in First Frame checkbox. You deselect this option because you will man
export the movie clip at the appropriate frame.

4. Give the symbol a linkage identifier.

5. Click on OK to close the Linkage Properties dialog box.

6. On the timeline on which you wish to attach the movie clip, create a new layer named Exp
Clips and create a new keyframe on the frame where you want to attach the movie clip.

7. Create an instance of the movie clip symbol on the keyframe from Step 6. You don't need
name the instance. And, typically, you will want to create the instance off stage and/or set
_alpha property to 0.

Once a symbol has been properly set for export, you can use ActionScript to add instances to the
movie. You can call the attachMovie( ) method from any existing movie clip, including _root. Th
method adds an instance of the Library symbol within the movie clip from which it is called. The
method requires at least three parameters: the linkage name, the new instance name, and the d

// Create an instance of the largeCircleSymbol symbol. The new movie clip is name
// myCircle1, and it has a depth of 1.
_root.attachMovie("largeCircleSymbol", "myCircle1", 1);

// Once the instance has been created, you can do anything with it that you can d
// with any other movie clip. For example, here myCircle1 is moved to x = 120.
myCircle1._x = 120;

The attachMovie( ) method also allows for a fourth, optional parameter: an initialization object. T

[Link] 2004-02-06
Strona 40 z 41

initialization object should be an instance of the Object class (whether created with the construct
as an object literal), and the properties of that object are then applied to the new movie clip. Thi
convenient way to initialize a movie clip. It is possible to accomplish the same task without the
initialization object, but in most cases it is easier to use the initialization object.

// This one line of code accomplishes the same task as the two lines of code in th
// previous code block.
_root.attachMovie("largeCircleSymbol", "myCircle1", 1, {_x: 120});

The initialization object is most useful when you want to initialize multiple properties within the n
movie clip:

// In this example four properties are initialized after creating the new movie c
// This requires five lines total.
_root.attachMovie("largeCircleSymbol", "myCircle1", 1);
myCircle1._x = 120;
myCircle1._y = 90;
myCircle1._xscale = 50;
myCircle1._yscale = 50;

// You can accomplish the same task with fewer lines using the initialization obj
_root.attachMovie("largeCircleSymbol", "myCircle1", 1,
{_x: 120, _y: 90, _xscale: 50, _yscale: 50});

You can use attachMovie( ) to create dynamically named movie clips. This is particularly useful w
you want to create movie clips with sequential names, such as mc0, mc1, mc2, and so on.

// This for statement creates 10 instances of largeCircleSymbol


// and names them mc0 through mc9.
for (var i = 0; i < 10; i++) {
_root.attachMovie("largeCircleSymbol", "mc" + i, i, {_x: i*20, _y: i*20});
}

The attachMovie( ) method also returns a reference to the movie clip it creates. This can be usef
cases where you use attachMovie( ) to create movie clips with dynamic names. For example, you
might use the contents of an XML document to dynamically create button movie clips in your mo
in which the buttons are named in sequence, such as myButton0, myButton1, myButton2, etc:

/* This example shows the onLoad( ) method of an XML object that loads the follow
document:
<navigation>
<button label="Housewares" categoryId="12" />
<button label="Books" categoryId="99" />
<button label="Electronics" categoryId="63" />
<button label="Software" categoryId="24" />
<button label="Furniture" categoryId="42" />
</navigation>

The attachMovie( ) method returns a reference to the movie clip that it create
this example, it is assigned to the variable btn. Without the use of the btn
reference, the subsequent lines of code would have to substitute
_root["myButton" + i] in place of btn.
*/
[Link] = function ( ) {
var categories = [Link];
var btn;
for (var i = 0; i < [Link]; i++) {
btn = _root.attachMovie("FPushButtonSymbol", "myButton" + i, i);
[Link] = categories[i].[Link];
btn._y = i * 25;
[Link] = function ( ) {

[Link] 2004-02-06
Strona 41 z 41

trace([Link]);
};
}
};

The preceding example is more useful with Flash 5 movies than with later versions of Flash beca
you can use the initialization object to accomplish the same task since its availability in Flash MX
can even use the initialization object to assign methods to a movie clip created with attachMovie

// Here is the previous example rewritten to use an initialization object.


[Link] = function ( ) {
var categories = [Link];

// Create a variable, init, to be the initialization object.


var init;
for (var i = 0; i < [Link]; i++) {
// For each element from the XML document, create a new initialization object
// using the Object class constructor. Then, assign to init all the propertie
// (and methods) you want assigned to the new movie clip.
init = new Object( );
[Link] = categories[i].[Link];
init._y = i * 25;
[Link] = function ( ) {
trace([Link]);
};

// Call attachMovie( ) and pass it init.


_root.attachMovie("FPushButtonSymbol", "myButton" + i, i, init);
}
};

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 33

[ Team LiB ]

Chapter 8. Text
Introduction

Recipe 8.1. Referring to a Text Field via ActionScript

Recipe 8.2. Creating a Text Field

Recipe 8.3. Creating an Outline Around a Text Field

Recipe 8.4. Creating a Background for a Text Field

Recipe 8.5. Making a User Input Field

Recipe 8.6. Making a Password Input Field

Recipe 8.7. Filtering Text Input

Recipe 8.8. Restricting the Maximum Field Length

Recipe 8.9. Displaying Dynamic Text at Runtime

Recipe 8.10. Displaying HTML-Formatted Text

Recipe 8.11. Condensing Whitespace

Recipe 8.12. Sizing Text Fields to Fit Contents

Recipe 8.13. Scrolling Text with the ScrollBar Component

Recipe 8.14. Scrolling Text Programmatically

Recipe 8.15. Responding to Scroll Events

Recipe 8.16. Formatting Existing Text

Recipe 8.17. Formatting User-Input Text

Recipe 8.18. Formatting a Portion of a Text Field

Recipe 8.19. Setting a Text Field's Font

Recipe 8.20. Embedding Fonts

Recipe 8.21. Creating Text That Can Be Rotated

Recipe 8.22. Displaying Unicode Text

Recipe 8.23. Assigning Focus to a Text Field

Recipe 8.24. Selecting Text with ActionScript

[Link] 2004-02-06
Strona 2 z 33

Recipe 8.25. Setting the Insertion Point in a Text Field

Recipe 8.26. Responding When Text Is Selected or Deselected

Recipe 8.27. Responding to User Text Entry

Recipe 8.28. Adding a Hyperlink to Text


[ Team LiB ]
[ Team LiB ]

Introduction
Flash MX introduces a rich ActionScript API for working with text in movies. Prior versions didn't
allow much more than being able to control the value displayed in manually created text fields.

The TextField class is used to create and manipulate text at runtime (though the text itself can
be created either at runtime or authoring time). Through methods and properties of a text field
you can alter text visibility, change the position of the text within the movie, enable user input,
and much more. In addition, there are two "helper" classes—TextFormat and Selection—that
play a supporting role. TextFormat objects are used to apply formatting options such as font,
font size, indentation, and margins to text fields. The Selection object allows programmatic
control over keyboard focus and is helpful for selecting and deselecting text fields and the text
within them.

Text fields share similar properties with MovieClip and Button objects to control an object's
position, rotation, and visibility, plus others shown in Table 8-1 (for a complete list of TextField
properties, refer to the online help under Help ActionScript Dictionary).

Table 8-1. TextField graphical display properties


Property Description
_x x coordinate of the text field relative to its parent
_y y coordinate of the text field relative to its parent
_width Width of the text field in pixels
_height Height of the text field in pixels
_xscale Horizontal scale of the text field as a percentage
_yscale Vertical scale of the text field as a percentage
_alpha Transparency of the text field (range of 0 to 100)
_visible Visibility of the text field object (true or false)
_rotation Clockwise rotation of the text field in degrees
_xmouse The cursor's x coordinate relative to the text field's coordinate system
_ymouse The cursor's y coordinate relative to the text field's coordinate system
[ Team LiB ]
[ Team LiB ]

Recipe 8.1 Referring to a Text Field via ActionScript


8.1.1 Problem

[Link] 2004-02-06
Strona 3 z 33

You want to refer to a text field via ActionScript.

8.1.2 Solution
Refer to author-time text fields by their instance names.

8.1.3 Discussion
Many of the recipes in this chapter require you to access a text field's methods or properties.
Sometimes text fields are created at runtime via ActionScript, but often you'll want to adapt a
recipe to work with your author-time content.

ActionScript refers to author-time text fields using the text field instance name, which must be
set, as follows:

1. Select the text field instance on the Stage.

2. Open the Property inspector.

3. Change the text field's type from "Static Text" to "Dynamic Text" (change it to "Input
Text" if you want to allow user input at runtime).

4. Specify an instance name in the Property inspector (the instance name field appears once
you complete Step 3).

8.1.4 See Also


To refer to a text field created at runtime, use the TextField instance returned by
[Link]( ), as demonstrated in Recipe 8.2. For details on creating editable
text fields, see Recipe 8.5.
[ Team LiB ]
[ Team LiB ]

Recipe 8.2 Creating a Text Field


8.2.1 Problem
You want to create a text field at runtime that you can use in your Flash movie.

8.2.2 Solution
You can create a text field using the [Link]( ) method.

8.2.3 Discussion
You can create a text field at authoring time using Flash's Text tool. Manual creation lets you
see the layout of the objects on the Stage. However, many projects benefit from creating text
fields dynamically. For example:

z An animation with randomized text elements

z A user interface in which items (and their labels) are created dynamically based on data
loaded into the movie from a text file, XML document, or other source

[Link] 2004-02-06
Strona 4 z 33

z A form that automatically adapts to a user's input

z Word games

The [Link]( ) method creates a text field at runtime. Note that the
createTextField( ) method is invoked on a MovieClip object, not a text field. (If we had a text
field already, we wouldn't need the recipe, now would we?)

The createTextField( ) method takes six required parameters:

[Link](name, depth, x, y, width, height);

where the parameters are as follows:

name

The instance name of the new text field

depth

The depth (within the MovieClip object) of the new text field

The x position relative to parentMovieClip's registration point

The y position relative to parentMovieClip's registration point

width

The width of the field in pixels

height

The height of the field in pixels

The new text field is created within the movie clip from which the method is invoked, which is
called the parent clip or container clip.

This example creates a new text field named myText—with a depth of 1, positioned at (0, 0),
and with a width of 100 and a height of 20—within _root:

_root.createTextField("myText", 1, 0, 0, 100, 20);

Once the text field is created, it can be targeted using [Link], as


follows:

_root.myText._x = 100;

The parent clip can be _root (the main timeline) or a movie clip created either at runtime or
authoring time. You can use the duplicateMovieClip( ), attachMovie( ), and
createEmptyMovieClip( ) methods of the MovieClip class to dynamically create a parent movie
clip to hold the text field:

[Link] 2004-02-06
Strona 5 z 33

_root.createEmptyMovieClip("myTextHolder", 1);
_root.[Link]("myText", 1, 0, 0, 100, 20);
_root.[Link] = "This is a new text object";

You can remove a TextField created with createTextField( ) simply by invoking the
removeTextField( ) method on that object, as follows:

[Link]( );

8.2.4 See Also


Recipe 7.11

[ Team LiB ]
[ Team LiB ]

Recipe 8.3 Creating an Outline Around a Text Field


8.3.1 Problem
You want to make a border around a text field.

8.3.2 Solution
Set the text field's border property to true. Additionally, you can change the color of the
border by setting the object's borderColor property.

8.3.3 Discussion
By default, a text field lacks a visible border, which may be desirable. For example, you may
not want a border around an item label. However, user input fields have borders by convention.
The border shows the user where to click to input a value. Simply setting a text field's border
property to true turns on the border around the object.

[Link] = true;

To turn off the border, simply set the border property to false.

The default border color is black but can be changed by setting the borderColor property,
which accepts a hex RGB value corresponding to the desired color:

[Link] = 0xFF00FF; // Make the border violet.

8.3.4 See Also


Recipe 3.2 and Recipe 8.4

[ Team LiB ]
[ Team LiB ]

Recipe 8.4 Creating a Background for a Text Field


8.4.1 Problem

[Link] 2004-02-06
Strona 6 z 33

You want to make a visible background behind the text in a text field.

8.4.2 Solution
Set the text field's background property to true. Additionally, you can change the color of the
background by setting the object's backgroundColor property.

8.4.3 Discussion
Text fields lack a visible background by default (in other words, the background is transparent).
However, you can create a background for a text field by setting the background property to
true for that object:

[Link] = true;

By default, the background for a text field is white (if made visible). You can, however, assign
the background color by setting the value of the object's backgroundColor property, which
accepts a hex RGB value corresponding to the desired color:

[Link] = 0x00FFFF; // Set the background to light blue.

[ Team LiB ]
[ Team LiB ]

Recipe 8.5 Making a User Input Field


8.5.1 Problem
You want to create a user input field to allow the user to enter text.

8.5.2 Solution
Set the text field's type property to "input". Alternatively, you can create and use a custom
createInputTextField( ) method.

8.5.3 Discussion
When a text field is created using createTextField( ), it defaults to being a dynamic field. This
means that it can be controlled with ActionScript but the user cannot input text into it. To enable
the field for user input, set the type property to "input", like so:

[Link] = "input";

Though it isn't a requirement, input fields generally also have a border and a background;
otherwise, the user may have difficulty finding and selecting the field so as to enter a value into
it:

[Link] = true;
[Link] = true;

For a user to be able to input text, the field's selectable property must be true, which is the
default. You do not need to explicitly set the selectable property to true unless you previously
set it to false.

[Link] 2004-02-06
Strona 7 z 33

As you can see, several steps are required to successfully make an input text field using
ActionScript. While none of them are particularly difficult, they can be a bit tedious when you are
creating multiple input text fields. Therefore, it is convenient to create a custom
createInputTextField( ) method for the MovieClip class that takes care of these steps with a sing
method call. You should add the following code to a [Link] file in your Flash Include
directory for easy inclusion in other projects. You may prefer to add it to the [Link] file use
throughout Chapter 7, since it adds a method to the MovieClip class.

[Link] = function (name, depth, x, y, w, h) {

// Define a default width and height for text fields created with this method
// (width defaults to 100 and height defaults to 20).
if (w == undefined) {
w = 100;
}
if (h == undefined) {
h = 20;
}

// Create the text field using the built-in [Link]( ) metho


[Link](name, depth, x, y, w, h);

// Then assign the necessary values to the object to make it an input field.
this[name].border = true;
this[name].background = true;
this[name].type = "input";
};

You can use the custom createInputTextField( ) method in your Flash movies to create an input
text field with a single line of code. The method takes the same parameters in the same order as
the createTextField( ) method, but it automatically makes the text field an input field with the
border and background turned on. Additionally, with the createInputTextField( ) method, you can
omit the w and h parameters and allow the text field to use default settings (as opposed to the
createTextField( ) method, which requires that you provide all parameters):

// Make sure to include the necessary ActionScript file. This assumes you added th
// custom createInputTextField( ) method to [Link] and not [Link].
#include "[Link]"

// Create an input text field allowing the x and y coordinates to default to (0,0
// and the width and height to default to 100 x 20.
_root.createInputTextField("myInputText", 1);
[ Team LiB ]
[ Team LiB ]

Recipe 8.6 Making a Password Input Field


8.6.1 Problem
You want to create an input text field so that characters entered into it are displayed as
asterisks—a feature commonly used on password input fields.

8.6.2 Solution
Set the text field's password property to true.

8.6.3 Discussion

[Link] 2004-02-06
Strona 8 z 33

When a user enters a password into a field, you generally do not want observers to be able to
read the password. This is a basic security precaution. The common convention is to display
only asterisks in the field as the user types. This way, the user can see that he is successfully
entering a value without observers being able to easily read the password.

To create an input field that is automatically masked with asterisks, you only need to set the
[Link] property to true:

[Link] = true;

When you set the password property to true, all text entered into the text field, either
programmatically or by user input, displays as asterisks:

[Link] = true;
[Link] = "some text"; // Text field displays: *********

8.6.4 See Also


Recipe 8.5. Password text fields only obscure the display of the text; the value contained by the
text property is unaltered. Therefore, when you send the password value across an Internet
connection, it is not encrypted. If you need additional security when sending the password, you
should use HTTPS or use an encryption technique such as a cryptographic hash algorithm, as
Branden Hall discusses at
[Link] Also, read
Macromedia Flash Security whitepaper
[Link] See Recipe 11.19 for
information on transmitting data securely.
[ Team LiB ]
[ Team LiB ]

Recipe 8.7 Filtering Text Input


8.7.1 Problem
You want to restrict the characters that a user can type into an input field.

8.7.2 Solution
Set the restrict property of the text field.

8.7.3 Discussion
By default, a user can type any character into an input field. However, in many scenarios you mi
want to restrict the allowable characters. For example, you might restrict characters to numbers
dashes in the case of an input field for telephone numbers.

The [Link] property lets you specify the allowed characters for user input in a fiel
Specify a string containing the allowable characters, such as:

[Link] = "abcdefg";

This example lets the user enter any of the following allowable characters: a, b, c, d, e, f, or g.
Other characters are disallowed. If the user tries to enter "freedom", only "feed" will appear, sinc
the letters r, o, and m are not in the allowable character set.

[Link] 2004-02-06
Strona 9 z 33

If the restrict string is set to the empty string, then all characters are
allowed. To prevent input entirely, set the text field's type to "dynamic".

Also note that ActionScript distinguishes between upper- and lowercase characters. In other wor
there is a difference between a and A. If the restrict property is set to "abcdefg", the uppercas
variants of the allowable characters (such as A, B, C) will be entered as the lowercase (allowable
equivalents a, b, and c. The same is true in reverse: if a lowercase character is entered when on
the uppercase counterpart is allowed, the character will be converted to uppercase.

The restrict property supports certain regular expressions, as described in Recipe 9.6. Therefo
you can also enter ranges by indicating the first character in the range and the last character in
range separated by a dash (-):

[Link] = "a-zA-Z"; // Allows only upper- and lowercase letters


[Link] = "a-zA-Z "; // Allows only letters and spaces
[Link] = "0-9"; // Allows only numbers

In addition to specifying allowable characters, you can also disallow characters with a restrict
string by using the caret character (^). All characters and ranges in a restrict string following t
caret will be disallowed. For example:

[Link] = "^abcdefg"; // Allows all except lowercase a-g


[Link] = "^a-z"; // Disallows all lowercase letters (but allows
// other characters, including uppercase)
[Link] = "0-9^5"; // Allows numbers only, with the exception of

You can also specify allowable characters using Unicode escape sequences. For example, if you w
to disallow users from entering the character (Ctrl-Z) into a field, you can specify its Unicode
code point in the restrict property, as follows:

[Link] = "^\u001A";

To allow a literal character that has a special meaning when used in a restrict string (such as a
dash or caret), you must escape the character in the restrict string by preceding it with two
backslashes (not just one):

[Link] = "0-9\\-"; // Allows numbers and dashes


[Link] = "0-9\\^"; // Allows numbers and caret marks

If you want to escape the backslash character, you must precede it with three backslashes for a
total of four backslashes:

[Link] = "0-9\\\\"; // Allows numbers and backslashes

8.7.4 See Also


Recipe 9.6. Also refer to Table A-1, which lists the Unicode code points for Latin 1 characters.
[ Team LiB ]
[ Team LiB ]

Recipe 8.8 Restricting the Maximum Field Length


8.8.1 Problem

[Link] 2004-02-06
Strona 10 z 33

You want to limit the length of the string input in a text field.

8.8.2 Solution
Set the text field's maxChars property.

8.8.3 Discussion
By default, an input text field allows a user to type in as many characters as she desires.
However, you may have good reason to want to set a maximum. For example, if an input field
prompts a user for her two-character country code, you might want to prevent the user from
entering more than two characters. Setting the maxChars property to a number limits the user
input to that many characters.

[Link] = 6; // Maximum of six characters can be input

Set maxChars to null to allow an entry of unlimited length.

8.8.4 See Also


Recipe 8.7

[ Team LiB ]
[ Team LiB ]

Recipe 8.9 Displaying Dynamic Text at Runtime


8.9.1 Problem
You want to display text within a movie.

8.9.2 Solution
Set the text property of a text field.

8.9.3 Discussion
Aside from being used as input fields, text fields are often used to display text to the user.
Setting a text field's text property causes the corresponding text to display in the field.

[Link] = "this will display in the field";

The Flash 5 technique of assigning a variable to a text field is still


available, though not preferred. However, if exporting to Flash 5 format,
you can assign a variable name to the text field using the variable
property.

Special characters, such as \t for tab and \n for newline, can be used within a text string.

Displaying text programmatically in a movie is useful for many reasons. If a movie contains
dynamic or frequently updated text, the values can be loaded from an external source, such as
a database or XML document, and then programmatically displayed in text fields.

[Link] 2004-02-06
Strona 11 z 33

The trace( ) command displays text in Flash's Output window, but the window is available
during authoring only. You can use a text field to display output at runtime using a custom
function:

function traceText (msg) {


[Link] += msg + newline;
}
traceText ("Here's my message");

8.9.4 See Also


Recipe 8.1 and Recipe 8.2. See Recipe 8.10 for information on support for HTML-formatted text.

[ Team LiB ]
[ Team LiB ]

Recipe 8.10 Displaying HTML-Formatted Text


8.10.1 Problem
You want to display HTML content in a text field.

8.10.2 Solution
Set the text field's html property to true, and set the htmlText property to the value of the
HTML content to display.

8.10.3 Discussion
Text fields can interpret and display basic HTML tags, if properly configured. Using HTML in a
text field is a convenient way to add links and simple formatting, such as font color and bold
text.

Text fields display plain text by default. To enable HTML formatting, set the field's html
property to true:

[Link] = true;

Once the html property is set to true, the value of the object's htmlText property will be
interpreted as HTML:

[Link] = "<u>this will display as underlined text</u>";

Set the html property to true before setting the htmlText property; otherwise, the value of
htmlText will not be interpreted as HTML.

When the html property is false (which is the default), the htmlText property value is
rendered as regular text. But when html is set to true, the htmlText property is rendered as
HTML complete with <p> and <font> markup tags:

[Link] = false;
[Link] = "test";
trace([Link]);

[Link] 2004-02-06
Strona 12 z 33

/* Output window displays:


test
*/
[Link] = true;
[Link] = "test";
trace([Link]);
/* Output window displays:
<P ALIGN="LEFT"><FONT FACE="Times New Roman"
SIZE="12" COLOR="#000000">test</FONT></P>
*/

No matter what, the text property of a text field is rendered as plain text. This means that
even if the html property is true, if the text property is set to <u>test</u>, the object will
display <u>test</u> instead of test.

Despite the fact that they appear to be two properties, text and htmlText are linked such that
when one is altered, the other is as well. This means that you should not try to assign different
values to each. Whatever value is assigned to text is also assigned to htmlText (with
additional HTML flourish if the html property is set to true), and vice versa. You can test this
for yourself, as follows:

[Link] = true;
[Link] = "htmlText value";
trace([Link]);
trace([Link]);
[Link] = "text value";
trace([Link]);
trace([Link]);
/* Output window displays:
htmlText value
<P ALIGN="LEFT"><FONT FACE="Times New Roman"
SIZE="12" COLOR="#000000">htmlText value</FONT></P>
text value
<P ALIGN="LEFT"><FONT FACE="Times New Roman"
SIZE="12" COLOR="#000000">text value</FONT></P>
*/

The htmlText property is a little quirky. Each time you append a new value to the htmlText
property, the appended value is wrapped in a <p> element. Therefore, if you assign a value to
an object's htmlText property and then append another value to htmlText, the two values will
be displayed on two different lines even if no line-breaking tags were specified:

[Link] = true;
[Link] = "a";
[Link] += "b";
trace([Link]);
/* Output window displays:
<P ALIGN="LEFT"><FONT FACE="_sans" SIZE="12"
COLOR="#000000">a</FONT></P>
<P ALIGN="LEFT"><FONT FACE="_sans" SIZE="12"
COLOR="#000000">b</FONT></P>
*/

A simple solution to this problem is to append the values to a string variable and then assign
that variable's value to the htmlText property in a single assignment statement:

[Link] = true;
var htmlTextVal = "a";
htmlTextVal += "b";
[Link] = htmlTextVal;
trace([Link]);

[Link] 2004-02-06
Strona 13 z 33

/* Output window displays:


<P ALIGN="LEFT"><FONT FACE="_sans" SIZE="12" COLOR="#000000">ab</FONT></P>
*/

If you want to display HTML code in its unrendered format, set the html property to false and
assign the HTML value to the text property of the text field, as follows:

[Link] = false;
[Link] = "<u>underlined text</u>";
/* Text field displays:
<u>underlined text</u>
*/

This can be a useful technique if, for example, you want to show both the rendered HTML and
the HTML source code in side-by-side text fields:

htmlCode = "<i>italicized text</i>";


[Link] = false;
[Link] = htmlCode;
[Link] = true;
[Link] = htmlCode;

You cannot display both rendered and unrendered HTML in the same text field. If you try to do
so, you will end up with unreliable results.

Additionally, you cannot toggle between the viewing of rendered and unrendered HTML in a text
field. For example, if you set a text field's html property to true, add HTML content, and set
the html property to false, the displayed text appears as rendered HTML, not as HTML source
code.

The set of HTML tags supported by Flash 5 text fields includes: <B>, <I>, <U>, <FONT> (with
FACE, SIZE, and COLOR attributes), <P>, <BR>, and <A>. Flash 6 adds support for <LI> and
<TEXTFORMAT> (with LEFTMARGIN, RIGHTMARGIN, BLOCKINDENT, INDENT, LEADING, and TABSTOPS
attributes corresponding to the TextFormat class's properties of the same names).

8.10.4 See Also


Appendix E of ActionScript for Flash MX: The Definitive Guide gives full details on Flash's
support for HTML tags.

[ Team LiB ]
[ Team LiB ]

Recipe 8.11 Condensing Whitespace


8.11.1 Problem
You want to condense whitespace in an HTML text field display.

8.11.2 Solution
Set the object's condenseWhite property to true.

8.11.3 Discussion

[Link] 2004-02-06
Strona 14 z 33

When you use HTML in a text field, the optional condenseWhite setting condenses whitespace,
as is done in most HTML browsers. For example, the following text would be rendered in a web
browser with only a single space between "hello" and "friend" in spite of the fact that the
original source has multiple spaces between the two words:

hello friend

In ActionScript text fields, however, all of the spaces are displayed unless you set the
condenseWhite property to true.

[Link] = true;
[Link] = true;
[Link] = "hello friend"; // Displays: "hello friend"

The condenseWhite property works only when the html property is true.

8.11.4 See Also


Recipe 8.10 and the [Link] property

[ Team LiB ]
[ Team LiB ]

Recipe 8.12 Sizing Text Fields to Fit Contents


8.12.1 Problem
You want to size a text field's viewable area to fit the text it contains.

8.12.2 Solution
Use the autoSize property, or create and use a custom createAutoTextField( ) method.

Alternatively, you can also set the _width and _height properties based on the values of the
textWidth and textHeight properties.

8.12.3 Discussion
You can set the autoSize property of a text field so that it automatically resizes itself in order to
fit its contents. By default, autoSize is set to "none", meaning that the text field does not
automatically resize. There are six possible values for the autoSize property, but two possible
values are redundant, leaving four effective settings:

"left" or true

Set the property to "left" or true if you want the text field to resize while fixing the upper-
left corner's position. In other words, the text field's lower-right corner will be the point tha
moves when it expands and contracts.

// These two lines do the same thing.


[Link] = "left";
[Link] = true;

[Link] 2004-02-06
Strona 15 z 33

"center"

Set the property to "center" if you want the text field to be anchored at its center point.
While the top of the object remains fixed, it will expand and contract downward and equall
to the right and left.

[Link] = "center";

"right"

Set the property to "right" if you want the upper-right corner of the text field to remain
steady while the object expands and contracts in the direction of the lower-left corner:

[Link] = "right";

"none" or false

Set the property to "none" or false if you want to turn off the autosize functionality, in
which case the text field dimensions do not adjust automatically:

// These two lines do the same thing.


[Link] = "none";
[Link] = false;

It is convenient to create auto-sizing text fields using a custom createAutoTextField( ) method.


You should add the following code to your [Link] file (or the [Link] file from Chapter
7) for easy inclusion in other projects:

[Link] = function (name,


depth,
x, y,
w, h,
val,
align) {

// The align parameter is assigned to the text field's autoSize property.


// Defaults to true (same as "left"). Ignored if width, w, is specified.
if (align == undefined) {
align = true;
}

// Create the text field using the built-in [Link]( ) metho


[Link](name, depth, x, y, w, h);

// If the width, w, was 0 or unspecified, set the autoSize property so that the
// text field resizes to fit its contents.
if (w == undefined || w == 0) {
this[name].autoSize = align;
}

// If a value was provided, assign it to the text field's text property.


if (val != undefined) {
this[name].text = val;
}
};

You can use the custom createAutoTextField( ) method as you would use the built-in
createTextField( ) method. The primary difference is that createAutoTextField( ) creates an auto-
sizing text field with a single method invocation. Additionally, the val parameter allows you to
assign text to the text field when creating it.

[Link] 2004-02-06
Strona 16 z 33

// Make sure to include the necessary ActionScript file. This assumes you added th
// custom createAutoTextField( ) method to [Link] and not [Link]
#include "[Link]"

// Create a text field that autosizes and give it a text value of "init value".
_root.createAutoTextField("myAutoText", 1, 0, 0, 0, 0, "init value");

If you do not want to use the auto-size feature, you can manually adjust the _width and _heigh
properties. This might be useful if, for example, you want to fix the text field width but adjust th
height manually to fit the content. You can retrieve the contents' width and height using the
textWidth and textHeight properties. Then you can set the _width or _height properties
accordingly.

Because a text field's total width and height are slightly larger than the width and height of the
text it contains, you will likely find that if you set the _width and _height properties to the
textWidth and textHeight properties, the text may be cut off. You can account for this
discrepancy by adding a margin as necessary. This example resizes the text field a little larger
than the reported property values to accommodate the contents:

myTextField._width = [Link] + 5;
myTextField._height = [Link] + 5;

8.12.4 See Also


Recipe 8.8
[ Team LiB ]
[ Team LiB ]

Recipe 8.13 Scrolling Text with the ScrollBar Component


8.13.1 Problem
You want to create a scrolling text field with a scrollbar.

8.13.2 Solution
Use the Flash UI Components ScrollBar component.

8.13.3 Discussion
Using the ScrollBar component, you can easily add both vertical and horizontal scrolling
capabilities to a text field. If the text field is created during authoring, you can simply drag a
scrollbar instance from the Components panel onto the text field itself. The ScrollBar
component will automatically snap (if View Snap to Objects is enabled) to the side on which
it is dropped (top, bottom, left, or right) and will automatically size itself to match the text field
object. Additionally, the scrollbar instance will automatically set its scroll target to be the text
field onto which it was dropped.

You can also add a scrollbar to a dynamically created text field, or add the scrollbar dynamically
for other reasons (such as when a text field is resized or its contents change). To make the
process fully dynamic, add the scrollbar using [Link]( ) (see Recipe 7.19). To
accomplish this, you must first make sure that the ScrollBar component is included in the
movie's Library by dragging an instance of it from the Components panel to the Stage. This
action will automatically link the ScrollBar component for export with the identifier
FScrollBarSymbol. With the symbol in the Library, you can attach the component using
ActionScript, as follows:

[Link] 2004-02-06
Strona 17 z 33

_root.attachMovie("FScrollBarSymbol", "myScrollBar_sb", 2);

To specify the text field to control, specify it as the target parameter for the ScrollBar
component's setScrollTarget( ) method:

myScrollBar_sb.setScrollTarget(myTextField);

While this is all that is technically required for a scrollbar to control the scrolling of a text field,
there are generally additional considerations. For example, you usually will want to position the
scrollbar adjacent to the text field. Likewise, you typically will want the scrollbar's dimensions to
match the width or height of the text field it controls (depending on whether it is a horizontal or
vertical scrollbar).

To align a vertical scrollbar instance so that it is flush with the top of the text field it controls,
set its _y property to match the text field's _y property. To align the same scrollbar instance so
that it touches the right side of the text field, offset its _x property by the text field's width
value. For example:

myScrollBar_sb._x = myTextField._x + myTextField._width;


myScrollBar_sb._y = myTextField._y;

To set the size of a scrollbar instance, use the setSize( ) method, which accepts the size (the
height) of the instance in pixels. To match the scrollbar length to the text field, pass the text
field's _height property to the setSize( ) method:

myScrollBar_sb.setSize(myTextField._height);

If you want the scrollbar to control the horizontal scrolling of a text field, use the setHorizontal
( ) method, passing it the value true. (This also orients the scrollbar horizontally.)

myScrollBar_sb.setHorizontal(true);

When a scrollbar instance is horizontal, setSize( ) sets its width instead of its height.

8.13.4 See Also


Recipe 8.14

[ Team LiB ]
[ Team LiB ]

Recipe 8.14 Scrolling Text Programmatically


8.14.1 Problem
You want to scroll text in a text field via ActionScript without requiring user input.

8.14.2 Solution
Use the scroll, maxscroll, bottomScroll, hscroll, and maxhscroll properties of the text field

8.14.3 Discussion

[Link] 2004-02-06
Strona 18 z 33

You can control the scrolling of a text field with ActionScript and without the aid of a scrollbar. Fo
example, you may want to scroll the contents of a text field automatically to display a word or
selection within the text. Or perhaps you don't want to use the ScrollBar component for file size-
optimization reasons. Regardless, you can programmatically control a text field's scrolling in both
the vertical and horizontal directions using some built-in properties. You should use the scroll,
maxscroll, and bottomScroll properties to control vertical scrolling, and use the hscroll and
maxhscroll properties to control horizontal scrolling.

Every text field has a number of lines, whether it is 1 or 100. Each of these lines is identified by
number starting at one. Some of these lines may be visible, and some may be beyond the borde
of the text field. Therefore, to view the lines that extend beyond the visible portion of the text
field you must scroll to them. Figure 8-1 illustrates this point. It depicts a text field's display, in
which the solid line indicates the object's border—the visible area—and the dotted line surrounds
the rest of the text contained within the object but lying outside the its visible area. To the left o
the text field are line numbers for each line of text. The three labels—scroll, bottomScroll, and
maxscroll—indicate the meaning of the text field properties of the same name.

Figure 8-1. Understanding scroll, bottomScroll, and maxscroll

The scroll property is a read/write property that indicates the top line of the text field's visible
area. In Figure 8-1, the scroll property's value is 1. To scroll the contents of a text field, assign
a new line number to the scroll property. Setting scroll to 6, for example, scrolls the contents
of the text field up until line 6 is the topmost line displayed. The value of scroll should always b
an integer. Flash cannot scroll to noninteger values.

[Link] = 1; // Scroll to the top.


[Link] += 1; // Scroll to the next line.
[Link] = 6; // Scroll to line 6.

You can scroll to the next page of a text field's contents using the bottomScroll property, which
indicates the bottommost visible line in the text field. While you cannot set bottomScroll, you
can use it to determine the new value to assign to scroll. In Figure 8-1, bottomScroll is 13. If
scroll is set to 6, then bottomScroll is automatically updated to 18.

// Scroll to the next page with the previous page's bottom line at the top.
[Link] = [Link];
// Scroll to the next complete page without the bottom line from the previous pag

[Link] 2004-02-06
Strona 19 z 33

[Link] = [Link] + 1;

You should use the maxscroll property to scroll to the last page of contents within a text field.
The maxscroll property is also a read-only property. This property contains the value of the
maximum line number that can be assigned to scroll. Therefore, the maxscroll property
changes only when the number of lines in the text field changes (either through user input or
through ActionScript assignment). In Figure 8-1, maxscroll is 14. This is because with 26 total
lines in the text field, and with 13 visible lines, when scroll is set to 14, the last visible line is 2
(the last line in the text field).

Don't try to set scroll to a value less than 1 or greater than the value of maxscroll. Although
this won't cause an error, it won't scroll the text beyond the contents. Add blank lines to the
beginning or end of the text field's contents to artificially extend its scrolling range:

[Link] = [Link]; // Scroll to the bottom.

The vertical scrolling properties are in units of lines, but the horizontal scrolling properties
(hscroll and maxhscroll) are in units of pixels. Other than that, hscroll and maxhscroll work
more or less in the same fashion as scroll and maxscroll (although there is no property for
horizontal scrolling that corresponds to bottomScroll). The hscroll property is a read/write
property that allows you to control the value of the leftmost visible pixel starting with 0. The
maxhscroll property is a read-only property that indicates the pixel value of the maximum value
that can be assigned to hscroll.

[Link] = 0; // Scroll to the far left.


[Link] += 1; // Scroll to the right one pixel.
[Link] = [Link]; // Scroll to the far right.

Figure 8-2 depicts maxhscroll for a dynamic text field (a field whose type is set to "dynamic").
Text shown in gray is outside the visible area of the text field.

Figure 8-2. The maxhscroll property for a dynamic text field

Figure 8-3 depicts maxhscroll for an input text field (a field whose type is set to "input"). Note
that Flash automatically adds buffer space to allow room for user input. Again, text shown in gra
is outside the visible area of the text field.

Figure 8-3. The maxhscroll property for an input text field

[ Team LiB ]
[ Team LiB ]

Recipe 8.15 Responding to Scroll Events


8.15.1 Problem

[Link] 2004-02-06
Strona 20 z 33

You want to have some actions performed when a text field's contents are scrolled.

8.15.2 Solution
Define the text field's onScroller( ) event handler method. Alternatively, you can use a listener
object.

8.15.3 Discussion
When a text field is scrolled vertically or horizontally (meaning that the scroll or hscroll
property has been changed either by your custom ActionScript code or by a scrollbar), the
onScroller( ) method is automatically invoked. By default, a text field's onScroller( ) method is
undefined, but you can assign it a reference to a function:

[Link] = function ( ) {
trace("text is scrolled");
};

You can also create listener objects for a text field that are notified when the text is scrolled.
Listener objects can be any kind of object, such as another text field, a movie clip, or a custom
object type. You should define an onScroller( ) method for the listener object, then register the
object to the text field using the addListener( ) method. Call the addListener( ) method from
the text field and pass it a parameter that references the listener object (the object with
onScroller( ) defined). For example:

myListener = new Object( );


[Link] = function ( ) {
// Actions go here.
};
[Link](myListener);

You can add multiple listeners to one text field. Working with listeners is a powerful technique
because it allows you to define actions that should occur on multiple objects when a text field is
changed in some way. This is much more powerful than placing all the actions within a single
method assigned to the text field for several reasons. For one thing, it is a good practice to
keep actions specific to an object assigned to that object and not to other objects. (For
example, a ScrollBar component configures itself as a listener for the targeted text field. If the
text field changes programmatically, the ScrollBar component independently updates its own
scroller position to match the text field.) Additionally, the listener objects can reference
themselves with the this keyword, which keeps your code as abstract as possible. Here, two
objects are added as listeners:

myListener = new Object( );


[Link] = function ( ) {
// Actions go here.
};

[Link] = function ( ) {
this._x++;
};

[Link](myMovieClip);
[Link](myListener);

You can remove a listener by invoking the text field's removeListener( ) method, passing it a
reference to the listener object to remove:

[Link](myListener);

[Link] 2004-02-06
Strona 21 z 33

8.15.4 See Also


For more information on listeners, see Recipe 12.11, along with Recipe 8.26 and Recipe 8.27.
[ Team LiB ]
[ Team LiB ]

Recipe 8.16 Formatting Existing Text


8.16.1 Problem
You want to format the existing text in a text field.

8.16.2 Solution
Pass a TextFormat object to the [Link]( ) method.

8.16.3 Discussion
A text field allows minimal control over the formatting of the text it displays, such as by setting
the htmlText property. Although you can set the color of the entire contents of the text field
using the textColor property, for example, the TextField class doesn't offer precise control
over character formatting. However, the TextFormat class is a "helper" class; TextFormat
objects can format the text displayed in text fields.

The first step in formatting text is to create a TextFormat object using the constructor method:

myTextFormat = new TextFormat( );

Next, assign values to the TextFormat object's properties as you desire:

[Link] = "center"; // Center-align the text.


[Link] = true; // Bold the text.
[Link] = 0xFFFF00; // Make the text yellow.
[Link] = 5; // Adjust the margin by five pixels.

You can apply text formatting to the existing text for an entire text field by passing a
TextFormat object to the text field's setTextFormat( ) method:

[Link](myTextFormat);

When you invoke the setTextFormat( ) method in this way, the formatting from the TextFormat
object is applied to the text already assigned to the text field. The formatting does not apply to
any text assigned to the text field after the setTextFormat( ) method is invoked. If additional
text is entered by the user, the original text retains its applied formatting, but the inserted text
does not have any special formatting applied to it. All formatting is removed if the text value is
modified by appending a value by way of ActionScript.

[Link] = "this is some text";


[Link](myTextFormat); // Formatting applied
[Link] = "this is other text"; // No formatting applied
[Link](myTextFormat); // Formatting reapplied
[Link] += "appended text"; // Formatting removed

If you make changes to the TextFormat object, you should reapply the formatting to the text
field by passing the modified object to the setTextFormat( ) method. Otherwise, the changes

[Link] 2004-02-06
Strona 22 z 33

are not automatically displayed.

8.16.4 See Also


Recipe 8.10 explains how to use HTML-formatted text, Recipe 8.17 explains how to apply
formatting to new text rather than existing text, and Recipe 8.18 applies formatting to
individual characters rather than an entire field.
[ Team LiB ]
[ Team LiB ]

Recipe 8.17 Formatting User-Input Text


8.17.1 Problem
You want to apply formatting to text as it is entered into a text field by the user.

8.17.2 Solution
Apply a TextFormat object using the setNewTextFormat( ) method of the text field.

8.17.3 Discussion
You should use the setNewTextFormat( ) method of a text field object to apply text formatting
to text as it is entered by the user. You should create a TextFormat object as in Recipe 8.16
and then pass that object to the text field's setNewTextFormat( ) method:

myTextFormat = new TextFormat( );


[Link] = 0x0000FF; // Make the text blue.
[Link](myTextFormat);

When you use setNewTextFormat( ), the formatting is applied to text that the user types into
the field.

8.17.4 See Also


Recipe 8.18
[ Team LiB ]
[ Team LiB ]

Recipe 8.18 Formatting a Portion of a Text Field


8.18.1 Problem
You want to add formatting to some, but not all, text in a text field, or you want to apply
different formatting to various parts of a text field.

8.18.2 Solution
Create a TextFormat object and use it to format a substring of the text field using one of the
setTextFormat( ) method variations.

8.18.3 Discussion

[Link] 2004-02-06
Strona 23 z 33

You can format an entire text field as shown in Recipe 8.16, or you can use one of the versions
of the setTextFormat( ) method to format just a portion of a text field. These variations allow
you to apply formatting to the specified character range only.

You can set the formatting for a single character within a text field by invoking the
setTextFormat( ) method and passing it two parameters:

index

The zero-relative index of the character to which the formatting should be applied

textFormatObj

A reference to a TextFormat object

This example applies the formatting to the first character only:

[Link](0, myTextFormat);

Alternatively, if you want to apply the formatting to a range of characters, you can invoke
setTextFormat( ) with three parameters:

startIndex

The beginning, zero-relative character index

endIndex

The index of the character after the last character in the desired range

textFormatObj

A reference to a TextFormat object

This example applies the formatting to the 1st through 10th characters:

[Link](0, 10, myTextFormat);

You may notice that certain formatting options are not applied under certain circumstances
when you try to format portions of a text field. For example, the text alignment will be applied
only if the formatting is applied to the first character in a line.

8.18.4 See Also


Recipe 8.16

[ Team LiB ]
[ Team LiB ]

Recipe 8.19 Setting a Text Field's Font


8.19.1 Problem

[Link] 2004-02-06
Strona 24 z 33

You want to use ActionScript to change the font used for some displayed text.

8.19.2 Solution
Set the font property of a TextFormat object and apply the formatting to the text field.

8.19.3 Discussion
You can programmatically specify the font that is used to display text using the font property
of a TextFormat object. You can assign to this property the string name of the font (or fonts)
that should be used:

[Link] = "Arial";

The font specified must be available on the computer on which the movie is running. Because
some computers may not have the preferred font installed, you can specify multiple font names
separated by commas, as follows:

[Link] = "Arial, Verdana, Helvetica";

The first font is used unless it cannot be found, in which case the Player attempts to use the
next font in the list.

Apply the font change to a text field, as follows:

[Link](myTextFormat);

If none of the specified fonts are found, the default system font is used.

8.19.4 See Also


Recipe 8.16, Recipe 8.18, and Recipe 8.20

[ Team LiB ]
[ Team LiB ]

Recipe 8.20 Embedding Fonts


8.20.1 Problem
You want to ensure that text will display properly even if the intended font is not installed on
the user's computer.

8.20.2 Solution
Export the font in the movie's Library, set the text field's embedFonts property to true, and use
a TextFormat object, setting its font property to the linkage identifier for the embedded font.

8.20.3 Discussion
You should embed fonts if you want to ensure that text will display using the intended font even
if the font is not installed on the user's computer. To embed a font, follow these steps:

[Link] 2004-02-06
Strona 25 z 33

1. Open the Flash document's Library and select New Font from the pop-up Options menu
(see Figure 8-4). A Font Symbol Properties dialog box appears.

Figure 8-4. Selecting New Font from the Library's pop-up Options
menu

2. In the Name field, specify the name given to the symbol in the Library. Any name will
suffice, so specify a name that makes sense to you.

3. From the Font drop-down list, select the font that you wish to embed.

4. If you wish to include the bold and/or italic versions of the font as well, select the
appropriate checkboxes. Be aware that including bold and italic font outlines will increase
the file size significantly, so do so only if you really need them.

5. Click OK in the Font Symbol Properties dialog box.

6. Select the new Font symbol in the Library and choose Linkage from the pop-up Options
menu.

7. In the Linkage Properties dialog box, select the Export for ActionScript and Export in First
Frame checkboxes.

8. Give the symbol a linkage identifier. This is the name that will be used in the ActionScript
code. The linkage identifier should be something that corresponds to the font. For
example, if the font you are embedding is Garamond, you might give it a linkage identifier
of garamondEmbedded.

9. Click OK in the Linkage Properties dialog box.

10. Set the embedFonts property of the text field to true. By default, the property is false,
which means that Flash uses device fonts. By setting the embedFonts property to true,
the text field can use embedded fonts only. If you attempt to assign a device font to a
text field with embedFonts set to true, nothing will be displayed.

[Link] = true;

11. Set the [Link] property to the linkage identifier of the embedded font. If this
value is not found, no text is displayed.

[Link] = "garamondEmbedded";

12. Apply the TextFormat object to the text field using setTextFormat( ):

[Link] 2004-02-06
Strona 26 z 33

[Link](myTextFormat);

8.20.4 See Also


Recipe 8.16 and Recipe 8.19
[ Team LiB ]
[ Team LiB ]

Recipe 8.21 Creating Text That Can Be Rotated


8.21.1 Problem
You want to make sure that text will continue to display even when it is rotated.

8.21.2 Solution
Use embedded fonts.

8.21.3 Discussion
Unless you specifically use an embedded font, text fields use device fonts. For most situations,
this is perfectly workable. However, in situations in which you want to animate a text field (or
its parent movie clip) by changing its _rotation property, you must use embedded fonts.
Device fonts will disappear in a rotated text field.

8.21.4 See Also


Recipe 8.20 explains how to embed fonts.

[ Team LiB ]
[ Team LiB ]

Recipe 8.22 Displaying Unicode Text


8.22.1 Problem
You want to display Unicode text in your movie, possibly including non-English characters.

8.22.2 Solution
Use the Unicode escape sequences for the characters within the assignment to the text field's
text property. You can also include the Unicode text directly in an external ActionScript file and
use an #include directive.

8.22.3 Discussion
All text in Flash MX is treated as Unicode text. Therefore, you can display any Unicode character
within a text field. If you know the escape sequence for the character, you can assign it to a
text field's text property. All Unicode escape sequences in ActionScript begin with \u and are
followed by four-digit hexadecimal numbers. The escape sequences must be enclosed in quotes.

// Display a famous equation with a superscripted 2.

[Link] 2004-02-06
Strona 27 z 33

[Link] = "E = MC\u00B2";

You can also display Unicode text without using escape sequences by inserting the desired
characters directly into an external ActionScript file and using the #include directive to load the
text into the Flash movie. This is necessary because Flash does not allow you to enter Unicode
characters directly into the Actions panel. You must make sure that the text editor you use
allows you to enter Unicode text and save it as UTF-8-encoded format (Notepad and TextEdit
both allow for this). You must also include the special //!-- UTF8 comment at the beginning of
the file to indicate that it contains UTF-8-encoded text. For example, you can save this text in a
file named [Link]:

//!-- UTF8
// This is the ActionScript file saved as something like [Link].
mySpanish = "Español";
myJapanese = " ";
myHebrew = " ";

Notice that non-English characters can be directly inserted into the


external file. Some of these characters might require the user to have the
proper language pack installed on his computer.

In the Flash document, include the external file and assign the loaded variables containing
Unicode characters to the text properties of existing text fields as desired. For example:

#include "[Link]"
[Link] = mySpanish;
[Link] = myJapanese;
[Link] = myHebrew;

8.22.4 See Also


Table A-1 lists the Unicode escape sequences. If you want a Unicode character reference online,
you can open up the Character Map utility under Windows using Start Programs
Accessories System Tools Character Map.
[ Team LiB ]
[ Team LiB ]

Recipe 8.23 Assigning Focus to a Text Field


8.23.1 Problem
You want to use ActionScript to bring focus to a text field.

8.23.2 Solution
Use the [Link]( ) method.

8.23.3 Discussion
You should use the Selection class's static getFocus( ) method to assign focus to a specific text
field programmatically. The method assigns focus to the object whose name is passed in as a
parameter. You should pass getFocus( ) a string value that evaluates to the path to the object.
You can specify the parameter as an absolute or relative path, such as:

// The following are equivalent, assuming that myTextField exists within _root.

[Link] 2004-02-06
Strona 28 z 33

[Link]("myTextField");
[Link]("_root.myTextField");

Additionally, to support Flash 4 and Flash 5 movies, the parameter passed to setFocus( ) can be
a string that evaluates to the path of the variable associated with the text field, and the path
can be given in slash notation if desired. For example:

[Link]("/myTextFieldVar");

To remove focus from a text field, you should call the setFocus( ) method and pass it the null
value:

[Link](null);

8.23.4 See Also


Recipe 11.21

[ Team LiB ]
[ Team LiB ]

Recipe 8.24 Selecting Text with ActionScript


8.24.1 Problem
You want to highlight a portion of the text within a text field.

8.24.2 Solution
Use the [Link]( ) method.

8.24.3 Discussion
The Selection class has a static method, setSelection( ), which highlights a portion of the text in
the text field. The selection is applied to the text field that has focus (see Recipe 8.23). The
setSelection( ) method takes two parameters:

startIndex

The beginning, zero-relative index of the text to highlight

endIndex

The index of the character after the text to highlight

For example:

[Link] = "this is some text"; // Set the text value.


[Link]("myTextField"); // Bring focus to the text field.
[Link](0, 4); // Highlight the word "this".

8.24.4 See Also

[Link] 2004-02-06
Strona 29 z 33

Recipe 8.25 and Recipe 8.26


[ Team LiB ]
[ Team LiB ]

Recipe 8.25 Setting the Insertion Point in a Text Field


8.25.1 Problem
You want to use ActionScript to set the insertion point in a text field.

8.25.2 Solution
Use the [Link]( ) method, specifying the same value for the starting and ending
indexes.

8.25.3 Discussion
You can use [Link]( ) to set the cursor position in a text field by setting the
beginning and ending index parameters to the same value. This example sets the cursor
position in the text field that has focus:

// Positions the insertion point before the first character


[Link](0, 0);

8.25.4 See Also


Recipe 8.24
[ Team LiB ]
[ Team LiB ]

Recipe 8.26 Responding When Text Is Selected or


Deselected
8.26.1 Problem
You want to perform a task when a text field is selected or deselected.

8.26.2 Solution
Assign a function definition to the text field's onSetFocus( ) and/or onKillFocus( ) methods.

8.26.3 Discussion
You should define the onSetFocus( ) method for any text field for which you want certain
actions to be performed when focus is brought to the field. Likewise, you should define the
onKillFocus( ) method for any text field for which you want certain actions to be performed
when focus leaves the field. These methods are invoked automatically when focus changes on a
text field, though by default the methods are undefined.

[Link] = function (prevFocus) {


trace(this._name + " selected, previously selected was " + prevFocus._name);
};
[Link] = function (newFocus) {

[Link] 2004-02-06
Strona 30 z 33

trace(this._name + " deselected, new selection is " + newFocus._name);


};

In each case, the method is automatically passed a parameter. In the case of onSetFocus( ),
the parameter is a reference to the object that previously had focus and for onKillFocus( ), the
parameter is a reference to the object that is gaining focus. In either case, you are not required
to handle this parameter or do anything with it, but it is there if you want or need it.

A handy use of the onSetFocus( ) event handler method might be to set the value of an
instructional output field's text when a particular input field is selected in a form:

[Link] = function ( ) {
_root.[Link] = "instructions for this form field";
};

A useful implementation of the onKillFocus( ) event handler method might be to check the value
of a form field once it loses focus:

[Link] = function ( ) {
if ([Link] != "valid answer") {
_root.[Link] = "invalid answer supplied";
}
};
[ Team LiB ]
[ Team LiB ]

Recipe 8.27 Responding to User Text Entry


8.27.1 Problem
You want to perform a task when the content of a text field is modified by user input.

8.27.2 Solution
Assign a function definition to the text field's onChanged( ) event handler method.
Alternatively, you can use a listener object with a defined onChanged( ) method and register it
with the text field using addListener( ).

8.27.3 Discussion
You can specify actions to be performed each time the content of a text field is changed by user
input, whether that change be deleting or cutting characters, typing in characters, or pasting
characters. You can define the text field's onChanged( ) event handler method:

[Link] = function ( ) {
trace("the value has been modified");
};

When a user makes any change to the value of an input text field with an onChanged( ) handler
defined, the actions defined in the onChanged( ) handler are executed.

You can also define an onChanged( ) method for a listener object (or listener objects). If
multiple objects need to be updated when a change occurs in the input text field's value, you
should register those objects as listeners. You should define an onChanged( ) method for the
listener object and then register the listener with the text field's addListener( ) method. The
addListener( ) method takes a reference to the listener object. For example:

[Link] 2004-02-06
Strona 31 z 33

myListener = new Object( );


[Link] = function ( ) {
// Actions go here.
};
[Link](myListener);

You can use addListener( ) to register as many listener objects as you need. In this way, each
listener object handles only the functionality that is applicable to it.

If you want to unregister a listener object, call the removeListener( ) method of the text field at
any time, passing it a reference to the listener object to be removed:

[Link](myListener);

Whether using the event handler method or using a listener object, attempt to keep your
onChanged( ) definition as streamlined as possible. Remember that the method is invoked
every time the user types a character into the text field, so if you try to do too much at once,
you can put unnecessary strain on your Flash movie.

8.27.4 See Also


Recipe 8.15

[ Team LiB ]
[ Team LiB ]

Recipe 8.28 Adding a Hyperlink to Text


8.28.1 Problem
You want to hyperlink some of the text displayed in a text field.

8.28.2 Solution
Use HTML <a href> tags within the object's htmlText property. Alternatively, use a TextFormat
object with a value assigned to the url property.

8.28.3 Discussion
Both solutions to this problem require you to set the text field's html property to true:

[Link] = true;

If you want to use HTML to add a hyperlink, add an <a href> tag to the text field's htmlText
property, as follows:

[Link] = "<a href=\"[Link] here</a>";

You can specify a target window in which to open the link by adding a target attribute to the <a
href> HTML tag. For example:

[Link] = "<a href=\"[Link] target=\"_blank\">cl


here</a>";

[Link] 2004-02-06
Strona 32 z 33

When text is hyperlinked in Flash, the mouse cursor changes to a hand when it is over the linked
text. Flash does not inherently provide any indication that the text is linked, as do most HTML
browsers (with an underline and color change). For this reason, it is helpful to add HTML markup
that underlines and colors the linked text:

htmlLink = "<font color=\"#0000FF\"><u>"; // Add the color and underline.


htmlLink += "<a href=\"[Link] here</a>";
htmlLink += "</u></font>";
[Link] = htmlLink; // Close the color and underline.

You can accomplish the same tasks without HTML by using a TextFormat object. The TextFormat
class includes a url property for just this purpose. Assigning the URL to the url property will lin
the formatted text. For example:

[Link] = "click here";


myTextFormat = new TextFormat( );
[Link] = "[Link]
[Link](myTextFormat);

To specify a target window in which to open the link, set the value of the TextFormat object's
target property, as follows:

[Link] = "click here";


myTextFormat = new TextFormat( );
[Link] = "[Link]
[Link] = "_blank";
[Link](myTextFormat);

As with the HTML technique, when using a TextFormat object to create a hyperlink, Flash does n
offer any indication as to the link's presence other than the hand cursor when it is moused over.
can add color and/or an underline to the linked text to let the user know that it is a link. You sho
use the TextFormat object's color and underline properties for this purpose:

[Link] = "click here";


myTextFormat = new TextFormat( );
[Link] = 0x0000FF;
[Link] = true;
[Link] = "[Link]
[Link](myTextFormat);

You can use either of the techniques in this recipe to add links that point not only to http and htt
protocols, as shown in the examples, but also links to other protocols. For example, you can use
same techniques to open a new email message:

[Link] = "email me";


myTextFormat = new TextFormat( );
[Link] = 0x0000FF;
[Link] = true;
[Link] = "[Link]
[Link](myTextFormat);

Be aware, however, that many other types of links (such as mailto links) will work only when the
movie is being played in a web browser in which a default client for the protocol has been define

8.28.4 See Also


Recipe 8.10 and Recipe 8.18. Recipe 9.6 shows how to test whether a string has the form of a va
email address. Recipe 18.4 explains how to send data to a server-side script, which could then s
the email for you. Recipe 20.10 explains how to call ColdFusion functions from Flash. You can us

[Link] 2004-02-06
Strona 33 z 33

Cold Fusion's <cfmail> tag to send email from a ColdFusion server.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 30

[ Team LiB ]

Chapter 9. Strings

Introduction

Recipe 9.1. Joining Strings

Recipe 9.2. Using Quotes and Apostrophes in Strings

Recipe 9.3. Inserting Special Whitespace Characters

Recipe 9.4. Searching for a Substring

Recipe 9.5. Extracting a Substring

Recipe 9.6. Matching Patterns with Regular Expressions

Recipe 9.7. Looking for a Pattern Match

Recipe 9.8. Parsing a String into Words

Recipe 9.9. Removing and Replacing Characters

Recipe 9.10. Processing One Character at a Time

Recipe 9.11. Converting Case

Recipe 9.12. Trimming Whitespace

Recipe 9.13. Reversing a String by Word or by Letter

Recipe 9.14. Converting Between Strings and Unicode or ASCII


[ Team LiB ]
[ Team LiB ]

Introduction
Strings are the fundamental textual element of the ActionScript language. You should use
strings for any data in your application that uses characters for any reason. For example:

myString = "this is a string";


myString = 'this is also a string';
myString = "strings can contain characters such as -(*+5~";

String values must always be enclosed within quotes. You can use either single or double
quotes, but the starting and ending quotes enclosing a string must be of the same type.

// Both of these strings cause errors because of mismatched quotes.


myString = "an incorrect string'; // Ending quote should be double
myString = 'another incorrect string"; // Ending quote should be single

[Link] 2004-02-06
Strona 2 z 30

ActionScript provides functionality that allows you to work with strings in many ways. Although
ActionScript does not provide native support for regular expressions (pattern matching), the
third-party RegExp class described in Recipe 9.6 does.

[ Team LiB ]
[ Team LiB ]

Recipe 9.1 Joining Strings


9.1.1 Problem
You want to concatenate (join) together two or more strings into a single value.

9.1.2 Solution
Use the string concatenation operator (+) or the combination concatenation-assignment operator
(+=). Alternatively, use the concat( ) method.

9.1.3 Discussion
You can join together multiple strings in a single expression using the concatenation operator (+)
between the two string operands:

// Results in a single value of "Thisworks" (no space)


myString = "This" + "works";

If you want to join together more than two strings, use additional concatenation operators and
string value operands in the appropriate order:

// Results in a single value of "This works" (with a space)


myString = "This" + " " + "works";

In the preceding examples, there is little reason why you would need to join together the string
literals instead of assigning a single string value ("This works" instead of "This" + " " + "works")
However, this demonstrates the technique you'll use when working with dynamic values. You can
use the concatenation operator to join together not only string literals, but also variables
containing string values (or values that can be converted to strings). For example:

num = 24.
// Results in a single value of "There are 24 people"
myString = "There are " + num + " people";

The concatenation operator automatically converts any non-string values to strings, as long as a
least one of the operands in the statement is a string. In the preceding example, the number
value, 24, is converted to the string value "24" automatically before being joined with the other
strings. However, if all the operands are numbers, the ActionScript interpreter treats the +
operator as the addition operator instead of the concatenation operator:

num1 = 24;
num2 = 42;
// Results in a number value of 66
myString = num1 + num2;

You can concatenate, rather than add, two or more numbers in several ways. One way is to
concatenate an empty string to the beginning of the statement:

[Link] 2004-02-06
Strona 3 z 30

num1 = 24;
num2 = 42;
// Results in a string value of "2442"
myString = "" + num1 + num2;

The empty string must be placed first in the expression because if the two numbers appear first,
they are added rather than concatenated, even though the final value is still converted to a
string:

num1 = 24;
num2 = 42;
// Results in a string value of "66"
myString = num1 + num2 + "";

Another option is to use the String( ) function to ensure that at least one of the numbers is
converted to a string before performing the concatenation:

num1 = 24;
num2 = 42;
// Results in a string value of "2442"
myString = String(num1) + num2;

When you use this technique for only two numbers, it does not matter which one you convert to
a string (or you can convert both). But if you are joining more than two numbers, you should
convert the first or second number to a string. Otherwise, the numbers preceding the value that
is converted to a string will be added rather than concatenated.

num1 = 24;
num2 = 42;
num3 = 21;
// Results in a string value of "6621"
myString = num1 + num2 + String(num3);

If you want to add, rather than concatenate, two number values in the middle of a string
concatenation statement, you should enclose that expression in parentheses. This evaluates the
expression first, treating it as an addition operation rather than a concatenation operation.

num1 = 24;
num2 = 42;
// Results in "There are 66 people"
myString = "There are " + (num1 + num2) + " people";

You can also append text to strings using multiple lines of code rather than trying to cram it all
into one statement. You should use the combination concatenation-assignment operator in these
cases.

myString = "a ";


myString += "quick ";
myString += "brown ";
myString += "fox";

This technique can be useful for several reasons. First of all, sometimes you want to join
together long string values, and your code remains more readable when you break it up into
multiple lines:

myString = "This is the first line of a long paragraph of text.\n";


myString += "By adding line by line to the string variable you make ";
myString += "your code more readable.";

You may have a good reason to append more text to a string over time rather than all at once.

[Link] 2004-02-06
Strona 4 z 30

For example, your movie may periodically poll a server-side script that returns updated data.
You can append this new data to an existing string using the combination concatenation-
assignment operator. For example:

// lv is a LoadVars object.
[Link] = function ( ) {
// Assume the server script returns a variable named newData. You can append it
// value (and a newline) to an existing string named myString.
_root.myString += [Link] + newline;
};

You can also use the [Link]( ) method to append new values to the end of an existing
string. The concat( ) method does not affect the original string. Instead, it returns a new string
containing the concatenated value.

myString = "first string value.";

// Set myNewString to "first string [Link] string value."


// The value of myString remains unchanged.
myNewString = [Link]("second string value.");

Flash 4 used the & operator for string concatenation. Flash converts the & operator to the add
operator when updating Flash 4 files to Flash 5 or Flash 6 format. The + operator is the preferred
string concatenation operator in Flash 5 and later.

[ Team LiB ]
[ Team LiB ]

Recipe 9.2 Using Quotes and Apostrophes in Strings


9.2.1 Problem
You want to use quotes or apostrophes within a string value.

9.2.2 Solution
Use a backslash to escape the quotes or apostrophes contained within the string. Alternatively,
use single quotes within double quotes, or vice versa.

9.2.3 Discussion
The ActionScript interpreter always matches up quotes of the same kind (single quotes with
single quotes and double quotes with double quotes) when used in strings. Therefore, if you
enclose a string literal within quotes of one type and try to include the same kinds of quotes in
the string value, ActionScript fails to interpret it as you intended.

This string assignment causes an error because of mismatched quotes. Flash interprets the "
character before the Y as the end of the string; it does not understand what to do with the
remaining characters.

myString = "He said, "Yes.""; // Wrong!

One possible solution is to use single quotes to enclose a string literal that contains double
quotes, or double quotes to enclose a string literal that contains single quotes:

[Link] 2004-02-06
Strona 5 z 30

// This assignment works. The result is a string: He said, "Yes."


myString = 'He said, "Yes."';

// This assignment also works. The result is a string: He said, 'Yes.'


myString = "He said, 'Yes.'";

However, if the string value contains both single and double quotes, this technique does not
work. Furthermore, you have to pay close attention to what type of quotes are used where. An
alternative solution, which will work all the time, is to use the backslash to escape any quote
used within the string value (i.e., escape the quote by preceding it by the backslash character):

// This assignment works. The result is a string: He said, "Yes."


myString = "He said, \"Yes.\"";

The backslash character tells the ActionScript interpreter to interpret the next character
literally, and not with any special meaning it might normally have. Therefore, when you precede
a quotation mark within a string value with the backslash character, you tell the ActionScript
interpreter that the quote does not signal the boundary of the string value, as it normally
would.
[ Team LiB ]
[ Team LiB ]

Recipe 9.3 Inserting Special Whitespace Characters


9.3.1 Problem
You want to add whitespace characters such as tabs or newline characters to your string.

9.3.2 Solution
Use the escape sequences for the special characters.

9.3.3 Discussion
There are five special whitespace characters with escape sequences, as shown in Table 9-1.

Table 9-1. Whitespace escape sequences


Whitespace character Escape sequence
Newline \n
Tab \t
Backspace \b
Form feed \f
Carriage return \r

You can use these escape sequences within a string. They are most useful when displaying a
string value in a text field:

// Results in a string value: these words are separated by tabs


myString = "these\twords\tare\tseparated\tby\ttabs";

/* Results in a string value:


these

[Link] 2004-02-06
Strona 6 z 30

words
are
separated
by
newlines
*/
myString = "these\nwords\nare\nseparated\nby\nnewlines";

ActionScript also provides a constant, newline, which can be used in place of the \n escape
sequence. The result is the same:

/* Results in a string value:


two
lines
*/
myString = "two" + newline + "lines";

Within Flash, the newline, form feed, and carriage return characters all result in the same
display. However, when you load content into Flash from external sources, some values will
have newline characters, some will have form feeds, and some will have carriage returns.
[ Team LiB ]
[ Team LiB ]

Recipe 9.4 Searching for a Substring


9.4.1 Problem
You want to find a value within a string.

9.4.2 Solution
Use the indexOf( ) or lastIndexOf( ) methods.

9.4.3 Discussion
You can use the indexOf( ) and lastIndexOf( ) methods to determine whether a string contains a
specified substring value. Each method returns the starting index of the substring if it is found. I
substring is not found, the value -1 is returned.

The indexOf( ) method takes up to two parameters:

substring

The substring value for which you want to search.

startIndex

The zero-relative starting position from which to search within the string. If undefined, the
method begins the search from the beginning of the string.

If you want to test whether a string contains another string, you can use the indexOf( ) method
only one parameter. For example:

myString = "This string contains the word cool twice. Very cool!";

// Get the index of the first occurrence of the substring "cool" within myString.

[Link] 2004-02-06
Strona 7 z 30

indexOfCool = [Link]("cool");

// If the indexOf( ) method returns -1, no occurrences of "cool" were found.


if (indexOfCool != -1) {
// Displays: "String contains word cool at index 30", because the first occurren
// of the substring appears starting at index 30 within myString.
trace("String contains word cool at index " + indexOfCool);
}

You can get the indexes of subsequent occurrences of a substring by specifying the second, optio
parameter of the indexOf( ) method, StartIndex. A simple and effective way to search for the n
occurrence of a substring is to pass the method a starting index parameter value of one more th
what was returned by the previous search:

myString = "This string contains the word cool twice. Very cool!";

// Get the index of the first occurrence of the substring "cool" within myString.
indexOfCool = [Link]("cool");

if (indexOfCool != -1) {
// Displays: "String contains word cool at index 30"
trace("String contains word cool at index " + indexOfCool);
}

// Get the index of the second occurrence of the substring "cool" within myString
// Pass the method the previous value of indexOfCool + 1. This starts the search p
// the starting index of the previous occurrence, thus finding the next occurrenc
// If you do not add 1 to indexOfCool when passing it to indexOf( ), the method
// returns the location of the first occurrence again.
indexOfCool = [Link]("cool", indexOfCool + 1);

if (indexOfCool != -1) {
// Displays: "String contains word cool at index 47", because the next occurren
// of the substring appears starting at index 47 within myString.
trace("String contains word cool at index " + indexOfCool);
}

You can use indexOf( ) in a while statement to get the indexes of every occurrence of a substring
example:

myString = "This string contains the word cool thrice. Very cool! Yes, cool!";

// Initialize indexOfCool to -1 so that the while statement searches the string f


// the 0 index (starting at indexOfCool + 1).
indexOfCool = -1;

// Loop until indexOf( ) returns -1.


while ( (indexOfCool = [Link]("cool", indexOfCool + 1)) != -1 ) {

/* Displays:
String contains word cool at index 30
String contains word cool at index 48
String contains word cool at index 59
*/
trace("String contains word cool at index " + indexOfCool);
}

The while conditional in the preceding code looks more complicated than it really is. If you take i
apart, you can see that it is composed of manageable and understandable parts. The first part
assigns to indexOfCool the value returned by the indexOf( ) method:

indexOfCool = [Link]("cool", indexOfCool + 1);

[Link] 2004-02-06
Strona 8 z 30

The first time that this statement is encountered, the initial value of indexOfCool is -1 because
set to -1 prior to the while statement. This is necessary to start the search at index 0 because 1
added to that value each time. If indexOfCool is undefined, the ActionScript interpreter resolves
0; adding 1 to 0 causes Flash to search the string starting at index 1 instead of 0, potentially mis
a match if the search string occurs at the beginning of the searched string. Each subsequent tim
while condition is evaluated, the value of indexOfCool has the value of the starting index of the
previously found match.

The second part of the while conditional tests to make sure that the result of the indexOf( ) meth
not -1. This is important because without it, the while loop would continue indefinitely. When ind
( ) returns -1, it means that no more matches exist, so the while loop terminates.

You can also search backward in a string. The lastIndexOf( ) method always returns the starting
index of the last occurrence of the substring (it returns the position of the beginning of the found
string, not its end, even though the search is performed backward). Therefore, it takes only one
parameter: the substring for which to search. If no match is found, lastIndexOf( ) returns -1.

myString = "This string contains the word cool twice. Very cool!";

// Get the index of the last occurrence of the substring "cool" within myString.
indexOfCool = [Link]("cool");

if (indexOfCool != -1) {
// Displays: "String contains word cool at index 47", because the last occurren
// of the substring appears starting at index 47 within myString.
trace("String contains word cool at index " + indexOfCool);
}

Both the indexOf( ) and lastIndexOf( ) methods are case-sensitive in their searches. For example
substring "cool" would not be found in the string "Cool" because the cases are not the same. To
perform a case-insensitive search, use the toLowerCase( ) method in conjunction with the indexO
or lastIndexOf( ) method:

// Create a string that spells "cool" as both "cool" and "Cool".


myString = "Cool! This is a string with the word cool. It spells cool as both ";
myString += "cool (lowercase) and Cool (capitalized).";

search = "cool";

// Output the index of the first occurrence of "cool". The result is 37 because i
// does not find "Cool" due to the case-sensitive search.
trace([Link](search));

// Output the index of the first occurrence of "cool" after lowercasing the strin
// with toLowerCase( ). The result is 0 because it now finds "Cool" (which has b
// converted to "cool") at the beginning of the string.
trace([Link]( ).indexOf(search));

// Output the index of the last occurrence of "cool". The result is 66 because it
// does not find the last "Cool" due to the case-sensitivity.
trace([Link]("cool"));

// Output the index of the last occurrence of cool after lowercasing the string.
// result is 87 because it finds the last "Cool".
trace([Link]( ).lastIndexOf(search));

// Now change the search string to "Cool" (capitalized).


search = "Cool";

// Output the index of the first occurrence of "Cool" after lowercasing the strin
// The result is -1 because "Cool" doesn't exist in the lowercase string.

[Link] 2004-02-06
Strona 9 z 30

trace([Link]( ).indexOf(search));

// This is similar to the preceding line of code, but the search string is also
// converted to lowercase. Therefore, the result is 0 because the starting index
// the first occurrence of "cool" (regardless of case) is 0. Lowercasing both the
// string being searched and the substring for which you are searching ensures th
// completely case-insensitive search is performed.
trace([Link]().indexOf([Link]( )));

You can use the indexOf( ) method to create a find feature for text fields:

// Include [Link] from Chapter 4 for its drawRectangle( ) method.


#include "[Link]"

// Add the custom find( ) method to [Link] so that it is available


// all text fields. The method takes three parameters: search is the search strin
// startIndex is the index from which to perform the search, and matchCase is a
// Boolean that specifies if the search should be case-sensitive. If matchCase is
// undefined, the search is case-insensitive.
[Link] = function (search, startIndex, matchCase) {

// Initialize the local variable, index.


var index;

// If matchCase is false or undefined, perform a case-insensitive search.


// Otherwise, do a case-sensitive match.
if (!matchCase) {
index = [Link]().indexOf([Link]( ), startIndex);
} else {
index = [Link](search, startIndex);
}

// Set the focus to the text field in case it is not already set.
[Link](this);

// If the search string was found, set the selection to highlight the match with
// the text field. Otherwise, position the cursor at the beginning of the text
// field.
if (index != -1) {
[Link](index, index + [Link]);
} else {
[Link](0, 0);
}

// Return the index of the match so that it can be used to pass the next startI
// value to this method when called again.
return index;
};

// Example usage:

// Create a text field.


_root.createTextField("myText", 1, 100, 100, 0, 0);
[Link] = true;
[Link] = true;
[Link] = "This is a text field with text. And more text.";

// Create a button movie clip.


_root.createEmptyMovieClip("myButton_mc", 2);
myButton_mc.lineStyle(0, 0x000000, 100);
myButton_mc.beginFill(0, 100);
myButton_mc.drawRectangle(100, 20);
myButton_mc.endFill( );
myButton_mc._x = 400;

[Link] 2004-02-06
Strona 10 z 30

myButton_mc._y = 300;

// When the button is clicked, call the find( ) method of the myText text field.
// it the search string text. The myButton_mc.currentIndex is set with each call
// find( ) to the value of the starting index of the found match. Therefore, pas
// currentIndex + 1 to find( ) makes it look for the next match.
myButton_mc.onRelease = function ( ) {
[Link] = _root.[Link]("text", [Link] + 1, false);
};
[ Team LiB ]
[ Team LiB ]

Recipe 9.5 Extracting a Substring


9.5.1 Problem
You want to extract a substring from a string.

9.5.2 Solution
Use the substring( ), substr( ), or slice( ) methods.

9.5.3 Discussion
The substring( ), substr( ), and slice( ) methods all return the value of a substring without affect
the original string. The only difference between the three methods is in the parameters they acce

The substr( ) method takes up to two parameters:

startIndex

The position of the first character of the substring. The value can be negative, in which cas
index is calculated from the end of the string, where -1 is the last character, -2 is the seco
last character, and so on.

length

The number of characters in the substring to extract. If this parameter is omitted, all the
characters from startIndex to the end are used:

myString = "Bunnies";
trace([Link](0)); // Displays: Bunnies
trace([Link](0, 3)); // Displays: Bun
trace([Link](3, 3)); // Displays: nie
trace([Link](-1)); // Displays: s
trace([Link](-2, 5)); // Displays: es

The substring( ) and slice( ) methods each take the same parameters:

startIndex

The position of the first character of the substring to extract.

endIndex

The position of one character after the last character in the substring to extract. If this

[Link] 2004-02-06
Strona 11 z 30

parameter is omitted, all the characters from the startIndex to the end are used.

The substring( ) and slice( ) methods differ in that substring( ) accepts positive index values only
interprets negative values as 0. Also, if endIndex is less than startIndex, the substring( ) meth
automatically reverses them before executing. The slice( ) method, on the other hand, accepts
negative values for both startIndex and endIndex; it interprets negative values as counting bac
from the end of the string. The slice( ) method fails if you specify an endIndex that is less than
startIndex.

myString = "Rabbits";

// Both of these output the entire string, beginning at index 0 and going to the
// index (the value of which is [Link] - 1).
trace([Link](0)); // Displays: Rabbits
trace([Link](0)); // Displays: Rabbits

// The substring( ) method outputs nothing because it converts the negative index
// 0. The slice( ) method outputs "it", which is the substring from the third-to
// character (index -3) to the next-to-last character (index -1).
trace([Link](-3, -1)); // Displays nothing
trace([Link](-3, -1)); // Displays: it

// Both of these output the substring "ab".


trace([Link](1, 3)); // Displays: ab
trace([Link](1, 3)); // Displays: ab

// The substring( ) method outputs the substring "ab" because it reverses the or
// the parameters automatically. The slice( ) method outputs nothing because the
// slice( ) method does not reverse the order of the parameters.
trace([Link](3, 1)); // Displays: ab
trace([Link](3, 1)); // Displays nothing

You will commonly use the substring-extraction methods in conjunction with the indexOf( ) and
lastIndexOf( ) methods. You can use the indexOf( ) and lastIndexOf( ) methods to search for the
substring within a string, and then you can use the substring-extraction methods to get the subs

This example extracts a file's extension and its filename (without the extension), which are presu
to be separated from each other by a period:

filename = "[Link]";
// Find the location of the period.
extIndex = [Link](".");

// The extensionless filename ("mydocument") is everything before the period.


strippedFileName = [Link](0, extIndex);
trace ("The filename is " + strippedFileName);

// The extension ("jpg") is everything after the period.


extension = [Link](extIndex + 1, [Link]);
trace ("The file extension is " + extension);

You can also use the split( ) method, assuming there is only one period in the filename:

filename = "[Link]";
// Split the string wherever the period occurs.
nameArray = [Link](".");

// The first element is "mydocument" (everything before the first period).


strippedFileName = nameArray[0];
trace ("The filename is " + strippedFileName);

[Link] 2004-02-06
Strona 12 z 30

// The next element is "jpg" (anything after the first


// period and before the next one).
extension = nameArray[1];
trace ("The file extension is " + extension);

Compare the two preceding examples. What happens if the filename contains no periods, such a
"mydocument"? (Hint: The first example fails in that case.) What if the filename contains more t
one period, such as "ascb_fig8.[Link]"? (Hint: The second example fails in that case.)

Here is a general solution:

// This function returns everything before the last period, if any.


function stripExtension (filename) {
// Find the location of the period.
extIndex = [Link](".");
if (extIndex == -1) {
// Oops, there is no period. Just return the filename.
return filename;
} else {
return [Link](0, extIndex);
}
}

// This function returns everything after the last period, if any.


function extractExtension (filename) {
// Find the location of the period.
extIndex = [Link](".");
if (extIndex == -1) {
// Oops, there is no period, so return the empty string
return "";
} else {
return [Link](extIndex + 1, [Link]);
}
}
// Example usage:
trace(stripExtension("[Link]")); // Displays: mydocument
trace(stripExtension("mydocument")); // Displays: mydocument
trace(stripExtension("[Link]")); // Displays: mydocument.1
trace(extractExtension("[Link]")); // Displays: jpg
trace(extractExtension("mydocument")); // Displays nothing
trace(extractExtension("[Link]")); // Displays: jpg

9.5.4 See Also


Recipe 9.8
[ Team LiB ]
[ Team LiB ]

Recipe 9.6 Matching Patterns with Regular Expressions


9.6.1 Problem
You want to match a pattern within a string instead of finding a specific substring.

9.6.2 Solution
Use a regular expression and the [Link]( ) method.

9.6.3 Discussion

[Link] 2004-02-06
Strona 13 z 30

Many programming languages support regular expressions to match patterns in strings. (You
may be familiar with other types of pattern matching. For example, Windows' file search feature
lets you use pattern matching with wildcards, such as * and ?. But regular expressions support
much more sophisticated pattern matching.) ActionScript does not provide native support for
regular expressions. However, there are several third-party classes that are publicly available.
One such class is the RegExp class by Pavils Jurjans, which is very similar to the JavaScript 1.3
RegExp class (JavaScript 1.5 implements new RegExp features not supported by the
ActionScript RegExp class).

Table 9-2 summarizes regular expression pattern-matching operations.

Table 9-2. Regular expressions


Expression Matches Example
ta?k matches "tak"
The preceding character zero or one time (i.e., preceding
? or "tk" but not "tik"
character is optional).
or "taak"
wo*k matches
"wok", "wk", or
* The preceding character zero or more times.
"woook", but not
"wak"
craw+l matches
+ The preceding character one or more times. "crawl" or "crawwl"
but not "cral"
[Link] matches
. (period) Any one character except newline. "crow" or "clow" but
not "cow"
^wap matches "wap"
^ Specified string at beginning of a line.
but not "swap"
Specified string at the end of a line. (The "$"
metacharacter should be at the end of the pattern, such
ow$ matches "ow"
$ as w$. Though RegExp accepts it at the beginning of the
but not "owl"
pattern, such as $w, this feature is not supported by the
ECMA standard.)
one|two matches
x|y Either statement. "one" or "two" but
not "ten"
l[aeo]g matches
[abc] Any of the characters within the brackets. "lag", "leg", or "log"
but not "lig"
[0-3]* matches
[a-z] Any characters within the range. "1320" but not
"4523"
l[^aeo]g matches
[^abc] Any character other than those listed. "lig" but not "lag",
"leg", or "log"
[^0-3]* matches
[^a-z] Any characters not in the range. "4758" but not
"4931"

cre{2}l matches
{n} Exactly n occurrences of the preceding character.
"creel" but not "crel"

[Link] 2004-02-06
Strona 14 z 30

or "creeel"
cre{2,3}l matches
At least n but no more than m instances of the preceding
{n,m} "creel" or "creeel"
letter.
but not "crel"
up\brow matches
\b Word boundary. "up row" but not
"uprow"
up\Brow matches
\B Letter not at the beginning of a word. "uprow" but not "up
row"
\d* matches
\d Any numeric digit; same as [0-9]. "13243" but not
"13A46"
\D* matches
\D Any non-digit character; same as [^0-9]. "1ABC3" but not
"13946"

Single whitespace character (space, tab, line feed, or form \s matches the
\s
feed). space in "King Tut"
\STut matches
\S Single non-whitespace character.
"gTut" but not "Tut"
a\wm matches "arm"
\w Any alphanumeric character; same as [A-Za-z0-9]
but not "a8m"
a\Wm matches "a7m"
\W Any nonalphanumeric character.
but not "aim"
\/ finds slashes;
\x Escaped character (non-metacharacters) specified by x. \( finds
parentheses, etc.

The RegExp class does not try to interpret escape sequences that are natively interpreted by
Flash. The ActionScript RegExp class interprets the escape sequences \d, \D, \s, \S, \w, \W, \b,
and \B, but other escape sequences, such as \n and \t, are interpreted by Flash itself.
Therefore, if you want to match a newline character, you should use the pattern "\n", but if you
want to match a digit, you use the pattern "\\d" (note the double backslash before "d").

You must first download and install the RegExp class if you wish to use it in your Flash
documents. You can download it from Pavils's web site:

[Link]

You can download the ActionScript file itself ([Link]) or a zip file ([Link]) that contains
additional support files. Whichever download you choose, copy the [Link] file into your
Flash installation's Include directory (Flash Installation/Configuration/Include). From there, you
can easily include it in any Flash document.

The main difference between Pavils's ActionScript RegExp class and its JavaScript kin is that
there is no way to define a regular expression using an object initializer. Instead, you must
always use the constructor method:

// This will work in JavaScript but not in ActionScript.


re = /[a-z]*/;

[Link] 2004-02-06
Strona 15 z 30

// This is the proper way to create a regular expression in ActionScript.


re = new RegExp("[a-z]*");

// Create a regular expression that matches a backslash.


re = new RegExp("\\\\");

Because the RegExp object is created by passing a string to the


constructor, all references to \ within the string must be escaped as \\.
Since \ is also a special character in RegExp patterns, to search for a
backslash in a regular expression, you must escape it like this: "\\\\".

When constructing a regular expression, you can specify a second parameter containing flags
that modify its behavior. The most common flags are "i" for case-insensitive matches and "g"
for global matching (finds all matches at once and returns them in an array). For example:

// This matches all letters a, b, c, A, B, and C.


re = new RegExp("[a-c]", "ig");

Once you have created a regular expression that describes the pattern for which you want to
search within the string, use the [Link]( ) method to perform the search. The exec( )
method takes the string as a parameter, and it returns the match. Each call to exec( ) searches
for the next match. If no match is found, it returns null.

// You must include the third-party [Link] file from


// [Link]
#include "[Link]"

// Create a regular expression that matches three-letter words.


re = new RegExp("\\b[a-z]{3}\\b", "g");

myString = "This string has two three-letter words";

// Search the string for the pattern and display the first result: has.
match = [Link](myString);
trace(match);

// Search the string again for the pattern and display the next result: two.
match = [Link](myString);
trace(match);

// Search the string again. No more matches, so the result is null.


match = [Link](myString);
trace(match);

The exec( ) method continues to cycle through the string with each call.
After the method returns null, it will return to the beginning of the string
for the next search.

You can use a while statement with the exec( ) method to find all the matches, like so:

#include "[Link]"

// Create a regular expression that matches three-letter words.


re = new RegExp("\\b[a-z]{3}\\b", "g");
myString = "This string has two three-letter words";

/* Loop until the exec( ) method returns null. This while loop outputs:
has
two

[Link] 2004-02-06
Strona 16 z 30

*/
while ((match = [Link](myString)) != null) {
trace(match);
}

The [Link]( ) method tests whether a string contains a match to a regular expression. The
method returns true if the pattern is matched, and false otherwise. You can use test( ) to test
whether a string is valid for a particular use, such as whether it takes the form of a valid email
address. For example:

#include "[Link]"

// Create a regular expression that matches an email pattern.


re = new RegExp("^([\\w\-\\.]+)@(([\\w\\-]+\\.)+[\\w\\-]+)$");

// Create an array of strings that may or may not be valid emails.


emails = new Array( );
[Link]("someone@[Link]");
[Link]("[Link]@[Link]");
[Link]("email goes here");

/* Test each array element to see whether it is a valid email. The results are:
true
true
false
*/
for (var i = 0; i < [Link]; i++) {
trace([Link](emails[i]));
}

9.6.4 See Also


A detailed discussion of regular expressions is beyond the scope of this book. A good primer on
the JavaScript RegExp class can be found at
[Link]
JavaScript: The Definitive Guide by David Flanagan (O'Reilly) includes detailed coverage of
using regular expressions in JavaScript. See Mastering Regular Expressions by Jeffrey E. F.
Friedl (O'Reilly) for extensive practice with regular expressions. Also refer to Recipe 9.4 and
Recipe 9.7. Recipe 9.9 demonstrates using regular expressions to remove nonalphanumeric
characters in a string. Also see Recipe 8.7, which covers filtering text input. Table A-1 lists the
Unicode code points for the Latin 1 character set. Recipe 11.4 discusses validating data input.
[ Team LiB ]
[ Team LiB ]

Recipe 9.7 Looking for a Pattern Match


9.7.1 Problem
You want to search a string for every match to a pattern.

9.7.2 Solution
Use the custom [Link]( ) method.

9.7.3 Discussion
The native ActionScript String class does not provide a match( ) method that allows you to find
matches to a pattern within a string. However, the custom [Link] file (see Recipe 9.6) adds

[Link] 2004-02-06
Strona 17 z 30

match( ) method to the String class.

You should call the match( ) method from a string and pass it a regular expression as a
parameter. The method searches the string for all matching substrings and places them in a new
array:

// You must include the third-party [Link] file from Recipe 9.6.
#include "[Link]"

myString = "Twenty twins went toward them";

// Create a new regular expression to perform a case-insensitive match on an enti


// string for any words that begin with "tow" or "tw".
re = new RegExp("\\bt(o)?w[\\w]+\\b", "ig");

// Find all the matches and put them in an array.


matches = [Link](re);

/* Loop through the array and display the results. The output is:
Twenty
twins
toward
*/
for (var i = 0; i < [Link]; i++) {
trace(matches[i]);
}

9.7.4 See Also


Recipe 9.6

[ Team LiB ]
[ Team LiB ]

Recipe 9.8 Parsing a String into Words


9.8.1 Problem
You want to process a string one word at a time.

9.8.2 Solution
Use the split( ) method.

9.8.3 Discussion
The split( ) method (see Recipe 6.6) splits a string into an array using the specified delimiter. To
split a string into separate words, use the split( ) method with a space as the delimiter:

// Create a string with multiple words.


myString = "this is a string of words";

// Split the string into an array of words using a space as the delimiter.
words = [Link](" ");

// Loop through the array and do something with each word. In this example, we ju
// output the values.

[Link] 2004-02-06
Strona 18 z 30

for (var i = 0; i < [Link]; i++) {


/* Displays:
this
is
a
string
of
words
*/
trace(words[i]);
}

You can process the individual words in many ways. Here is an example that uses this technique
split a string into words and then creates movie clips containing those words. The user can then
drag the words around on stage to form various sentences or statements, as in the popular
magnetic poetry kits.

// Create a string and split the string into an array of words.


myString = "This is a string of ActionScript poetry words";
words = [Link](" ");

// Loop through all the words in the array.


for (var i = 0; i < [Link]; i++) {

// Create a new movie clip for each word.


word_mc = _root.createEmptyMovieClip("word" + i, i);

// Create a text field within each movie clip.


word_mc.createTextField("word_txt", 1, 0, 0, 0, 0);

// The text field should autosize to fit its contents. It should also have a bo
// and background so that it mimics the look of poetry magnets.
word_mc.word_txt.autoSize = true;
word_mc.word_txt.border = true;
word_mc.word_txt.background = true;

// Set each movie clip's text field value to one of the words from the array.
word_mc.word_txt.text = words[i]

// The movie clip is draggable when clicked and stops


// being draggable when released.
word_mc.onPress = function ( ) {
[Link]( );
};
word_mc.onRelease = function ( ) {
[Link]( );
};

// Randomize the position of the movie clips containing words.


rx = [Link]( ) * [Link] - word_mc._width;
ry = [Link]( ) * [Link] - word_mc._height;
word_mc._x = rx;
word_mc._y = ry;
}

The preceding use of split( ) by itself is all that you need when the original string value contains
words and spaces but no punctuation. If the string has punctuation or other miscellaneous
characters, you should remove them first by using a regular expression and the custom
[Link]( ) method included in [Link]. The regular expression to remove everything ex
letters, numbers, and spaces is [^a-zA-Z0-9 ]:

// You must include the third-party [Link] file from Recipe 9.6.

[Link] 2004-02-06
Strona 19 z 30

#include "[Link]"

// Create a string that uses punctuation.


myString = "Here are some words. Also, here is some punctuation!";

// Create an array of words from the string without first removing punctuation.
words = [Link](" ");

// Display the elements of the array. Some of the elements also contain
// punctuation. This is most likely undesirable.
for (var i = 0; i < [Link]; i++) {

/* Outputs:
Here
are
some
words.
Also,
here
is
some
punctuation!
*/
trace(words[i]);
}
// Create a regular expression that can be used to remove all nonalphanumeric
// characters and spaces.
re = new RegExp("[^a-zA-Z0-9 ]", "g");

// Call the replace( ) method of myString. Pass it the regular expression, re, an
// tell it to replace all nonmatching characters with the empty string.
nString = [Link](re, "");

// Split nString, the result of the replace( ) method call, into an array of wor
words = [Link](" ");

// Output all the elements of the words array. This time each element is a word,
// none of the elements include punctuation.
for (var i = 0; i < [Link]; i++) {

/* Outputs:
Here
are
some
words
Also
here
is
some
punctuation
*/
trace(words[i]);
}

Including the [Link] file also enhances the [Link]( ) method to support regular expressi
as a parameter directly. Therefore, instead of calling [Link]( ) separately, as shown in th
preceding example:

re = new RegExp("[^a-zA-Z0-9 ]", "g");


nString = [Link](re, "");
words = [Link](" ");

you can pass the RegExp object directly to split( ), as follows:

[Link] 2004-02-06
Strona 20 z 30

re = new RegExp("[^a-zA-Z0-9]", "g");


words = [Link](re);

9.8.4 See Also


Recipe 6.6, Recipe 9.6, Recipe 9.5, and Recipe 9.9

[ Team LiB ]
[ Team LiB ]

Recipe 9.9 Removing and Replacing Characters


9.9.1 Problem
You want to remove characters from a string and optionally replace them.

9.9.2 Solution
Create a custom [Link]( ) method. Alternatively, for replacing patterns, use the
[Link]( ) method included in [Link].

9.9.3 Discussion
ActionScript does not provide a native method that replaces substrings within a string. Therefore
you must use a custom method to do so. If you want to replace instances of a specific substring,
you can create a custom simpleReplace( ) method for the String class. This method should accep
to three parameters:

search

The substring you want to find and replace.

replace

The value with which to replace the occurrences of the search substring.

matchCase

If true, the method performs a case-sensitive search. Otherwise, it performs a case-insens


search.

Here is our custom [Link]( ) method:

[Link] = function (search, replace, working) {

// temp stores the string value with the replaced substrings.


var temp;

// working holds the value of the string.


var working = this;

// Perform a case-insensitive search if so directed.


if (!matchCase) {
working = [Link]( );
search = [Link]( );

[Link] 2004-02-06
Strona 21 z 30

// searchIndex holds the starting index of a matches. startIndex stores the valu
// of the index after the replaced substring.
var searchIndex = -1;
var startIndex = 0;

// Find each match to the search substring.


while ((searchIndex = [Link](search, startIndex)) != -1) {

// Append to temp the string value from the end of the last match to just bef
// the current match. Then, append the replace string in place of the search
// substring.
temp += [Link](startIndex, searchIndex);
temp += replace;

// startIndex holds the index one after the final character of the matched
// substring. This starts the next search-and-replace operation after the
// substring that is being replaced.
startIndex = searchIndex + [Link];
}

// Return the temp value plus the remainder of the original


//string value (after the last match).
return temp + [Link](startIndex);
};

// Create a string to test.


myString = "It's a bird, it's a plane, it's ActionScript Man!";

// Replace all instances of "it's" with "it is". Perform a case-insensitive match
// Outputs: it is a bird, it is a plane, it is ActionScript Man!
trace([Link]("it's", "it is", false));

In cases where you need to perform pattern matching as part of the replacement within a string,
you should use the custom [Link]( ) method, which is available only if you include the
[Link] file. This method allows you to match and replace using a regular expression instead
string literal, which offers a great deal more flexibility.

You should first create a regular expression using the RegExp constructor. The regular expressio
should be the pattern that you want to match and replace in your string. Then pass that regular
expression as the first parameter to the [Link]( ) method. The second parameter should
a string literal that you want to use to replace all occurrences of the pattern.

// You must include the third-party [Link] file from Recipe 9.6.
#include "[Link]"

// Create a string that includes people and telephone numbers. The telephone numb
// follow the same pattern, so you can use a regular expression to replace them.
myString = "Regina [555-1212], Henriette [555-1234]";

// Create a regular expression that matches the telephone number pattern and sear
// globally. This pattern matches U.S. telephone number patterns (without area co
// and is provided for demo purposes only. You must adapt it to match telephone
// number patterns in your country. You could also use "\\d" instead of "[0-9]" in
// the following expression.
re = new RegExp("[0-9]{3}-[0-9]{4}", "g");

// Output the string with the telephone numbers replaced by X's. The result is:
// Regina [XXX-XXXX], Henriette [XXX-XXXX]
trace([Link](re, "XXX-XXXX"));

[Link] 2004-02-06
Strona 22 z 30

[ Team LiB ]
[ Team LiB ]

Recipe 9.10 Processing One Character at a Time


9.10.1 Problem
You want to process a string one character at a time.

9.10.2 Solution
Use a for statement and the [Link]( ) method. Alternatively, use [Link]( ) with the
empty string as the delimiter to split the string into an array of all the characters, then use a for
statement to loop through the array.

9.10.3 Discussion
The simplest way to process each character of a string is to use a for statement that loops from
to the length of the string, incrementing by 1. Within the for statement body, you can use the
charAt( ) method to extract each character for processing.

myString = "a string";

// Loop from 0 to the length of the string.


for (var i = 0; i < [Link]; i++) {
/* Output each character, one at a time. This displays:
a

s
t
r
i
n
g
*/
trace([Link](i));
}

You can achieve the same effect by using the split( ) method to first split the string into an array
characters and then looping through the array to process each character. You should use the
empty string as the delimiter parameter for the split( ) method to split between each character.

myString = "a string";

// Split the string into an array of characters (one-character strings).


chars = [Link]("");

// Loop through all the elements of the chars array.


for (var i = 0; i < [Link]; i++) {
/* Output each character element. This displays:
a

s
t
r
i
n
g

[Link] 2004-02-06
Strona 23 z 30

*/
trace(chars[i]);
}

Both techniques are generally interchangeable, though the second offers some advantages if you
want to process the characters using common array methods. For example, if you first split a str
into an array of characters, you can sort that array. This is not as easily done when you use the
charAt( ) technique:

myString = "a string";

chars = [Link]("");

// Alphabetically sort the array of characters.


[Link]( );

for (var i = 0; i < [Link]; i++) {


/* Displays:

a
g
i
n
r
s
t
*/
trace(chars[i]);
}

Also, if you want to use this process to remove every instance of a particular character, it is easi
with an array than with the charAt( ) technique:

myString = "a string";

chars = [Link]("");

for (var i = 0; i < [Link]; i++) {


// Remove all "r" elements from the array. Be sure to decrement i if an element
// removed. Otherwise, the next element is improperly skipped.
if (chars[i] == "r") {
[Link](i, 1);
i--;
}
}

// Displays: a sting
trace([Link](""));

9.10.4 See Also


Though the preceding technique for replacing characters works for simple cases, you should see
Recipe 9.9 for more capable alternatives.
[ Team LiB ]
[ Team LiB ]

Recipe 9.11 Converting Case


9.11.1 Problem

[Link] 2004-02-06
Strona 24 z 30

You want to change the case of a string or perform a case-insensitive comparison.

9.11.2 Solution
User the toUpperCase( ) and toLowerCase( ) methods.

9.11.3 Discussion
The toUpperCase( ) and toLowerCase( ) methods return new strings in which all the characters
are uppercase or lowercase, respectively. This is useful in situations in which you want to
ensure uniformity of case. For example, you can use toLowerCase( ) or toUpperCase( ) to
perform case-insensitive searches within strings, as shown in Recipe 9.4. Both methods affect
alphabetical characters only, leaving nonalphabetic characters unchanged.

myString = "What case?";

// Displays: what case?


trace([Link]( ));

// Displays: WHAT CASE?


trace([Link]( ));

// The original string value is unchanged: What case?


trace(myString);

Both methods return a new string. To alter the original string, reassign the return value to it, as
follows:

myString = [Link]( );

You can use toLowerCase( ) and toUpperCase( ) in concert to capitalize the first letter of a
word, as implemented in the following custom toInitialCap( ) method:

[Link] = function ( ) {
// Convert the first character to uppercase and the remainder to lowercase.
return [Link](0).toUpperCase() + [Link](1).toLowerCase( );
};

myString = "bRuCE";
trace([Link]( )); // Displays: Bruce

Here is another function that converts a string to so-called title case (initial letters capitalized).
Unlike the preceding example, it doesn't lowercase subsequent characters.

[Link] = function ( ) {
working = this;
var words = [Link](" ");
for (var i = 0; i < [Link]; i++) {
words[i] = words[i].charAt(0).toUpperCase( ) + words[i].substr(1)
}
return ([Link](" "));
};

myString = "the actionScript cookbook";


trace([Link] ( )); // Displays: The ActionScript Cookbook

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 25 z 30

Recipe 9.12 Trimming Whitespace


9.12.1 Problem
You want to trim the whitespace from the beginning and end of a string.

9.12.2 Solution
Write a custom trim( ) method that splits the string into an array of characters, removes the
whitespace from the beginning and end, and then returns the character array as a string using jo
( ).

9.12.3 Discussion
Extra whitespace at the beginning and end of a string is a common enough annoyance that you
should have a way of dealing with it. ActionScript does not provide a native trim( ) method, so y
have to write your own.

The custom trim( ) method presented here:

1. Splits the string into an array or characters

2. Removes whitespace elements at the beginning of the array until it finds an element that i
not a whitespace character (tab, form feed, carriage return, newline, or space)

3. Removes whitespace elements at the end of the array

4. Uses join( ) to form the array characters into a single string and returns that value

Here is our custom [Link]( ) function. You can add it to a [Link] file for easy inclusion in
other projects.

[Link] = function ( ) {

// Split the string into an array of characters.


var chars = [Link]("");

// Remove any whitespace elements from the beginning of the array using splice(
// Use a break statement to exit the loop when you reach a non-whitespace chara
// to prevent it from removing whitespace in the middle of the string.
for (var i = 0; i < [Link]; i++) {
if (chars[i] == "\r" ||
chars[i] == "\n" ||
chars[i] == "\f" ||
chars[i] == "\t" ||
chars[i] == " ") {
[Link](i, 1);
i--;
} else {
break;
}
}

// Loop backward through the array, removing whitespace elements until a


// non-whitespace character is encountered. Then break out of the loop.
for (var i = [Link] - 1; i >= 0; i--) {
if (chars[i] == "\r" ||

[Link] 2004-02-06
Strona 26 z 30

chars[i] == "\n" ||
chars[i] == "\f" ||
chars[i] == "\t" ||
chars[i] == " ") {
[Link](i, 1);
} else {
break;
}
}

// Recreate the string with the join( ) method and return the result.
return [Link]("");
};

// Create a string with beginning and ending whitespace.


myString = "\n\r\f\ta string\t\t\n\n";

/* Display the value before calling the trim( ) method. Displays:


this string value is:

a string

<end>
*/
trace("this string value is: " + myString + "<end>");

// Set myString to the value returned by the trim( ) method.


myString = [Link]( );

/* Now, display the value again using the same trace( ) statement. Displays:
this string value is: a string<end>
*/
trace("this string value is: " + myString + "<end>");

For good measure, here is a trim function implemented using the RegExp class:

// You must include the third-party [Link] file from Recipe 9.6.
#include "[Link]"
var trimRe = new RegExp("^\\s*|\\s*$");
var myString = "\r\n\f\t test string \r\n\f\t";
trace([Link](trimRe, "")); // Displays: test string

[ Team LiB ]
[ Team LiB ]

Recipe 9.13 Reversing a String by Word or by Letter


9.13.1 Problem
You want to reverse a string either by word or by letter.

9.13.2 Solution
Use the split( ) method to create an array of the words/characters and use the reverse( ) and
join( ) methods on that array.

9.13.3 Discussion

[Link] 2004-02-06
Strona 27 z 30

You can reverse a string by word or by character using the same process. The only difference is
in the delimiter you use in the split( ) method and the joiner you use in the join( ) method. In
either case, you should first split the string into an array—using a space as the delimiter for
words or the empty string as the delimiter for characters. Then call the reverse( ) method of
the array, which reverses the order of the elements. Finally, use the join( ) method to
reconstruct the string. When reversing by word, use a space as the joiner, and when reversing
by character, use the empty string as the joiner.

myString = "hello dear reader";

// Split the string into an array of words.


words = [Link](" ");

// Reverse the array.


[Link]( );

// Join the elements of the array into a string using spaces.


myStringRevByWord = [Link](" ");

// Outputs: reader dear hello


trace(myStringRevByWord);

// Split the string into an array of characters.


chars = [Link]("");

// Reverse the array elements.


[Link]( );

// Join the array elements into a string using the empty string.
myStringRevByChar = [Link]("");

// Outputs: redaer raed olleh


trace(myStringRevByChar);
[ Team LiB ]
[ Team LiB ]

Recipe 9.14 Converting Between Strings and Unicode or


ASCII
9.14.1 Problem
You want to convert between characters and their corresponding Unicode code point (a.k.a. char
codes) or ASCII codes.

9.14.2 Solution
Use the [Link]( ) and [Link]( ) methods.

9.14.3 Discussion
You can use fromCharCode( ) to display characters that you cannot enter into your Flash docume
directly. The method is a static method, which means that it is invoked from the top-level String
instead of from a string instance. For values less than 128, fromCharCode( ) essentially converts
numeric ASCII code to its equivalent character.

/* Outputs:
New paragraph: ¶
Cents: ¢

[Link] 2004-02-06
Strona 28 z 30

Name: Joey
*/
trace("New paragraph: " + [Link](182));
trace("Cents: " + [Link](162));
trace("Name: " + [Link](74, 111, 101, 121));

You can use the charCodeAt( ) method to retrieve the code point of the character at a particular
of a string. For characters whose Unicode code point is less than 128, charCodeAt( ) essentially
converts a character to its equivalent ASCII code.

myString = "abcd";

// Outputs the code point, 97, of the first character, a


trace([Link](0));

The fromCharCode( ) method is an alternative to using Unicode escape sequences to display spe
characters. However, you can also use fromCharCode( ) in concert with charCodeAt( ) to test for
existence of special characters:

myString = [Link](191) + "Donde es el ba" +


[Link](241) + "o?";

// Test whether the first character of the string has the code point of 191. If s
// displays: The string "¿Donde es el baño?" has a ¿ at the beginning.
if ([Link](0) == 191) {
message = "The string \"" + myString + "\" has a " + [Link](191) +
" at the beginning.";
trace(message);
}

The following methods are useful for creating cryptographic word games, but
they are not secure and should not be used for sensitive data.

You can use the charCodeAt( ) and fromCharCode( ) methods in concert to encode and decode a
string:

[Link] = function ( ) {

// The codeMap property is assigned to the String class when the encode( ) meth
// first run. Therefore, if no codeMap is defined, it needs to be created.
if ([Link] == undefined) {

// The codeMap property is an associative array that maps each original code
// point to another code point.
[Link] = new Object( );

// Create an array of all the code points from 0 to 255.


var origMap = new Array( );
for (var i = 0; i <= 255; i++) {
[Link](i);
}

var rand;

// Create a temporary array that is a copy of the origMap array.


var charTemp = [Link]( );

// Loop through all the character code points in origMap.


for (var i = 0; i < [Link]; i++) {

// Create a random number that is between 0 and the last index of charTemp.

[Link] 2004-02-06
Strona 29 z 30

rand = [Link]([Link]( ) * ([Link]-1));

// Assign to codeMap values such that the keys are the original code points
// the values are the code points to which they should be mapped.
[Link][origMap[i]] = charTemp[rand];

// Remove the elements from charTemp that were just assigned to codeMap. Th
// prevents duplicates.
[Link](rand, 1);
}
}

// Split the string into an array of characters.


var chars = [Link]("");

// Replace each character in the array with the corresponding value from codeMap
for (var i = 0; i < [Link]; i++) {
chars[i] = [Link]([Link][chars[i].charCodeAt(0)]);
}

// Return the encoded string.


return [Link]("");
};

[Link] = function ( ) {

// Split the encoded string into an array of characters.


var chars = [Link]("");

// Create an associative array that reverses the keys and values of codeMap. Th
// allows you to do a reverse lookup based on the encoded character rather than
// original character.
var reverseMap = new Object( );
for (var key in [Link]) {
reverseMap[[Link][key]] = key;
}

// Loop through all the characters in the array and replace


// them with the corresponding value from reverseMap, thus
// recovering the original character values.
for (var i = 0; i < [Link]; i++) {
chars[i] = [Link](reverseMap[chars[i].charCodeAt(0)]);
}

// Return the decoded string.


return [Link]("");
};

// Example usage:
myString = "She sells sea shells by the sea shore";

// Create the encoded version of myString using the encode( ) method.


encoded = [Link]( );

// Output the value of the encoded string. This will be randomly generated each t
// you run the movie. It might look something like this:
// êEk¶°k§§°¶°k¬¶°Ek§§°¶v_¶¥Ek¶°k¬¶°E _k
trace(encoded);

// Output the value returned by the decode( ) method.


// Displays: She sells sea shells by the sea shore
trace([Link]( ));

9.14.4 See Also

[Link] 2004-02-06
Strona 30 z 30

Table A-1 lists the Unicode code points for the Latin 1 character set.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 20

[ Team LiB ]

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


[ Team LiB ]
[ Team LiB ]

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 [Link] 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.

[ Team LiB ]
[ Team LiB ]

Recipe 10.1 Finding the Current Date and Time

[Link] 2004-02-06
Strona 2 z 20

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 scr
any other server-side language to return the server time, and create a new Date object from tha
value.

10.1.3 Discussion
The date and time that ActionScript can calculate on its own is based on the client computer's da
and time settings. Therefore, if the user's computer has the incorrect time, so will the Flash mov
With that caveat in mind, you can retrieve the current client-side date and time by creating a ne
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 tim
from a server. This technique can ensure more accurate dates and times. Although the server's t
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 i
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 n
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
create a PHP page to output the current time and date as the number of seconds since the Epoch
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 outp
the number of seconds since the Epoch:

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

[Link] 2004-02-06
Strona 3 z 20

print time;

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

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

z Many servers disable the remote execution of scripts without particular file extensions. The
extension is commonly allowed. Try naming the script [Link].

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

z 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 [Link]( ) method instea

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. Ther
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, th
// should point to the appropriate PHP page, such as [Link], instead.
[Link]("[Link]

// onLoad( ) is invoked automatically when the server returns a response.


[Link] = function ( ) {

// Create a Date object by passing (seconds * 1000) to the Date( ) constructor


// value returned by the server is [Link], in this case, [Link].
// Convert the string value to a number before multiplying it by 1000.
var serverDate = new Date(Number([Link]) * 1000);

// Display the date and time returned by the server (in this case, with time zon
// 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
the UTC methods). So if the user's computer has the incorrect time zone setting, the display mig
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 d
and time servers on the Internet from which you can retrieve reasonably accurate date and time
information. For example, see [Link] For details on synchronizing time via t

[Link] 2004-02-06
Strona 4 z 20

Network Time Protocol, see [Link] and Recipe 10.7.


[ Team LiB ]
[ Team LiB ]

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 [Link] file that you can include in other projects:

// Create days and months arrays as properties of the Date class.


[Link] = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday"];
[Link] = ["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([Link][[Link]( )]);

// Displays: December
trace([Link][[Link]( )]);

10.2.4 See Also


Recipe 10.3
[ Team LiB ]
[ Team LiB ]

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 [Link]( ), or create a custom [Link]( ) method that returns the date and time a

[Link] 2004-02-06
Strona 5 z 20

string in the requested format.

10.3.3 Discussion
The [Link]( ) 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 [Link]( ) method to return a different date format:

[Link] = function ( ) {
return "Milliseconds since the Epoch: " + [Link]( );
};
// 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 fo
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
December or 12 (depends on context: MM means 12 ; MMM
M Month in year
means December)
d Day in month 01
Hour in A.M./P.M. (1-
h 12
12)
H Hour in day (0-23) 21
m Minute in hour 24
s Second in minute 33
S Millisecond in second 960
Sunday or Sun (depends on context: E means Sunday; EEE
E Day in week
means Sun)
a A.M./P.M. AM or PM

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

[Link] 2004-02-06
Strona 6 z 20

[Link] = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",


"Friday", "Saturday"];
[Link] = ["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 tha
// single-digit values are formatted correctly (e.g., 5 becomes 05).
[Link] = 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.
[Link] = function (hour, min) {
var amPm = "AM";
if (hour > 12) {
hour = hour - 12;
amPm = "PM";
} else if (hour == 0) {
hour = 12;
}
return [Link](hour) + ":" + [Link](min) + " " + amPm;
};

[Link] = function (format) {

// Create local variables with the date's values.


var day = [Link]( );
var monthDate = [Link]( );
var month = [Link]( );
var year = [Link]( );
var hour = [Link]( );
var min = [Link]( );
var sec = [Link]( );
var millis = [Link]( );

// 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 form
// within Table 10-1.
switch (format) {
case "MM-dd-yyyy":
return [Link](month + 1) + "-" + [Link](monthDate) + "-" + year;
case "MM/dd/yyyy":
return [Link](month + 1) + "/" + [Link](monthDate) + "/" + year;
case "dd-MM-yyyy":
return [Link](monthDate) + "-" + [Link](month + 1) + "-" + year;
case "dd/MM/yyyy":
return [Link](monthDate) + "/" + [Link](month + 1) + "/" + year;
case "hh:mm a":
return [Link](hour, min);
case "EEE, MMM dd, yyyy":
return [Link][day].substr(0, 3) + ", " + [Link][month] + " " +
[Link](monthDate) + ", " + year;
case "E, MMM dd, yyyy":
return [Link][day] + ", " + [Link][month] + " " +
[Link](monthDate) + ", " + year;
}
};

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

[Link] 2004-02-06
Strona 7 z 20

// 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([Link]("MM-dd-yyyy")); // Displays: 12-01-2002


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

10.3.4 See Also


It is possible to enhance the format( ) method by adding more cases to the switch statement. Th
is left to you as an exercise. You may find the information at
[Link] useful as a reference. Also refer to Recipe 10.

[ Team LiB ]
[ Team LiB ]

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, [Link]( ).

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 [Link]
file for easy inclusion in other projects.

[Link] = function (millis) {

// Determine the minutes and seconds portions of the time.


var seconds = millis / 1000;
var m = [Link](seconds/60);
var s = [Link](seconds - (m * 60));

// Add leading zeros to one-digit numbers.


if (m < 10) {
m = "0" + m;
}

[Link] 2004-02-06
Strona 8 z 20

if (s < 10) {
s = "0" + s;
}
return m + ":" + s;
};

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

#include "[Link]"

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


dur = mySound_sound.duration;

// Create a text field to display the value.


[Link]("displayTime_txt", 1, 0, 0, 100, 20);

// Display the time value formatted as mm:ss.


displayTime_txt.text = [Link](dur);

10.4.4 See Also


Recipe 10.3 and Recipe 10.5

[ Team LiB ]
[ Team LiB ]

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 1
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 Epo
milliseconds value. To retrieve that value, call the getTime( ) method from the Date object:

[Link] 2004-02-06
Strona 9 z 20

// For Pacific Standard Time, displays: 277124100000


// The output may vary depending on your time zone.
trace([Link]( ));

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 nee
to be converted to milliseconds by multiplying by 1,000). Also, when performing date mathemat
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 us
// 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.


[Link] = "This is a special day!";

// Use the setTime( ) method to set the date to 3:55 AM, Friday, October 13, 197
// This doesn't overwrite the label property, as calling the Date constructor wou
[Link](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.

[ Team LiB ]
[ Team LiB ]

Recipe 10.6 Calculating Elapsed Time or Intervals Betwee


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

10.6.2 Solution

[Link] 2004-02-06
Strona 10 z 20

For simple elapsed time, add and subtract from the Epoch milliseconds or the value returned by
getTimer( ). For more complex conversions, create custom [Link]( ) and [Link]
( ) 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
week is 604,800,000 milliseconds, and so on. Unless you have a spectacular gift for rememberin
these conversion values, storing them as constants of the Date class is the easiest option. You ca
add the following constants to your [Link] file for convenience:

// There are 1000 milliseconds in a second, 60 seconds in a minute, 60 minutes in


// hour, 24 hours in a day, and 7 days in a week.
[Link] = 1000;
[Link] = [Link] * 60;
[Link] = [Link] * 60;
[Link] = [Link] * 24;
[Link] = [Link] * 7;

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

#include "[Link]"
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 [Link] (the number of milliseconds in a day).
[Link]([Link]( ) + [Link]);

// Displays: Sat Oct 14 03:55:00 GMT-0700 1978


trace(myDate);

Here are some more examples of simple conversions using [Link]( ), [Link]( ), an
the aforementioned constants:

#include "[Link]"
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)
[Link]([Link]( ) - [Link]);
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)
[Link]([Link]( ) + [Link] + [Link]);
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)
[Link]([Link]( ) - (3 * [Link]) - (55 * [Link]));
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 the

[Link] 2004-02-06
Strona 11 z 20

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.
[Link]("timer_txt", 1, 100, 100, 50, 20);

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

The global getTimer( ) function, not to be confused with the [Link]( ) 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( );


[Link]("timer_txt", 1, 100, 100, 50, 20);

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

Note that the following code is not necessarily an acceptable alternative to the preceding examp
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 whe
a new movie loads:

[Link]("timer_txt", 1, 100, 100, 50, 20);


[Link] = function ( ) {
// Convert from milliseconds to seconds and round to the nearest second.
this.timer_txt.text = [Link](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.
[Link]("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.
[Link] = function ( ) {
// Determine the elapsed time.
var elapsedTime = getTimer( ) - startingTime;
// Convert from milliseconds to seconds and round to the nearest second.
elapsedTime = [Link](elapsedTime / 1000);
if (elapsedTime >= maxTime) {
this.timer_txt.text = "0";

[Link] 2004-02-06
Strona 12 z 20

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. Whe
it comes to adding and subtracting years and months from dates, we cannot rely on constants. T
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 [Link]( ) method that performs the calculations for
you. Such a method should take up to seven numeric parameters, each of which can be positive
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 [Link]( ) method, which you can add to your [Link] file for easy
inclusion in other projects:

[Link] = 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([Link]( ));

// Add the specified intervals to the original date.


[Link]([Link]( ) + years);
[Link]([Link]( ) + months);
[Link]([Link]( ) + days);
[Link]([Link]( ) + hours);
[Link]([Link]( ) + minutes);

[Link] 2004-02-06
Strona 13 z 20

[Link]([Link]( ) + seconds);
[Link]([Link]( ) + milliseconds);

// Return the new date value.


return d;
};

// Example usage:
#include "[Link]"
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([Link](1, 0, 0, -3, -55));

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

// Add 24 years. Displays: Sun Oct 13 03:55:00 GMT-0700 2002


trace([Link](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 operati
yields the number of milliseconds between the two dates, the result isn't convenient to manipula
when the times are not within the same day. Manually converting the number of milliseconds to
number of years, months, and days is difficult due to the varying number of days per month, lea
year, etc. Furthermore, handling negative elapsed times can be cumbersome.

Therefore, it is easiest to create a custom [Link]( ) method that returns a Date objec
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( ) metho
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 th
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 objec
represents a later time. You should add the elapsedTime( ), elapsedYears( ), and elapsedDays( )
methods to your [Link] file for easy inclusion in other projects.

// Calculate the elapsed time (as an absolute value) between a Date object and a
// specified date.
[Link] = 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([Link]( ));
}

[Link] 2004-02-06
Strona 14 z 20

// Use the original Date object's time as one endpoint. Make a copy so as not t
// alter the original.
var t2 = new Date([Link]( ));

// 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([Link]() - [Link]( ));
return t;
};

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

[Link] = function ( ) {
return [Link]( ) - 1970;
};

[Link] = function ( ) {
return [Link]( ) - 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 elapse
years and days.

#include "[Link]"
// Calculate someone's age based on his birthday.
birthday = new Date(1966, 3, 17);
age = [Link]( );
trace("Elapsed Years " + [Link]( ));
trace("Elapsed Months " + [Link]( ));
trace("Elapsed Days " + [Link]( ));
trace("Elapsed Hours " + [Link]( ));
trace("Elapsed Minutes " + [Link]( ));
trace("Elapsed Seconds " + [Link]( ));
trace("Elapsed Milliseconds " + [Link]( ));

// 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 " + [Link]( ));
trace("Elapsed Months " + [Link]( ));
trace("Elapsed Days " + [Link]( ));
trace("Elapsed Hours " + [Link]( ));
trace("Elapsed Minutes " + [Link]( ));
trace("Elapsed Seconds " + [Link]( ));
trace("Elapsed Milliseconds " + [Link]( ));

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.

[Link] 2004-02-06
Strona 15 z 20

10.6.4 See Also


Recipe 10.8

[ Team LiB ]
[ Team LiB ]

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 retur
native Date objects from other applications. Even if you are not working with Flash Remoting, yo
can pass values between Flash and other applications using Epoch seconds/milliseconds. Howeve
you need to parse a string into a date, you should use a custom parseDate( ) method. This meth
takes the string value as a parameter, parses out each of the date's parts (the year, hour, etc.),
then returns a new Date object. Add the following code to your [Link] 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 [Link] file from Recipe 9.6. One way to en
// this is to add #include "[Link]" to the [Link] file in which you also add
// parseDate( ) method.
#include "[Link]"

// This method also relies on the [Link] array from Recipe 10.2.
[Link] = function (dateStr) {

// Create local variables to hold the year, month, date of month, hour, minute,
// 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 = [Link](dateStr);

// If the date string matches the pattern, parse the date from it and return a n
// 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(" ");

[Link] 2004-02-06
Strona 16 z 20

// Set the month to the second element of the array. This is the abbreviated n
// of the month, but we want the number of the month (from 0 to 11), so loop
// through the [Link] array until you find the matching element, and set
// month to that index.
month = dateAr[1];
for (var i = 0; i < [Link]; i++) {
if ([Link][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[[Link] - 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 d
// strings in this format omit the timezone offset).
if ([Link] == 6) {
var timezone = dateAr[4];

// Multiply the offset (in hours) by 60 to get minutes.


var offset = 60 * Number([Link](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 whethe
// 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 = [Link](dateStr);
if (match != null) {
// Get the month, date, and year from the match. First, use the forward slash
// the delimiter. If that returns an array of only one element, use the dash
// delimiter instead.
var mdy = match[0].split("/");
if ([Link] == 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

[Link] 2004-02-06
Strona 17 z 20

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 = [Link](dateStr);
if (match != null) {
// If the length is 4, the time is given as h:mm. If so, then the length of th
// 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 th
// match is greater than five, assume that it includes seconds.
hour = Number([Link]([Link], firstLength));
minute = Number([Link]([Link] + firstLength + 1, 2));
if (match[0].length > 5) {
second = Number([Link]([Link] + 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 D
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 "[Link]"

// Displays: Sat Oct 9 00:00:00 GMT-0700 1982


trace([Link]("10/09/1982"));

// Displays: Fri Oct 13 03:55:00 GMT-0700 1978


trace([Link]("It was 3:55 on 10-13-78"));

// Displays: Tue Dec 3 06:36:02 GMT-0800 2002


trace([Link]("Fri Dec 2 14:36:02 GMT+0800 2002"));

[ Team LiB ]
[ Team LiB ]

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.

[Link] 2004-02-06
Strona 18 z 20

10.8.3 Discussion
This recipe explains how to perform actions at set intervals and create a clock showing the absol
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 ca
abort the action in the future. You can choose from several variations depending on how you wa
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 [Link] fil
#include "[Link]"

// This function is called by setInterval( ), and it displays the current time.


function displayTime( ) {
var d = new Date( );
trace([Link]("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 additio
parameters to the setInterval( ) function, those values are passed along to the function that is ca
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,
setInterval( ) itself is called only once. Therefore, the values of the parameters that are passed t
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
// passed a parameter. Notice that even though the parameter is [Link]( ) (
// method that generates a random value between 0 and 1), the same value is alway
// passed to displayValue( ) because [Link]( ) is evaluated only once.
dvInterval = setInterval(displayValue, 5000, [Link]( ));

You can also use setInterval( ) to call methods of an object, in which case you must pass it at lea
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 interva

// Include the [Link] file from Chapter 4 for its drawTriangle( ) meth
#include "[Link]"

// Create a movie clip object and draw a triangle in it.


_root.createEmptyMovieClip("triangle", 1);
[Link](1, 0x000000, 100);
[Link](300, 500, 60);

// Add a method to the triangle that moves the

[Link] 2004-02-06
Strona 19 z 20

// movie clip 10 pixels in the x and y directions.


[Link] = 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
milliseconds parameter in the setInterval( ) call:

// Include the [Link] file from Chapter 4 for its drawTriangle( ) meth
#include "[Link]"

_root.createEmptyMovieClip("triangle", 1);
[Link](1, 0x000000, 100);
[Link](300, 500, 60);

// Modify the move( ) method to accept parameters.


[Link] = 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 upd
once per second:

// Include the [Link] file from Chapter 4 for its drawCircle( ) metho
#include "[Link]"

// 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 bas
// 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 = [Link]( );

[Link] 2004-02-06
Strona 20 z 20

var min = [Link]( );


var sec = [Link]( );

// Set the rotation of the hands according to the hour, min, and sec values, Th
// _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 c
// (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
// 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.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 56

[ Team LiB ]

Chapter 11. Forms

Introduction

Recipe 11.1. Adding UI Components at Runtime

Recipe 11.2. Positioning Form Elements

Recipe 11.3. Adding Menus to a Form

Recipe 11.4. Making Dependent Menus

Recipe 11.5. Resizing Menus to Fit Their Contents

Recipe 11.6. Detecting the Selected Menu Items

Recipe 11.7. Adding Radio Buttons to a Group

Recipe 11.8. Aligning Radio Buttons Automatically

Recipe 11.9. Getting the Selected Radio Button Value

Recipe 11.10. Adding Checkboxes to a Form

Recipe 11.11. Getting Checkbox Values

Recipe 11.12. Assembling an Advanced Form

Recipe 11.13. Submitting a Form

Recipe 11.14. Validating Form Input

Recipe 11.15. Alerting Users to Validation Errors

Recipe 11.16. Making a Multipage Form

Recipe 11.17. Submitting a Multipage Form

Recipe 11.18. Validating a Multipage Form

Recipe 11.19. Transmitting Data Securely

Recipe 11.20. Prepopulating a Form

Recipe 11.21. Customizing the Tab Order

Recipe 11.22. Using Tables to Arrange Form Elements

Recipe 11.23. Creating Auto-Complete Text Fields

[Link] 2004-02-06
Strona 2 z 56

Recipe 11.24. Customizing a Component's Appearance

Recipe 11.25. Customizing All Components' Appearances


[ Team LiB ]
[ Team LiB ]

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:

z 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.

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

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

z 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 [Link] file and save it to your Flash Include directory so that you can reuse the code
easily in your applications.
[ Team LiB ]
[ Team LiB ]

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

[Link] 2004-02-06
Strona 3 z 56

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
[ Team LiB ]
[ Team LiB ]

Recipe 11.2 Positioning Form Elements


11.2.1 Problem
You want to programmatically position form elements.

11.2.2 Solution
Set the _x and _y properties of each element.

11.2.3 Discussion
Components are derived from movie clips, so they support the basic movie clip properties and
methods, including the _x and _y properties. You can set the _x and _y properties of any
component to position it on stage, just as you would with a movie clip:

myComponentInstance._x = 90;
myComponentInstance._y = 180;

[Link] 2004-02-06
Strona 4 z 56

11.2.4 See Also


For more complex positioning, see Recipe 11.8 and Recipe 11.22

[ Team LiB ]
[ Team LiB ]

Recipe 11.3 Adding Menus to a Form


11.3.1 Problem
You want to add menus to your form.

11.3.2 Solution
Use combo boxes or list boxes.

11.3.3 Discussion
You can choose from two types of form menus in your Flash movies: combo boxes and list boxes
A combo box is analogous to the standard HTML <SELECT> tag, which is often referred to as a
pop-up menu or drop-down list. A combo box allows a user to select one item at a time, and it
displays only the selected item when the menu is not in use. Additionally, if you set a combo box
editable property to true (using setEditable(true)), the user can enter a value by typing it into th
text area associated with the combo box.

You can populate a combo box one item at a time using the addItem( ) and addItemAt( )
methods. However, it is usually easier to use the setDataProvider( ) method to assign all the
menu items at once. You can pass the setDataProvider( ) method a reference to an array or any
object that inherits from the DataProvider class, such as a RecordSet object. The combo box is
automatically populated with the data provider elements in the order they appear within the data
provider object. Here is an example:

// Create a new combo box.


_root.attachMovie("FComboBoxSymbol", "myComboBox_cb", 1);

// Place the combo box at (100,100).


myComboBox_cb._x = 100;
myComboBox_cb._y = 100;

// Create an array to use as the data provider.


menuItems = ["item a", "item b", "item c", "item d"];

// Populate the combo box.


myComboBox_cb.setDataProvider(menuItems);

A list box allows the user to see more than one item at a time. You can populate a list box the
same way you would a combo box, that is, by using setDataProvider( ). List boxes also have two
additional initialization parameters: the number of menu items to display at a time and a Boolea
indicating whether to allow multiple selections. List boxes default to six visible menu items, but
you can change this value with setRowCount( ). List boxes do not allow for multiple selections by
default, but you can change this using setSelectMultiple(true). Users can then select multiple
values by Shift-clicking to select multiple contiguous values or Ctrl-clicking (Windows) or Cmd-
clicking (Macintosh) to select multiple, noncontiguous values. Here is an example:

[Link] 2004-02-06
Strona 5 z 56

// Create a new list box.


_root.attachMovie("FListBoxSymbol", "myListBox_lb", 1);

// Place the list box at (100,100).


myListBox_lb._x = 100;
myListBox_lb._y = 100;

// Create an array to use as the data provider.


menuItems = ["item a", "item b", "item c", "item d"];

// Populate the list box.


myListBox_lb.setDataProvider(menuItems);

// Set the list box to display three menu items at a time.


myListBox_lb.setRowCount(3);

// Allow the user to select multiple items at once.


myListBox_lb.setSelectMultiple(true);

Each menu item has two properties: label and data. The label value is displayed in the menu,
while the data value is available behind the scenes. This allows you to associate data with each
item displayed in the menu. When you populate a menu with an array of strings, as shown earlie
the label properties of the menu items correspond to the array elements, but the data propertie
are left as undefined. It is a best practice to associate both label and data values with each
menu item, which you can accomplish using an array of objects, where each object contains both
label and data properties. For example:

// Create an array of objects. Each element should have both label and data
// properties. The label is displayed in the menu, while data is available for
// programmatic use.
menuItems = [{label: "item a", data: "test a"}, {label: "item b", data: "test b"}]

// Populate the menu using the setDataProvider( ) method, as usual.


myListBox_lb.setDataProvider(menuItems);

11.3.4 See Also


Recipe 11.6

[ Team LiB ]
[ Team LiB ]

Recipe 11.4 Making Dependent Menus


11.4.1 Problem
You want to make the items in one menu (list box or combo box) dependent on the user's select
another form element.

11.4.2 Solution
Create a custom makeDependent( ) method for all combo boxes and list boxes.

11.4.3 Discussion
Making a menu depend on the value of another form element is a common practice. For example

[Link] 2004-02-06
Strona 6 z 56

might want to present a list of minivans instead of sports cars if the customer indicates that safe
and seating capacity are his top priorities when choosing a new car.

You can create a dependent menu by following these steps:

1. Create an array of data providers (i.e., an array of arrays) for the dependent menu. Each d
provider element should correspond to an item in the master menu. In other words, the in
of each data provider element should match up with the index of an item in the master me

2. Define the setChangeHandler( ) method of the master menu so that it automatically calls a
function when the user selects an item from the master menu.

3. In the callback function, use the getSelectedIndex( ) method to determine the selected ind
the master menu. Then set the data provider of the dependent menu to the element from
data providers array of the same index.

This feature set is much easier to implement if you make a custom makeDependent( ) method fo
FSelectableListClass. This class is the superclass for both list boxes and combo boxes, so our cus
method is available to both kinds of menus. The makeDependent( ) method should take two
parameters:

multiDataProvider

This should be a reference to an array of data providers. Each element of the array should
match up with one of the items in the master menu.

master

This is a reference to the master menu.

Here is our custom makeDependent( ) method:

[Link] = function (multiDataProvider, maste


// Set the master menu's change handler to the updateView( ) method (see follow
// for the dependent menu.
[Link]("updateView", this);

// Set the dependent menu's properties to the values passed in as parameters.


[Link] = multiDataProvider;
[Link] = master;
};

// updateView( ) is called whenever the user selects an item from the master menu
[Link] = function ( ) {

// Get the index of the selected menu item from the master menu.
var selectedIndex = [Link]( );

// Get the data provider that corresponds to the selected index.


var dp = [Link][selectedIndex];

// Set the data provider for the dependent menu.


[Link](dp);
};

Here is an example of the makeDependent( ) method in use:

// In this example, create two combo boxes. Space them 150 pixels apart.
_root.attachMovie("FComboBoxSymbol", "myComboBox1", 1);

[Link] 2004-02-06
Strona 7 z 56

_root.attachMovie("FComboBoxSymbol", "myComboBox2", 2, {_x: 150});

// Create the master menu's data provider array and use it to populate the menu.
items1 = ["a", "b", "c"];
[Link](items1);

// Create the multiDataProvider array. It has three elements, each corresponding


// an item in the master menu. Each element is a data provider. In this case, the
// are each simple array data providers.
items2 = new Array( );
[Link](["1 a", "2 a", "3 a"]);
[Link](["1 b", "2 b", "3 b"]);
[Link](["1 c", "2 c", "3 c"]);

// Call the makeDependent( ) method from the dependent menu. Pass it the
// multiDataProvider and a reference to the master menu.
[Link](items2, myComboBox1);

It is important to note that the makeDependent( ) method sets the master menu's change handl
which would conflict with another change handler defined for the master menu. In the majority o
situations this should not be an issue because it is unlikely that you will need to add any other ac
to the master menu's change handler. However, should you decide to do this, make sure to inclu
call to the master menu's updateView( ) method in the new change handler function. For examp

function newOnChange(masterMenu) {

// You must include a call to the menu's updateView( ) method.


[Link]( );

// Additional actions go here.


}

[Link]("newOnChange");

[ Team LiB ]
[ Team LiB ]

Recipe 11.5 Resizing Menus to Fit Their Contents


11.5.1 Problem
You want to adjust the width of a menu to accommodate the widest menu item.

11.5.2 Solution
Create and invoke a custom adjustWidth( ) method.

11.5.3 Discussion
Both combo boxes and list boxes default to a width of 100 pixels, and they do not automatically
to fit their contents when you populate them. Therefore, the text of menu items wider than 100
is cut off. Although the menu components offer no documented means of adjusting the width to
contents, there are several undocumented properties you can use to create a custom method tha
sizes the menus appropriately. Menu components (list boxes and combo boxes) are subclasses o
FSelectableListClass. Therefore, if you add a custom adjustWidth( ) method to FSelectableListCla
is accessible to both list boxes and combo boxes.

[Link] 2004-02-06
Strona 8 z 56

Furthermore, all objects derived from FSelectableListClass have three undocumented properties
are valuable in calculating the necessary width to accommodate all the menu items:

labels

An array of string values displayed in the menu when the menu is populated from the
Parameters panel at authoring time.

dataprovider

A reference to a DataProvider object that is created when the menu is populated at runtim
the setDataProvider( ) method. It has an items property that is an array of objects with la
properties (yielding the needed string values).

textstyle

A reference to the TextFormat object used to format the menu items.

You can use these properties to determine the text width of each menu item (using the
[Link]( ) method).

Here is our custom [Link]( ) method:

// Add the adjustWidth( ) method to FSelectableListClass.


[Link] = function ( ) {

// maxW stores the largest text extent.


var maxW = 0;

// w stores the text extent of each label element.


var w;

// The local variable labels is an array of the string values for each menu item
// Assign it the value of the labels property to begin with.
var labels = [Link];

// If the menu was not populated at authoring time, set labels to an array of th
// values obtained from the data provider.
if (labels == undefined) {
labels = new Array( );

// Append the label property for each data provider to the labels array.
for (var i = 0; i < [Link]( ); i++) {
[Link]([Link](i).label);
}
}

// Loop through all the elements of the labels array.


for (i = 0; i < [Link]; i++) {

// Use [Link]( ) to obtain the width of each label (it retu


// object with width and height properties, and we extract width here).
w = [Link](labels[i]).width;

// Store the width of the widest label in maxW.


maxW = [Link](w, maxW);
}

// Use setSize( ) to set the menu width to maxW + 25. The 25 is padding so tha
// items are not cut off by scrollbars. Pass the current height to setSize( )
// leave the list box's height unchanged (height does not apply to combo boxes)

[Link] 2004-02-06
Strona 9 z 56

[Link](maxW + 25, this._height);


};

You can test the custom adjustWidth( ) method as follows:

// Create a new list box.


_root.attachMovie("FListBoxSymbol", "myListBox_lb", 1);

// Create an array to use as the data provider.


menuItems = ["item a", "item b", "item c", "item d"];

// Populate the list box.


myListBox_lb.setDataProvider(menuItems);

// Call adjustWidth( ) to set the menu width to fit the contents.


myListBox_lb.adjustWidth( );

[ Team LiB ]
[ Team LiB ]

Recipe 11.6 Detecting the Selected Menu Items


11.6.1 Problem
You want to get the user-selected value(s) from a menu.

11.6.2 Solution
Use getSelectedItem( ) (for both combo boxes and list boxes) or getSelectedItems( ) (for list bo
only).

11.6.3 Discussion
You can use the getSelectedItem( ) method to get the selected value from a menu. The method
returns an object with a label and a data property, representing the user-selected menu item.
The getSelectedItem( ) method works for both combo boxes and single-selection list boxes. If yo
use getSelectedItem( ) with a multiple-selection list box, it returns the last selected item in the l

// Displays the label property of the selected item from a combo box.
trace(myComboBox_cb.getSelectedItem( ).label);

// Displays the data property of the selected item from a combo box.
trace(myComboBox_cb.getSelectedItem( ).data);

Generally, the label property is used for display purposes, while the data property is used to
submit the form data to a server for further processing.

If implementing a list box that allows multiple selections at once (see Recipe 11.3), you should
retrieve the selections using getSelectedItems( ), which returns an array of objects with label a
data properties:

// Store the array of selected items in selectedItems.


selectedItems = myListBox_lb.getSelectedItems( );

// Display the label and data properties of each element in the selectedItems arr
for (var i = 0; i < [Link]; i++) {

[Link] 2004-02-06
Strona 10 z 56

trace(selectedItems[i].label);
trace(selectedItems[i].data);
}

Typically, ActionScript developers use either getSelectedItem( ) or getSelectedItems( ) within th


click handler for a form's Submit button:

function onClick ( ) {
var selectedVal = myListBox_lb.getSelectedItem( ).data;

// Do something with data here.


}

[Link]("onClick");

[ Team LiB ]
[ Team LiB ]

Recipe 11.7 Adding Radio Buttons to a Group


11.7.1 Problem
You want to add a group of radio buttons to your form.

11.7.2 Solution
Add radio button instances and assign them to a group using setGroupName( ).

11.7.3 Discussion
Use the radio button UI component to present several mutually exclusive choices. Radio buttons
work best with a limited number of items. For a choice among many items, use a combo box or
box. When you create a radio button, you must assign it a label and, in many cases, some data.
label is the text displayed next to the radio button, and the data is stored for programmatic
processing when the radio button has been selected and submitted. Even in cases where the labe
and data are the same, you should still assign a value for the data. This is because it is good
practice to retrieve the value for a selected radio button from its data value, not from its label. Y
should set these values using the setLabel( ) and setData( ) methods. The label should be a strin
value, but the data can be of any datatype.

This example shows that the data can be a complex object, which can be stored, used for proces
or transmitted to a server when the radio button is selected:

// Create a radio button named myRadioButton0_rb.


_root.attachMovie("FRadioButtonSymbol", "myRadioButton0_rb", 1);

// Set the label so that the radio button displays the text "item 1".
myRadioButton0_rb.setLabel("item 1");

// Assign a data object with id and description properties.


myRadioButton0_rb.setData({id: 25, description: "this is an item"});

Every radio button must belong to a radio button group so that ActionScript can ensure that radi
buttons within the group are mutually exclusive.

[Link] 2004-02-06
Strona 11 z 56

If you want to allow a user to select multiple options from a list, use multiple
checkboxes or a multiple-selection list box instead of radio buttons. If there is
only one choice, use a checkbox.

You should add each radio button to a group using the setGroupName( ) method, which takes a
string parameter specifying the name of the group to which the radio button should be assigned

myRadioButton0_rb.setGroupName("myRadioButtons");

Radio button group names are arbitrary, but it is good practice to give each group a name that
relates to its purpose and name each radio button in a numbered sequence. Here is an example
illustrates this point with a radio button group for travel preferences:

// Create four radio buttons, travelPrefs0_rb through travelPrefs3_rb, and assign


// them to the "travelPrefs" group. Note that the label and data are both set for
// each button, as is good practice, even though both contain the same value.
_root.attachMovie("FRadioButtonSymbol", "travelPrefs0_rb", 1);
_root.attachMovie("FRadioButtonSymbol", "travelPrefs1_rb", 2);
_root.attachMovie("FRadioButtonSymbol", "travelPrefs2_rb", 3);
_root.attachMovie("FRadioButtonSymbol", "travelPrefs3_rb", 4);

travelPrefs0_rb.setGroupName("travelPrefs");
travelPrefs1_rb.setGroupName("travelPrefs");
travelPrefs2_rb.setGroupName("travelPrefs");
travelPrefs3_rb.setGroupName("travelPrefs");

travelPrefs0_rb.setLabel("spring");
travelPrefs0_rb.setData ("spring");
travelPrefs1_rb.setLabel("summer");
travelPrefs1_rb.setData ("summer");
travelPrefs2_rb.setLabel("fall");
travelPrefs2_rb.setData ("fall");
travelPrefs3_rb.setLabel("winter");
travelPrefs3_rb.setData ("winter");

// Position the radio buttons.


travelPrefs0_rb._y = 100;
travelPrefs1_rb._y = 120;
travelPrefs2_rb._y = 140;
travelPrefs3_rb._y = 160;

There are two important potential problems that become apparent when you start programmatic
adding radio buttons. First of all, the labels appear on the right side of the buttons by default.
Fortunately, you can use setLabelPlacement("left") to place the label to the left of the radio butto
Furthermore, to set the same placement for all radio buttons in a group, you can invoke
setLabelPlacement( ) on the radio group object (the group object is automatically created with th
name passed to setGroupName( )). You must create the radio buttons and assign them to the gr
before invoking setLabelPlacement( ) on the group object. For example:

// Align the labels to the left of the radio buttons in the travelPrefs group.
travelPrefs0_rb.setGroupName("travelPrefs");
travelPrefs1_rb.setGroupName("travelPrefs");
travelPrefs2_rb.setGroupName("travelPrefs");
travelPrefs3_rb.setGroupName("travelPrefs");
[Link]("left");

Another problem becomes apparent when you add labels that extend beyond the default width o
the radio button, in which case the text is cut off in the display. You can create a custom method
FRadioButtonGroupClass (the class for radio button groups) to automatically resize the labels of
the radio buttons in the group. To do so, you must be able to reference the text fields containing

[Link] 2004-02-06
Strona 12 z 56

radio buttons' label text. The path to the text field of a radio button is undocumented, but it can
referenced as this.fLabel_mc.labelField from each radio button. You also must be able to
reference each radio button from the group object. Radio button groups have an undocumented
property—an array named radioInstances containing references to the radio button instances—
that works perfectly for this purpose.

Here is a custom adjustWidth( ) method for the FRadioButtonGroupClass class, which you can ad
your [Link] file for easy inclusion in future projects:

[Link] = function ( ) {
var tf;

// Loop through all the radio buttons in the group.


for (var i = 0; i < [Link]; i++) {

// Set each label text field to auto size.


tf = [Link][i].fLabel_mc.labelField;
[Link] = true;

// Set the width of each radio button to the width of the text field plus 13.
// 13 pixels account for the width of the button graphic.
[Link][i].setSize(tf._width + 13);

}
};
[ Team LiB ]
[ Team LiB ]

Recipe 11.8 Aligning Radio Buttons Automatically


11.8.1 Problem
You want to align all the radio buttons in a group into one or more columns.

11.8.2 Solution
Create and invoke a custom setPositions( ) method for FRadioButtonGroupClass.

11.8.3 Discussion
Another problem that very quickly presents itself after you add more than one radio button is tha
placement on the Stage. You can position each radio button (each of which is a movie clip)
individually using the _x and _y properties. On the other hand, you can also add a custom metho
to FRadioButtonGroupClass that automatically aligns all the radio buttons within the group. You c
use the radioInstances property (see Recipe 11.7) to loop through each button in a radio butto
group and space them evenly.

Our custom setPositions( ) method accepts up to four parameters:

The x coordinate within the parent movie clip where the first radio button should be placed
undefined, then 0 is assumed.

The y coordinate within the parent movie clip where the first radio button should be placed

[Link] 2004-02-06
Strona 13 z 56

undefined, then 0 is assumed.

cols

The number of columns into which the radio buttons should be organized. If undefined, th
radio buttons are aligned in one vertical column.

spacing

The vertical spacing between the radio buttons, in pixels. If undefined, defaults to 15. The
horizontal spacing is five pixels more than the widest item in the column

Here, then, is our custom setPositions( ) method, which you should add to your [Link] file for e
inclusion in future projects:

[Link] = function (x, y, cols, spacing) {

// Set the spacing to 15 pixels, if undefined.


if (spacing == undefined) {
spacing = 15;
}

// If the value of cols is either undefined or greater than the number of items
// use one column.
if ( (cols == undefined) || (cols > [Link]) ) {
cols = 1;
}

// The itemsPerColumn array is used to determine how many items are placed into
// each column. Initialize remainingItems to the number of elements in the
// radioInstances array.
var itemsPerColumn = new Array( );
var remainingItems = [Link];

// Determine how many items to place in each column.


for (var i = 0; i < cols; i++) {

// Divide the number of items remaining by the number of columns minus i. Thi
// tells you how many items should go into the column.
itemsPerColumn[i] = [Link](remainingItems / (cols - i));

// Update the number of remaining items for the next iteration.


remainingItems -= itemsPerColumn[i];

// If this is the last column, add all remaining items to the column.
if (i == (cols - 1)) {
itemsPerColumn[i] += remainingItems;
}
}

// The index variable is incremented with each iteration through the nested for
// loop to successfully loop through each element of the radioInstances array.
var index = 0;

// maxW stores the maximum width of any item in a column.


var maxW = 0;

// colStartX is used to track the x coordinate for each column. The initial x
// coordinate is specified by the x parameter.
var colStartX = x;
var item;

[Link] 2004-02-06
Strona 14 z 56

// Loop through each item in each column using nested for statements. i is the
// current column, and j is the current item within the current column.
for (var i = 0; i < [Link]; i++) {
for (var j = 0; j < itemsPerColumn[i]; j++) {

// Set item to the current radio button reference.


item = [Link][index];

// Record the width of the widest button in the column so far, in maxW.
maxW = [Link](item._width, maxW);

// The x coordinate is the same for each radio button in a given column. Th
// coordinate depends on the y and spacing parameters plus the item number
// within the column.
item._x = colStartX;
item._y = (j * spacing) + y;

// Increment index to refer to the next radio button the next time around.
index++;
}

// To set the starting position for the next column, add the maximum width of
// this column plus five pixels to provide some spacing between columns.
colStartX += maxW + 5;

// Reset maxW so that it does not carry over to the next column
maxW = 0;
}
};

You should call the setPositions( ) method only after you have added all the radio buttons to the
group using the setGroupName( ) method for each radio button. Here is an example that uses
setPositions( ) to place four radio buttons in two columns:

#include "[Link]"

// Create four radio buttons, named emailPrefs0_rb through emailPrefs3_rb, and as


// them to the "emailPrefs" group.
_root.attachMovie("FRadioButtonSymbol", "emailPrefs0_rb", 1);
_root.attachMovie("FRadioButtonSymbol", "emailPrefs1_rb", 2);
_root.attachMovie("FRadioButtonSymbol", "emailPrefs2_rb", 3);
_root.attachMovie("FRadioButtonSymbol", "emailPrefs3_rb", 4);

emailPrefs0_rb.setGroupName("emailPrefs");
emailPrefs1_rb.setGroupName("emailPrefs");
emailPrefs2_rb.setGroupName("emailPrefs");
emailPrefs3_rb.setGroupName("emailPrefs");

// Add labels and data.


emailPrefs0_rb.setLabel("once a day");
emailPrefs0_rb.setData ("day");
emailPrefs1_rb.setLabel("once a week");
emailPrefs1_rb.setData ("week");
emailPrefs2_rb.setLabel("once a year");
emailPrefs2_rb.setData ("year");
emailPrefs3_rb.setLabel("no email");
emailPrefs3_rb.setData ("none");

// Position the radio buttons.


[Link](10, 10, 2, 30);
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 15 z 56

Recipe 11.9 Getting the Selected Radio Button Value


11.9.1 Problem
You want to get the value of the selected radio button from a group.

11.9.2 Solution
Use the getValue( ) method.

11.9.3 Discussion
First, create two radio buttons and assign them to the same group (named "answerGroup"):

_root.attachMovie("FRadioButtonSymbol", "answer0_rb", 1);


_root.attachMovie("FRadioButtonSymbol", "answer1_rb", 2);

answer0_rb.setGroupName("answerGroup");
answer1_rb.setGroupName("answerGroup");
answer0_rb.setLabel("Hot");
answer0_rb.setData (212);
answer1_rb.setLabel("Cold");
answer1_rb.setData (32);

[Link]( );

Some time later, you can obtain the data value of the selected radio button by invoking the
getValue( ) method on the radio button group. The radio button group is created automatically
when a radio button is added to a group using setGroupName( ). In this example, the return
value is either 212 or 32, not the string "Hot" or "Cold". That is, getValue( ) returns the
selected button's data property, not its label property.

selectedVal = [Link]( );

As with getting the selected values for other UI components, you typically get the values for
radio button groups once the user clicks on the Submit button:

function onClick ( ) {
var selectedVal = [Link]( );

// Do something with data here.


}

[Link]("onClick");

11.9.4 See Also


Recipe 11.6, Recipe 11.7, and Recipe 11.11
[ Team LiB ]
[ Team LiB ]

Recipe 11.10 Adding Checkboxes to a Form


11.10.1 Problem
You want to add checkboxes to your form.

[Link] 2004-02-06
Strona 16 z 56

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 op
from a list, but you do not wish to use a list box. Or, alternatively, you should use a checkbox in
situation that calls for a simple yes/no or true/false answer. Checkboxes function similarly to rad
buttons, except for a few important distinctions:

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

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

z 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
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 w
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 f


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
// the functionality of FRadioButtonGroupClass is accessible to the new class.
_global.FCheckBoxGroupClass = function ( ) {};
[Link] = new FRadioButtonGroupClass( );

// Define a setGroupName( ) method for checkboxes, which is the


// same as the one for radio buttons.
[Link] = function (groupName) {
for (var i = 0; i < this._parent[[Link]].[Link]; i++) {
if (this._parent[[Link]].radioInstances[i] == this) {
delete this._parent[[Link]].radioInstances[i];
}

[Link] 2004-02-06
Strona 17 z 56

}
[Link] = groupName;
[Link]( );
};

// Define addToRadioGroup( ) for checkboxes. This method is called from


// setGroupName( ). It is the same method as the addToRadioGroup( ) method for
// buttons, except it creates a new instance of FCheckBoxGroupClass instead of
// FRadioButtonGroupClass.
[Link] = function ( ) {
if (this._parent[[Link]] == undefined) {
this._parent[[Link]] = new FCheckBoxGroupClass( );
}
this._parent[[Link]].addRadioInstance(this);
};

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

#include "[Link]"
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
and Recipe 11.8 are:

// You can call the adjustWidth( ) and setPositions( ) methods from checkbox gr
[Link]( );
[Link](100, 100, 2);

Another useful feature of checkbox groups is that you can also call the setLabelPlacement( ) met
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.
[Link]("left");

[ Team LiB ]
[ Team LiB ]

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

[Link] 2004-02-06
Strona 18 z 56

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 o
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 [Link] file for easy
inclusion in future projects:

// Create the new method, getValues( ), for the FCheckBoxGroupClass.


[Link] = 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 < [Link]; i++) {

// cb refers to the current checkbox.


cb = [Link][i];

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

// Add the object to the array.


[Link](obj);
}

// Return the array of values.


return dataAr;
};

11.11.4 See Also


Recipe 11.9 and Recipe 11.10

[ Team LiB ]
[ Team LiB ]

Recipe 11.12 Assembling an Advanced Form

[Link] 2004-02-06
Strona 19 z 56

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.


[Link] = new Array( );
};

// The addElement( ) method adds an element to the formElements array.


[Link] = function (element) {
[Link](element);
};

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

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 b
submitted. Or, alternatively, write a submitToURL( ) method for the custom Form class to handle
process dynamically for any form.

[Link] 2004-02-06
Strona 20 z 56

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 b
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 _
property value). For radio buttons, the name of the property should be the group name. Th
value of each property should be the value of the element as retrieved using the recipes in
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( );
[Link] = [Link]( );
[Link]("[Link]

The preceding process is not too taxing, as long as there are only a few simple elements in the fo
However, as you add more elements to the form, the process becomes more burdensome and er
prone. You can simplify the process by creating a submitToURL( ) method for the Form class (se
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 UR
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 name
their values, and then returns that array.

[Link] = 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 < [Link]; i++) {
elem = [Link][i];

// Process each form element, as appropriate to its type. The instanceof oper
// 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 = [Link];
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 = [Link]( );
obj[elem._name] = value;
}
else if (elem instanceof FCheckBoxGroupClass) {
// Get the values within the checkbox group (requires custom checkbox group

[Link] 2004-02-06
Strona 21 z 56

// class (see Recipe 11.11).


values = [Link]( );

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

// If value is an object (and not a primitive string), this means that the
// 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 regardin
// assigning data objects.)
if (value instanceof Object) {
value = [Link];
}

// 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 = [Link]( );

// Create an element whose key is the list box name. Assign to this element
// 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 < [Link]; j++) {
value = (values[j].data == undefined) ? values[j].label : values[j].data;
if (value instanceof Object) {
value = [Link];
}
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 th
// data to a URL.
[Link] = function (url) {

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

[Link] 2004-02-06
Strona 22 z 56

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

// 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.


[Link](url);

};

11.13.4 See Also


Recipe 11.12

[ Team LiB ]
[ Team LiB ]

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 a
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

[Link] 2004-02-06
Strona 23 z 56

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

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 arra
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,
validator has been assigned for the element, verify that the element value meets the criteria. Th
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 partia
listing of our enhanced Form class, which should be stored in a [Link] file. Everything in this
listing should be added to the existing contents of [Link], with one exception. The constructor
method shown here should replace the existing constructor code.

// Make sure to include [Link] (see Recipe 9.6) in the [Link] file. The
// validation methods require it.
#include "[Link]"

_global.Form = function ( ) {
[Link] = new Array( );

// Our updated constructor creates the validators associative array.


[Link] = new Object( );
};

// The setValidator( ) method adds elements to the validators associative array.


[Link] = function (elementName, validator) {
[Link][elementName] = validator;
};

// The validate( ) method attempts to validate the form.


[Link] = function ( ) {

// Retrieve the form's values using getValues( ).


var values = [Link]( );
var valid, re;

// Loop through all the values.


for (var item in values) {

// Get the validator for the current form element.


valid = [Link][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@[Link].
re = new RegExp("^([\\w\-\\.]+)@(([\\w\\-]+\\.)+[\\w\\-]+)$");
if (![Link](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

[Link] 2004-02-06
Strona 24 z 56

// string. Create a regular expression from the string and test for a match
re = new RegExp(valid);
if (![Link](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 retu
true, then the form is ready to submit; otherwise, you can direct the user to correct the errors o
the form. The following is an example of its use. Note that this example uses a trace( ) statemen
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 wh
the Flash Player is running in a browser, see Recipe 11.15.

#include "[Link]"

// 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 fiel
// must contain some text, and email_txt must have a valid email format.
[Link](name_txt);
[Link]("name_txt", true);
[Link](email_txt);
[Link]("email_txt", "email");

btn_pb.onRelease = function ( ) {

// Call the validate( ) method of the form.


var valid = _root.[Link]( );

// If the form validates, submit the form. Otherwise, display the name of the f
// 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

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 25 z 56

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 erro
The MessageBox component can be found in Flash UI Components Set 2, which is available for fr
from the Macromedia Flash Exchange ([Link] You can 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,
described in Recipe 11.1. The linkage identifier for the symbol is FMessageBoxSymbol.

[Link]("FMessageBoxSymbol", "myMessageBox", 1);

Normally, you don't want a message box to be visible when the movie starts. Instead, you want
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 ca
do this either after attaching the movie clip or during the attachMovie( ) invocation by using an
initialization object, as follows:

[Link]("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 labe
for the buttons. Typically, a message box has only one button—an OK button.

[Link](["OK"]);

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

[Link]("Hello, friend.");

The following is an example of a message box used in conjunction with form validation. To test t
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 [Link] file includes all the necessary functionalit
in doubt, download the completed [Link] file from [Link]

#include "[Link]"

// 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});
[Link](["OK"]);

[Link] 2004-02-06
Strona 26 z 56

// 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.


[Link](email_txt);
[Link]("email_txt", "email");

btn_pb.onRelease = function ( ) {

// Call the validate( ) method of the form.


var valid = _root.[Link]( );

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

}
};

11.15.4 See Also


Recipe 11.14
[ Team LiB ]
[ Team LiB ]

Recipe 11.16 Making a Multipage Form


11.16.1 Problem
You want to create a form that spans multiple "pages" rather than being all on one screen.

11.16.2 Solution
Create a custom MultiPageForm class.

11.16.3 Discussion
You can create multipage forms in Flash without having to create a custom class. However, there
several drawbacks to this approach:

z The most convenient way to do it is to create a form that spans multiple frames. Flash doe
automatically remember the values the user selected for form elements across frames.
Therefore, you have to programmatically store and repopulate the user's selections for the
pages when they click back and forth.

z The process requires a lot of custom coding each time you want to create a new multipage
rather than relying on an existing infrastructure. Basically, it means a lot of duplicated effo
code.

[Link] 2004-02-06
Strona 27 z 56

z It is possible, but not very easy, to leverage the functionality of the Form class.

For these reasons, it is far superior to create a class that handles all the details of working with a
multipage form. By creating a MultiPageForm class, you can use and reuse the functionality easil

The MultiPageForm class has the following characteristics:

z The class has an array property that contains Form objects. A multipage form is composed
therefore, of multiple forms that are stored in an array property.

z The class has methods that set the current visible page of the form by making the other pa
invisible. All forms are created on the same frame.

Here is the next enhancement to the Form class, a setVisible( ) method, which should be added
[Link] file:

// setVisible( ) accepts a Boolean value--true or false--and uses it to set the


// _visible property of each element. Thus, it hides or shows an entire form page
[Link] = function (visible) {
for (var i = 0; i < [Link]; i++) {

// If the element is a radio button group (or a checkbox group, which inherit
// from the same class), set the visibility of each item in the group.
if ([Link][i] instanceof FRadioButtonGroupClass) {
for (var j = 0; j < [Link][i].[Link]; j++) {
[Link][i].radioInstances[j]._visible = visible;
}
} else {
// Otherwise, set the _visible property of the individual element.
[Link][i]._visible = visible;
}
}
};

And here is our MultiPageForm class, which should also be added to our [Link] file:

// Create the MultiPageForm class constructor.


_global.MultiPageForm = function ( ) {
// Initialize currentPage to display the first page of the form.
[Link] = 1;

// Create an array to hold all the form "pages".


[Link] = new Array( );

// If the caller passed in any parameters, assume they are references to existin
// Form objects to add to this multipage form. Therefore, loop through the
// arguments array and invoke the addForm( ) method for each argument.
for (var i = 0; i < [Link]; i++) {
[Link](arguments[i]);
}
};

// Add a Form object to the MultiPageForm object.


[Link] = function (frm) {
[Link](frm);
};

// setPage( ) sets the current page. The first page of the form is 1.
[Link] = function (page) {

// Show the current page and hide all the other pages.

[Link] 2004-02-06
Strona 28 z 56

for (var i = 0; i < [Link]; i++) {


if (page == (i + 1)) {
[Link][i].setVisible(true);
} else {
[Link][i].setVisible(false);
}
}

// Remember the current page.


[Link] = page;
};

// nextPage( ) goes to the next page (or to the first page if this is the last p
[Link] = function ( ) {

// Increment the current page.


[Link]++;

// If we're past the last page, go to the first page instead.


if ([Link] > [Link]) {
[Link] = 1;
}

// Display the new current page and hide the other pages.
[Link]([Link]);
};

// prevPage( ) goes to the previous page (or the last page if this is the first
[Link] = function ( ) {

// Decrement the current page.


[Link]--;

// If we're before the first page, go to the last page instead.


if ([Link] < 1) {
[Link] = [Link];
}

// Display the new current page and hide the other pages.
[Link]([Link]);
};

Here is an example that implements a multipage form:

#include "[Link]"

// Create two input text fields.


_root.createTextField("name_txt", 1, 100, 100, 100, 20);
_root.createTextField("email_txt", 2, 100, 130, 100, 20);
name_txt.border = true;
name_txt.type = "input";
email_txt.border = true;
email_txt.type = "input";

// Create a Form object and add the text fields to it. This is the first page.
myForm0 = new Form( );
[Link](name_txt);
[Link](email_txt);

// Create two radio buttons and add them to a group.


_root.attachMovie("FRadioButtonSymbol", "myRadioButton0_rb", 3);
_root.attachMovie("FRadioButtonSymbol", "myRadioButton1_rb", 4);
myRadioButton0_rb.setGroupName("myRadioButtonGroup");
myRadioButton1_rb.setGroupName("myRadioButtonGroup");

[Link] 2004-02-06
Strona 29 z 56

myRadioButton0_rb.setLabel("item 1");
myRadioButton1_rb.setLabel("item 2");
myRadioButton0_rb.setData("label 1");
myRadioButton1_rb.setData("label 2");
[Link](100, 100);

// Create a Form object and add the radio button group (not the individual radio
// buttons) to it. This is the second page of the form.
myForm1 = new Form( );
[Link](myRadioButtonGroup);

// Create a combo box and a Submit button.


_root.attachMovie("FComboBoxSymbol", "myComboBox_cb", 5, {_x: 100, _y: 100});
_root.attachMovie("FPushButtonSymbol", "submitBtn_pb", 6, {_x: 100, _y: 130});
myComboBox_cb.setDataProvider(["a", "b", "c"]);

// Create a Form object and add the combo box and Submit button to it. This is th
// third page. The Submit button should be visible on the last page only.
myForm2 = new Form( );
[Link](myComboBox_cb);
[Link](submitBtn_pb);

// Create a MultiPageForm object and pass it the three Form pages.


myMultiForm = new MultiPageForm(myForm0, myForm1, myForm2);

// Display the first page of the form.


[Link](1);

// Implement the Submit button as shown in Recipe 11.17.


submitBtn_pb.setLabel("submit");
submitBtn_pb.onRelease = function ( ) {
// Submit form
};

// Create previous and next buttons to navigate the pages of the form.
_root.attachMovie("FPushButtonSymbol", "prevBtn_pb", 7, {_x: 100, _y: 160});
_root.attachMovie("FPushButtonSymbol", "nextBtn_pb", 8, {_x: 210, _y: 160});

prevBtn_pb.setLabel("previous page");
prevBtn_pb.onRelease = function ( ) {
_root.[Link]( );
};
nextBtn_pb.setLabel("next page");
nextBtn_pb.onRelease = function ( ) {
_root.[Link]( );
};

When you create a multipage form in this manner, you must ensure that all objects have unique
depths. All the forms are really on the same frame but are hidden at different times. Therefore, i
create form elements with the same depths, the latter one will overwrite the former.

11.16.4 See Also


Recipe 11.17
[ Team LiB ]
[ Team LiB ]

Recipe 11.17 Submitting a Multipage Form


11.17.1 Problem

[Link] 2004-02-06
Strona 30 z 56

You want to submit a multipage form to a URL.

11.17.2 Solution
Create and invoke a submitToURL( ) method for the custom MultiPageForm class.

11.17.3 Discussion
Once you have created the getValues( ) and submitToURL( ) methods of the Form class (see
Recipe 11.13), you can leverage them to create versions of the same methods for the
MultiPageForm class. Add the following code to your [Link] file for easy inclusion in other
projects:

// The multipage version of submitToURL( ) is the same as the regular Form versi
[Link] = function (url) {
var lv = new LoadVars( );
var vals = [Link]( );
for (var item in vals) {
lv[item] = vals[item];
}
[Link](url);
};

// [Link]( ) uses [Link]( ) to create an object with th


// elements of all the form pages and their values.
[Link] = function ( ) {
var obj = new Array( );
var formVals, elem;

// Call the getValues( ) method of each form page and add those results to the
// multipage values associative array.
for (var i = 0; i < [Link]; i++) {
formVals = [Link][i].getValues( );
for (elem in formVals) {
obj[elem] = formVals[elem];
}
}
return obj;
};

The HTML paradigm for multipage forms requires server-side persistence (or client-side cookies)
involving saving values to session or client variables. This is necessary with HTML because HTTP
a stateless environment. Flash, however, is stateful; ActionScript can remember values between
pages, so there is no need to use session or client variables on the server when using Flash form

[ Team LiB ]
[ Team LiB ]

Recipe 11.18 Validating a Multipage Form


11.18.1 Problem
You want to validate elements within a multipage form.

11.18.2 Solution
Create and invoke a validate( ) method for the custom MultiPageForm class.

[Link] 2004-02-06
Strona 31 z 56

11.18.3 Discussion
You can create a [Link]( ) method that leverages the [Link]( ) method a
works in much the same way. Add the following code to your [Link] file for easy inclusion in ot
projects:

[Link] = function ( ) {
var vRes;

// Loop through all the form pages.


for (var i = 0; i < [Link]; i++) {

// Call the validate( ) method for each page of the form. If validate( ) re
// something other than true, some form element didn't validate. In that case
// display that page of the form and return the name of the offending element
vRes = [Link][i].validate( );
if (!vRes) {
[Link](i + 1);
return vRes;
}
}
return true;
};

You can use the validate( ) method for multipage forms as you did for single-page forms:

submitBtn_pb.onRelease = function ( ) {
// Call the validate( ) method of the multipage form.
var valid = _root.[Link]( );

// If the form validates, submit the form. Otherwise, display the name of the f
// element that needs to be corrected. If an element fails to validate, the
// multipage form automatically displays the page with the problem.
if (valid) {
// Submit form
} else {
trace(valid);
}
};

11.18.4 See Also


Recipe 11.14 and Recipe 11.15

[ Team LiB ]
[ Team LiB ]

Recipe 11.19 Transmitting Data Securely


11.19.1 Problem
You want to transmit data, such as credit card numbers, securely.

11.19.2 Solution
Use HTTPS to send the data over a secure channel or, if that is not possible, use a third-party

[Link] 2004-02-06
Strona 32 z 56

encryption library for one-way data encryption.

11.19.3 Discussion
Flash can use the same protocols for data transmission—HTTP or HTTPS—as HTML pages. A
great many servers are set up to accept communication over the insecure HTTP protocol only.
Transmitting unencrypted data over HTTP is not secure and should be used only when the data
is not sensitive. For example, if you are sending survey responses to the server, you might not
care if that information can be seen by others. But insecure transmission compromises credit
card numbers and other sensitive financial or personal information. Although a complete
discussion of security is beyond the scope of this book, there are many good resources on
security.

If you are not using an off-the-shelf solution, such as SSL, there are
many non-obvious pitfalls that can leave your data vulnerable. Leave the
protocol design to the experts or consult a book that addresses secure
cryptographic protocol design: Secure Programming Cookbook for C and
C++ by Viega and Messier (O'Reilly) or Practical Cryptography (not
Applied Cryptography) by Schneier and Ferguson (Wiley).

Be aware that various encryption algorithms deemed secure today may be considered insecure
in the near future due to advances in cryptanalysis and computer processor performance
increases.

So what should you, as a Flash developer, do? First, you should recognize that Flash data
transmission isn't secure if you are sending it over HTTP. Not only can your unsecured data be
intercepted and decoded, but more nefariously, unauthenticated data can be forged. You should
make sure that your e-commerce page doesn't accept bogus transactions, for example. There
are two basic strategies you can use to secure Flash data transmissions. You can rely on the
secure HTTPS protocol for transmission, or you can manually encrypt data (one-way
encryption) with a third-party encryption library. If available, HTTPS is the simplest, fastest,
and often the most secure to implement. So let's tackle that approach first.

When data is sent over HTTP, it is normally not encrypted in any way. This means that people
with the known-how and resources can tap into that transmitted data and read it. However,
when you use HTTPS, although the data sent between client and server might be intercepted, it
likely cannot be decrypted by the eavesdropper. To use HTTPS from Flash, specify the https
protocol as part of the URL when calling LoadVars or XML methods, or similar methods that
accept a URL parameter. For example:

[Link]("[Link]

However, for this to work, two things have to be true:

z Your server has to actually support HTTPS. Otherwise, the URL will be unavailable. For
your sever to support HTTPS it will need an SSL (Secure Socket Layer) certificate. If you
use a shared host, contact the webmaster to see whether she already has something
available for you. If you administer your own server, you will need to get a certificate
from a certificate authority such as Verisign ([Link] You'll need to get
a certificate to match the level of encryption (128-bit, 256-bit, etc.) that you want.
Generally, the more bits, the stronger the encryption.

z The Flash movie needs to be playing in a web browser that supports encryption that
matches the level of SSL encryption in use. For example, if using an encryption algorithm
that requires 128-bit encryption (strong encryption), then you'll need to require that all
users have a 128-bit-encryption-capable browser.

[Link] 2004-02-06
Strona 33 z 56

How does HTTPS/SSL work within Flash? When the Flash movie wants to send data to the
server, it uses the browser's built-in HTTPS/SSL capabilities to request a public key from the
server. A public key is exposed publicly by the server so the client-side data can be encrypted.
Once the server returns the public key (and the key is validated), the data from Flash is
encrypted in the browser and sent to the server. The data is then decrypted on the server
(using a corresponding private key) before it is sent to any server-side scripts.

So how secure are HTTPS transactions? The answer depends on the level of security you
demand and the capabilities of the browser. For example, modern browsers support at least
128-bit encryption, which is more secure than older 56-bit encryption. Although 56-bit
encryption might appear better than nothing, a false sense of security is worse than no
security; 56-bit encryption is considered vulnerable by modern standards and should not be
used when true security is necessary. Many companies require 128-bit security because it is
considerably less likely to be decrypted by a third party. Financial institutions, for example,
almost always require 128-bit encryption. Although HTTPS/SSL is an industry standard for
transmitting data securely, no encryption is perfect. That said, 256-bit encryption, when
implemented on a properly secured system, is considered unbreakable by today's standards.

There is at least one additional issue of which you should be aware when working with
HTTPS/SSL. Although data transmitted via HTTPS is encrypted during transmission, once the
data has been received and decrypted by the server, it is again vulnerable to espionage. You
must take precautions to secure the server and data sources. Implementing security measures
to protect your server from crackers is arguably even more important than securing the data
transmission. After all, you don't want to go to all the trouble to secure the data transmission
but offer the cracker the convenience of being able to grab your entire database of credit card
and social security numbers!

If you cannot use HTTPS/SSL (for example, if you don't have an SSL certificate or if your Flash
movie plays as a Projector or is embedded in a Director application), you'll need another
solution. Let's examine the third-party encryption options that are available.

There are three types of encryption we'll touch on: public key, symmetric, and one-way hash
encryption. Each of these types of encryption is best suited to different types of scenarios.
We've already seen an example of public key encryption when we looked at SSL. SSL
certificates grant a server a public and private key. When a client wants to encrypt data to send
to the server, it must first request the public key. It uses the public key to encrypt the data and
then sends it to the server. The corresponding private key, which can be used to decrypt the
data, is available only on the server with the SSL certificate. (Interestingly, data encrypted with
the private key can be decrypted with the public key. What purpose does it serve to encrypt
data that can be decrypted with a widely available public key? Although it doesn't offer privacy,
encrypting data with a private key offers so-called authentication. If the public key can
successfully decrypt the data, you are reasonably sure that it was encrypted by the entity to
whom the corresponding private key belongs. Furthermore, you are assured that the data
wasn't modified in transit.)

Symmetric encryption uses one key for both encryption and decryption. This means that the
key must be known to both the encrypting and decrypting client and/or server (but not known
to other parties). For this reason, symmetric encryption is best suited for scenarios in which the
data is sent to the server in encrypted format for storage in encrypted format. The same client
can later retrieve the data and decrypt it client-side with the same key.

One-way hash algorithms provide only one-way encryption—the data cannot be decrypted. This
may seem like a strange idea. Why would you want to encrypt data if you can never decrypt it?
The answer is that, in many cases, you would not. For example, you would not want to one-way
encrypt text data such as an address or a name because chances are you'll want that data to be
human-readable at some point. But one-way encryption is a great for things such as passwords.
The data can be encrypted and stored on the server in encrypted format. When the user tries to
log in, the password he enters is encrypted using the same algorithm, and the two encrypted
passwords are compared. Since both values were encrypted using the same algorithm, the

[Link] 2004-02-06
Strona 34 z 56

encrypted values match if the original, unencrypted values matched.

There are several third-party ActionScript libraries available for encrypting data. Most
implement one-way encryption algorithms. So we'll look at those first.

Gerrit E.G. Hobbelt has ported a JavaScript MD5 encryption algorithm to ActionScript, and you
can download the code from [Link] Once you've
downloaded the .zip file, extract the [Link] file using WinZip or another utility program. Save
the [Link] file to your Flash Include directory. Once you extract the .as file, you can include it
in any Flash movie and use the calcMD5( ) function to encrypt a string before sending it to the
server.

When you use Hobbelt's MD5 library, you need to include the [Link] file in the scope where
you want to invoke the calcMD5( ) function or provide a target path when invoking the function,
as shown in the following example.

#include "[Link]"

send_btn.onRelease = function ( ) {

// Encode the value that the user enters into a text field.
var encodedPasswordStr = _root.calcMD5(_root.password_txt.text);

var lv = new LoadVars( );


[Link] = encodedPasswordStr;
[Link]("[Link]");
}

Branden Hall also has a port of the MD5 algorithm as well as the SHA1 algorithm. These
libraries are available for download at [Link] and
[Link]

What if you need two-way encryption? The ActionCrypt project is an open source library that
wraps well-known encryption library functions. So if you need additional encryption algorithms,
consider ActionCrypt. At the time of this writing, ActionCrypt provides two-way encryption with
the 56-bit blowfish algorithm. ActionCrypt also promises additional encryption algorithms,
including other two-way as well as one-way and public key encryption. You can download
ActionCrypt from [Link]

11.19.4 See Also


For two good books on security, see the warning note earlier in this recipe. For more
information on encryption, see
[Link] Also see
[Link] for information related to Internet security in general. See
[Link] for more information
regarding security in Flash. See [Link] for information
on Hobbelt's MD5 port. See [Link] and
[Link] for information on Branden Hall's MD5 and SHA1
ports. See [Link] for more information regarding the ActionCrypt
project. Also refer to Recipe 8.6.

[ Team LiB ]
[ Team LiB ]

Recipe 11.20 Prepopulating a Form

[Link] 2004-02-06
Strona 35 z 56

11.20.1 Problem
You want to programmatically fill in a form with defaults or a user's previous selections.

11.20.2 Solution
Use the methods and properties that allow you to set the values of each form element.

11.20.3 Discussion
Each type of form element has different ways of selecting the element or modifying its value. Fo
example, you can set a text field's contents using the text property:

myTextField_txt.text = value;

You can set combo boxes and single-select list boxes using the setSelectedIndex( ) method. This
method requires you to know the index of the item you wish to select programmatically (and not
its label or data value).

// Selects the third item from the menu


myComboBox_cb.setSelectedIndex(2);

You can programmatically select multiple items from a multiple-selection list box using the
setSelectedIndices( ) method, which accepts an array of indexes to select:

// Selects the first three menu items from a multiple-selection list box
myListBox_lb.setSelectedIndices([0, 1, 2]);

To set a combo box or list box by its label or data property (rather than by its index), you can
a custom setSelectedItem( ) method for FSelectableListClass (the superclass of both combo boxe
and list boxes). The method should accept a single parameter, which is the data value for the de
menu item. If the data properties of the menu items are all undefined, the setSelectedItem( ) m
should try to match the value to a label instead. Additionally, you can create a setSelectedItems
method using the same basic process but you would set multiple items in a multiple-selection lis
The setSelectedItems( ) method should take an array of values (data values or labels to select).

Here is the custom setSelectedItem( ) method for list boxes and combo boxes. Make sure to add
code to your [Link] file, and remember that you can always download the completed version fr
[Link]

[Link] = function (val) {


var item, itemVal;

// Loop through all the items in the menu. The getLength( ) method returns the
// number of items in the menu.
for (var i = 0; i < [Link]( ); i++) {

// Get each item using the getItemAt( ) method.


item = [Link](i);

// If the data property is not undefined, use it for testing matches. Otherwi
// use the label property.
if ([Link] != undefined) {
itemVal = [Link];
} else {
itemVal = [Link];
}

[Link] 2004-02-06
Strona 36 z 56

// If the data or label property matches the input val, call setSelectedIndex
// passing it the current for loop index (i). Use a return statement to exit
// one item is found and selected.
if (val == itemVal) {
[Link](i);
return;
}
}
};

Here is the custom setSelectedItems( ) method for multiple-selection list boxes:

[Link] = function (vals) {


var item, itemVal;

// Create an array to holds the indexes of the items to select.


var selectedIndices = new Array( );

// Loop through all the menu items. This uses similar logic as the
// setSelectedItem( ) method.
for (var i = 0; i < [Link]( ); i++) {
item = [Link](i);
if ([Link] != undefined) {
itemVal = [Link];
} else {
itemVal = [Link];
}

// The vals parameter is an array of values, so we loop through each element


// see if it matches with data or label. If so, it's a match, so add the curr
// for statement index (i) to the selectedIndices array.
for (var j = 0; j < [Link]; j++) {
if (vals[j] == itemVal) {
[Link](i);
}
}
}

// Call the setSelectedIndices( ) method for this list box and pass it the
// selectedIndices array that was populated in the preceding for statement.
[Link](selectedIndices);
};

Here is an example of how to use these methods:

#include "[Link]"

// Create a combo box and populate it using a simple array. This means that each
// has a label property but not a data property.
_root.attachMovie("FComboBoxSymbol", "myComboBox0_cb", 1);
myComboBox0_cb.setDataProvider(["a", "b", "c"]);

// Call setSelectedItem( ) with the label value "b" to select that item.
myComboBox0_cb.setSelectedItem("b");

// Create a combo box and populate it using a more complex data provider such tha
// each menu item has both a label and a data property.
_root.attachMovie("FComboBoxSymbol", "myComboBox1_cb", 2, {_y: 40});
dp = new Array( );
[Link]({label: "a", data: 24});
[Link]({label: "b", data: 33});
[Link]({label: "c", data: 42});
myComboBox1_cb.setDataProvider(dp);

[Link] 2004-02-06
Strona 37 z 56

// Call the setSelectedItem( ) method with a data value. This selects the menu i
// that corresponds (label "c").
myComboBox1_cb.setSelectedItem(42);

// Create a multiple-selection list box and populate it using the same data provi
// as the preceding combo box.
_root.attachMovie("FListBoxSymbol", "myListBox_lb", 3, {_y: 80});
myListBox_lb.setSelectMultiple(true);
myListBox_lb.setDataProvider(dp);

// Call setSelectedItems( ) with an array of data values. This selects the menu
// with labels "a" and "c".
myListBox_lb.setSelectedItems([24, 42]);

[ Team LiB ]
[ Team LiB ]

Recipe 11.21 Customizing the Tab Order


11.21.1 Problem
You want to specify the order in which form elements are chosen when the Tab key is pressed.

11.21.2 Solution
Use the tabIndex properties of the form elements to specify their sequence.

11.21.3 Discussion
Flash provides an automatic sequence through which items on the Stage are accessed when the
key is pressed. This automatic sequence includes all movie clips, buttons, and text fields, and is
necessarily in an order that makes sense for your application. This poses two challenges. First of
only certain elements in the movie (such as form elements) should be selectable via the Tab key
Second, you want form elements to be accessed in a specific order. It can be frustrating for your
application's users if pressing the Tab key after the first form element takes them to the last
element instead of the next one.

Fortunately, you can specify a custom tab order for Flash to use and can include only those
elements that you want to be selectable via the Tab key.

The Tab key cannot be detected reliably in Test Movie mode unless you select
the Control Disable Keyboard Shortcuts option. Select Control Test
Movie, wait for the .swf file to appear, and then disable keyboard shortcuts
from the Control menu.

To specify a custom tab order, assign an order (integer) value to each of the element's tabIndex
properties. When a text field is selected and the user presses the Tab key, Flash looks at the tex
field's tabIndex value; it then looks for the element with the next highest value and switches to
There is one caveat, however: although the user can switch out of text fields using the Tab key,
other form elements can be switched to, but not out of, using the Tab key.

#include "[Link]"

/* Create the form elements. They appear on the Stage in this order:

[Link] 2004-02-06
Strona 38 z 56

myTextField0_txt
myComboBox0_cb
myTextField1_txt
myComboBox1_cb
myTextField2_txt
myCheckBox_ch
myListBox_lb
myRadioButton0_rb
myRadioButton1_rb
myRadioButton2_rb
*/
_root.createTextField("myTextField0_txt", 1, 0, 0, 100, 20);
myTextField0_txt.border = true;
myTextField0_txt.type = "input";
_root.attachMovie("FComboBoxSymbol", "myComboBox0_cb", 2, {_y: 30});
_root.createTextField("myTextField1_txt", 3, 0, 60, 100, 20);
myTextField1_txt.border = true;
myTextField1_txt.type = "input";
_root.attachMovie("FComboBoxSymbol", "myComboBox1_cb", 4, {_y: 90});
_root.createTextField("myTextField2_txt", 5, 0, 120, 100, 20);
myTextField2_txt.border = true;
myTextField2_txt.type = "input";
_root.attachMovie("FCheckBoxSymbol", "myCheckBox_ch", 6);
myCheckBox_ch.setGroupName("myCheckBoxGroup");
[Link](0, 150);
_root.attachMovie("FListBoxSymbol", "myListBox_lb", 7, {_y: 180});
_root.attachMovie("FRadioButtonSymbol", "myRadioButton0_rb", 8);
_root.attachMovie("FRadioButtonSymbol", "myRadioButton1_rb", 9);
_root.attachMovie("FRadioButtonSymbol", "myRadioButton2_rb", 10);
myRadioButton0_rb.setGroupName("myRadioButtonGroup");
myRadioButton1_rb.setGroupName("myRadioButtonGroup");
myRadioButton2_rb.setGroupName("myRadioButtonGroup");
[Link](0, (myListBox_lb._y + myListBox_lb._height + 10))

// Specify the desired tab order by assigning each element's tabIndex property.
myTextField0_txt.tabIndex = 1;
myComboBox0_cb.tabIndex = 2;
myTextField1_txt.tabIndex = 3;
myComboBox1_cb.tabIndex = 4;
myTextField2_txt.tabIndex = 5;
myCheckBox_ch.tabIndex = 6;

// There is no real reason to include these last two because they can never be tab
// to or out of (it would require a text field to precede them to switch to them)
// However, as you can see by this example, it does no harm to also add them in th
// sequence.
myListBox_lb.tabIndex = 7;
myRadioButton0_rb.tabIndex = 8;

[ Team LiB ]
[ Team LiB ]

Recipe 11.22 Using Tables to Arrange Form Elements


11.22.1 Problem
You want to use ActionScript to arrange the elements of a form on the screen.

11.22.2 Solution

[Link] 2004-02-06
Strona 39 z 56

Create and use a custom Table class.

11.22.3 Discussion
You can use ActionScript to control the position of any graphical object such as a movie clip, a bu
or a text field, using the _x and _y properties. Additionally, each graphical object has properties
return its height and width (_height and _width). You can use these properties in concert to po
instances on the Stage relative to one another.

For example, we can position a movie clip horizontally such that its x coordinate is five pixels to
right of a text field. The text field's _x property plus its _width property yields the x coordinate o
right edge of the text field. In this example, we add five pixels to allow for spacing between the
clip and the text field. We also align the top of the movie clip with the top of the text field.

myMovieClip_mc._x = myTextField_txt._x + myTextField_txt._width + 5;


myMovieClip_mc._y = myTextField_txt._y;

In this example, we position the movie clip such that its y coordinate is five pixels below the bott
the text field. This example is similar to the preceding one but uses the _y and _height properti
calculate the vertical position. We also align the left edges of the movie clip and the text field.

myMovieClip_mc._y = myTextField_txt._y + myTextField_txt._height + 5;


myMovieClip_mc._x = myTextField_txt._x;

The code we've looked at up to this point assumes that the movie clip we are positioning has its
registration point in its upper-left corner. However, as you are likely aware, the registration poin
clip defaults to its center and can be placed at an arbitrary location. Fortunately, it is relatively s
to correct for this offset by subtracting the movie clip's minimum x and y values as obtained from
getBounds( ) method. Here, we revisit the two previous code examples, shown with offsets that
accommodate movie clips with registration points of any kind.

This code positions a movie clip to the right of the text field:

bnds = myMovieClip_mc.getBounds( );
myMovieClip_mc._x = myTextField_txt._x + myTextField_txt._width + 5 - [Link];
myMovieClip_mc._y = myTextField_txt._y - [Link];

This code positions the movie clip below the text field:

bnds = myMovieClip_mc.getBounds( );
myMovieClip_mc._y = myTextField_txt._y + myTextField_txt._height + 5 - [Link];
myMovieClip_mc._x = myTextField_txt._x - [Link];

The preceding code examples show the logic that you can use to position instances relative to on
another within a Flash movie. However, when you start working with more objects, your code ca
to get long and confusing. To arrange 20 elements on a form requires at least 40 lines of code. A
effective way to approach this problem is to create a Table class that you can use to position any
of graphical object on the Stage.

The following code creates a custom Table class that you can use to effectively arrange graphica
objects. The Table class relies on two other custom classes—TableColumn and TableRow—that a
defined in the code that follows. Every table is composed of one or more rows, and every row is
composed of one or more columns. The columns are, in turn, composed of elements. The elemen
be any kind of graphical object (movie clip, button, or text field). A cell (the intersection of a row
table) can contain a single table element. Alternatively, nested tables within tables allow a single
to contain multiple elements, allowing for more advanced layouts.

Here is our code to define the Table, TableColumn, and TableRow classes. It should be placed in

[Link] 2004-02-06
Strona 40 z 56

[Link] file in your Include directory for future use. The Table class is designed so that the spac
works as it does in HTML tables—a single value is used for both the vertical and horizontal spacin
applied around each element, column, and row. It is left as an exercise for you to redefine the cl
constructors to accept and implement separate horizontal and vertical spacing parameters.

// Define the TableColumn constructor. spacing defines the amount of space (in
// pixels) between elements in the column.
_global.TableColumn = function (spacing) {
// Store the spacing parameter in an instance property of the same name. spacin
// defaults to 5 if not otherwise specified.
[Link] = (spacing == undefined) ? 5 : spacing;

// The _width and _height properties store the total width and height of the
// column. Initialize them to 0.
this._width = 0;
this._height = 0;

// The elements array holds all the elements of the column.


[Link] = new Array( );

// If any parameters are passed to the constructor, from the second position
// onward, add these values (assumed to be references to graphical objects
// or to a Table object) to the column using the addElement( ) method.
for (var i = 1; i < [Link]; i++) {
[Link](arguments[i]);
}
};

// The addElement( ) method adds elements to a column.


[Link] = function (element) {

if (element instanceof Table) {


// If the element is a Table object, set the containsTable
// property to true. Reinitialize elements to ensure that the table
// is the only element in the column.
[Link] = true;
[Link] = new Array( );
[Link](element);

// Reset the width and height of the column.


this._width = 0;
this._height = 0;
}
else {
// Otherwise, the element is not a table. Reinitialize all the properties if
// column previously held a table.
if ([Link]) {
[Link] = false;
[Link] = new Array( );
this._width = 0;
this._height = 0;
}

// Add the element to the elements array.


[Link](element);
}

// Make sure the column's width reflects the width of the widest element.
this._width = [Link](this._width, element._width);

// Increment the column's height by the height of the element plus the spacing.
this._height += element._height + [Link];
};

[Link] 2004-02-06
Strona 41 z 56

// [Link]( ) positions the elements within the column relative to on


// another. The startx and starty parameters give the x and y coordinates for the
// first element in the column. The [Link]( ) method is called by th
// render( ) method of the row in which the column is contained.
[Link] = function (startx, starty) {

// The startx and starty parameters default to 0 if not specified.


if (startx == undefined) {
startx = 0;
}
if (starty == undefined) {
starty = 0;
}

// If the column contains a table, call the render( ) method of that table.
// Otherwise, set the x and y coordinates for each element in the column.
if ([Link]) {
[Link][0].render(true, startx, starty);
}
else {
var bnds;
for (var i = 0; i < [Link]; i++) {

// Get the bounds of the elements in case the


// registration point is not in the upper-left corner.
bnds = [Link][i].getBounds( );

// The y coordinate of the element is given by starty plus the height of th


// previous element in the column, plus the spacing between them. To
// accommodate any offsets due to registration points, subtract the element
// minimum y coordinate.
[Link][i]._y = [Link][i-1]._height + [Link] +
starty - [Link];

// The x coordinate of the element is given by startx plus spacing. To


// accommodate any offsets due to registration points, subtract the element
// minimum x coordinate.
[Link][i]._x = startx + [Link] - [Link];

// Increment starty each time by the height of the previous element plus th
// spacing between elements.
starty += [Link][i - 1]._height + [Link];
}
}
};

// removeElementAt( ) removes an element from a column at the given index. Note


// the index is zero-relative (the first column is column 0).
[Link] = function (index) {
[Link](index, 1);
};

// The [Link]( ) method recalculates the width and height of a column


// is called automatically by [Link]( ) (which is, in turn, called by
// [Link]( )).
[Link] = function ( ) {

// Reset the width and height to 0.


this._width = 0;
this._height = 0;

// If the column contains a table, call the resize( ) method of the table to en
// that the correct size of that table has been calculated.
if ([Link]) {

[Link] 2004-02-06
Strona 42 z 56

[Link][0].resize( );
}

// Set the column width to the widest element and calculate the column height.
for (var i = 0; i < [Link]; i++) {
this._width = [Link](this._width, [Link][i]._width);
this._height += [Link][i]._height + [Link];
}
};

// Define the TableRow constructor. The spacing parameter defines the number of
// pixels between columns in the row.
_global.TableRow = function (spacing) {
// Store the spacing parameter in an instance property of the same name. spacin
// defaults to 5 if not otherwise specified.
[Link] = (spacing == undefined) ? 5 : spacing;

// The columns array contains all the columns in the row.


[Link] = new Array( );

// Initialize the width and height of the row.


this._width = 0;
this._height = 0;

// If any parameters are passed to the constructor, from the second position
// onward, add these values (references to columns) to the row using the
// addColumn( ) method.
for (var i = 1; i < [Link]; i++) {
[Link](arguments[i]);
}
};

// The addColumn( ) method adds columns to the row.


[Link] = function (column) {

// Add the column to the columns array.


[Link](column);

// Increase the row's width by the width of the column plus the spacing. Also,
// the column has a greater height than any of the existing columns, set the row
// height to the height of the column.
this._width += column._width + [Link];
this._height = [Link](this._height, column._height);
};

// [Link]( ) positions the columns within a row relative to one another


// relative to x and y coordinates given by startx and starty. [Link](
// called automatically by the render( ) method of the table that contains the r
[Link] = function (startx, starty) {

// Call each column's render( ) method. Position each column to the right of th
// preceding one.
for (var i = 0; i < [Link]; i++) {
[Link][i].render(startx, starty);
startx += [Link][i]._width + [Link];
}
};

// removeColumnAt( ) removes a column from a row at the given index. Note that th
// index is zero-relative (the first row is row 0).
[Link] = function (index) {
[Link](index, 1);
};

[Link] 2004-02-06
Strona 43 z 56

// [Link]( ) recalculates the height and width of a row. It is called


// automatically by [Link]( ).
[Link] = function ( ) {

// Reset the width and height to 0.


this._width = 0;
this._height = 0;

for (var i = 0; i < [Link]; i++) {


// Recalculate the size of each column and use those values to calculate the
// height and width for the row.
[Link][i].resize( );
this._width += [Link][i]._width;
this._height = [Link] (this._height, [Link][i]._height);
}
};

// Define the Table constructor. spacing determines the number of pixels between
// rows. startx and starty define the position of the table's upper-left corner.
_global.Table = function (spacing, startx, starty) {

// Store the spacing parameter in an instance property of the same name. spacin
// defaults to 5 if not otherwise specified.
[Link] = (spacing == undefined) ? 5 : spacing;

// Store the startx and starty parameters in instance properties of the same nam
// Use Number( ) to convert undefined values to 0, if necessary.
[Link] = Number(startx);
[Link] = Number(starty);

// The rows array contains the rows in the table.


[Link] = new Array( );

// Initialize the height and width of the table.


this._height = 0;
this._width = 0;

// If any parameters are passed to the constructor, from the fourth position
// onward, add these values (assumed to be references to rows)
// to the table using addRows( ).
for (var i = 3; i < [Link]; i++) {
[Link](arguments[i]);
}

// Render the table to start.


[Link](false, [Link], [Link]);
};

// addRow( ) adds a new row to the table and recalculates its height and width.
[Link] = function (row) {
[Link](row);
this._height += row._height + [Link];
this._width = [Link](this._width, row._width);
};

// [Link]( ) positions the rows within the table. The doResize parameter
// determines whether it should call [Link]( ). The startx and starty param
// determine the position of the upper-left corner of the table.
[Link] = function (doResize, startx, starty) {

// If doResize is true, call the table's resize( ) method. This is useful to up


// the table size after something in the table changes.
if (doResize) {
[Link]( );

[Link] 2004-02-06
Strona 44 z 56

// Reposition the table at (startx,starty). Position defaults to previous posit


// if a new position is not specified.
if (startx != undefined) {
[Link] = startx;
}
if (starty != undefined) {
[Link] = starty;
}
var x = [Link];
var y = [Link];

// Render the rows of the table (which in turn renders the columns).
for (var i = 0; i < [Link]; i++) {
[Link][i].render(x, y);
y += [Link][i]._height + [Link];
}
};

// removeRowAt( ) removes a row from the table at a given index. Note that the in
// is zero-relative (the first row is row 0).
[Link] = function (index) {
[Link](index, 1);
};

// The resize( ) method calculates the height and width for a table.
[Link] = function ( ) {
this._width = 0;
this._height = 0;
for (var i = 0; i < [Link]; i++) {
[Link][i].resize( );
this._width = [Link](this._width, [Link][i]._width);
this._height += [Link][i]._height + [Link];
}
};

Now that you have seen the code for the Table class (as well as the TableColumn and TableRow
classes), let's look a little more closely at how it all works.

All three classes have the same basic functionality, so understanding one class helps to understa
them all. Let's look at the TableRow class first.

Each constructor handles four basic setup tasks. First of all, the constructor records the spacing
between the subunits. In the case of the TableRow class, the subunits are columns within the row
constructor also creates an array property to store the subunits. Next, the constructor initializes
_width and _height properties. Using the names _width and _height for all subunits allows us
determine their width and height without having to know if the subunit is a table, row, column, m
clip, button, or text field. Finally, the constructor adds any subunits to the object that were passe
the constructor as parameters. This is accomplished using the arguments array so that there is n
predefined limit to the number of subunits that can be passed to a constructor:

_global.TableRow = function (spacing) {


[Link] = spacing;
if (spacing == undefined) {
[Link] = 5;
}
[Link] = new Array( );
this._width = 0;
this._height = 0;
for (var i = 1; i < [Link]; i++) {
[Link](arguments[i]);

[Link] 2004-02-06
Strona 45 z 56

}
};

Next, each class defines a method to add subunits. In the case of the TableRow class this metho
named addColumn( ). The method adds the subunit to the appropriate array and recalculates the
height and width of the object. Using the [Link]( ) method is a shorthand way of saying "stor
largest value for this dimension." For example, if an element's height is greater than that of any
preceding element in the row, this._height is updated to store the new maximum height of the

[Link] = function (column) {


[Link](column);
this._width += column._width + [Link];
this._height = [Link](this._height, column._height);
};

The render( ) method of both the Table and the TableRow classes works by calling the render( )
method of each of the subunits of the object. The actual positioning of the movie clips, buttons,
text fields occurs within the [Link]( ) method, so let's examine that method a little
closely. If the column contains a nested table, we call the render( ) method to render it. Otherwi
column contains graphical objects, and each of those objects needs to be positioned. This is
accomplished by looping through the elements array and setting the _x and _y properties of eac
element appropriately. The elements of a column should all appear one below the other, spaced
number of pixels specified by spacing. Therefore, the _x properties are the same for all element
column, and the _y properties are determined by the _y value of the previous element plus the h
of the previous element, plus the spacing between elements.

[Link] = function (startx, starty) {


if (startx == undefined) {
startx = 0;
}
if (starty == undefined) {
starty = 0;
}
if ([Link]) {
[Link][0].render(true, startx, starty);
} else {
for (var i = 0; i < [Link]; i++) {
[Link][i]._y = [Link][i - 1]._height + [Link] + starty;
[Link][i]._x = startx + [Link];
starty += [Link][i - 1]._height + [Link];
}
}
};

The resize( ) method for each of the three classes is used when new subunits are added or exist
subunits are removed or resized. The resize( ) method recalculates the height and width of an ob
based on the updated subunits. This is accomplished by resetting the _width and _height prope
to 0, then looping through all the subunits and incrementing the object's _width and _height
properties appropriately:

[Link] = function ( ) {
this._width = 0;
this._height = 0;
for (var i = 0; i < [Link]; i++) {
[Link][i].resize( );
this._width += [Link][i]._width;
this._height = [Link](this._height, [Link][i]._height);
}
};

[Link] 2004-02-06
Strona 46 z 56

Next, let's look at some code that can help you get a sense of how to use table objects in your m
This code creates 10 text fields to start. Additionally, a push button instance is created using
attachMovie( ) (so you must make sure that the PushButton component symbol is included in yo
Library). The text fields and the button are added to the table in six rows. The first two rows hav
three columns of text fields, the third and fifth rows have a single column, and the fourth row ha
columns. The push button instance is added to the sixth (final) row. An example of this is shown
Figure 11-1.

Figure 11-1. A sample table layout showing how each row can have
different number of columns

Finally, the button is assigned actions such that when it is pressed and released, a new text field
created and assigned to a random column, and the table is rerendered to accommodate the new
layout. This code is for demonstration purposes only and illustrates that the table can accommod
changes dynamically.

// Include [Link] from Chapter 8.


#include "[Link]"
// Include [Link] from Chapter 7.
#include "[Link]"
// Include the recipes needed from this chapter.
#include "[Link]"
#include "[Link]"
// Create 10 text fields with names text0_txt through text9_txt.
for (var i = 0; i < 10; i++) {
_root.createInputTextField("text" + i + "_txt", _root.getNewDepth( ));
_root["text" + i + "_txt"].text = "input text " + i;
}

// Create a push button instance.


_root.attachMovie("FPushButtonSymbol", "addBtn", _root.getNewDepth( ));
[Link]("random new text");

// Create six table columns. Each column is assigned one or more text fields
// or the push button instance. The spacing is set to five pixels.
tc0 = new TableColumn(5, text0_txt, text1_txt);
tc1 = new TableColumn(5, text2_txt, text3_txt, text4_txt);
tc2 = new TableColumn(5, text5_txt, text6_txt);
tc3 = new TableColumn(5, text7_txt);
tc4 = new TableColumn(5, text8_txt, text9_txt);
tc5 = new TableColumn(5, addBtn);

// Create three table rows and add the table columns to them.
tr0 = new TableRow(5, tc0, tc1, tc2);
tr1 = new TableRow(5, tc3, tc4);
tr2 = new TableRow(5, tc5);

// Add a table with the table rows added to it. The table is positioned at (0,0).
myTable = new Table(5, 0, 0, tr0, tr1, tr2);

// Add the actions to the button such that, when it is pressed and released, a new
// text field is created and added to the table.
[Link] = function ( ) {

[Link] 2004-02-06
Strona 47 z 56

// Randomly determine the column to which the text field should be added. We
// introduce randomness here only for the purpose of demonstrating the table's
// ability to dynamically accommodate changes.
var rn = [Link]([Link]( ) * 4);

// Create the new text field.


_root.createInputTextField("text" + _root.i, _root.getNewDepth( ));
_root["text" + _root.i].text = "input text " + _root.i;

// Select the random table column and add the text field to it.
_root["tc" + rn].addElement(_root["text" + _root.i]);

// Increment the i variable so that the next text field created will have a uni
// name and depth.
_root.i++;

// Rerender the table. Specify true for the doResize parameter so that the tabl
// automatically recalculates the size before rendering.
_root.[Link](true);
};

Next, let's look at how a table is used to lay out a form. First, here is the code that creates a sim
form and positions the elements using a table. (The result is shown in Figure 11-2.) Also, if you t
this code in your own movie, make sure that you add all the necessary component symbols to th
movie's Library.

#include "[Link]"
#include "[Link]"

// Create a list box, a combo box, a push button, and two text field labels.
[Link]("FListBoxSymbol", "interests_lb", 1);
[Link]("FComboBoxSymbol", "favUICmpnt_cb", 2);
[Link]("FPushButtonSymbol", "submitBtn", 3);
[Link]("interestsLabel_txt", 4, 0, 0, 120, 20);
[Link]("favUICmpntLabel_txt", 5, 0, 0, 120, 20);

// Populate the list box and adjust its size to accommodate the values.
interests_lb.addItem("Flash", "flash");
interests_lb.addItem("ActionScript", "as");
interests_lb.addItem("More ActionScript", "as+");
interests_lb.setSelectMultiple(true);
interests_lb.adjustWidth( );
interests_lb.setRowCount(interests_lb.getLength( ));

// Populate the combo box and adjust its size accordingly.


favUICmpnt_cb.addItem("Checkbox", "ch");
favUICmpnt_cb.addItem("Combo box", "cb");
favUICmpnt_cb.addItem("List box", "lb");
favUICmpnt_cb.addItem("Radio button", "rb");
favUICmpnt_cb.addItem("Push button", "pb");
favUICmpnt_cb.adjustWidth( );

// Add a label to the push button.


[Link]("submit");

// Add text to the text fields.


interestsLabel_txt.text = "Interests:";
favUICmpntLabel_txt.text = "Favorite Component: ";

// Create three table rows. The first contains two columns: a label and the list b
// The second row contains two columns: a label and the combo box.
// The third row contains the submit button.

[Link] 2004-02-06
Strona 48 z 56

tr0 = new TableRow(5, new TableColumn(0, interestsLabel_txt),


new TableColumn(0, interests_lb));
tr1 = new TableRow(5, new TableColumn(0, favUICmpntLabel_txt),
new TableColumn(0, favUICmpnt_cb));
tr2 = new TableRow(5, new TableColumn(0, submitBtn));

// Create the table. Position the table with the upper-left corner at (120,90) an
// add the three rows.
formTable = new Table(5, 120, 90, tr0, tr1, tr2);

Figure 11-2. A basic form layout using a table

Next, let's enhance our example further. The following code block can be appended to the previo
code block. In this example we add two radio buttons, another label, and a text area with a mes
to the user. We then add a new table that nests the previous table to create a complex layout. (T
result is shown in Figure 11-3.) Since this code includes radio buttons, make sure that you add t
radio button symbol to your movie's Library if you test this code.

// Add two radio buttons.


[Link]("FRadioButtonSymbol", "asRating0_rb", 6);
[Link]("FRadioButtonSymbol", "asRating1_rb", 7);

// Add two text fields. One is a label, the other is a text area.
[Link]("words_txt", 8, 0, 0, 120, 150);
[Link]("asRatingLabel_txt", 9, 0, 0, 120, 20);

// Add labels to the radio buttons and add them to a radio button group.
asRating0_rb.setLabel("ActionScript is fun.");
asRating1_rb.setLabel("ActionScript is REALLY fun.");
asRating0_rb.setGroupName("asRating");
asRating1_rb.setGroupName("asRating");

// Use the adjustWidth( ) method to make sure the radio button labels are visibl
[Link]( );

// Set the value for the text label.


asRatingLabel_txt.text = "Rate of ActionScript";

// Set the text area text field to word wrap and display multiple lines.
// Then set the text value.
words_txt.wordWrap = true;
words_txt.multiline = true;
words_txt.text = "We hope you have enjoyed this form. It was designed using" +
"ActionScript and the custom Table class";

// Create a table row that includes two columns: one with the nested table, and th
// other with the new elements.
mainTr0 = new TableRow(5, new TableColumn(0, formTable), new TableColumn(5,
asRatingLabel_txt, asRating0_rb, asRating1_rb, words_txt));

// Create the new table. Position the table at (60,120). Notice that the position
// the nested table is overwritten

mainTable = new Table(5, 60, 120, mainTr0);

Figure 11-3. A form containing a complex table structure

[Link] 2004-02-06
Strona 49 z 56

[ Team LiB ]
[ Team LiB ]

Recipe 11.23 Creating Auto-Complete Text Fields


11.23.1 Problem
You want to create auto-complete input text fields in which entering a few letters automatically f
a previously entered value.

11.23.2 Solution
Use a shared object to store the previously entered values for a text field. Then use the onChang
onSetFocus( ), and onKillFocus( ) methods of the text field to create, position, size, and populate
box with possible values.

11.23.3 Discussion
Internet Explorer and similar browsers offer a feature that remembers values that users have
previously entered for a text field in an HTML form. When a user brings focus to the text field, sh
use the arrow keys or the mouse to select from a list of previously entered values. This feature c
help the user save time typing if she wants to fill out the form with values similar to those entere
previously. Flash text fields do not have this kind of functionality built into them, but with a little
work, you can add this feature to your forms.

You can use a local shared object to store previously entered values for text fields within a doma
Then you can use a list box to display those values to the user when she brings focus to a text fi
the user types into the text field, the values in the list box should be updated to partially match t
string she has entered into the text field. For example, if the user types a "t" in the text field, the
box should display only those values that begin with "t." And if the user selects a value from the
box (either by mouse or by keyboard), that value should automatically be placed within the text

Follow these steps to create auto-completing text fields in your movie:

1. Create a new Flash document or open an existing one.

2. On the main timeline add a new layer named AutoCompleteCode.

3. Add a list box symbol to the movie's Library by dragging the ListBox component from the
Components panel onto the Stage. Then delete the instance from the Stage (we will add th
box to the movie using ActionScript). The symbol will remain in the movie's Library. The Li
symbol is automatically linked for export.

4. Open the Actions panel and add the following code to the first frame of the AutoCompleteC
layer.

5. Make sure you have assigned names to the input text fields in your movie and then test.

[Link] 2004-02-06
Strona 50 z 56

Here is the auto-complete code. Add it to an [Link] file. (And remember that you
find the completed [Link] file at [Link]

// Create the local shared object at the top level of the domain so that all text
// fields in all Flash movies in the domain can access it.
[Link] = [Link]("textfieldAutoComplete", "/");

// When the user types into the text field or when she brings focus to it (either
// programmatically, by tab index, or by clicking with the mouse) call the custom
// makeAutoCompleteOptions( ) method.
[Link] = function ( ) {
[Link]( );
};

[Link] = function ( ) {
[Link]( );
};

[Link] = function ( ) {

// Create a copy of the array stored in the shared object for the text field wi
// the current text field's name.
var history = [Link][this._name].concat( );

// If the text is not empty, find any partial matches in the history array.
if ([Link] != "") {
for (var i = 0; i < [Link]; i++) {
// Removes any elements that don't match from the history array.
if (history[i].indexOf([Link]) != 0) {
[Link](i, 1);
i--;
}
}
}

// If the history array is undefined or has no elements, remove any existing li


// box and exit this method.
if ([Link] == 0 || [Link] == undefined) {
this._parent.[Link]( );
return;
}

// Create a list box and position it just underneath the text field.
this._parent.attachMovie("FListBoxSymbol", "autoCompleteHistory", 100000);
this._parent.autoCompleteHistory._x = this._x;
this._parent.autoCompleteHistory._y = this._y + this._height;

// Resize the list box to fit the width of the text field.
this._parent.[Link](this._width, 50);

// If history has fewer than three elements, shorten the list box.
if ([Link] < 3) {
this._parent.[Link]([Link]);
}

// Fill the list box with the elements from history and set the handler to call
// when any changes occur to the list box (via changes to the text field's
// contents).
this._parent.[Link](history);
this._parent.[Link]("setValue", this);
};

// The setValue( ) method is the change handler function for the list box.
[Link] = function (lb) {

[Link] 2004-02-06
Strona 51 z 56

// usingArrows indicates whether the method was called due to the user pressing
// arrow keys. If she used the arrow keys, exit the function because we don't w
// to close the list box until she has actually selected a value.
if ([Link]) {
[Link] = false;
return;
}

// Set the text field value to the selected value from the list box and then rem
// the list box.
[Link] = [Link]( ).label;
[Link]( );
};

[Link] = function ( ) {

// Remove the list box when the text field loses focus.
_root.[Link]( );

// If the text field contains no text, exit the method.


if ([Link] == "") {
return;
}

// Get the array stored in the shared object for the text field. Exit this meth
// if the array already contains the text field's text value.
var history = [Link][this._name];
for (var i = 0; i < [Link]; i++) {
if ([Link] == history[i]) {
return;
}
}

// If the shared object doesn't already have an array


// for this text field, create one.
if ([Link][this._name] == undefined) {
[Link][this._name] = new Array( );
}

// Add the text field's text value to the shared object's array.
[Link][this._name].push([Link]);
};

// Create a key listener to respond whenever keys are pressed.


keyListener = new Object( );

[Link] = function ( ) {

// Get the current focus and create a reference to the list box.
var focus = eval([Link]( ));
var ach = focus._parent.autoCompleteHistory;

// Get the key code for the key that has been pressed.
var keyCode = [Link]( );

if (keyCode == 40) {
// If the key is the down arrow, set usingArrows to true and set the selected
// index in the list box to the next value in the list.
[Link] = true;
var index = ([Link]( ) == undefined) ?
0 : [Link]( ) + 1;
index = (index == [Link]( )) ? 0 : index;
[Link](index);

[Link] 2004-02-06
Strona 52 z 56

// Scroll if the selected index is not visible in the list box.


if (index > [Link]( ) + 2) {
[Link](index);
} else if (index < [Link]( )) {
[Link](index);
}
} else if (keyCode == 38) {
// If the key is the up arrow, do a similar thing as when the down arrow is
// pressed, but move the selected index up instead of down.
[Link] = true;
var index = ([Link]( ) == undefined) ?
0 : [Link]( ) - 1;
index = (index == -1) ? [Link]( ) - 1 : index;
[Link](index);
if (index > [Link]( ) + 2) {
[Link](index);
} else if (index < [Link]( )) {
[Link](index);
}
} else if (keyCode == [Link]) {
// If the user has pressed the Enter key, call setValue( ) to set the text f
// value and close the list box.
[Link](ach);
} else {
// Otherwise, if the user presses any other key, exit the function.
return;
}

// Set the focus to the text field.


[Link](focus);
};

// Add the key listener to the Key object.


[Link](keyListener);

To use the auto-complete feature, you only need to include [Link] in a Flash docu
that contains input text fields. Then test the movie. The text fields will automatically remember a
contents you entered into them.

#include "[Link]"

[Link]("myTextField0", 1, 100, 100, 100, 20);


[Link]("myTextField1", 1, 100, 140, 100, 20);

[ Team LiB ]
[ Team LiB ]

Recipe 11.24 Customizing a Component's Appearance


11.24.1 Problem
You want to customize the appearance of an instance of a UI component.

11.24.2 Solution
Use the setStyleProperty( ) method to set the colors for each of the style properties or use a
FStyleFormat object to apply changes to more than one component at a time.

[Link] 2004-02-06
Strona 53 z 56

11.24.3 Discussion
You can change the appearance of all Flash UI components using the globalStyleFormat object,
as detailed in Recipe 11.25. You can also modify the artwork in the Skins folders in the Library
to change all instances of a particular type of component. However, you can also modify the
appearance of each instance individually using the setStyleProperty( ) method. The advantage
of this technique is, of course, that you can modify each instance without affecting all other
instances.

Each Flash UI component instance has the following style properties:

arrow

Color of the arrow in the scrollbar and combo boxes

background

Color of the background when the component is active

backgroundDisabled

Color of the background when the component is disabled (dimmed)

check

Color of the checkbox's check

darkshadow

Color of the darkened inner shadow for a border

embedFonts

Whether or not to embed fonts for text

face

The main color of a component

focusRectInner

Color of inner focus rectangle

focusRectOuter

Color of outer focus rectangle

foregroundDisabled

Color of a component's foreground when in a disabled state

highlight

Color of the inner portion of the highlight

[Link] 2004-02-06
Strona 54 z 56

highlight3D

Color of the outer portion of the highlight

radioDot

Color of a radio button's dot

scrollTrack

Color of a scroll track on a scrollbar

selection

Color of the highlight for a selection, such as in a combo box or list box

selectionDisabled

Color of the highlight for a selection in a component in a disabled state

selectionUnfocused

Color of the highlight for a selection in a component when the instance does not have
keyboard focus

shadow

Color of the shadow

textAlign

How to align the text ("right", "left", or "center")

textBold

Either true (bolded) or false (regular)

textColor

Color of text

textDisabled

Color of text in a disabled component instance

textFont

Name of the font to use

textIndent

Number of pixels by which to indent the first line of text

textItalic

[Link] 2004-02-06
Strona 55 z 56

Either true (italicized) or false (regular)

textLeftMargin

Left margin in pixels

textRightMargin

Right margin in pixels

textSelected

Color of text in a selected item (such as in a combo box or list box)

textSize

The size of the text font in points

textUnderline

Either true (underlined) or false (regular)

You can set the style properties of an instance by using the setStyleProperty( ) method. The
method takes two parameters: the name of the style property as a string and the value you
want to assign to the property. For example:

[Link]("arrow", 0x0000FF);

Notice that not all components support all styles. If you set a style that is not applicable for a
component instance, Flash will simply ignore that style. For example, a checkbox component
uses the "check" style but ignores the "radioDot" style. Conversely, a radio button uses the
"radioDot" style but ignores the "check" style.

You can also use an FStyleFormat object to affect the same types of changes. FStyleFormat is
most useful when you want to apply the same color scheme to more than one (but not all)
components in your movie. Here are the steps:

1. Create a new FStyleFormat object:

fstyle = new FStyleFormat( );

2. Use the addListener( ) method to define the component or components that the object
should affect:

[Link](myComboBox, myListBox, myCheckBox);

3. Set the values for the properties of the FStyleFormat object:

[Link] = 0x0000FF;
[Link] = 0xFF0000;

4. Apply the changes with applyChanges( ):

[Link]( );

[Link] 2004-02-06
Strona 56 z 56

[ Team LiB ]
[ Team LiB ]

Recipe 11.25 Customizing All Components'


Appearances
11.25.1 Problem
You want to apply global style changes to all Flash UI Component instances.

11.25.2 Solution
Use the globalStyleFormat object. Assign values to the style properties you want to modify and
then call the applyChanges( ) method.

11.25.3 Discussion
You can learn how to modify the style property settings for individual component instances in
Recipe 11.24. This can be a useful technique when you want to adjust the appearance of each
instance separately. However, to apply the same style changes to all instances of all UI
components, you should use the globalStyleFormat object.

The globalStyleFormat object is available in your Flash movie whenever you have added at least
one UI component to your movie. The globalStyleFormat object has properties corresponding to
the same style properties listed in Recipe 11.24, but the values for the globalStyleFormat object
apply to all instances of UI components instead of just one.

To apply changes to the global styles, you should set the values of the corresponding properties
on the globalStyleFormat object itself. You can access the properties using dot notation:

// Apply a new value to the global face property.


[Link] = 0x97DE50;

Once you have changed a value, you need to tell Flash to update the view by calling the
applyChanges( ) method from the globalStyleFormat object, as follows:

[Link]( );

You can change more than one property before applying changes. For example:

[Link] = 0x97DE50;
[Link] = 0xF5F538;
globalStyleFormat.highlight3D = 0xF33AEB;
[Link]( );

11.25.4 See Also


Recipe 11.24

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 29

[ Team LiB ]

Chapter 12. Objects and Custom


Components

Introduction

Recipe 12.1. Using Methods and Properties of Built-in Objects

Recipe 12.2. Creating an Instance of a Class

Recipe 12.3. Adding Properties to an Object Instance

Recipe 12.4. Adding Custom Methods to an Object Instance

Recipe 12.5. Creating a Custom Class

Recipe 12.6. Creating Smart Getter/Setter Properties

Recipe 12.7. Defining Read-Only Properties

Recipe 12.8. Creating Subclasses

Recipe 12.9. Implementing Subclass Versions of Superclass Methods

Recipe 12.10. Listening for Events

Recipe 12.11. Adding Listeners to Custom Classes

Recipe 12.12. Creating a Component That Subclasses MovieClip

Recipe 12.13. Program: Color Selector Component


[ Team LiB ]
[ Team LiB ]

Introduction
ActionScript is an object-based language. The entire language is composed primarily of
blueprints, called classes, for different types of objects. Based on these blueprints you, as the
developer, can construct many instances of many types of objects, and each instance
automatically inherits all the characteristics and functionality of its class. For example, all arrays
are members of the Array class, which defines the length property as well as the methods
(concat( ), sort( ), etc.) that all arrays inherit.

Therefore, whenever you are using ActionScript, you are inherently using objects and object-
oriented programming. Understanding the inner workings of ActionScript classes and objects
puts you well on your way to harnessing the true power of the language. This knowledge enables
you to add properties and methods to individual instances (objects) and to the classes they
inherit from.

Every class in ActionScript has a prototype property. The prototype property contains an

[Link] 2004-02-06
Strona 2 z 29

object from which all instances of the class inherit properties and methods.

If you add a property or method to a class's prototype, all instances of


that class automatically inherit that custom property or method.

Here is an example:

// Add a property to the Array class.


[Link] = "this is a test property";

// Create a new array. This array inherits, along with all of the other propertie
// and methods of the Array class, the custom testProperty property.
myArray = new Array( );

// Displays: "this is a test property"


trace([Link]);

Here is another example in which we add a method to a class's prototype:

// Add the testMethod( ) method to the Array class.


[Link] = function ( ) {
trace("This is a test method.");
};

// Create a new array. All arrays now inherit the testMethod( ) method.
myArray = new Array( );

// Displays: "This is a test method."


[Link]( );

Prototype-based inheritance works as follows. When a property is accessed or a method is


invoked from an object, the ActionScript interpreter checks if that property or method is
explicitly defined for the object. If not, ActionScript looks for it on the object's class's prototype
property. Classes can even inherit from other classes. This is known as subclassing or extending
a superclass. Therefore, if ActionScript cannot find a method or property in the class's prototype
property, it looks next in the superclass's prototype property. This same process is followed
until ActionScript finds the method or property, or until it reaches the final superclass. The
Object class is the superclass of all other classes, so any properties or methods added to the
Object class are automatically inherited by every object of any type:

// Add a property to the Object class.


[Link] = "this is a property for all objects";
// Create a new Date object.
myDate = new Date( );
// Displays: "this is a property for all objects"
trace([Link]);

However, a subclass can implement a property or method of the same name, effectively
obscuring the property or method found in the superclass. For example, although the Object
class implements a toString( ) method, the Date class implements its own custom toString( )
method that is more appropriate for its needs:

// Displays: [object Object]


trace (new Object().toString( ));
// Displays: Mon May 26 14:17:26 GMT-0400 2003
trace (new Date().toString( ));

[Link] 2004-02-06
Strona 3 z 29

See Also
Recipe 12.8, Recipe 12.9, and Recipe 12.12

[ Team LiB ]
[ Team LiB ]

Recipe 12.1 Using Methods and Properties of Built-in


Objects
12.1.1 Problem
You want to use the properties and methods of a built-in object, such as the Math object, to
perform a given operation.

12.1.2 Solution
Access the properties and methods of the built-in object directly, such as [Link]( ) or
[Link].

12.1.3 Discussion
ActionScript includes several objects with methods and properties that you can access directly
from the class (there is no need to construct an instance of the class). Methods and properties
accessed in this manner are often referred to as static methods and properties. Static methods
and properties are often defined for classes that do not get instantiated. For example, the built-
in Math, Key, Selection, and Stage objects define multiple static methods and properties.
However, there are also classes, from which objects are otherwise instantiated, that include one
or more static methods or properties. For example, the String class is typically instantiated
(meaning you normally create string objects) but it also includes the static method
[Link]( ). Likewise, the Object class is typically instantiated but it includes the
static method [Link]( ). In these cases, the static methods or properties are
associated with the class because they don't pertain to a specific instance of the class.

In any case, you can access static methods and properties by invoking them directly from the
class using dot syntax. Here are two examples:

trace([Link]);
trace([Link](108));

12.1.4 See Also


Recipe 12.2. Also refer to the "Language Reference" section of ActionScript for Flash MX: The
Definitive Guide (O'Reilly), which documents ActionScript's Accessibility, Capabilities,
Arguments, Key, Math, Mouse, Selection, SharedObject, Stage, and System objects in detail.

[ Team LiB ]
[ Team LiB ]

Recipe 12.2 Creating an Instance of a Class

[Link] 2004-02-06
Strona 4 z 29

12.2.1 Problem
You want to access the properties and methods of a class to achieve a particular goal, but those
properties and methods are specific to a particular object (i.e., an instance of the class).

12.2.2 Solution
Create a instance of the class using the new operator and the constructor function for the class
of interest. Then access the properties and methods of the instance, as returned by the
constructor function.

12.2.3 Discussion
Many ActionScript classes must be instantiated before you can use them. This means that you
must create an object that is based on the blueprint defined by the class. For example, the
Array class defines the blueprint for all arrays. All arrays have the same kinds of properties and
methods; however, each array instance is an individual object that merely inherits the common
properties and methods from the class. It is important that each instance be individualized so
that when you request the value for [Link], you retrieve the length of arrayA and not
the length of some other array, such as arrayB.

To create an instance of a class, you typically use the new operator with the appropriate
constructor function for the class. The constructor function takes the same name as the class
itself and is followed by the function operator (that is, parentheses). For example, the following
creates a new generic instance of the Object class:

myObject = new Object( );

Note that the new statement returns an instance of the class, which you ordinarily store in a
variable, in this case myObject.

Most classes allow you to construct objects in an analogous manner. For example, here is one
way to create a new Array object:

myArray = new Array( );

As with all functions, constructor functions can accept parameters. In some cases, no
parameters are accepted. In other cases, parameters are optional, and in still other cases, they
are required. As you can see in the preceding example, you do not need to specify any
parameters when constructing an array. However, the Array( ) constructor function accepts
optional parameters of several varieties, as discussed in the Introduction to Chapter 6. This
example constructs an array with two elements:

myArray = new Array("first element", "second element");

Once you have created an object (an instance), you can access the properties and methods of
that object via dot syntax. For example, arrays have a length property that returns the number
of elements:

trace([Link]); // Displays: 2

Returning to the subject of specifying parameters when constructing an object, when you
construct a Color object, you must provide the target movie clip as a parameter. For example:

myColor = new Color(myMovieClip);

[Link] 2004-02-06
Strona 5 z 29

See Recipe 3.1 for more details.

There are alternative ways to create certain types of objects without using the new operator.
For example, using object literal notation, which employs curly braces, you can create a generic
object. This example creates an object of the Object class:

myObj = {};

You can specify zero or more properties to add to the object within the curly braces. This
example creates an object and adds two properties (x and y) to it:

myPoint = {x: 100, y: 50};


trace(myPoint.x); // Displays: 100

Likewise, a new array can be created using array literal notation, which employs square
brackets. This example creates an array with three elements:

myArray = ["a", "b", "c"];

There are other indirect ways to create objects. Most notably, some methods of existing objects
return entirely new objects. For example, the [Link]( ) method returns a new array
(remember that arrays are one type of object). In this example, the call to [Link]( )
returns a second array containing the elements deleted from myArray, which we arbitrarily
store in the variable deletedElems:

myArray = ["a", "b", "c", "d"];


// Remove two elements from myArray starting at index 0. The removed elements are
// stored in a new array, deletedElems.
deletedElems = [Link](0, 2);

We've seen alternatives to using the new operator to construct an object. In fact, there are
some classes that cannot be instantiated using a new operator, which necessitates an
appropriate alternative. One alternative is to create an object in the authoring tool. For
example, placing a movie clip on the Stage effectively creates an instance of the MovieClip
class. Likewise, placing a text field on the Stage effectively creates an instance of the TextField
class.

But what if you want to create a new text field object at authoring time? You might be tempted
to use the following syntax, but it won't work:

myTextField = new TextField( ); // This will not work!

Instead, you must invoke the createTextField( ) method on a movie clip object to create a new
text field, as described in Recipe 8.2.

To create a new movie clip object, you might be tempted to use the following syntax:

myClip = new MovieClip( ); // This creates a subclass, not a new clip!

However, the preceding example won't have the intended effect. Instead of creating a new
movie clip, it actually creates a subclass, as described in Recipe 12.12. To create new movie
clips at runtime, use the [Link]( ) method, or alternatives, as described in
Recipe 7.19.

We've seen a variety of ways to construct new objects, including using the new operator and a
constructor function. The exceptions and special cases are discussed in appropriate recipes
throughout the book.

[Link] 2004-02-06
Strona 6 z 29

12.2.4 See Also


Recipe 7.1, Recipe 7.19, Recipe 8.1, Recipe 8.2, Recipe 12.12, Recipe 16.1, and Recipe 16.6.
Recipe 12.5 discusses how to define your own custom classes rather than merely instantiating
objects of an existing class. Recipe 12.1 explains how to access the properties and methods of
any class, whether custom or built-in. See the "Language Reference" section of ActionScript for
Flash MX: The Definitive Guide, which documents the properties and methods of ActionScript's
Array, Boolean, Button, Color, Date, Function, LocalConnection, LoadVars, MovieClip, Number,
Object, Sound, String, TextField, TextFormat, XML, XMLnode, and XMLSocket classes in detail.
[ Team LiB ]
[ Team LiB ]

Recipe 12.3 Adding Properties to an Object Instance


12.3.1 Problem
You want to add custom properties (variables) to an object (an instance of a class).

12.3.2 Solution
Attach the new property to the object using an assignment statement and the dot operator.

12.3.3 Discussion
Objects derived from the built-in ActionScript classes have standard properties. For example, arr
have length properties, movie clips have _x and _y properties, and Sound objects have positio
and duration properties. But aside from these standard properties, you can add custom propert
to any object in ActionScript. You can add new properties to an object (or change an existing
property's value) by simply assigning the property a value. If the property does not yet exist,
ActionScript creates it automatically.

[Link] = "some value";

Custom properties of movie clip objects are sometimes referred to as timeline variables, as
discussed in Recipe 1.12. However, you can apply the same principle not only to movie clip objec
but to any kind of object. For example, the following code adds a custom property to an array
named myArray:

[Link] = "some value";

Attaching properties to objects is invaluable. For example, custom properties can store a value th
is used internally within an event handler method (or any other method) of the object. You can
initialize the property value outside any of the methods and then access it within a method. This
technique creates a variable whose value persists between function invocations for the life of the
object. For example:

// Initialize a clickedTimes property to 0. This happens only once.


[Link] = 0;

// Increment the clickedTimes property whenever the movie clip is clicked.


[Link] = function ( ) {
[Link]++;
};

Another valuable reason for creating custom properties is to establish relationships between obje
Only movie clips can be targeted using relative paths. By creating a property of one object, obje

[Link] 2004-02-06
Strona 7 z 29

that is a reference to another object, objectB, objectA can use or call objectB from within its
methods in a relative fashion (without requiring the fully qualified target path). In this example,
targetArray property stored in the LoadVars object offers a convenient, relative way to access
newArray:

// Create an array.
myArray = new Array( );

// Create a LoadVars object.


lv = new LoadVars( );

// Assign myArray to a custom property of the LoadVars object.


[Link] = myArray;

// When the data from the server loads into the LoadVars object, use the targetAr
// property to add a value to myArray. (val is a variable returned from the serve
[Link] = function ( ) {
[Link]([Link]);
};

It is important to understand the distinction between adding custom properties to objects


(instances) and adding custom properties to classes. When you add a property to an object, it is
available to that object only. In some situations, as discussed in this recipe, this is exactly what
want. If, however, you want to define a property that is accessible to all instances of a particular
class, you should add that property to the class, as discussed in the Introduction and Recipe 12.

[ Team LiB ]
[ Team LiB ]

Recipe 12.4 Adding Custom Methods to an Object Instanc


12.4.1 Problem
You want to add custom methods to an object.

12.4.2 Solution
Assign a function definition (or a reference to one) to a custom property of an object that is alrea
instantiated.

12.4.3 Discussion
You can add functionality to objects (instances of a class) by assigning new methods to them. Fi
define a function. Then store a reference to the function in a custom property of the object. That
definitions of an object's methods are stored as properties of the same object.

This example stores a function in the custom property methodProp:

myObject = new Object( );


[Link] = function ( ) {
trace("Hello World");
};

The preceding example assigns a method to an object using an anonymous function. However, y
also use object properties to store references to named functions. The following example stores
reference to the named function, myMethodFunction( ), in the property myMethod:

[Link] 2004-02-06
Strona 8 z 29

// Define a named function.


function myMethodFunction ( ) {
trace([Link]);
}

// Create a new object. You can assign custom methods to any kind of object (movi
// clip, array, etc.), but this example uses a generic object of the Object class
myObject = new Object( );

// Create a custom property on the object. This is not necessary, per se, but we u
// it here to illustrate that when a function is called as a method of an object,
// function has access to all the properties of that object by means of
// self-references (using the this keyword).
[Link] = "this is a custom property";

// Store a reference to myMethodFunction( ) in the property named myMethod. Note


// lack of parentheses following myMethodFunction.
[Link] = myMethodFunction;

// Invoke the custom myMethodFunction( ) method by referring to the property tha


// stores it. myMethod( ) is used to invoke myMethodFunction( ). Note the inclu
// the parentheses following myMethod.
[Link]( ); // Displays: "this is a custom property"

Once you have assigned a function to a property of an object, you can invoke that function as a
of the object, as shown in the preceding example.

Do not include the parentheses after the function name when assigning it to the
object's property. Without the parentheses, the function name is treated as a
reference to the function definition, and the function is not invoked. However,
use parentheses when invoking the function, as shown in the last line of the
preceding example.

The preceding example shows how to store a named function in an object property. However, cu
methods are commonly assigned using anonymous functions, as was briefly introduced earlier. A
anonymous function is never given a name in its definition but is assigned directly to a variable (
a custom property). Note that anonymous functions, also called function literals, should be follow
a semicolon after the closing curly brace. This technique results in slightly more compact code be
the assignment and function definition all take place at once.

Here is the analog of the preceding example, implemented with an anonymous function instead o
named function:

myObject = new Object( );


[Link] = "this is a custom property";

// Assign an anonymous function to the custom myMethod property. The myMethod( )


// method is now a reference to an anonymous function.
[Link] = function ( ) {
trace([Link]);
};

// Invoke the custom method.


[Link]( ); // Displays: "this is a custom property"

Adding methods to objects is absolutely essential in many cases. For example, to use event hand
methods for movie clips or onLoad( ) methods for XML objects or LoadVars objects, you have to
an appropriate method for that object, albeit with a specific name. For example, here we assign
custom function to the onEnterFrame property of a movie clip. This reserved property name hold
method to be invoked whenever the Flash Player triggers an onEnterFrame event, which occurs

[Link] 2004-02-06
Strona 9 z 29

whenever the playhead reaches a frame.

[Link] = function ( ) {
trace("onEnterFrame( ) was reached");
};

In the preceding example, the onEnterFrame( ) method applies only to the movie clip to which it
attached. It has no effect on other movie clips. Because the Flash Player automatically generates
onEnterFrame events during movie playback, our movie clip can respond to the event by simply
defining a method (deemed an event handler) with the same name as the event. Although there
appear to be a difference between defining a custom method and defining an event handler,
ActionScript makes no such distinction. Both are implemented by defining functions and storing t
function reference in a property of the object. The only difference is that Flash movies automatic
invoke the event handler methods when certain events occur, while custom methods are genera
invoked manually by other ActionScript code written by you, the developer.

As seen in Chapter 7, you might want to define custom methods and make them available to all
clips.

Often, custom methods are defined for the entire class and not just an object
(an instance of the class). To add a method to the class, assign it to the class's
prototype property, rather than directly to an instance.

For example, Recipe 7.10 defines a new method that is made accessible to all movie clips by ass
it to the [Link] property from which all movie clip instances inherit.

12.4.4 See Also


Recipe 12.5. Recipes throughout this book implement custom functions and attach them to the c
prototype property to enhance that class. Also refer to Chapter 7 and Chapter 9.

[ Team LiB ]
[ Team LiB ]

Recipe 12.5 Creating a Custom Class


12.5.1 Problem
You want to create a custom ActionScript class to implement custom functionality or encapsulate
related properties.

12.5.2 Solution
Create a constructor function and then assign properties and methods to its prototype.

12.5.3 Discussion
You might want to implement a custom class to encapsulate a series of methods and properties w
related functionality. For example, you might implement a custom class that communicates with
external application. To create a custom class, first define a constructor function. The constructo
function is defined in the typical fashion—either as a named function or as an anonymous functio
assigned to a variable. The latter technique is useful for creating globally accessible classes. Clas
defined with named functions are accessible only within the scope of the timeline in which they a

[Link] 2004-02-06
Strona 10 z 29

defined or by using the fully qualified target path.

Here are two degenerate examples. Obviously, constructor functions do not need to be empty; t
can take parameters and they can have function bodies.

// Create a local class named MyClass.


function MyClass ( ) {}

// Create a global class named MyClass.


_global.MyClass = function ( ) {};

By convention, class names are capitalized. ActionScript is case-insensitive, so it is not technical


an issue, but following this naming convention helps you easily distinguish classes from objects
(instances of a class). Furthermore, the ECMA-262 standard, on which JavaScript is based, dema
case-sensitivity. So by adopting the JavaScript convention, you make it easier for other develope
to understand or port your code.

Typically, a constructor function's body initializes properties of that class. The this keyword, wh
used within a constructor (or any of the class methods), is a reference to the instance of the clas
from which it is invoked:

_global.MyClass = function (param1) {


// Stored the passed-in parameter in a property of the object
this.myProperty1 = param1;
};

You can add methods to a class using the same technique as shown in Recipe 12.4. However, as
the method to a property of the class's prototype so that the method is inherited by all instance
that class (i.e., all objects derived from the class).

[Link] = function ( ) {
trace(this.myProperty1);
};

You can create instances of the class by calling the constructor function using the new keyword, a
discussed in Recipe 12.2. For example:

// Define the class constructor for the MyClass class. It accepts one parameter an
// assigns its value to the myProperty1 property, which is unique for every instan
_global.MyClass = function (param1) {
this.myProperty1 = param1;
};

// Define a custom method for the class.


[Link] = function ( ) {
trace(this.myProperty1);
};

// Create an instance of MyClass and pass a parameter to assign to myProperty1.


MyClassInstance0 = new MyClass("this is one instance");

// Invoke the myMethod( ) method of the object.


[Link]( ); // Displays: "this is one instance"

// Create a second instance of MyClass.


MyClassInstance1 = new MyClass("this is another instance");

// Invoke myMethod( ) on the second object. Note its unique value for myProperty
[Link]( ); // Displays: "this is another instance"

[Link] 2004-02-06
Strona 11 z 29

12.5.4 See Also


Recipe 12.2
[ Team LiB ]
[ Team LiB ]

Recipe 12.6 Creating Smart Getter/Setter Properties


12.6.1 Problem
You want to create a class property on which you can perform calculations before setting or
returning a value.

12.6.2 Solution
Use the addProperty( ) method.

12.6.3 Discussion
You can create what are called getter/setter properties for classes using the addProperty( ) meth
within the class's constructor. Getter/setter properties are accessed from object instances just lik
any other properties, but they secretly invoke methods rather than directly accessing an actual
property of the same name. This allows your properties to be "smart"—you can perform calculat
on the fly rather than just assigning or retrieving a simple value.

To create a getter/setter property, first create getter and setter methods for the class. The gette
method should not require any parameters and should return a value. The setter method should
take a single parameter and not return any value. By convention, the getter and setter methods
named getPropertyName( ) and setPropertyName( ), where PropertyName is the name of the
property name they control (with the first letter capitalized).

The addProperty( ) method creates a new property with the name you specify in the first parame
The second and third parameters passed to addProperty( ) specify the getter and setter methods
is important that you do not include the parentheses as part of the getter and setter method nam
when you pass them to the addProperty( ) method (i.e., methodName( ) is wrong, and methodNa
is correct). For example:

// Create a new Square class.


_global.Square = function (side) {

// Assign the value passed to the constructor to the side property.


[Link] = side;

// Create a getter/setter property named area that uses the getArea( ) and
// setArea( ) methods. Qualify the references with the this keyword so that it
// references the methods of the instance from which the area property is invok
// not some other instance. Note the lack of parentheses.
[Link]("area", [Link], [Link]);
};

// Define the getter method, which should not take any parameters. It should retu
// value, in this case the area of the square (the side length squared).
[Link] = function ( ) {
return [Link]([Link], 2);
};

// Define the setter method, which should accept the new value to be assigned. It
// should not return a value, but it should set the hidden property. In this examp

[Link] 2004-02-06
Strona 12 z 29

// the value of side is calculated as the square root of the area.


[Link] = function (area) {
[Link] = [Link](area);
};

// Create a new square with a side of length 5.


sq = new Square(5);
trace([Link]); // Displays: 25

// Set the area to 36.


[Link] = 36;
trace([Link]); // Displays: 36
trace([Link]); // Displays: 6

Ideally, you should not read and write the hidden property directly from the object. Instead, use
setter method to set the property and the getter method to read it:

sq = new Square(5);
trace([Link]( )); // Displays: 25

You can use the setter method to impose restrictions or recalculate a value before assigning it to
property. For example, an area cannot be negative, and the square root of a negative number
results in an imaginary number. This version of setArea( ) converts a negative number to its
absolute value to avoid such problems:

[Link] = function (area) {


[Link] = [Link]([Link](area));
};

12.6.4 See Also


Recipe 12.7
[ Team LiB ]
[ Team LiB ]

Recipe 12.7 Defining Read-Only Properties


12.7.1 Problem
You want to create a read-only property for a class.

12.7.2 Solution
Use the addProperty( ) method in the constructor and pass it a null value for the setter method

12.7.3 Discussion
There are at least two good reasons to have read-only properties:

z You want to create constants for your class.

z You want to have a dependent property whose value depends on other properties/factors.

The addProperty( ) method creates getter/setter properties for a class. Passing addProperty( ) a
null value for the setter method parameter effectively creates a read-only property.

[Link] 2004-02-06
Strona 13 z 29

See Recipe 12.6 for the initial implementation of the Square class. Here is a modified version:

_global.Square = function (side) {


[Link] = side;

// Create a read-only property, version (effectively, a constant). The getter


// method is getVersion( ). The setter method is null.
[Link]("version", [Link], null);

// Create a dependent property, area (its value changes, so it is not a constan


// but the value depends on other property values and can't be set directly). Th
// getter method is getArea( ). The setter method is null.
[Link]("area", [Link], null);
};

[Link] = function ( ) {
return [Link]([Link], 2);
};

[Link] = function ( ) {
// Always returns the string "1.0". Therefore, it is a constant.
return "1.0";
};

// Create a square with a side length of 5.


sq = new Square(5);
trace([Link]); // Displays: 25

// Try to set area directly. This is a read-only property. Though no error is


// displayed, it does not set the area.
[Link] = 36;

// Check the area again.


trace([Link]); // Displays: 25 (because area is not changed)

// Set the side property.


[Link] = 10;
trace([Link]); // Displays: 100 (dependent on the value of side)
trace([Link]); // Displays: 1.0

// Try to set version. It is a constant, so it will not be overwritten.


[Link] = "version 2.0";
trace([Link]); // Displays: 1.0

12.7.4 See Also


Recipe 12.6
[ Team LiB ]
[ Team LiB ]

Recipe 12.8 Creating Subclasses


12.8.1 Problem
You want to create a subclass that inherits from a superclass.

12.8.2 Solution
Define a new class and set the new class's prototype property to an instance of the superclass.

[Link] 2004-02-06
Strona 14 z 29

12.8.3 Discussion
A subclass is a class that has its own methods and properties but also uses the methods and pro
of another class (its superclass). For example, all the built-in ActionScript classes, such as the Da
and TextField classes, are subclasses of the top-level Object superclass. You can create a subclas
inherits all the properties and methods of a superclass by assigning a new instance of the superc
the subclass's prototype property. For example:

[Link] = new MySuperClass( );

Assigning a new instance of the superclass to the subclass's prototype wipes


out any existing methods or properties of that prototype. You must assign the
value to prototype before you add any methods or properties to the subclass.

This is the correct order in which to perform the actions:

1. Define the constructor method for the subclass.

2. Assign inheritance (i.e., the superclass) to the prototype property.

3. Add methods and properties to the prototype property.

Here is a example of the correct order in which to perform the actions:

_global.MySubClass = function ( ) {};


[Link] = new MySuperClass( );
[Link] = function ( ) {};
[Link] = "some property value";

The following is incorrect! Because the inheritance is defined after the addition of methodA( ) an
myProperty, those items are lost after the superclass assignment.

_global.MySubClass = function ( ) {};


[Link] = function ( ) {};
[Link] = "some property value";
[Link] = new MySuperClass( ); // This should come earlier.

Here is a complete example with superclass and subclass definitions:

// Define a superclass.
_global.MySuperClass = function ( ) {};

// Define a method for the superclass.


[Link] = function ( ) {
return "[Link]( ) called";
};

// Define the subclass.


_global.MySubClass = function ( ) {};

// Define the inheritance such that MySubClass is a subclass of MySuperClass.


[Link] = new MySuperClass( );

// Define some methods and properties for the subclass, if desired.


[Link] = function ( ) {};
[Link] = "some property value";

// Create an instance of MySubClass.

[Link] 2004-02-06
Strona 15 z 29

sub = new MySubClass( );

// Call myMethod( ) from the subclass instance. Even though myMethod is not expl
// defined for MySubClass, the subclass automatically inherits it from the
// superclass, MySuperClass.
trace([Link]( ));

Subclassing is essential to creating components (see Recipe 12.12). Additionally, it has practical
applications in situations in which there is a natural relationship between two classes. In many ca
makes sense for one class to inherit from another rather than to create redundancy. The followin
example with a Rectangle superclass and a Square subclass illustrates the point. Squares are a s
of rectangles—they are specialized rectangles in which all four sides are the same length. In this
example, the Rectangle class defines the important functionality while the Square class inherits i
without having to redefine it:

// Define the Rectangle class. The constructor accepts two parameters for the len
// of the sides (the height and width).
_global.Rectangle = function (sideA, sideB) {
[Link] = sideA;
[Link] = sideB;

// Create a read-only area property.


[Link]("area", [Link], null);
};

// Define the getArea( ) method for the area property.


[Link] = function ( ) {
return ([Link] * [Link]);
};

// Define a Square class. The constructor accepts one parameter only, since the w
// and height are always equal. Since the Square class subclasses Rectangle, it
// inherits the sideA and sideB properties. In the Square constructor, set both
// properties to the same value.
_global.Square = function (side) {
[Link] = [Link] = side;
};

// Set Square to inherit from Rectangle.


[Link] = new Rectangle( );

// Create a new Square instance with a side of length 6.


sq = new Square(6);

// Display the value of the area property. Even though Square does not explicitly
// define an area property, it inherits it from Rectangle.
trace([Link]); // Displays: 36

12.8.4 See Also


Recipe 12.9 and Recipe 12.12. For a detailed discussion on an alternative, unsupported method o
superclass assignment, refer to the discussion of so-called _ _proto_ _-based inheritance in the
"Advanced Issues with Superclass Assignment" section of Chapter 12 in ActionScript for Flash MX
Definitive Guide. That chapter is freely downloadable from [Link]
[ Team LiB ]
[ Team LiB ]

Recipe 12.9 Implementing Subclass Versions of Supercla


Methods

[Link] 2004-02-06
Strona 16 z 29

12.9.1 Problem
You want to write a subclass implementation of a superclass method (that is, you want a subclas
have a different implementation of a particular method than its superclass).

12.9.2 Solution
Add a method to the subclass with the same name as the superclass method. If you want to call
superclass method from within the subclass method, use the super keyword to reference the
superclass.

12.9.3 Discussion
Although a subclass inherits all the methods and properties of a superclass, a subclass does not
need to use the superclass implementations. Sometimes, a superclass method may be a generic
implementation of particular functionality, and you want to create a more specific implementatio
within the subclass. When you explicitly define a method within the subclass, the ActionScript
interpreter does not search up the prototype chain to find any other versions of that method (the
prototype chain is the hierarchy of prototype properties from the subclass up to any superclasse
that it inherits from). However, if you want a subclass implementation to call the superclass vers
of the method, use the super keyword to reference the superclass from within the subclass. For
example:

// Define a superclass.
_global.MySuperClass = function ( ) {};

// Create a superclass implementation of myMethod( ).


[Link] = function ( ) {
return 12;
};

// Create a subclass.
_global.MySubClass = function ( ) {};

// Define the inheritance for the subclass.


[Link] = new MySuperClass( );

// Define a subclass implementation of myMethod( ). This effectively obscures th


// inherited method of the same name (within the subclass only). However, you can
// have the subclass implementation call the superclass version of myMethod( ) u
// the super keyword.
[Link] = function ( ) {

// Explicitly invoke the superclass implementation of myMethod( ). Invoking th


// superclass implementation from within the subclass version of the method is
// fairly typical.
var superVal = [Link]( );

// Return the superclass result plus three.


return superVal + 3;
};

// Create an instance of MySubClass


sub = new MySubClass( );

// Displays: 15 (which is 12 from the superclass plus 3 from the subclass)


trace([Link]( ));

12.9.4 See Also

[Link] 2004-02-06
Strona 17 z 29

For a detailed discussion of the prototype chain, refer to the "The Almighty Prototype Chain" sect
of Chapter 12 in ActionScript for Flash MX: The Definitive Guide. That chapter is freely download
from [Link]

[ Team LiB ]
[ Team LiB ]

Recipe 12.10 Listening for Events


12.10.1 Problem
You want to perform an action in response to an event, such as when the Stage is resized, a
text field's contents are scrolled, the mouse button is clicked, or a key is pressed.

12.10.2 Solution
Create a listener object that defines an appropriate listener event handler and add it as a
listener using addListener( ).

12.10.3 Discussion
ActionScript's listener events allow one or more objects to respond to events that occur for
other objects. A listener object (an object that has been added as a listener) is notified when
one of the supported listener events occurs. For example, the built-in Key, Mouse, Selection,
and Stage objects generate one or more listener events, as do instances of the TextField class,
as shown in Table 12-1.

Table 12-1. Listener events for built-in objects and instances


Object Built-in listener events
onKeyDown
Key object
onKeyUp
onMouseDown

Mouse object onMouseMove

onMouseUp
Selection object onSetFocus
Stage object onResize
onChanged
TextField instance
onScroller

A listener object can be any kind of object, such as a text field, movie clip, or custom object.
The listener object must define a listener event handler to respond to the event of interest, and
it must be added as a listener using addListener( ). For example, to respond to text-scrolling
events, you can create an object that defines an onScroller( ) method and then register the
object to the text field of interest using the addListener( ) method. Note that the addListener( )
method is called from the text field (or other object from Table 12-1) that is generating the
listener events. The addListener( ) method accepts a parameter that is a reference to the

[Link] 2004-02-06
Strona 18 z 29

listener object (in this case, the object that defines an onScroller( ) listener event handler).

You can register multiple listeners to a single object using separate calls to addListener( ). For
example, the following code adds two objects as listeners. The first listener object is a generic
instance of the Object class that responds to any changes in the text field named myTextField.
The second listener object is a movie clip named myMovieClip. It alters its own x coordinate in
response to scroll events for myTextField.

// Create a new object and define an onChanged( ) listener event handler.


myListener = new Object( );
[Link] = function (changedTextField) {
// Actions go here.
trace("The text field changed.");
};

// Define an onScroller( ) listener event handler for an existing movie clip.


[Link] = function (scrolledTextField) {
this._x = [Link] * 10;
};

// Register the custom object and movie clip as listeners for the text field.
[Link](myListener);
[Link](myMovieClip);

When a listener object is added as a listener, it is eligible to receive all the listener events that
the listened-to object generates. For example, an object registered as a listener using
[Link]( ) can respond to onMouseDown, onMouseMove, and onMouseUp events by
defining corresponding handlers for the events of interest. In the preceding example, note that
both myListener and myMovieClip are eligible to receive onChanged and onScroller events
from the text field. However, myListener implements an onChanged( ) event handler only, so it
ignores onScroller events. Conversely, myMovieClip implements an onScroller( ) event handler
but ignores onChanged events.

Furthermore, note that the onChanged( ) and onScroller( ) listener event handlers receive a
parameter indicating which text field generated the event. This is handy when the listener
object wants to access a property of the text field (for example, our movie clip listener sets its x
location based on the text field's scroll property). It also allows a listener object to distinguish
between multiple text fields, if it is receiving listener events from more than one.

Listener events are powerful because they allow you to define actions that should occur on
multiple objects when another object is changed in some way. Likewise, listener events allow a
single object to respond to changes that occur on multiple objects.

You can remove a listener by invoking the removeListener( ) method on the object that
generates the events, passing it a reference to the listener object to remove. For example:

[Link](myListener);

All objects listed in Table 12-1 support both the addListener( ) and removeListener( ) methods.

12.10.4 See Also


Recipe 8.15, Recipe 8.27, and Recipe 12.11

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 19 z 29

Recipe 12.11 Adding Listeners to Custom Classes


12.11.1 Problem
You want your objects of a custom class to generate listener events and support listener objects

12.11.2 Solution
Write an addListener( ) method for your custom class. Use the watch( ) method to watch a prope
When the property is modified, call all listeners.

12.11.3 Discussion
Listeners are a convenient way to automatically trigger methods on one or more objects when a
property is modified within the object being listened to. Listeners are available for some classes
objects such as Key, Mouse, and TextField (see Table 12-1). Although ActionScript does not prov
listeners for all objects, you can use the watch( ) method to generate listener events in response
property changes.

The watch( ) method requires at least two parameters: a string value that is the name of the
property to watch and a reference to a callback function to call when the property is changed. (A
callback function is simply a function that is called in response to a particular trigger.) The callba
function is always automatically passed at least three values: the property name, the old value,
the new value. If the callback function returns true, the new value is assigned to the property, a
if it returns false, the old value is assigned to the property. The watch( ) method can also be us
for other purposes, such as implementing logic in the callback function to determine which value
(old or new) should be assigned to the property. For the purposes of creating listener functionali
your callback function should always return true because you don't want to interfere with the
setting of the property value (you only want to detect the change so that your code can trigger a
method on the listener objects).

To add listener functionality to a class, you also need to create methods for adding and removing
listeners. The addListener( ) method takes a reference to an object and adds it to an array prope
The removeListener( ) method also takes a reference to an object, which it removes from the ar
if found. Here is an example of how to create listener functionality for a custom class and how to
it:

// Create a constructor for a class, MyClass.


_global.MyClass = function (val) {

// Store the value passed to the constructor in a property (myProperty).


[Link] = val;

// Tell the object to watch for any changes to myProperty. When a change occurs
// call the object's propSet( ) method.
[Link]("myProperty", [Link]);
};

// The propSet( ) method is the callback function invoked automatically when


// myProperty's value changes. The callback is passed the property name
// ('myProperty' in this case) plus the old value and the proposed new value.
[Link] = function (prop, oldVal, newVal) {

// Call onMyPropertyChange( ), if it exists, for each listener object. Pass th


// method the old value and new value.
for (var i = 0; i < [Link]; i++) {
[Link][i].onMyPropertyChange(oldVal, newVal);
}

[Link] 2004-02-06
Strona 20 z 29

// Return the new value so that it is assigned to the property. This is very
// important. Without this line, myProperty becomes undefined.
return newVal;
};

// The addListener( ) method adds a listener object to an array of listeners.


[Link] = function (listener) {

// If the listeners array property does not exist, create it.


if ([Link] == undefined) {
[Link] = new Array( );
}

// Add the new listener to the existing array of listeners.


[Link](listener);
};

// The removeListener( ) method removes a listener object from the listeners arr
[Link] = function (listener) {

// Remove the listener from the listeners array, if it is found.


for (var i = 0; i < [Link]; i++) {
if ([Link][i] == listener) {
[Link](i, 1);
break;
}
}
};

// Create a listener object. It must have a method named onMyPropertyChange( ) t


// listen effectively (because that's what the propSet( ) method tries to call).
obj = new Object( );
[Link] = function (oldVal, newVal) {
trace(oldVal + " " + newVal);
};

// Create a new instance of MyClass and add obj as a listener on the instance.
myClassInstance = new MyClass(6);
[Link](obj);

// Each time myProperty is set, the callback function (propSet( )) invokes the
// onMyPropertyChange( ) method of each listener object.
[Link] = 24;
[Link] = 42;
[Link] = 36;

The preceding code gives you an idea of the process involved in adding listeners to objects.
However, it is not as versatile as it could be. The primary issue is that it requires you to define th
listener code for every class to which you want to add listeners. There is a better way.

All ActionScript classes inherit from the Object class. Therefore, if you add the listener code to th
Object class, all classes automatically inherit it. The only real difference when you add the listene
code to the Object class is that you cannot rely on having access to each class's constructor as y
did in the preceding example. Therefore, you should create another method, startListeners( ), th
initiates the watch for a watched property. The startListeners( ) method should accept the name
the property to watch as a parameter. For future use, add the following code to an external
[Link] file and include it in any project for which you want to implement custom listener
functionality:

// The startListeners( ) method calls watch( ) to start watching


// a property. If you want an object to listen for multiple
// properties, call startListeners( ) for each property.

[Link] 2004-02-06
Strona 21 z 29

[Link] = function (propName) {


[Link](propName, [Link]);
};

// The addListener( ) and removeListener( ) methods are the same as the precedin
// example, except that they are applied to the Object class.
[Link] = function (listener) {
if ([Link] == undefined) {
[Link] = new Array( );
}
[Link](listener);
};

[Link] = function (listener) {


for (var i = 0; i < [Link]; i++) {
if ([Link][i] == listener) {
[Link](i, 1);
break;
}
}
};

// The propSet( ) method is the callback function for the watched properties. Th
// method is slightly more generic than in the preceding example, insofar as it
// allows for any property to be watched.
[Link] = function (prop, oldVal, newVal) {

// Loop through all the listeners.


for (var i = 0; i < [Link]; i++) {

// This calls the onPropertyNameChange( ) method on each listener, where


// PropertyName is given by the prop parameter.
[Link][i]["on" + prop + "Change"](oldVal, newVal);
}

// Return the new value so it is properly assigned to the property.


return newVal;
};

Here is an example that uses our new listener code:

#include "[Link]"

// Define a custom class. This time, the class does not need any of the listener
// because it automatically inherits it from the Object class.
_global.MyClass = function (val0, val1) {
this.myProperty0 = val0;
this.myProperty1 = val1;
};

// Create a listener object. Define methods named onMyProperty0Change( ) and


// onMyProperty1Change( ) so the object can listen for changes to the watched
// properties named myProperty0 and myProperty1.
obj = new Object( );
obj.onMyProperty0Change = function (oldVal, newVal) {
trace("myProperty0: " + oldVal + " " + newVal);
};
obj.onMyProperty1Change = function (oldVal, newVal) {
trace("myProperty1: " + oldVal + " " + newVal);
};

// Create a new instance of MyClass.


myClassInstance = new MyClass(6, 9);

[Link] 2004-02-06
Strona 22 z 29

// Add obj as a listener. Start listeners for myProperty0 and myProperty1.


[Link](obj);
[Link]("myProperty0");
[Link]("myProperty1");

// The following changes to the properties invoke the listener methods


// onMyProperty0Change( ) and onMyProperty1Change( ).
myClassInstance.myProperty0 = 24;
myClassInstance.myProperty0 = 42;
myClassInstance.myProperty1 = 36;

/* The results displayed in the Output window are as follows:


myProperty0: 6 24
myProperty0: 24 42
myProperty1: 9 36
*/

Be aware that Flash cannot watch some properties, so you cannot add listeners in the following
cases:

Legacy Flash 4 properties

Properties available in Flash 4, such as _x, _y, _width, _height, etc., cannot be watched.
These properties are listed in the Actions Toolbox under the Properties folder.

Getter/setter properties

You cannot create custom listeners for any custom getter/setter properties you have define
(see Recipe 12.6). You cannot add listeners for any of the built-in getter/setter properties
shown in Table 12-2.

Properties inherited from the object's prototype chain

A property must be defined on the object itself before you can watch it. For example, if you
add a custom property, myCustomProperty, to the Object class, then that property cannot
watched for an instance of the Object class. In practice, this is generally a moot point. As s
as you assign a new value to the instance's property, you define the property for the instan
and it can be watched. If you have previously called the watch( ) method to watch the
property, once you define the property for the actual instance, the callback function is
invoked. For example:

// Add a custom property to the Object class.


[Link] = "This cannot be watched.";

// Create a new object.


myObject = new Object( );

// Define a watch callback function.


[Link] = function (prop, oldVal, newVal, useData)
trace(prop + " changed from " + oldVal + " to " + newVal);
};

// At first, Flash cannot watch myCustomProperty because it is inherited.


[Link]("myCustomProperty", [Link]);

// Now, myCustomProperty can be watched, because it has been assigned on the


// object instance directly.
[Link] = "This will be watched.";

Table 12-2 lists the getter/setter properties that cannot be watched, but it does not list all

[Link] 2004-02-06
Strona 23 z 29

unwatchable properties. Note that Flash 4 legacy properties such as the _x, _y, _width, and
_height movie clip properties also cannot be watched.

Table 12-2. Getter/setter properties, which cannot be watched


Object Getter/setter properties
Button tabIndex

Camera All properties


Microphone All properties
MovieClip tabIndex

NetStream All properties


duration
Sound
position

Stage All properties


TextField All properties
TextFormat All properties
Video All properties
contentType

docTypeDecl

ignoreWhite
XML
loaded

status

xmlDecl
attributes

childNodes

firstChild

lastChild

nextSibling
XMLnode
nodeName

nodeType

nodeValue

parentNode

previousSibling

12.11.4 See Also

[Link] 2004-02-06
Strona 24 z 29

Recipe 12.10. Also refer to the [Link]( ) entry in the "Language Reference" section of
ActionScript for Flash MX: The Definitive Guide, from which Table 12-2 is reproduced.
[ Team LiB ]
[ Team LiB ]

Recipe 12.12 Creating a Component That Subclasses


MovieClip
12.12.1 Problem
You want to create a reusable component, such as an interface widget, or even a component wit
UI.

12.12.2 Solution
Create a new movie clip symbol. On the first frame of the symbol's timeline define the componen
(which must inherit from MovieClip). Then register the class using [Link]( ). All this
should be encapsulated between #initclip and #endinitclip pragmas.

12.12.3 Discussion
Components are a way of encapsulating movie clips so that the instances can be easily configure
setting parameters through a user interface at authoring time or through a programmatic interfa
runtime. For those who prefer to use Flash as a design tool, many ready-made components (suc
those included in the Flash UI Component sets) can be dragged into a movie, and parameters ca
set via the Property inspector or Parameters panel. These components add functionality to the m
without the designer having to write all the code that is entailed. For our purposes, however, we
the term "component" more specifically to refer to subclasses of MovieClip. In other words, we w
discussing components that are controlled programmatically instead of those that are controlled
author-time user interface.

There are five (or six) basic steps to creating a component:

1. Create a new movie clip symbol and give it a name in the Symbol Property dialog box (acc
under Properties from the Library panel's pop-up Options menu). As a best practice, use th
you want to give to your component (such as MyComponent).

2. Export the movie clip symbol for ActionScript usage (using Linkage from the Library panel'
up Options menu). As a best practice, give the symbol a linkage identifier that follows the
convention of ComponentNameSymbol (such as MyComponentSymbol).

3. Define the component class on the first frame of the component timeline. The class should
these guidelines:

a. As a best practice, name the class ComponentNameClass (such as MyComponentClass

b. The class constructor should not take any parameters.

c. The class must inherit from MovieClip by assigning to the prototype property a new
MovieClip instance. (Subclassing the MovieClip class is the only time you use a Movie
constructor in a new statement.)

4. After defining the class, register it using [Link]( ). The first parameter should
symbol linkage identifier name specified as a string. The second parameter should be a ref
to the class.

[Link] 2004-02-06
Strona 25 z 29

5. Place all the code for the component (the class definition and the registration code) betwee
#initclip and #endinitclip pragmas so that the component class is defined and registe
before it is called. This is essential. Without the #initclip pragma, the component class is
only after instances of the component are loaded into the movie.

6. The sixth step in creating a component is to specify the component definition for the symb
optionally create a custom UI. You can read more about this process in the Macromedia tec
"Creating components in Macromedia Flash MX" at
[Link] For our purposes
will consider this step optional. Technically, a component is a component only if you have
completed this step. But this step only enables an author-time interface for configuring the
component instances—something we are not particularly interested in from an ActionScript
standpoint.

Using our working definition, components are the only way to effectively subclass MovieClip, so t
the only way to create custom object classes with a visual aspect. Basic components can be crea
that do not subclass MovieClip. These types of components allow users to set values in the Comp
Parameters panel at authoring time but not via ActionScript. Basic components cannot be instant
with ActionScript. In contrast, you can use ActionScript to control MovieClip subclass component
can even instantiate them using the attachMovie( ) method. Because a component class is regist
a particular linkage identifier name, Flash automatically knows to call the component class const
function when a new instance of the symbol is created using attachMovie( ).

Here is an example in which a component, MyComponent, is created and used within a movie. T
component creates a text field within itself and has methods that allow it to move about the scre
random spots at a set interval.

1. Create a new movie clip symbol named MyComponent.

2. Open the Linkage properties for MyComponent using the Library panel's pop-up Options m
Export the symbol for ActionScript and give it a linkage identifier of MyComponentSymbol.

3. On the first frame of the default layer of MyComponent, add the following code:

// Make sure everything is between the #initclip and #endinitclip pragmas.


#initclip

// The constructor function accepts no parameters.


function MyComponentClass ( ) {

// Create a text field that auto sizes. Set an initial text value.
[Link]("myTextField", 1, 0, 0, 0, 0);
[Link] = true;
[Link] = "init text value";
}

// MyComponentClass subclasses MovieClip, as must all scriptable components.


[Link] = new MovieClip( );

// Define a method that sets the text.


[Link] = function (val) {
[Link] = val;
};

// The beginMove( ) method uses setInterval( ) to call the move( ) method


// component at the specified rate. The interval reference is stored in a
// property so that it can be removed later.
[Link] = function (rate) {
[Link] = setInterval(this, "move", rate);
};

[Link] 2004-02-06
Strona 26 z 29

// The endMove( ) method calls clearInterval( ) to stop the calls to move(


[Link] = function ( ) {
clearInterval([Link]);
};

// The move( ) method is called automatically at an interval when beginMove


// called. This method sets the _x and _y properties of the component to ran
// values within the Stage.
[Link] = function ( ) {
this._x = [Link]( ) * 550;
this._y = [Link]( ) * 400;
};

// Register the component class to the linkage identifier for the symbol.
[Link]("MyComponentSymbol", MyComponentClass);

#endinitclip

4. Return to the first frame of the main timeline and add the following code to create an insta
the component:

// Create a new instance of the component. Because MyComponentClass is regis


// to the MyComponentSymbol linkage identifier, when this new instance is
// created, it will automatically call the MyComponentClass constructor.
_root.attachMovie("MyComponentSymbol", "myC", 1);

// Call the beginMove( ) method with a rate of 100. This causes the componen
// move approximately 10 times per second.
[Link](100);

// Define an interval callback function that calls the endMove( ) method fo


// and clears the interval.
function doEndMove ( ) {
[Link]( );
clearInterval(cInterval);
}

// Set an interval at which doEndMove( ) is called after 10 seconds have el


cInterval = setInterval(doEndMove, 10000);

12.12.4 See Also


Chapters 14 and 16 of ActionScript for Flash MX: The Definitive Guide cover movie clip subclasse
component development in detail.
[ Team LiB ]
[ Team LiB ]

Recipe 12.13 Program: Color Selector Component


In this program, you create a color selector component similar to the one that is available from t
Tools panel when you click on either the stroke or fill color. To do this, follow these steps:

1. Create a new movie clip symbol named ColorSelector.

2. Open the Linkage properties for ColorSelector using the Library panel's pop-up Options me
Export the symbol for ActionScript and give it a linkage identifier of ColorSelectorSymbol.

3. On the first frame of the default layer of ColorSelector, add the following code:

[Link] 2004-02-06
Strona 27 z 29

#initclip

function ColorSelectorClass ( ) {

// Define color transform object properties. resetTransform resets a movie


// clip's colors back to the symbol's original values. selectTransform set
// movie clip's colors to white.
[Link] = {ra: 100, rb: 0, ga: 100, gb: 0, ba: 100, bb: 0, aa:
ab: 0};
[Link] = {ra: 100, rb: 255, ga: 100, gb: 255, ba: 100, bb: 2
aa: 100, ab: 0};

// The num property is used to count the number of color swatches.


[Link] = 0;

// The selectedColor property is the RGB value


// of the color selected by the user.
[Link] = 0;

// Call the startListeners( ) method to watch the selectedColor property.


// custom handler is implemented in Recipe 12.11.
[Link]("selectedColor");

// Create a movie clip to hold all the color swatches and initially set it
// be invisible. Set the y position of the swatches_mc movie clip to 15 so
// it appears below the button (see code that follows).
[Link]("swatches_mc", 1);
this.swatches_mc._visible = false;
this.swatches_mc._y = 15;

// Create the button movie clip that opens and closes the color selector.
// a square within it and add a text field with a c for color.
[Link]("open_btn", 2);
this.open_btn.lineStyle(0, 0x000000, 100);
this.open_btn.beginFill(0xFFFFFF, 0);
this.open_btn.drawRectangle(15, 15, 0, 0, 7.5, 7.5);
this.open_btn.endFill( );
this.open_btn.createTextField("label_txt", 1, 0, 0, 0, 0);
this.open_btn.label_txt.autoSize = true;
this.open_btn.label_txt.text = " c";
this.open_btn.label_txt.selectable = false;

// When the button is pressed, toggle the visibility of the swatches.


this.open_btn.onRelease = function ( ) {
this._parent.toggle( );
};

var swatch, r, g, b, rgb;

// Create the swatches. There are 216 swatches total--six blocks of six-by
// swatches. Create three nested for loops to accomplish this.
for (var redModifier = 0; redModifier < 6; redModifier ++) {
for (var blueModifier = 0; blueModifier < 6; blueModifier++) {
for (var greenModifier = 0; greenModifier < 6; greenModifier++) {

// The red, green, and blue values of each swatch follow a pattern th
// you can see for yourself if you experiment with the color selecto
// the Flash authoring environment. This code follows the same patte
r = 0x33 * redModifier;
g = 0x33 * greenModifier;
b = 0x33 * blueModifier;
rgb = (r << 16) | (g << 8) | b;

// Create each swatch with the createSwatch( )

[Link] 2004-02-06
Strona 28 z 29

// method (see the following code).


swatch_mc = [Link](rgb);

// Move each swatch to its correct position.


swatch_mc._y = 10*blueModifier;
swatch_mc._x = 10*greenModifier + (redModifier*60);
if (redModifier >= 3) {
swatch_mc._y += 60;
swatch_mc._x -= 180;
}
}
}
}
}

// Component classes must inherit from MovieClip.


[Link] = new MovieClip( );

[Link] = function ( ) {
this.swatches_mc._visible = !this.swatches_mc._visible;
};

[Link] = function (rgb) {

// Create the movie clips for the swatches. Each swatch name and depth mus
// unique. Accomplish this by using the value of the num property, which i
// incremented each time a new swatch is created.
var swatch_mc = this.swatches_mc.createEmptyMovieClip(
"swatch" + [Link] + "_mc", [Link]);

// Within the swatch_mc clip, create outline_mc and fill_mc movie clips so
// the outline color can be controlled without affecting the fill color. Th
// is important for the rollover effect.
swatch_mc.createEmptyMovieClip("outline_mc", 2);
swatch_mc.createEmptyMovieClip("fill_mc", 1);

// In the outline_mc clip, draw a square outline.


swatch_mc.outline_mc.lineStyle(0, 0x000000, 100);
swatch_mc.outline_mc.drawRectangle(9, 9, 0, 0, 5, 5);

// In the fill_mc clip, draw a filled square.


swatch_mc.fill_mc.lineStyle(0, 0x000000, 0);
swatch_mc.fill_mc.beginFill(rgb, 100);
swatch_mc.fill_mc.drawRectangle(10, 10, 0, 0, 5, 5);
swatch_mc.fill_mc.endFill( );

// Set an rgb property for the swatch to be


// equal to the value of the fill color.
swatch_mc.rgb = rgb;

// Create a Color object to control the outline color.


// On rollover, set the color transform to the selectTransform
// object to turn the outline white. On rollout use the
// resetTransform object to set the color back to the original value.
swatch_mc.col = new Color(swatch_mc.outline_mc);
swatch_mc.onRollOver = function ( ) {
[Link](this._parent._parent.selectTransform);
};
swatch_mc.onRollOut = function ( ) {
[Link](this._parent._parent.resetTransform);
};

// When the swatch is clicked, set the selectColor property of the color
// selector component instance to the value of the swatch's rgb property.

[Link] 2004-02-06
Strona 29 z 29

// selectColor property is listened to, so the onSelectedColorChange( ) m


// of any listener objects is automatically invoked.
swatch_mc.onRelease = function ( ) {
this._parent._parent.selectedColor = [Link];
this._parent._parent.toggle( );
};

// Increment num (used to generate unique names and depths).


[Link]++;

// Return a reference to the swatch_mc clip.


return swatch_mc;
};

// Register the class to the ColorSelectorSymbol linkage identifier.


[Link]("ColorSelectorSymbol", ColorSelectorClass);

#endinitclip

4. On the main timeline, add the following code to the first frame of the default layer. This co
adds an instance of the component to the Stage.

// You must include [Link] (from Chapter 4) and [Link] (from


// Recipe 12.11) in any movie in which you use the color selector component.
#include "[Link]"
#include "[Link]"

// Add an instance of the component.


_root.attachMovie("ColorSelectorSymbol", "colorSelector", 1);

// Create a listener object with the onSelectedColorChange( ) method.


colorListener = new Object( );
[Link] = function (oldVal, newVal) {

// When the color is selected, this method is called and the hexadecimal v
// is displayed in the Output window.
trace([Link](16));
};

// Add the listener to the color selector.


[Link](colorListener);

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 34

[ Team LiB ]

Chapter 13. Programming Sound


Introduction

Recipe 13.1. Creating an Object to Control Sound

Recipe 13.2. Attaching Sounds at Runtime

Recipe 13.3. Playing and Stopping a Sound

Recipe 13.4. Getting Playback Time

Recipe 13.5. Looping a Sound

Recipe 13.6. Setting In and Out Points

Recipe 13.7. Pausing and Resuming a Sound

Recipe 13.8. Performing Actions When a Sound Ends

Recipe 13.9. Queuing Sounds

Recipe 13.10. Adding Sounds to Buttons and UI Components

Recipe 13.11. Setting the Volume of a Sound

Recipe 13.12. Controlling the Panning of a Sound

Recipe 13.13. Creating Advanced Stereo Panning Effects

Recipe 13.14. Fading In a Sound

Recipe 13.15. Fading Out a Sound

Recipe 13.16. Program: A Sound Controller Component


[ Team LiB ]
[ Team LiB ]

Introduction
You can add sound to your movie at authoring time by importing a sound into a layer of a
timeline. Furthermore, you can set the volume and pan for that sound at authoring time.
However, to control the sound during runtime, you must use a Sound object. With a Sound
object, you can programmatically control sounds in ways that you cannot at authoring time,
including:

z Attaching sounds from the library

z Loading external MP3s

z Starting and stopping playback based on user input, server responses, or other runtime

[Link] 2004-02-06
Strona 2 z 34

events

z Adjusting the sound panning and volume based on user input, server responses, or other
runtime events

z Controlling the playback position of a sound (such as user-controlled seek, fast-forward,


and rewind)

z Accessing the sound's total duration

z Reading song data (artist name, title, etc.) from ID3 tags (for loaded MP3s only)

Sound objects control the sounds that exist within a movie clip—whether those sounds were
placed within the movie clip at authoring time or attached or loaded into the movie clip at
runtime using ActionScript. It is possible to place multiple sounds within a single movie clip;
however, doing so is poor practice because it prevents you from controlling the sounds
individually. Rather, in most cases, you should create separate movie clips for each sound and
create a separate Sound object to target each movie clip.

The hierarchy of movie clip timelines also comes into play with sounds. The volume and
panning for sounds in parent movie clips affect the perceived volume and panning of nested
sounds. For example, if you set the volume to 50 for a Sound object targeting _root, the
playback of all sounds in the movie will be at half their own volume settings.

There are a few things to keep in mind when using sounds in Flash. First of all, Flash can play
only up to eight sounds simultaneously. So make sure you plan accordingly.

See Also
This chapter looks at sounds that are added to a movie programmatically. When you add
sounds programmatically, Flash does not provide a way to automatically synchronize the
animation to the sound. For basic information on using and synchronizing sound in Flash, see
Help Using Flash Adding Sound. See Recipe 15.3 for loading an external MP3 sound.
Also refer to Chapter 14 for recipes dealing with audio as it relates to the FlashCom server.

[ Team LiB ]
[ Team LiB ]

Recipe 13.1 Creating an Object to Control Sound


13.1.1 Problem
You want to control sounds at runtime.

13.1.2 Solution
Create a new movie clip to serve as the sound's target and then pass a reference to that clip to t
Sound( ) constructor function (to create a new Sound object). Alternatively, create and invoke a
custom createNewSound( ) method that handles this process automatically.

13.1.3 Discussion
Sound objects enable you to load sounds, play sounds, and control the volume and panning of so

[Link] 2004-02-06
Strona 3 z 34

within your Flash movies. A Sound object must target a movie clip (_root is also a valid target).
audio that is controlled must be placed in the movie clip's timeline at authoring time or loaded in
movie clip at runtime using attachSound( ) or loadSound( ). In any case, it is possible for you to
place/load multiple sounds into a single movie clip, but doing so is discouraged. The reason for t
that a Sound object controls the audio in the target movie clip as a whole, so it is not possible to
control each of the sounds separately if they are all in the same movie clip. Therefore, it is bette
place/load each sound into its own movie clip and create a separate Sound object to control each

When you create a new Sound object, specify a target movie clip. To control sounds in the curre
timeline, specify the keyword this as the target.

// Incorrect. This does not work properly.


mySound0_sound = new Sound( );
// Correct. This targets the current timeline.
mySound1_sound = new Sound(this);
// Correct. This targets mySoundHolder_mc.
mySound2_sound = new Sound(mySoundHolder_mc);

When you are using attachSound( ) or loadSound( ) to add audio to a movie clip, it is a good ide
use the createEmptyMovieClip( ) method to create the clip:

// Create a new movie clip to hold the audio. Then create a new Sound object and p
// it a reference to the holder movie clip.
_root.createEmptyMovieClip("soundHolder0_mc", 1);
mySound0_sound = new Sound(soundHolder0_mc);

// To manage additional sounds, use the same process to create holder movie clips
// Sound objects for each one.
_root.createEmptyMovieClip("soundHolder1_mc", 2);
mySound1_sound = new Sound(soundHolder1_mc);

As you can see, the preceding process is repetitive when creating multiple sounds in your movie
have to create each holder movie clip and Sound object and keep track of numbers and depths. Y
can automate this process with a custom createNewSound( ) method. The createNewSound( ) m
should be defined as a static method for the Sound class. (A static method is a method that is ca
directly from the class, rather than from an object instance of the class.)

The custom createNewSound( ) method returns a new Sound object, which targets an automatic
generated holder movie clip. The movie clip is created within _root by default, but you can spec
parent movie clip as an optional parameter. Add the following createNewSound( ) method definit
an external [Link] file for easy inclusion in other projects:

// This code requires getNewDepth( ) from Chapter 7, so include [Link].


#include "[Link]"

[Link] = function (parentClip) {

// If parentClip is specified, create the sound holder movie clip in that timel
// Otherwise, use _root as the parent clip.
if (parentClip == undefined) {
parentClip = _root;
}

// soundCount keeps track of the holder movie clips that are created.
// It defaults to 0.
if ([Link] == undefined) {
[Link] = 0;
}

// Create a holder movie clip in parentClip using createEmptyMovieClip( ). Giv


// unique name by appending the value of soundCount to "soundHolderClip". Give

[Link] 2004-02-06
Strona 4 z 34

// clip a unique depth using getNewDepth( ).


var soundHolder_mc = [Link]("soundHolderClip" +
[Link] + "_mc",
[Link]( ));

// Create the Sound object that targets the holder movie clip.
var soundObj_sound = new Sound(soundHolder_mc);

// Store a reference to the clip in the Sound


// object's mc property for later access.
soundObj_sound.mc = soundHolder_mc;

// Use soundHolder_mc.parent to establish a relationship between the sound


// holder movie clip and the Sound object. Use soundObj_sound.parent to establi
// a relationship between the Sound object and the parent clip. These
// relationships are important so that each object has a reference to one
// another, which they would not otherwise have. This enables communication
// between the objects for the purposes of creating fades (see Recipe 13.14
// and Recipe 13.15) and other functionality.
soundHolder_mc.parent = soundObj_sound;
soundObj_sound.parent = parentClip;

// Increment soundCount so that the next holder movie clip has a unique name.
[Link]++;

// If an allSounds array doesn't already exist, create it. It contains referenc


// to all the sounds that have been created using createNewSound( ). This is u
// when you want to perform an action on all the sounds.
if (_global.allSounds == undefined) {
_global.allSounds = new Array( );
}

// Add the new sound to the array.


_global.[Link](soundObj_sound);

// Return the Sound object.


return soundObj_sound;
};

Here is an example of how to use the createNewSound( ) method:

#include "[Link]"

// Create a new Sound object named mySound_sound by calling the static method
// [Link]( ). You do not need to create a holder movie clip becaus
// method does it for you.
mySound_sound = [Link]( );

// You can access the target holder movie clip via the Sound object's mc property
// Assuming that mySound_sound was the first Sound object created in a movie, thi
// trace( ) statement displays: _level0.soundHolderClip0
trace(mySound_sound.mc);

13.1.4 See Also


This recipe explained how to create a Sound object to control sounds, but not how to play a soun
Refer to subsequent recipes for details on using a Sound object's full capabilities. For details on
attaching sounds at runtime using attachSound( ), see Recipe 13.2. For tips on using loadSound
see Recipe 15.5. See Recipe 2.7 to determine whether the device is capable of playing sounds.
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 5 z 34

Recipe 13.2 Attaching Sounds at Runtime


13.2.1 Problem
You want to use ActionScript to attach a sound from the Library to your movie at runtime.

13.2.2 Solution
Use the [Link]( ) method.

13.2.3 Discussion
A sound must be attached to a movie clip before it can be played at runtime. You can
programmatically attach sounds from the Library to Sound objects using the attachSound( )
method. To use this method properly, you must first do three things:

1. Create a Sound object (see Recipe 13.1).

2. Import a sound into the Library using File Import to Library.

3. Set the sound symbol to export and set its linkage identifier using the Linkage option in
the Library panel's pop-up Options menu.

After successfully completing these three steps, you can call the attachSound( ) method and
pass it the linkage identifier of the sound symbol to attach:

// Include [Link] to access the createNewSound( ) method from Recipe 13.1.


#include "[Link]"

// Create a new Sound object.


mySound_sound = [Link]( );

// Attach a sound to the Sound object. The Library sound symbol


// must be set to export and have a linkage identifier that matches
// the string passed to attachSound( ).
mySound_sound.attachSound("MySoundSymbol");

13.2.4 See Also


Attached sounds do not start playing until you tell them to do so. See Recipe 13.3 for more
information about playing sounds. Recipe 14.5 demonstrates how to use
[Link]( ) to attach audio data from the user's microphone.

[ Team LiB ]
[ Team LiB ]

Recipe 13.3 Playing and Stopping a Sound


13.3.1 Problem
You want to play a sound or stop one from playing.

13.3.2 Solution

[Link] 2004-02-06
Strona 6 z 34

Use the start( ) and stop( ) methods of the Sound object that controls the sound.

13.3.3 Discussion
Sounds added to timelines at authoring time begin playback as soon as the playhead enters the
frame containing the sound. In contrast, sounds that are added programmatically (see Recipe
13.2 and Recipe 15.5) do not begin playing until they are started with the [Link]( )
method:

mySound_sound.start( );

You can also specify the number of seconds into the audio track at which to begin playback by
passing the start( ) method an optional offset parameter. For example, if you pass the start( )
method a value of 6, the audio will begin playing six seconds into the sound clip:

mySound_sound.start(6);

Unless directed otherwise, a sound plays once (until it reaches its end). You can stop a sound
prematurely with the stop( ) method:

mySound_sound.stop( );

You can also stop all sounds in a movie using the stopAllSounds( ) global function:

stopAllSounds( );

When you stop a sound and then restart it, Flash automatically restarts the sound at the
beginning.

13.3.4 See Also


To pause and resume sounds, see Recipe 13.7. See Recipe 13.6 for setting in and out points.

[ Team LiB ]
[ Team LiB ]

Recipe 13.4 Getting Playback Time


13.4.1 Problem
You want to determine the time of the current playback position of a sound.

13.4.2 Solution
Use the position property. To get the total time of a sound, use the duration property.

13.4.3 Discussion
Flash offers a convenient way to access information about the playback time for a sound. All
Sound objects have position and duration properties (both are read-only), which reflect the
current playback position and the total duration of a sound. Both values are given in
milliseconds.

[Link] 2004-02-06
Strona 7 z 34

trace(mySound_sound.position);
trace(mySound_sound.duration);

The position property updates automatically during playback. You can get the percentage that
corresponds to the current playback position by dividing position by duration and multiplying
the result by 100:

trace(mySound_sound.position/mySound_sound.duration * 100);

[ Team LiB ]
[ Team LiB ]

Recipe 13.5 Looping a Sound


13.5.1 Problem
You want to loop a sound so that it plays repeatedly.

13.5.2 Solution
Use the start( ) method of the Sound object that controls the sound and pass it a looping
parameter.

13.5.3 Discussion
The start( ) method tells a sound to begin playing. The optional first parameter indicates how
far into the sound (in seconds) to begin playing it. You also can specify a second parameter
(looping) that instructs the sound to loop the playback a certain number of times. (If you omit
the second parameter, the sound plays once.)

// Play the sound six times in a row.


mySound_sound.start(0, 6);

If you want the entire sound to loop, you should pass the start( ) method the value 0 for the
first parameter (the offset) as shown in the preceding example. (To specify the looping
parameter, you must specify an offset, even if it is 0.) If you pass the method a nonzero
offset parameter value, the sound loop begins that many seconds into the audio with each
pass. There is no pause between the loops even when you specify a nonzero offset.

// Play the sound six times, each time starting the audio three seconds from the
// beginning of the sound clip. There is no pause between each playback.
mySound_sound.start(3, 6);

13.5.4 See Also


Recipe 13.6 explains how to set an out point for the sound.
[ Team LiB ]
[ Team LiB ]

Recipe 13.6 Setting In and Out Points


13.6.1 Problem

[Link] 2004-02-06
Strona 8 z 34

You want to play a sound with start and stop (in and out) points other than the beginning or end
sound.

13.6.2 Solution
Create and invoke a custom play( ) method.

13.6.3 Discussion
The start( ) method plays and optionally loops a sound with a configurable starting offset. You ca
define a custom method that also allows you to stop the sound after an elapsed period of time. W
achieve this by using an empty movie clip to poll the sound's elapsed time and telling Flash to st
sound when the maximum time is exceeded.

Our custom play( ) method works with Sound objects created using the custom createNewSound
method from Recipe 13.1. If you don't want to use the createNewSound( ) method, you must cre
an empty movie clip and store a reference to that movie clip in a property named mc within the S
object (createNewSound( ) does this automatically).

Here is our custom play( ) method, which accepts up to three parameters: the starting offset (in
seconds), the number of loops, and the maximum playing time (in milliseconds). We'll also defin
another method that allows us to add a callback function that is invoked when the sound stops. M
sure you add these methods to your [Link] file for easy inclusion in other projects.

[Link] = function (startOffset, loops, maxTime) {

// Call the start( ) method with the values for offset and loops.
[Link](startOffset, loops);

// If maxTime is undefined, then don't do anything else.


if (maxTime == undefined) {
return;
}
// Otherwise, we have to detect the elapsed playing time. Record the time when
// sound started.
var startTime = getTimer( );

// Use an interval to monitor the sound by calling the custom monitorPlayback


// method every 100 ms. Pass the method the values for startTime and maxTime.
[Link] = setInterval(this, "monitorPlayback",
100, startTime, maxTime);
};

// Here is the function invoked at each interval.


[Link] = function (startTime, maxTime) {

// If the elapsed time exceeds the maxTime, stop the sound, clear the interval,
// invoke the callback function.
if (getTimer( ) - startTime >= maxTime) {
[Link]( );
clearInterval([Link]);
[Link][[Link]]( );
}
};

// The setOnStop( ) method allows you to define a callback function to invoke wh


// sound stops. The parameters are a string specifying the function name and an
// optional path to the function. If path is undefined, the Sound object's parent
// property is used instead. The parent property is defined only if you use the
// custom createNewSound( ) method to create the sound.
[Link] = function (functionName, path) {

[Link] 2004-02-06
Strona 9 z 34

[Link] = (path == undefined) ? [Link] : path;


[Link] = functionName;
};

Here is an example of how to use the play( ) method:

#include "[Link]"

// Create the sound.


mySound_sound = [Link]( );

// Attach a sound from the Library. Alternatively, you can load the sound from an
// external source (see Recipe 15.5).
mySound_sound.attachSound("MySoundSymbol");

// Define a callback function.


function onSoundStop ( ) {
trace("Sound has stopped.");
}

// Play the sound from the beginning. Loop it (up to) six times and stop the soun
// after 15 seconds.
mySound_sound.play(0, 6, 15000);

// Set the callback function.


mySound_sound.setOnStop("onSoundStop");

Note that the starting offset is measured in seconds, but the maximum playing time is measured
milliseconds (and the timing is accurate only to within about 100 milliseconds). You can modify t
play( ) method to use seconds or milliseconds for both values. Also note that the play( ) method
not check the current time of the sound. Instead, it tracks the total amount of time that the soun
been playing. Depending on the duration of the sound and the specified inputs, the sound may n
complete, or it may loop multiple times before the maximum elapsed time is reached. In either c
maxTime is not a true out point, but simply an elapsed time limit.

Here is a modified version, playToPoint( ), that accepts a true out point as the third parameter. T
out point is relative to the sound's beginning, in milliseconds, regardless of the starting offset. Ad
following code to your [Link] file for easy inclusion in other projects:

[Link] = function (startOffset, loops, outPoint) {


// If loops is invalid, play the sound once from startOffset to outPoint.
if (loops <= 1) {
loops = 1;
}
// Store the input parameters in object properties of the same name.
[Link] = loops;
[Link] = startOffset;

// If outPoint is undefined or 0, then just play the sound as usual.


if (outPoint == undefined || outPoint == 0) {
// Call the start( ) method with the values for startOffset and loops.
[Link](startOffset, loops);
return;
}

// Call the start( ) method with a starting offset (startOffset) only.


// We'll manually loop the sound the specified number of times
// from within onSoundComplete( ), each time restarting the sound
// at startOffset once it reaches outPoint.
[Link](startOffset);

// Initialize the loopCount to 0.

[Link] 2004-02-06
Strona 10 z 34

[Link] = 0;

// Use onSoundComplete( ) to catch when the sound completes after each loop if
// specified outPoint happens to be past the duration of the sound.
[Link] = function ( ) {
[Link]++;
if ([Link] < [Link]) {
[Link]([Link]);
}
};

// Monitor the sound status every 50 ms and pass the interval callback method th
// values for startOffset, loops, and outPoint.
[Link] = setInterval(this, "monitorOutpoint",
50, startOffset, loops, outPoint);
};

// Here is the function invoked at each interval.


[Link] = function (startOffset, loops, outPoint) {

// If the sound's current time exceeds the outPoint, stop the sound and incremen
// the loopCount.
if ([Link] >= outPoint) {
[Link]( );
[Link]++;

// If the total number of loops is reached, stop the sound, clear the interva
// and invoke the callback function.
if ([Link] > loops) {
[Link]( );
clearInterval([Link]);
[Link][[Link]]( );
} else {
// Otherwise, restart the sound from startOffset and wait for it to reach
// outPoint again.
[Link](startOffset);
}
}
};

// Our setOnStop( ) method, which defines a callback function to invoke when the
// stops, is the same as in the previous example.
[Link] = function (functionName, path) {
[Link] = (path == undefined) ? [Link] : path;
[Link] = functionName;
};

Here is an example of how to use the playToPoint( ) method:

#include "[Link]"

// Create the sound.


mySound_sound = [Link]( );

// Attach a sound from the Library.


mySound_sound.attachSound("MySoundSymbol");

// Define a callback function.


function onSoundStop ( ) {
trace("Sound has stopped.");
}

// Play the sound from two seconds in. Loop it three times and stop the sound each
// time it reaches the out point six seconds into the sound.

[Link] 2004-02-06
Strona 11 z 34

mySound_sound.play(2, 3, 6000);

// Set the callback function.


mySound_sound.setOnStop("onSoundStop");

[ Team LiB ]
[ Team LiB ]

Recipe 13.7 Pausing and Resuming a Sound


13.7.1 Problem
You want to pause and then resume a sound.

13.7.2 Solution
To pause a sound, store the sound's current position and call the stop( ) method. To resume the
sound, call the start( ) method, passing it the value of the sound's stopping position. Alternative
you can create custom pause( ) and resume( ) methods to automate this process.

13.7.3 Discussion
The Sound class does not provide built-in methods to pause and resume a sound. However, with
little bit of code, you can achieve the same result. The key is to store the sound's position
property before stopping (pausing) the sound and then use that value to tell Flash at what point
resume playback.

Therefore, to pause a sound:

1. Get the value of the sound's position property and store it in a variable:

pauseTime = mySound_sound.position;

2. Call the stop( ) method:

mySound_sound.stop( );

And when you want to resume the sound, simply do the following:

1. Convert the stored position, in milliseconds, into a starting offset, in seconds, by dividing b
1000.

2. Call the start( ) method and pass it the appropriate value for the offset:

mySound_sound.start(pauseTime/1000);

You can automate the preceding process by creating two custom methods: pause( ) and resume
Add the following code to your [Link] file for easy inclusion in other projects:

[Link] = function ( ) {
// Get the current position and then stop the sound.
[Link] = [Link];
[Link]( );
};

[Link] 2004-02-06
Strona 12 z 34

[Link] = function ( ) {
// Start the sound at the point at which it was previously stopped.
[Link]([Link]/1000);
};

Here is an example that uses the custom pause( ) and resume( ) methods:

// Attach a push button from the Library. You must first drag a push button from
// Components panel to the Stage to create the Library symbol.
[Link]("FPushButtonSymbol", "myPushButton", 1);

// Create a sound holder movie clip.


[Link]("soundHolder_mc", 2);

// Create the Sound object.


mySound_sound = new Sound(soundHolder_mc);

// Attach the sound from the Library. You must have a sound with the linkage
// identifier of MySoundSymbol for this to work.
mySound_sound.attachSound("MySoundSymbol");

// Define two callback functions. One resumes the sound, and the other pauses the
// sound. When each is called, it toggles the click handler for the push button t
// the other function.
function resumeSound ( ) {
mySound_sound.resume( );
[Link]("pauseSound");
[Link]("Pause Sound");
}

function pauseSound ( ) {
mySound_sound.pause( );
[Link]("resumeSound");
[Link]("Resume Sound");
}

// Define the initial click handler and label for the push button.
[Link]("pauseSound");
[Link]("Pause Sound");

// Tell the sound to start.


mySound_sound.start( );
[ Team LiB ]
[ Team LiB ]

Recipe 13.8 Performing Actions When a Sound Ends


13.8.1 Problem
You want to perform certain actions when a sound finishes playing.

13.8.2 Solution
Define an onSoundComplete( ) method for the Sound object.

13.8.3 Discussion
The Sound class has a built-in event that triggers the onSoundComplete( ) method for a Sound
object when the sound finishes playing. This is a convenient place to perform actions when the
sound ends. All you need to do is define the onSoundComplete( ) method and Flash will

[Link] 2004-02-06
Strona 13 z 34

automatically invoke it at the correct time:

mySound_sound.onSoundComplete = function ( ) {
trace("The sound has finished playing.");
};

The onSoundComplete( ) method is invoked only when the end of the sound is reached. It is
not invoked when the sound is stopped by way of ActionScript. Also, onSoundComplete( ) is
invoked only when the last loop completes, not each time the sound loops, when a sound's
playback is looped.

13.8.4 See Also


Recipe 13.6 and Recipe 13.9
[ Team LiB ]
[ Team LiB ]

Recipe 13.9 Queuing Sounds


13.9.1 Problem
You want to play multiple sounds in series (one after another).

13.9.2 Solution
Create an array of Sound objects and use an onSoundComplete( ) method to play the next soun
the array when the preceding sound ends.

13.9.3 Discussion
You can queue sounds rather easily by using an onSoundComplete( ) method to start the next so
when the preceding one finishes. The most convenient way to keep track of the order of sounds
place the Sound objects in an array.

The following example creates a queue of three sounds (all songs judging by the names), which
one after another:

// Create an array of Sound objects. This example assume that the Sound objects
// already exist.
_global.soundQueue_array = new Array(song0_sound, song1_sound, song2_sound);

// Use a custom property to track the current sound


// element index. Initialize it to 0.
_global.soundQueue_array.currentIndex = 0;

// Define the function that we assign to the onSoundComplete( ) method for each
// Sound objects. This function starts the next sound in the array.
function startNextInQueue ( ) {
_global.soundQueue_array[++_global.soundQueue_array.currentIndex].start( );
}

// Assign the startNextInQueue( ) function as the onSoundComplete( ) handler fo


// of the elements of the array.
for (var i = 0; i < soundQueue_array.length; i++) {
startNextInQueue[i].onSoundComplete = startNextInQueue;
}

// Start the first sound. The remaining sounds play after it finishes.

[Link] 2004-02-06
Strona 14 z 34

_global.soundQueue_array[0].start( );

13.9.4 See Also


Recipe 13.6 and Recipe 13.8
[ Team LiB ]
[ Team LiB ]

Recipe 13.10 Adding Sounds to Buttons and UI


Components
13.10.1 Problem
You want to add sounds that play when the user clicks a button or when a UI component selectio
made.

13.10.2 Solution
Add the sound to the movie by attaching it from the Library or by loading an external MP3. Creat
Sound object to target the sound. Then add a call to start( ) within the button's or component's
handler function.

13.10.3 Discussion
You can use sounds in conjunction with buttons and UI components to alert the user that someth
has happened (i.e., a selection has been made, a button has been pressed, etc.). To accomplish
this, you need to complete the following three steps:

1. Add the sound to the movie, either programmatically by attaching the sound from the Libra
or by loading the sound from an external MP3. If you are loading the sound, be sure to do
on a keyframe of a timeline prior to when you want to use the sound. You should make su
you give Flash enough time to download the sound.

// Create a sound holder movie clip.


[Link]("soundHolder_mc", 1);

// Create the Sound object.


click_sound = new Sound(soundHolder_mc);

// Attach a sound from the library.


click_sound.attachSound("MySoundSymbol");

2. Call the [Link]( ) method from within the handler function or method for the button o
component. For example, if you want the sound to play when a push button is clicked, plac
the call to start( ) inside the click handler function:

function onClick ( ) {
click_sound.start( );
}

[Link]("onClick");

Alternatively, if you are using a standard button or a movie clip as a button, you can place
call to start( ) inside the onPress( ) or onRelease( ) event handler method:

myButton_mc.onRelease = function ( ) {

[Link] 2004-02-06
Strona 15 z 34

_root. click_sound.start( );
};

Or, if you are using another type of UI component, you can add the call to start( ) to the
appropriate handler function. For example, you can play a sound when a selection is made
combo box by adding the call to start( ) in the change handler function:

function onChange ( ) {
click_sound.start( );
}

[Link]("onChange");

If you want to play a sound when a message box opens, you cannot use this exact techniq
because there is no open handler function available. Instead, place the call to start( ) on th
line before or after the line of code in which you open the message box. (For more informa
on using message boxes, see Recipe 11.15.)

myMessageBox._visible = true;
click_sound.start( );

You can use these techniques to play short sounds such as clicks or beeps when buttons are pres
or selections are made, as in the preceding snippets of code. Here is a complete example:

// Attach a push button from the Library. You must first drag a push button from
// Components panel to the Stage to create the Library symbol.
[Link]("FPushButtonSymbol", "myPushButton", 1);

// Create a sound holder movie clip.


[Link]("soundHolder_mc", 2);

// Create the Sound object.


click_sound = new Sound(soundHolder_mc);

// Load a sound from an external MP3. You can use this URL as long as you are pla
// the movie in the test Player or standalone Player.
click_sound.loadSound("[Link]

// Define the click handler function.


function onClick ( ) {
click_sound.start( );
}

// Set the click handler function and label for the push button.
[Link]("onClick");
[Link]("Click!");

You can also use the same technique to play longer sounds. The following example shows how y
can use a push button to toggle between playing and stopping an attached sound:

// Attach a push button from the Library. You must first drag a push button from
// Components panel to the Stage to create the Library symbol.
[Link]("FPushButtonSymbol", "myPushButton", 1);

// Create a sound holder movie clip.


[Link]("soundHolder_mc", 2);

// Create the Sound object.


click_sound = new Sound(soundHolder_mc);

// Attach the sound from the library. You must have a sound symbol with a linkage

[Link] 2004-02-06
Strona 16 z 34

// identifier of MySoundSymbol for this to work.


click_sound.attachSound("MySoundSymbol");

// Create two click handler functions: one to start the sound and one to stop the
// sound. When one is called, it switches the push button's click handler and lab
// so that the sound alternately plays and stops.
function startSound ( ) {
click_sound.start( );
[Link]("stopSound");
[Link]("Stop Sound");
}

function stopSound ( ) {
click_sound.stop( );
[Link]("startSound");
[Link]("Start Sound");
}

[Link]("startSound");
[Link]("Start Sound");

13.10.4 See Also


For basic information on using sounds in Flash, see Help Using Flash Adding Sound. Also
refer to Recipe 13.2, Recipe 13.3, and Recipe 15.5.

[ Team LiB ]
[ Team LiB ]

Recipe 13.11 Setting the Volume of a Sound


13.11.1 Problem
You want to set the volume of a sound.

13.11.2 Solution
Use the setVolume( ) method of the Sound object that controls the sound.

13.11.3 Discussion
You can use [Link]( ) to set the volume on a scale of 0 to 100, in which 0 is mute and
100 is the loudest:

mySound_sound.setVolume(50);

It is possible to set the volume beyond the range of 0 to 100. If you set the volume to less than
Flash converts the volume setting to its absolute value. For example, if you set the volume to -5
Flash plays the sound at 50% volume (+50). If you exceed the value of 100, the sound gets
increasingly distorted. The exception to this is if you are setting the volume of a parent Sound
object in which the nested Sound objects' volumes are less than 100. For example, if your neste
Sound object has a volume of 50 and you set the parent Sound object to a volume of 200, the
result is that the nested sound seems to play at full (100 percent) volume.

// Create two Sound objects. One targets the current timeline, and the other targ
// a nested timeline.

[Link] 2004-02-06
Strona 17 z 34

parent_sound = new Sound(this);


nested_sound = new Sound(this.soundHolder_mc);

// Attach a sound to the nested Sound object.


nested_sound.attachSound("MySoundSymbol");

// Play the nested sound.


nested_sound.start( );

// Set the volume of the nested sound to 50 and the volume of the parent sound to
// 200. The result is that the nested sound seems to play at full volume.
nested_sound.setVolume(50);
parent_sound.setVolume(200);

[Link]( ) sets the volume to an absolute value. Regardless of the previous volume
setting, the preceding example sets the volume to an absolute value of 50. You can use getVolum
( ) in conjunction with setVolume( ) to set the volume relative to the current value.

The getVolume( ) method returns the sound's current volume level. You can add or subtract a
number to or from the current value to increment or decrement the sound's volume relatively:

// Increment the current volume by 6.


mySound_sound.setVolume(mySound_sound.getVolume( ) + 6);

The volume value is given in percentage of perceived loudness. This means that 100 sounds twic
as loud as 50.

It is also important to note that ActionScript controls only the volume of sounds within the Flash
Player. It cannot control the user's system volume or speaker volume. This means that if the use
has his system volume or speaker volume turned all the way down, he will not hear any sounds.
There is also no way to detect the user's current system or speaker volume settings.

Because each Sound object targets a movie clip, there exists a parent/child relationship between
Sound objects that corresponds to the parent/child relationship of the target movie clips. This do
not mean that one Sound object can target another using _parent, but the volume (or panning)
settings applied to a "parent" Sound object affect the resultant volumes (or panning) of "child"
sounds. For example, if you set the volume of a Sound object targeting _root to 50, all nested
sounds play at half their own volume settings. Therefore, you can easily adjust the volume of all
sounds in your movie by creating a Sound object that targets _root and setting that object's
volume. For example:

master_sound = new Sound(_root);

// Mute all sounds.


master_sound.setVolume(0);

13.11.4 See Also


Recipe 13.16
[ Team LiB ]
[ Team LiB ]

Recipe 13.12 Controlling the Panning of a Sound


13.12.1 Problem
You want to control the panning of a sound.

[Link] 2004-02-06
Strona 18 z 34

13.12.2 Solution
Use the setPan( ) method of the Sound object that controls the sound.

13.12.3 Discussion
You can use [Link]( ) to programmatically control the panning of a sound between the le
and right channels. A value of -100 plays the sound entirely in the left channel, and a value of 10
plays the sound entirely in the right channel. A value of 0 plays the sound equally between the tw
channels.

// Set the panning of a sound to 100, meaning that the sound is played completely
// the right channel and not in the left.
mySound_sound.setPan(100);

You can use getPan( ) in conjunction with setPan( ) to set the panning relative to its current valu

// Set the panning of a sound 15% to the left of the previous setting.
mySound_sound.setPan(mySound_sound.getPan( ) - 15);

You can use setPan( ) over time to create the effect of a sound moving from one side to the othe

#include "[Link]"

mySound_sound = [Link]( );

// Attach a sound from the Library.


mySound_sound.attachSound("MySoundSymbol");

// Start the playback of the sound.


mySound_sound.start( );

// Define a function to call on an interval.


function moveLeftToRight ( ) {

// Get the current position and the total duration of the sound.
var pos = mySound_sound.position;
var dur = mySound_sound.duration;

// Determine the panning for the current position. At the beginning of the soun
// the panning should be to the left, and at the end of the sound the panning
// should be to the right.
var pan = (pos - dur/2)/(dur/2) * 100;

// Set the panning.


mySound_sound.setPan(pan)
}

// Create an interval on which to call the sound panning function.


panInterval = setInterval(moveLeftToRight, 100);

Here's another example in which the sound's panning follows the mouse. When the mouse move
to the left, the panning moves to the left. When the mouse moves to the right, the panning mov
to the right.

#include "[Link]"

mySound_sound = [Link]( );
mySound_sound.attachSound("MySoundSymbol");
mySound_sound.start( );

[Link] 2004-02-06
Strona 19 z 34

// Create a mouse listener object. Add a property to


// the object that references the sound.
mouseListener = new Object( );
[Link] = mySound_sound;

// Define an onMouseMove( ) method for the listener so that it is called every t


// the mouse moves.
[Link] = function ( ) {
// Set the panning of the sound so that it follows the mouse.
var x = _root._xmouse;
var w = [Link];
[Link]((x - w/2)/(w/2) * 100);
};

// Add the listener to the Mouse object.


[Link](mouseListener);

13.12.4 See Also


Recipe 13.13
[ Team LiB ]
[ Team LiB ]

Recipe 13.13 Creating Advanced Stereo Panning Effects


13.13.1 Problem
You want to be able to modify the panning of both the right and left channels of a stereo sound.

13.13.2 Solution
Use the setTransform( ) method.

13.13.3 Discussion
When working with monaural sounds, the basic getPan( ) and setPan( ) methods suffice.
However, when dealing with stereo sounds, you can use the getTransform( ) and setTransform
( ) methods to control the panning of each channel.

The setTransform( ) method takes a single parameter: a sound transform object. You can
create a sound transform object by creating a generic instance of the Object class with the
following properties:

ll

Percentage of the left channel playing through the left speaker

lr

Percentage of the left channel playing through the right speaker

rl

Percentage of the right channel playing through the left speaker

[Link] 2004-02-06
Strona 20 z 34

rr

Percentage of the right channel playing through the right speaker

Here is an example in which the right channel is played through the left speaker and the left
channel is played through the right speaker:

soundTransform = {ll: 0, lr: 100, rl: 100, rr: 0};


mySound_sound.setTransform(soundTransform);

You can also use getTransform( ) in conjunction with setTransform( ) to perform changes
relative to the current settings:

// Get the current transform object.


soundTransform = mySound_sound.getTransform( );

// Add 50 to the current ll property.


[Link] += 50;

// Set the new transform object.


mySound_sound.setTransform(soundTransform);

13.13.4 See Also


Recipe 13.12

[ Team LiB ]
[ Team LiB ]

Recipe 13.14 Fading In a Sound


13.14.1 Problem
You want to fade in a sound from the beginning of playback over a span of a number of milliseco

13.14.2 Solution
Create and invoke a custom fadeIn( ) method.

13.14.3 Discussion
Although there is no built-in method for fading in sounds, you can create a custom method for th
Sound class to handle this task. Our custom fadeIn( ) method, shown in the following code,
accomplishes this by using an onEnterFrame( ) event handler method to monitor the position of
sound's playback and to set the volume accordingly. The fadeIn( ) method requires that the Sou
object is created using our custom createNewSound( ) method from Recipe 13.1 because it uses
sound holder movie clip.

The fadeIn( ) method accepts up to six parameters:

millis

The number of milliseconds over which the sound should fade in

[Link] 2004-02-06
Strona 21 z 34

minVol

The volume percentage from which the sound should start to fade (defaults to the current
volume or 0 if the current volume is 100)

maxVol

The volume percentage at which the sound fade should end (defaults to 100)

startFadeTime

The number of milliseconds from the beginning of the sound at which to begin the fade
(defaults to 0)

startSound

A Boolean value that indicates whether to start the playback of the sound automatically

startPlayOffset

The number of milliseconds from the beginning of the sound at which to begin playback

Add the following custom fadeIn( ) method to an external [Link] file for easy inclusion in othe
projects:

[Link] = function (millis, minVol, maxVol, startFadeTime,


startSound, startPlayOffset) {

// If the sound is already at 100% volume, set the volume to 0. That is, becaus
// sounds default to 100% volume, we want to fade from 0 (unless minVol specifi
// otherwise). By default, if the volume is not already 100%, fade up from the
// current volume.
if ([Link]( ) == 100) {
[Link](0);
}

minVol = (minVol == undefined) ? [Link]( ) : minVol;


maxVol = (maxVol == undefined) ? 100 : maxVol;
startFadeTime = (startFadeTime == undefined) ? 0 : startFadeTime;

// If startSound is true, start the playback of the sound.


if (startSound) {
[Link](startPlayOffset/1000);
}

// Invoke the custom monitorFadeIn( ) method every 100 ms. Pass it the paramet
// needed to make the sound fade calculations.
[Link] = setInterval(this, "monitorFadeIn",
100, millis, minVol, maxVol, startFadeTime);
};

// Invoke this function periodically to monitor the sound fade.


[Link] = function (fadeInMillis, minVol, maxVol,
startFadeTime) {
// Once the fade-in time is exceeded, make sure the maximum volume is reached an
// clear the interval to end the fade.
var pos = [Link];
if (pos > fadeInMillis + startFadeTime) {

// Make sure the volume is set to maximum before terminating the fade.

[Link] 2004-02-06
Strona 22 z 34

[Link](maxVol);
clearInterval([Link]);

// Call the onFadeInStop( ) callback function if one is defined.


[Link][[Link]]( );
}

// Set the volume based on the current sound position, fading from minVol to max
// over the specified time duration. Make sure the volume doesn't exceed maxVol
// accidentally.
var volumePercent = [Link](minVol + (pos - startFadeTime) / fadeInMillis *
(maxVol - minVol), maxVol);
volumePercent = (volumePercent < 0) ? 0 : volumePercent;

// Set the volume of the sound repeatedly to simulate a sound fade.


[Link](volumePercent);
};

// The setOnFadeInStop( ) method allows you to define a callback function to inv


// when the sound finishing fading in. The parameters are a string specifying the
// function name and an optional path to the function. If path is undefined, the
// Sound object's parent property is used instead. The parent property is defined
// only if you use the custom createNewSound( ) method to create the sound.
[Link] = function (functionName, path) {
[Link] = (path == undefined) ? [Link] : path;
[Link] = functionName;
};

Once defined, the fadeIn( ) method can be used to fade in any sound created using our custom
createNewSound( ) method. You can use the fadeIn( ) method with sounds that were not create
using the createNewSound( ) method, as long as you add a property named mc that is a referenc
an empty movie clip. Also, notice that we've created a way to define a callback function for when
fade in stops (reaches the maximum volume).

Here is an example of how to use the fadeIn( ) method. Notice that the sound begins playback o
you start the sound with the start( ) method or pass the fadeIn( ) method a value of true for the
startSound parameter.

#include "[Link]"

mySound_sound = [Link]( );

// Attach a sound from the Library.


mySound_sound.attachSound("MySoundSymbol");

// Tell the sound to fade in over 5,000 milliseconds (5 seconds), starting from z
// volume and going to 100%. Also, start the playback of the sound at three secon
// into the sound and begin the fade at six seconds in.
mySound_sound.fadeIn(10000, 0, 100, 6000, true, 3000);

Optionally, you can also define a callback function for the fade in:

function onFadeInStop ( ) {
trace("Sounded has faded in.");
}

mySound_sound.setOnFadeInStop("onFadeInStop");

13.14.4 See Also


Recipe 13.15

[Link] 2004-02-06
Strona 23 z 34

[ Team LiB ]
[ Team LiB ]

Recipe 13.15 Fading Out a Sound


13.15.1 Problem
You want to fade out a sound over a span of a number of milliseconds.

13.15.2 Solution
Create and invoke a fadeOut( ) method.

13.15.3 Discussion
As with fading in a sound, there is no built-in functionality for fading out a sound. However, you
create a custom fadeOut( ) method to handle this task. Our custom fadeOut( ) method is slightly
longer than the fadeIn( ) method, but it utilizes similar logic. In addition, the fadeOut( ) method
allows you to use two callback functions: one that is called when the sound begins to fade out, a
another that is called when the fade out has completed. This can be useful in situations in which
want a particular action or actions to occur when the fade out begins and/or ends. For example,
may want to begin the playback of a second sound when the first begins to fade out. As the seco
sound fades in, you achieve a cross-fade effect.

As with the fadeIn( ) method, the fadeOut( ) method works only with sounds that are created us
our custom createNewSound( ) method.

The fadeOut( ) method accepts up to five parameters:

millis

The number of milliseconds over which the sound should fade out.

minVol

The volume percentage to which the sound should fade (default is 0).

maxVol

The volume percentage from which the sound should start fading (default is current volum

startTime

The point (in milliseconds) in the sound that the fade out should begin. If no value is speci
the fade out begins millis milliseconds before the end of the sound (so that it completes
when the sound ends).

stopSound

A Boolean indicating whether the sound should stop when the fade is done (default is fals

Add the following custom fadeOut( ) method to your external [Link] file for easy inclusion in
other projects:

[Link] 2004-02-06
Strona 24 z 34

[Link] = function (millis, minVol, maxVol, startTime, stopSound)

// If startTime is undefined, fade the sound out at the very end.


startFadePos = (startTime == undefined) ? [Link] - millis : startTime;

minVol = (minVol == undefined) ? 0 : minVol;


maxVol = (maxVol == undefined) ? [Link]( ) : maxVol;

// The madeCallback property is initialized to false. This property is set to t


// when the fade-out-start callback function has been invoked. It ensures that
// callback is called once and only once, when the fade out begins.
[Link] = false;

// Invoke the custom monitorFadeOut( ) method every 100 milliseconds.


[Link] = setInterval(this, "monitorFadeOut", 100, millis,
startFadePos, minVol, maxVol, stopSound);
};

[Link] = function (fadeOutMillis, startFadePos, minVol,


maxVol, stopSound) {
var pos = [Link];
var dur = [Link];

// Execute the fade out once the desired point in the sound is reached.
if (pos >= startFadePos) {

// Call the fade-out-start callback function if it has not been called yet.
if (![Link]) {
[Link] = true;
[Link][[Link]]( );
}

// If the ending fade out position has been reached, clear the interval so as
// not to needlessly monitor the sound anymore.
if (pos >= dur || pos >= startFadePos + fadeOutMillis) {

// Make sure the volume is set to the minVol before terminating the fade.
[Link](minVol);
clearInterval([Link]);
}

// Set the volume based on the current sound position, fading to minVol over
// specified time span. The fade occurs relative to maxVol.
var volumePercent = ((startFadePos + fadeOutMillis) - pos) /
fadeOutMillis * maxVol;

// If the volume reaches minVol, call the fade-out-stop callback, clear the
// interval, and, if appropriate, stop the sound.
if (volumePercent <= minVol) {
volumePercent = minVol;
[Link][[Link]]( );
clearInterval([Link]);
if (stopSound) {
[Link]( );
}
}

// Set the volume of the sound.


[Link](volumePercent);
}
};

// The setOnFadeOutStop( ) and setOnFadeOutStart( ) methods allow you to define


// callback functions to invoke when the fade starts and stops. The parameters ar

[Link] 2004-02-06
Strona 25 z 34

// string specifying the function name and an optional path to the function. If p
// is undefined, the Sound object's parent property is used instead. The parent
// property is defined only if you use the custom createNewSound( ) method to cr
// the sound.
[Link] = function (functionName, path) {
[Link] = (path == undefined) ? [Link] : path;
[Link] = functionName;
};

[Link] = function (functionName, path) {


[Link] = (path == undefined) ? [Link] : path;
[Link] = functionName;
};

You can use the fadeOut( ) method to fade out any sound created using our custom
createNewSound( ) method. Note that the fadeOut( ) method merely queues the sound to fade.
must start the sound using [Link]( ).

#include "[Link]"

mySound_sound = [Link]( );

// Attach a sound from the Library.


mySound_sound.attachSound("MySoundSymbol");

// Fade out the sound over 5,000 milliseconds (5 seconds) at the end of the sound
mySound_sound.fadeOut(5000);

// Start the playback of the sound.


mySound_sound.start( );

The preceding example does not use the callback functions. Here is an example that uses callbac
which will be notified when the sound fade out begins and ends:

#include "[Link]"

mySound_sound = [Link]( );

mySound_sound.attachSound("MySoundSymbol");

// Tell the sound to fade out over 5000 milliseconds (5 seconds) starting when th
// sound's playback position is at 20,000 milliseconds (20 seconds).
mySound_sound.fadeOut(5000, 0, 100, 20000);

// Define callback functions.


function doWhenFadeOutStart( ) {
trace("Sound fade out has begun");
}

function doWhenFadeOutStop( ) {
trace("Sound fade out has completed");
}

// Set the callback functions for the sound.


mySound_sound.setOnFadeOutStart("doWhenFadeOutStart");
mySound_sound.setOnFadeOutStop("doWhenFadeOutStop");

// Start the playback of the sound.


mySound_sound.start( );

13.15.4 See Also

[Link] 2004-02-06
Strona 26 z 34

Recipe 13.14

[ Team LiB ]
[ Team LiB ]

Recipe 13.16 Program: A Sound Controller Component


This example program implements a sound controller component that creates a controller for the
playback of sounds. The entire component is created using ActionScript, including the drawing of
buttons and sliders.

In this example, you'll learn how to create, package, install, and use the component.

To create the component, do the following:

1. Open a new Flash document and save it as Sound [Link]. Normally, it is not good pr
include spaces in the names of your Flash documents. However, components that are insta
using the Extensions Manager show up in the Components panel under a menu selection w
same name as the .fla from which the components were installed. So we want to use a rea
user-friendly name.

2. Create a new movie clip symbol named Slider. This movie clip will be a component used by
sound controller component. It creates a slider controller.

3. Although it is usually considered a best practice not to attach code directly to a symbol, cre
reusable component mandates an exception to the rule. Therefore, attach the following cod
directly to the first frame of the Slider symbol:

#initclip

// Include the .as files for getNewDepth( ), drawRectangle( ), and drawCir


// [Link] is from Chapter 7, and [Link] is from Chapter 4.
#include "[Link]"
#include "[Link]"

// The constructor does not need to do anything, but it does need to exist.
function SliderClass( ) {}

// The SliderClass class is a subclass of MovieClip.


[Link] = new MovieClip( );

/*
The create( ) method draws the slider and adds its functionality.
Parameters:
size - The length of the slider in pixels
fill - true applies a fill to the slider and false does not
fillColor - A color for the fill, in 0xRRGGBB format (applies if fill is
true)
horizontal - true creates a horizontal slider (otherwise vertical)
*/
[Link] = function (size, fill, fillColor, horizontal) {

[Link] = size;

// Create the track_mc movie clip to act as the path along which the slide
// be moved. Draw a rectangle at the specified size (minus two because the
// drawing API adds on a pixel at either end).
[Link]("track_mc", [Link]( ));
this.track_mc.lineStyle(1, 0x000000, 100);

[Link] 2004-02-06
Strona 27 z 34

this.track_mc.drawRectangle(size - 2, 3, 0, 0, size/2);

// If fill is true, create a trackFill_mc movie clip and draw a filled


// rectangle, filled with the color specified by fillColor.
if (fill) {
[Link]("trackFill_mc", [Link]( ));
this.trackFill_mc.lineStyle(0, 0x000000, 0);
this.trackFill_mc.beginFill(fillColor, 100);
this.trackFill_mc.drawRectangle(size - 2, 3, 0, 0, size/2);
this.trackFill_mc.endFill( );
}

// Create a thumbSlide_mc clip to be moved by the user along the track_mc


// Use built-in and custom drawing methods to create a white circle.
[Link]("thumbSlide_mc", [Link]( ));
this.thumbSlide_mc.lineStyle(1, 0x000000, 100);
this.thumbSlide_mc.beginFill(0xFFFFFF, 100);
this.thumbSlide_mc.drawCircle(5);
this.thumbSlide_mc.endFill( );

// Allow the thumbSlide_mc clip to be dragged between 0 and the size of th


// track while the mouse is depressed.
this.thumbSlide_mc.onPress = function ( ) {
this._parent.isDragging = true;
[Link](false, 0, 0, this._parent.size, 0);
};

this.thumbSlide_mc.onRelease = function ( ) {
this._parent.isDragging = false;
[Link]( );

// Search for and invoke a function, on the same timeline as the slider
// instance, with the name that was passed to the setOnRelease( ) metho
this._parent._parent[[Link]]( );
};

// If horizontal is not true, rotate the clip to the vertical.


if (!horizontal) {
this._rotation = -90;
}
};

// The setOnReleaes( ) method allows for a callback function


// to be automatically called when the thumb slide is released.
// The method is passed a function name as a string. The function
// must reside on the same timeline as the slider instance.
[Link] = function (functionName) {
this.thumbSlide_mc.onReleaseFunctionName = functionName;
};

// Set the position of the thumb slide from 0 to 100%.


[Link] = function (percent) {
this.thumbSlide_mc._x = ([Link] * percent) / 100;
};

// Get the percentage (0 to 100) to which the


// current thumb slide position corresponds.
[Link] = function ( ) {
return (this.thumbSlide_mc._x / [Link]) * 100;
};

// Register the class to the linkage identifier name of SliderSymbol.


[Link]("SliderSymbol", SliderClass);

[Link] 2004-02-06
Strona 28 z 34

#endinitclip

4. Open the Linkage properties of the Slider symbol using the Library panel's pop-up Options
Select the Export for ActionScript and Export in First Frame checkboxes and specify a symb
linkage identifier of SliderSymbol. Click OK.

5. Create a new Font symbol by choosing New Font from the Library panel's pop-up Options m

6. In the Font Symbol Properties dialog box, enter Verdana in the Name field and choose Ver
from the drop-down Font list. Leave the checkboxes unchecked. Click OK.

7. Select the Verdana font symbol in the Library and open its linkage properties, again using
Library panel's pop-up Options menu. Select the Export for ActionScript and Export in First
checkboxes and specify a symbol linkage identifier of VerdanaFontSymbol. Click OK.

8. Create a new movie clip symbol named Sound Controller using Insert New Symbol. Th
main sound controller component.

9. Again, following the recommended approach to create reusable components, edit the Soun
Controller symbol and attach the following code directly to its first frame:

#initclip

// Include the custom Table class from Chapter 11 and the custom Date class
// methods from Chapter 10.
#include "[Link]"
#include "[Link]"

// The SoundControllerClass constructor


function SoundControllerClass( ) {

// The creation of the buttons, sliders, and text fields is divided among
// methods that are defined later in this class. Call these methods here t
// create the controller elements.
[Link]( );
[Link]( );
[Link]( );

// Create a table that positions the elements of the controller using cust
// methods from our Table class, which is defined in Chapter 11.
var tc0 = new TableColumn(5, [Link]);
var tc1 = new TableColumn(5, [Link]);
var tc2 = new TableColumn(5, [Link]);
var tc3 = new TableColumn(5, [Link]);
var tc4 = new TableColumn(5, [Link]);
var tc5 = new TableColumn(5, [Link]);
var tr0 = new TableRow(2, tc0, tc1, tc2, tc3, tc4, tc5);
var t = new Table(5, tc0._width/2, [Link]._height, tr0);

// The volumeSlider and time elements overlay other parts of the controlle
// they must be positioned without the use of the table.
[Link]._x = [Link]._x;
[Link]._y = [Link]._y;
[Link]._y += [Link]._y;
[Link]._x = [Link]._x + ([Link]._width/2) -
([Link]._width/2);
}

// The SoundControllerClass class is a subclass of MovieClip.


[Link] = new MovieClip( );

[Link] 2004-02-06
Strona 29 z 34

[Link] = function ( ) {

// The makeButton( ) method is defined later in this class. It creates a m


// clip of the specified name to use as a button. "Btn" is appended to the
// specified name, so the first clip created here is named rwBtn.
[Link]("rw");
[Link]("play");
[Link]("pause");
[Link]("ff");
[Link]("volume");

// When the play button is clicked, call the controller's start( ) method
[Link] = function ( ) {
this._parent.start( );
};

// When the pause button is clicked, call the stop( ) method of the Sound
// object that the controller targets.
[Link] = function ( ) {
this._parent.[Link]( );
};

// The fast-forward and rewind buttons work similarly. When they are press
// an interval is defined that continually increments or decrements the
// currentPosition property of the controller and then tells the controlle
// begin playing the sound. This creates a fast-forward or rewind effect. W
// the buttons are released, the interval is deleted to stop the action.
[Link] = function ( ) {
[Link] = setInterval(this._parent, "fastForward", 100);
};
[Link] = function ( ) {
clearInterval([Link]);
};
[Link] = function ( ) {
[Link] = setInterval(this._parent, "rewind", 100);
};
[Link] = function ( ) {
clearInterval([Link]);
};
};

[Link] = function ( ) {
// Fast-forward 500 milliseconds at a time
[Link] += 500;
[Link]( );
};

[Link] = function ( ) {
// Rewind 500 milliseconds at a time
[Link] -= 500;
[Link]( );
};

[Link] = function ( ) {

// Create an instance of the slider component to control the volume. This


// slider is 75 pixels high, and the initial volume position is 100%.
[Link]("SliderSymbol", "volumeSlider", [Link]( ));
[Link](75);
[Link](100);

// The volume slider is invisible until the volume button is pressed.


[Link]._visible = false;

[Link] 2004-02-06
Strona 30 z 34

// When the volume button is pressed, make the button semi-transparent and
// the volume slider visible.
[Link] = function ( ) {
this._alpha = 42;
this._parent.volumeSlider._visible = true;
};

// Set the onRelease callback for the volume slider to the volumeOnRelease
// method defined within this class.
[Link]("volumeOnRelease");

// Create the position slider. This slider controls the playback of the sou
// Make the slider 150 pixels wide and give it a blue fill, which is used
// display the load progress when a sound is loaded from an external MP3.
[Link]("SliderSymbol", "positionSlider", [Link]( ));
[Link](150, true, 0x0000FF, true);

// Set the onRelease callback for the volume slider to the positionOnRelea
// method defined within this class.
[Link]("positionOnRelease");
};

// This method is called automatically when the volume slider is released. I


// sets the volume slider to be invisible, makes the volume button fully opa
// and sets the volume of the sound appropriately.
[Link] = function ( ) {
[Link]._visible = false;
[Link]._alpha = 100;
[Link]([Link]( ));
};

// This method is called automatically when the position slider is released.


// sets the current position within the sound based on the slider position.
[Link] = function ( ) {
[Link]([Link]( )/100);
};

[Link] = function ( ) {

// This creates a text field to contain the time value for the sound in th
// format of "current position/total duration". For the sound controller t
// semi-transparent, the text field must use an embedded font.
[Link]("time", [Link]( ), 0, 0, 0, 0);
var tf = new TextFormat( );
[Link] = "VerdanaFontSymbol";
[Link] = 9;
[Link] = true;
[Link](tf);
[Link] = "00:00 / 00:00";
[Link] = false;
};

// This method starts the sound.


[Link] = function ( ) {

// If the sound is already playing, stop it.


[Link]( );

// Start the sound at the position that is currently stored in the control
// This enables a sound to be paused and then started again from the same
// position. [Link]( ) accepts the start position in seconds, but th
// current position is stored in milliseconds, so divide by 1000.
[Link]([Link]/1000);

[Link] 2004-02-06
Strona 31 z 34

// Call the monitorPosition( ) method (defined later in this class) to be


// watching the position of the sound.
[Link]( );
};

// This method stops a sound, as opposed to just pausing it. It stops the sou
// from playing, stops monitoring the sound position, and resets the current
// position to 0.
[Link] = function ( ) {
[Link]( );
clearInterval([Link]);
[Link] = 0;
};

// The moveSoundPosition( ) method starts playing a sound at a new point


// determined by ratio, which should be between 0 and 1.
[Link] = function (ratio) {
[Link]( );
[Link]((ratio * [Link])/ 1000);
};

// The setTarget( ) method sets the sound to be controlled by the component


// autoPlay is true, the sound plays automatically once it is loaded.
[Link] = function (snd, autoPlay) {
[Link] = snd;

// If the sound is loaded and autoPlay is true, start the sound.


// Otherwise, if the sound is not loaded, call monitorLoad( )
// to watch the sound until it is loaded.
if ([Link] && autoPlay) {
[Link]( );
} else if (![Link]) {
[Link](autoPlay);
}
};

// The setPositionSlider( ) method sets the position of the slider to corre


// to the position of the playback of the sound.
[Link] = function ( ) {

// If the slider is not being dragged, set _x of the position slider's sli
if (![Link]) {
var positionRatio = [Link] / [Link];
[Link]._x = positionRatio * [Link];
}
};

// Monitor the sound position, and update the position slider and time displ
[Link] = function ( ) {
[Link] = setInterval(this,
"monitorPositionActions", 100);
};

[Link] = function ( ) {
var pos = [Link];
var dur = [Link];

// Set the time display.


[Link] = [Link](pos) + " / " +
[Link](dur);

// Set the currentPosition property of the sound controller and set the
// position of the position slider.
[Link] = pos;

[Link] 2004-02-06
Strona 32 z 34

[Link]( );

// If the position is greater than or equal to the duration of the sound,


// the current position back to 0.
if (pos >= dur) {
this._parent.currentPosition = 0;
[Link] (0);
}
};

// The monitorLoad( ) method monitors the load progress of a


// sound loaded from an external MP3.
[Link] = function (autoPlay) {
[Link] = autoPlay;
[Link] = setInterval(this, "monitorLoadActions", 100);
};

[Link] = function ( ) {

var pLded = [Link];

// Display the percentage of the sound that has loaded using a fill.
[Link].trackFill_mc._xscale = pLded;

// Once the sound finishes loading, play it if autoPlay is true.


if ([Link]) {
if ([Link]) {
[Link]( );
}
clearInterval([Link]);
}
};

// The makeButton( ) method is attached to the MovieClip class so that it c


// called from a movie clip such as the sound controller. It automatically
// generates a new movie clip of the name nameBtn.
[Link] = function (name) {
[Link](name + "Btn", [Link]( ));

// The button is composed of two parts: a circle created with drawButton(


// a symbol, such as an arrow, created with drawSymbol( ).
this[name + "Btn"].drawButton( );
this[name + "Btn"].drawSymbol(name);
};

// The drawButton( ) method draws a filled and outlined circle within a clip
[Link] = function ( ) {
[Link]("btn", [Link]( ));
[Link](0, 0x000000, 100);
[Link](0xFFFFFF, 100);
[Link](10);
[Link]( );
};

// The drawSymbol( ) method draws the requested shape within a clip.


[Link] = function (shape) {
[Link]("symbol", [Link]( ));
[Link](0, 0x000000, 100);
[Link](0x000000, 100);

// Use a switch statement to determine the shape to draw (play, rewind, et


switch (shape) {
case "play":
[Link](10, 10, 60);

[Link] 2004-02-06
Strona 33 z 34

[Link]( );
[Link]._rotation = 30;
break;
case "pause":
[Link](9, 3);
[Link](9, 3, 0, 0, 0, 6);
[Link]( );
[Link]._rotation = 90;
[Link]._x += 3;
break;
case "ff":
[Link](7, 7, 60, 30);
[Link](7, 7, 60, 30, 6);
[Link]( );
[Link]._rotation = 180;
[Link]._x += 3;
break;
case "rw":
[Link](7, 7, 60, 30);
[Link](7, 7, 60, 30, 6);
[Link]( );
[Link]._x -= 3;
break;
case "volume":
[Link](3, 5);
[Link](9, 9, 60, 30, 4);
[Link]( );
[Link](8, 3);
[Link](10, 0, 8, -3);
[Link](8, 6);
[Link](15, 0, 8, -6);
[Link]._x -= 5;
}
[Link]( );
};

[Link]("SoundControllerSymbol", SoundControllerClass);

#endinitclip

10. Drag an instance of the Slider symbol onto the Stage within the Sound Controller compone
symbol. The Slider symbol is never used, and it does not have a visual appearance within t
component. The purpose of this is to establish a nested relationship between the two symb
that when the Sound Controller component is copied into a movie's Library, the Slider sym
well.

11. Open the Linkage properties of the Sound Controller symbol using the Library panel's pop-
Options menu. Select the Export for ActionScript and Export in First Frame checkboxes and
a symbol linkage identifier of SoundControllerSymbol. Click OK.

12. Select the Sound Controller symbol in the Library and choose Component Definition from t
up Options menu. Select the Display in Components Panel checkbox and click OK.

13. Save the Sound [Link] document and close it.

14. Open a new text document and add the following code to it:

<macromedia-extension name="Sound Controller Component" version="1.0.0"


type="flash component">
<description>
<![CDATA[The sound controller component]]>
</description>

[Link] 2004-02-06
Strona 34 z 34

<ui-access>
<![CDATA[This component is installed in the Components panel]]>
</ui-access>
<author name="Your Name" />
<products>
<product name="Flash" version="6" required="true" />
</products>
<files>
<file source="Sound [Link]" destination="$flash/components/" />
</files>
</macromedia-extension>

15. Save this document as [Link] in the same directory as the Sound Controller.
document.

16. Open the Macromedia Extensions Manager (using Help Manage Extensions) and choos
Package Extension option from the Extension Manager's File menu.

17. Browse to and select the [Link] document in the window that appears. Click

18. Save the package as [Link].

19. Once you have successfully completed the packaging of the component, choose Install Ext
from the Extension Manager's File menu, and choose the [Link] file as the p
to install.

20. The component should successfully install. Once this is done, close Flash if it is open.

21. Open Flash, and open a new document within the application.

22. Open the Components panel and select Sound Controller from the drop-down menu.

23. Copy an instance of the Sound Controller component into the new Flash movie's Library by
dragging an instance onto the Stage (after doing so, you can delete the instance from the

24. Open the Library and make sure it shows three symbols: Sound Controller, Slider, and Ver

25. On the first frame of the main timeline, add the following code:

#include "[Link]"

// Create the new Sound object and load a sound into it from an external sou
// (See Recipe 15.5 for more information about loading external MP3s.) Repla
// urlToMp3 with a valid URL to an MP3 for the sound controller to play.
mySound_sound = [Link]( );
mySound_sound.loadSound(urlToMp3);

// Create an instance of the sound controller and set its target to mySound_
// with autoPlay set to true.
_root.attachMovie("SoundControllerSymbol", "sc", 1);
[Link](mySound_sound, true);

13.16.1 See Also


Recipe 15.5 and Chapter 26

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 156

[ Team LiB ]

Part II: Remote Recipes


Chapter 14

Chapter 15

Chapter 16

Chapter 17

Chapter 18

Chapter 19

Chapter 20

Chapter 21

[ Team LiB ]
[ Team LiB ]

Chapter 14. FlashCom Server

Introduction

Recipe 14.1. Creating a New FlashCom Application

Recipe 14.2. Connecting to the FlashCom Server

Recipe 14.3. Adding a Video Object at Runtime

Recipe 14.4. Capturing and Displaying Video from a Web Cam

Recipe 14.5. Capturing and Playing Audio from a Microphone

Recipe 14.6. Controlling FlashCom Audio

Recipe 14.7. Subscribing to Audio/Video Content

Recipe 14.8. Creating Playlists

Recipe 14.9. Recording and Publishing Video and Audio

Recipe 14.10. Publishing Live Content

Recipe 14.11. Pausing and Resuming a Net Stream

Recipe 14.12. Fast-Forwarding and Rewinding a Net Stream

[Link] 2004-02-06
Strona 2 z 156

Recipe 14.13. Seeking Relative to the Total Stream Length

Recipe 14.14. Implementing Server-Side ActionScript

Recipe 14.15. Tracking Clients Connected to the Application

Recipe 14.16. Invoking Server-Side Functions from the Client Movie

Recipe 14.17. Invoking Client-Side Functions from the Server

[ Team LiB ]
[ Team LiB ]

Introduction
Flash Communication Server MX (FlashCom) is a technology that enables your Flash movies
(which are referred to as clients in the context of FlashCom) to communicate in real time with
the server. FlashCom enables clients to communicate using text-based commands and enables
Flash clients to record, stream live, and play back video and audio. The applications of using
FlashCom are vast. You can:

z Create text-based chat rooms

z Create video conferencing applications

z Broadcast live events, conferences, and seminars, and archive the audio and video
streams for future playback

z Create interactive education systems

This chapter addresses many problems specific to publishing and subscribing to audio and video
content. Additionally, we'll take a look at the server-side aspects of FlashCom. See Recipe 16.6,
Recipe 17.5, and Recipe 17.6 for applications using FlashCom remote shared objects.

Here is some of the terminology used throughout this chapter. Note that our conventions are
not used universally by the developer community.

Stream

A stream is used to control the flow of data—be it text, audio, video, or other media—to,
from, and within a Flash movie. A net stream, which takes its name from ActionScript's
NetStream class, is any audio or video content that is sent to a FlashCom server from the
client (publishing stream) or from the server to the client (subscribing stream).

FlashCom

We use the term FlashCom to refer to the software that enables realtime communication
(including the server software and the client components), or as an adjective to modify
other words, as in "FlashCom audio" or "FlashCom application."

FlashCom server

The term FlashCom server refers to the computer running FlashCom or the address at
which such a device can be found.

[Link] 2004-02-06
Strona 3 z 156

Full coverage of FlashCom is beyond the scope of this book. For basic information about
FlashCom, refer to the list of online documentation at:

[Link]

For a list of online articles, tutorials, and more, see:

[Link]

Macromedia has a free developer edition of FlashCom to get you started. You can download the
software from:

[Link]

[ Team LiB ]
[ Team LiB ]

Recipe 14.1 Creating a New FlashCom Application


14.1.1 Problem
You want to create a new FlashCom application.

14.1.2 Solution
Create a new subdirectory in the FlashCom server's applications directory, which automatically
creates a FlashCom application of the same name.

14.1.3 Discussion
To create a new FlashCom application you need to create a new subdirectory (officially called a
registered application directory) within the FlashCom server's applications directory. Of course,
a complete, working application requires additional elements, such as one or more Flash
movies, but creating the application's subdirectory is the first step.

As of FlashCom version 1.5, the default location of the applications directory is Program
Files\Macromedia\Flash Communication Server MX\applications in a Windows environment
and /opt/macromedia/fcs/applications/ in Linux. The name of the new FlashCom application is
automatically determined by the name you give the new subdirectory you create. This is also
the name you use to connect Flash to the intended FlashCom application. For example, you can
create a new FlashCom application named myNewApplication by creating a new subdirectory
called myNewApplication within the FlashCom server's applications directory.

14.1.4 See Also


Once you have created the new FlashCom application, you need to connect to an instance of
that application. The connection is initiated by the client-side Flash movie, as discussed in
Recipe 14.2. For an example of a full-blown FlashCom server application, see the video chat
application implemented in Chapter 24.

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 4 z 156

Recipe 14.2 Connecting to the FlashCom Server


14.2.1 Problem
You want to connect to a FlashCom server application.

14.2.2 Solution
Create a NetConnection object and invoke the connect( ) method from it. To detect the connectio
status, add an onStatus( ) method to the NetConnection object.

14.2.3 Discussion
The NetConnection class enables you to connect your Flash movies to a FlashCom server applica
You first need to create a NetConnection object and then invoke the connect( ) method from tha
instance. The connect( ) method requires the Universal Resource Identifier (URI) to the FlashCom
server application to which you wish to connect.

// Create the NetConnection object.


myConnection = new NetConnection( );

// Call the connect( ) method to connect to an application named myApplication, w


// the FlashCom server is on the same machine as the .swf file.
[Link]("rtmp:/myApplication/");

The URI must always be in one of two formats (the items in brackets are optional):

rtmp://host[:port]/applicationName[/instanceName]

or:

rtmp:[:port]/applicationName[/instanceName]

You should use the first URI format when the FlashCom server and the .swf file are not on the sa
machine (as is the case in most production environments). The second URI format is acceptable
the FlashCom server and the .swf file are on the same machine (which is typically the case in a
development environment). In either case, you need to supply the port number only if the FlashC
server is running on a port other than the default FlashCom port (1935). The applicationName
portion of a URI must match the name of a subdirectory in the FlashCom applications directory (
Recipe 14.1). Finally, the optional instanceName allows you to create unique instances of an
application. Each instance shares the common functionality of the application but manages client
other values separately. The common example is that of a chat room application in which all roo
share the same basic functionality of the main application, but each room (instance) has its own
of users (clients). If you don't specify an instance name, then your Flash movie will connect to th
default instance, _definst_.

Here's an example of a URI for an application named myVideoChatApp that resides on


[Link]. The server is running FlashCom on the default port, so there is no n
to specify the port. Notice that when you specify a URI that includes the host portion, you must f
rtmp: with two forward slashes (//).

rtmp://[Link]/myVideoChatApp

If [Link] is running FlashCom on a port other than 1935, such as port 194
then you would need to use the following URI:

[Link] 2004-02-06
Strona 5 z 156

rtmp://[Link]/myVideoChatApp

Next, here is an example of a URI that connects to myGroupPaintApp, which resides on the same
host as the .swf file. Because they are on the same host, there is no need to specify the host as
of the URI. Notice that when you don't specify the host, the rtmp: is followed by only one forwar
slash (/).

rtmp:/myGroupPaintApp

If the localhost is running FlashCom on a nondefault port, you should specify the port in the URI,
shown here (note the two colons):

rtmp::1940/myGroupPaintApp

You can monitor the connection status by adding an onStatus( ) method to the net connection
instance. The onStatus( ) method is automatically invoked every time there is a change in conne
status. Each time the onStatus( ) method is invoked, it is automatically passed an info object wh
code property indicates the change in status. For example, when the connection is successfully m
the onStatus( ) method is invoked and passed an info object with a code property of
"[Link]". Any other value indicates that the connection has either been
or rejected.

Here is an example onStatus( ) handler that detects the code property of the info object:

[Link] = function (infoObj) {


if ([Link] == "[Link]") {
trace("connection made successfully");
}
};

The onStatus( ) method is invoked both when there is a connection attempt and when the conne
is closed. You can use the onStatus( ) method to:

z Perform proper initialization when a connection is made

z Perform necessary actions when a connection is closed (i.e., try to reestablish a connection
tell the user that the application has closed)

z Perform error handling when a connection fails or is closed unexpectedly

Here is an example onStatus( ) handler that deals with various status conditions:

[Link] = function (infoObj) {


switch ([Link]) {
case "[Link]":
// Perhaps initialize necessary elements in the movie, such as video object
break;
case "[Link]":
// The connection failed because the server is unreachable. Perhaps try aga
// or simply tell the user that the server cannot be reached.
break;
case "[Link]":
// The connection was rejected because of invalid credentials or parameters
// Perhaps prompt the user for valid information.
break;
case "[Link]":
// The connection failed because no application by the specified name exist
break;

[Link] 2004-02-06
Strona 6 z 156

case "[Link]":
// The connection was closed successfully by a command issued from the clien
// Use the opportunity to remove any elements, such as video objects; go to
// another screen; or otherwise inform the user of the closure.
break;
case "[Link]":
// The application was shut down by an administrator or because the server
// being shut down or restarted.

}
} ;

[ Team LiB ]
[ Team LiB ]

Recipe 14.3 Adding a Video Object at Runtime


14.3.1 Problem
You want to add video (and audio) to a Flash movie at runtime.

14.3.2 Solution
Create a movie clip symbol with an embedded Video object and set it to export for ActionScript.

14.3.3 Discussion
Although there is no way to create a Video object at runtime, you can create a movie clip with
an embedded video at authoring time. If you set the movie clip symbol to export for
ActionScript, you can add instances of the movie clip (including the embedded video) to your
Flash movie at runtime with an attachMovie( ) method, as you would with any other exported
movie clip. Here are the steps you should follow:

1. Open the Library and choose New Video from the Library's pop-up Options menu to create
a new video symbol.

2. Create a new movie clip symbol (using New Symbol) and drag an instance of the
video symbol into it, positioning the video at (0,0).

3. Name the instance of the video symbol my_video.

4. Open the linkage settings for the movie clip symbol (by choosing Linkage from the
Library's pop-up Options menu) and select the Export for ActionScript and Export in First
Frame checkboxes. Enter a linkage identifier of VideoMcSymbol.

5. At the point in your ActionScript code where you wish to add a video, use the attachMovie
( ) method to attach an instance of VideoMcSymbol, as follows:

// Create an instance of the video movie clip symbol.


_root.attachMovie("VideoMcSymbol", "myVideo_mc", 1);

6. When you want to target the video (to attach a net stream, for example), you should use
the path to the wrapper movie clip instance followed by my_video (to target the nested
video object), as follows:

[Link] 2004-02-06
Strona 7 z 156

// Invoke the attachVideo( ) method of the nested video object.


_root.myVideo_mc.my_video.attachVideo(myNetStream);

There are occasions when it is appropriate to create all the video instances at authoring time.
However, in other cases it may be desirable or even imperative to add video instances
dynamically at runtime. For example, to create a video conferencing application that can accept
any number of clients, you must add video instances using the technique discussed in this
recipe.

14.3.4 See Also


Recipe 7.19. Note that adding the video symbol to the movie doesn't play any content. To play
a video, you must add video content, as shown in step 6. For information on adding local video
content to a video object, see Recipe 14.4. For information on adding video content from a net
stream (content from the FlashCom server), see Recipe 14.7.

[ Team LiB ]
[ Team LiB ]

Recipe 14.4 Capturing and Displaying Video from a Web


Cam
14.4.1 Problem
You want to display the video from a user's web cam within a Flash movie, or you want to captur
data in order to publish it to a FlashCom application. (See Recipe 14.9.)

14.4.2 Solution
Use the [Link]( ) method.

14.4.3 Discussion
The [Link]( ) method returns a stream of (video-only) data from the camera attached to th
user's computer. For example:

myCam_cam = [Link]( );

If there are multiple cameras, Flash defaults to the camera that is selected under the Camera ta
the Player Settings dialog box. Flash also assigns each recognized camera an index, starting from
and incrementing by 1. For example, if there are three cameras attached to the computer, Flash
identifies them by the indexes 0, 1, and 2. You can pass the index to the [Link]( ) method
open a data stream from that camera:

// Open a stream from the second camera.


myCam_cam = [Link](1);

In practice, developers ordinarily use one of two strategies:

z Allow the user to select a camera before opening the camera stream.

z Open the camera stream to the default camera, and then allow the user to choose a differe
camera.

[Link] 2004-02-06
Strona 8 z 156

You can allow a user to choose a camera in at least two ways:

z Open the Player Settings dialog box to the Camera tab using the following code:

[Link](3);

However, this technique is intrusive because it confronts the user with the Player Settings
dialog box. Therefore, you should provide a button to open the Player Settings dialog box,
which offers the user more control. For example:

openSettings_btn.onRelease = function ( ) {
[Link](3);
};

z Create a custom UI, such as combo box, to allow the user to select his camera. You can
populate the combo box using the [Link] array, which contains the names of the
available cameras.

// Include [Link] from Chapter 11 to access its custom adjustWidth( ) meth


// This is optional, as it is for formatting purposes only.
#include "[Link]"

// For this example to work, you must add a combo box named cameraSelector_cb
// your movie.

// Define a change handler function for the combo box.


function selectCamera (cb) {
// Create a camera object using the index of the camera the user has selec
selected_cam = [Link]([Link]( ));
}

// Loop through all the elements of the [Link] array.


for (var i = 0; i < [Link]; i++) {
// Add the camera names to the combo box.
cameraSelector_cb.addItem([Link][i], i);
}

// Adjust the width of the combo box to accommodate the contents.


cameraSelector_cb.adjustWidth( );

// Set the change handler function to selectCamera( )


cameraSelector_cb.setChangeHandler("selectCamera");

In isolation, the [Link]( ) method is not very useful. You need to apply the camera data to
stream for publishing to a FlashCom application (see Recipe 14.9) or to a video object within the
same Flash movie. To apply the camera data to a video object, use the attachVideo( ) method. T
useful for allowing a user to see what his camera is displaying (perhaps while simultaneously
publishing that same data to a FlashCom server).

The video displayed from a local web cam is, in most cases, of higher quality
than the video published to the FlashCom server. Although the aforementioned
technique of monitoring local web cam data is useful, the user should be
warned that subscribers to the published stream will not necessarily see video
of the same quality.

Some users might not have any camera connected to their computer. Therefore, you should test
see whether a camera exists before trying to get the camera stream. A simple way to check for a
camera is to make sure that the [Link] array has at least one element.

[Link] 2004-02-06
Strona 9 z 156

This example checks whether a camera exists before attempting to display the video:

// Make sure that the user has a camera.


if ([Link] > 0) {
// The user has a camera, so let's get and use the camera data source.
myCam = [Link]( );

// Apply the data to a video object, my_video, which is nested within myVideo_m
// as covered in Recipe 14.3. This code displays the user's video output in a V
// object within the same Flash movie.
myVideo_mc.my_video.attachVideo(myCam);
} else {
// The user doesn't have a video camera.
// Display an alert using a message box named alertBox.
[Link]("This application requires that you have a camera.");
alertBox._visible = true;
}

Although it is possible to attach camera data to a video object (or a net stream) with an anonym
reference, it is usually better to first create a variable to which you assign the camera stream, an
then pass that variable to the attachVideo( ) method. The reason for this is that sometimes, eve
after a stream has been removed from the embedded video or the net stream has stopped
publishing, the camera might not be released properly by Flash. This is not necessarily a problem
terms of the functioning of the FlashCom application, but it can be a little strange when a user's
cam light is still on even after a FlashCom application has stopped using it. You can set the came
stream variable to null at any point to release the camera. If you have used an anonymous
reference to the camera stream, this option isn't open to you.

This example passes an anonymous reference to a camera stream to attachVideo( ):

myVideo_mc.my_video.attachVideo([Link]( ));

This example assigns a variable to the camera reference before passing it to attachVideo( ):

myCam = [Link]( );
myVideo_mc.my_video.attachVideo(myCam);
// At a later point, setting myCam (the camera stream variable)
// to null releases the camera.
myCam = null;

If you are not concerned with Flash properly releasing the camera, there is nothing wrong with
passing attachVideo( ) an anonymous reference to the camera.

14.4.4 See Also


Recipe 14.5, Recipe 14.9, and the video chat application in Chapter 24
[ Team LiB ]
[ Team LiB ]

Recipe 14.5 Capturing and Playing Audio from a


Microphone
14.5.1 Problem
You want to get audio from a user's microphone to add it to a movie clip within the Flash movie
or publish it to a FlashCom application. (See Recipe 14.9.)

[Link] 2004-02-06
Strona 10 z 156

14.5.2 Solution
Use the [Link]( ) method.

14.5.3 Discussion
The [Link]( ) method is analogous to the [Link]( ) method, but it gets audio data
from a user's microphone instead of video from his camera. For example:

myMic_mic = [Link]( );

The [Link]( ) method gets the data for the default microphone (as set in the
Microphone tab of the Player Settings dialog box) if you do not specify any parameters.
However, if the user has multiple microphones, you can use the [Link] array to
offer the user a choice among them. See Recipe 14.4, which demonstrates the analogous
technique for multiple video cameras.

There is the possibility that the user does not have any microphone. The elements in the
[Link] array correspond to the recording devices reported by the user's operating
system. In most cases the devices are sound cards, and the existence of a sound card does not
necessarily mean there is a microphone. Therefore, there is no definitive way to determine
whether a user has a microphone (although if the [Link] array is empty, he does
not).

if ([Link] > 0) {
// Place code here for getting and using a microphone stream. The user may, in
// fact, have only a sound card and no microphone.
} else {
// Place code here to handle the case in which the user has no microphone.
}

Like the [Link]( ) method, [Link]( ) is not useful in isolation. You need to actually
do something with the data it returns. You can apply that data to a net stream, in order to
publish it to a FlashCom application (see Recipe 14.9), or you can apply it to a movie clip within
the same Flash movie. Applying the microphone audio to a movie clip allows a user to hear his
microphone output locally (generally, at the same time it is being published to a FlashCom
application). You can attach the microphone output to a movie clip using the attachAudio( )
method, as follows:

[Link](myMic_mic);

Attaching the audio to a movie clip automatically plays the audio that is being retrieved from
the microphone. You do not need to tell it to start, and you cannot tell the sound to stop. You
can, however, set the Microphone object to null, which closes the stream, effectively stopping
the sound. Alternatively, you can use a Sound object to control the volume, as discussed in
Recipe 14.6. You cannot rewind or otherwise seek through microphone output.

If you are allowing a user to monitor both his microphone and camera output, and you have
created the video object using the technique from Recipe 14.3, then it is convenient to attach
the microphone audio to the video object's wrapper movie clip:

// Attach the camera video to the nested video object (my_video).


myVideo_mc.my_video.attachVideo([Link]( ));

// Attach the microphone audio to the wrapper movie clip (myVideo_mc).


myVideo_mc.attachAudio([Link]( ));

[Link] 2004-02-06
Strona 11 z 156

14.5.4 See Also


Recipe 13.2, Recipe 14.3, Recipe 14.4, Recipe 14.6, and Recipe 14.9

[ Team LiB ]
[ Team LiB ]

Recipe 14.6 Controlling FlashCom Audio


14.6.1 Problem
You want to control the volume and pan of audio attached to a movie clip with the attachAudio( )
method.

14.6.2 Solution
Create a Sound object that targets the movie clip to which the sound is attached and use the pan
and volume methods of the Sound class.

14.6.3 Discussion
FlashCom audio (meaning audio coming from either a microphone stream or from a net stream)
can be heard once it has been attached to a movie clip using the attachAudio( ) method.
However, the volume and panning levels play at the default settings. But don't fret. There is a
solution.

Although you cannot use a Sound object to control the playback (stopping, starting, or seeking)
an attached FlashCom sound, you can control the volume and panning with a Sound object that
targets the movie clip to which the sound has been attached:

// Attach audio (which can be either local microphone data or a net stream) to a
// movie clip.
[Link]([Link]( ));

// Create a Sound object that targets the movie clip to


// which the sound has been attached.
mySound = new Sound(mySoundHolderMc);

// Control the sound using the Sound class's get/set methods as you would normall
// Shown here is an example that sets the volume.
[Link](60);

Early revisions of Flash Player 6 had a bug that prevented you from
controlling attached sounds in this way. This technique requires Flash
Player [Link] or later. Not only should you update your plugin Player but
also the Test Player, which is used during authoring. Go to
[Link] for more
information and to download the latest players.

14.6.4 See Also


Recipe 13.2, Recipe 13.11, and Recipe 13.12

[Link] 2004-02-06
Strona 12 z 156

[ Team LiB ]
[ Team LiB ]

Recipe 14.7 Subscribing to Audio/Video Content


14.7.1 Problem
You want to subscribe a Flash movie to audio and/or video content from a FlashCom server.

14.7.2 Solution
Create a net stream that uses an existing net connection to the FlashCom server and invoke the
[Link]( ) method.

14.7.3 Discussion
FlashCom audio and video are transferred between the client and server using net streams.
When a Flash movie accesses a net stream of a FlashCom application, it is said to be subscribed
to that net stream.

The NetStream class, from which you can create net stream objects, facilitates the sending and
receiving of audio and video. You should use a net stream object to subscribe to any audio or
video content from a FlashCom server. The same code is used to receive the stream whether
that content has been previously recorded or whether it is live. When you create a net stream
object, you must specify the net connection object (see Recipe 14.2) over which the stream
should be sent. For example:

// Create the NetConnection object.


myConnection = new NetConnection( );

// Call the [Link]( ) method to connect to an application.


[Link]("rtmp:/myApplication/");

// Create a net stream that uses the net connection myConnection.


subscribe_ns = new NetStream(myConnection);

Once you have created a net stream object, you can use the [Link]( ) method to
subscribe to any available audio and/or video content on the server. The play( ) method
requires you to specify the name of the stream as it was published to the FlashCom application.
It is your responsibility to know the name of the stream. Presumably, you either created the
movie that published the stream in the first place, or you can discover the information from the
developer who did.

subscribe_ns.play("myAVStream");

The preceding example is the simplest form of the play( ) method. When you provide only the
name of the stream to play, Flash automatically does the following:

z Attempts to locate a live stream of the specified name. If the live stream is found, Flash
opens the live stream and plays it until it is no longer available.

z If no live stream is found, Flash looks for a recorded stream of the specified name. If a
recorded stream is found, Flash opens the recorded stream and plays it until it ends.

z If no existing live or recorded stream is found, Flash opens a live stream with the
specified name and waits for someone to publish to it.

[Link] 2004-02-06
Strona 13 z 156

You can also specify start and length parameters to wield more control over how Flash looks
for and plays streams. The start parameter is an optional second parameter with the following
possible values:

0 or greater

Looks for a recorded stream only. If no such stream is found, Flash plays the next stream
in the playlist, if applicable. If found, Flash begins playback at the specified number of
seconds from the beginning of the stream.

-1

Looks for a live stream only. If no live stream is found, the behavior depends on the value
for the length parameter. If length is negative, Flash waits indefinitely for someone to
publish to the stream. Otherwise, Flash times out after the number of seconds specified
by length and moves to the next stream in the playlist, if applicable.

Numbers less than or equal to -2

Same as the default behavior described earlier: looks for a live stream first. If no live
stream is found, it looks for a recorded stream. If no recorded stream is found, it opens a
live stream and waits for someone to publish to it.

The length parameter is an optional third parameter with the following possible values:

Any negative number

Plays the stream until it ends (for a recorded stream) or until it is no longer available (for
a live stream).

Plays a single frame at the time specified by the start parameter. This is useful for
showing stills from a stream.

Any positive number

Plays the stream for the specified number of seconds or until the stream ends.

If the net stream content includes video content, you must use the attachVideo( ) method to
attach the net stream to a video object in order to display the video. Again, it is your
responsibility to know whether or not the stream contains video.

myVideo_mc.my_video.attachVideo(subscribe_ns);

You do not need to attach the audio portion of a net stream to a movie clip using attachAudio( )
in order to hear it. However, if you want to be able to control the sound, you must attach it to a
movie clip (see Recipe 14.6).

The video and/or audio from a subscribed net stream begins playback as soon as enough has
buffered into the Player. The default buffer time for a stream is 0. This means that the stream
begins playing immediately (technically, the Flash Player still buffers approximately 10
milliseconds). However, if there is any lag in the connection, unbuffered content is interrupted.
To help ensure smooth playback, use the setBufferTime( ) method to specify the number of
seconds to buffer before playing the stream. The buffer is ignored for live streams.

[Link] 2004-02-06
Strona 14 z 156

// Set the buffer time to 30 seconds.


subscribe_ns.setBufferTime(30);

14.7.4 See Also


Recipe 13.2, and Recipe 14.6 for information on controlling attached audio

[ Team LiB ]
[ Team LiB ]

Recipe 14.8 Creating Playlists


14.8.1 Problem
You want to create a playlist in which streams are queued and played one after another.

14.8.2 Solution
Use the [Link]( ) method to add streams to the playlist. Specify false for the
flushPlaylists parameter.

14.8.3 Discussion
You can create playlists for net streams relatively quickly and easily with built-in functionality.
The [Link]( ) method allows you to create playlists by simply appending new streams
with each subsequent play( ) invocation. However, the default behavior is for play( ) to flush
the current playlist. To avoid flushing the existing playlist, specify false for the optional fourth
parameter, flushPlaylists. You can use -2 and -1 as placeholders for the intervening start
and length parameters to maintain the default behavior:

// Add three streams to a net stream object's playlist.


mySubscribe_ns.play("stream0", -2, -1, false);
mySubscribe_ns.play("stream1", -2, -1, false);
mySubscribe_ns.play("stream2", -2, -1, false);

Flash automatically begins playback of the next stream in the playlist when the current stream
stops, which depends on the values specified for the start and length parameters. These
parameters are discussed in Recipe 14.7.

You can create a new playlist at any time by calling play( ) with a value of true for the
flushPlaylists parameter. This flushes the current playlist, stops the current stream, and
plays the new stream.

mySubscribe_ns.play("stream3", -2, -1, true);

Omitting the fourth parameter performs the same operation:

mySubscribe_ns.play("stream3", -2, -1);

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 15 z 156

Recipe 14.9 Recording and Publishing Video and Audio


14.9.1 Problem
You want to record video and/or audio from a Flash movie and publish it to a FlashCom server.

14.9.2 Solution
Create a net stream, attach the video using attachVideo( ), and attach the audio using
attachAudio( ). Then call the [Link]( ) method and specify "record" or "append" as
the recording mode.

14.9.3 Discussion
To use a net stream object to record and publish video and/or audio to a FlashCom server,
follow these steps:

1. Establish a net connection:

myConnection = new NetConnection( );

2. Connect to the FlashCom server:

[Link]("rtmp:/myApplication/");

3. Create the net stream:

publishRecorded_ns = new NetStream(myConnection);

4. Attach the audio and/or video to the net stream using the attachAudio( ) and/or
attachVideo( ) method:

myMic_mic = [Link]( );
myCam_cam = [Link]( );
publishRecorded_ns.attachAudio(myMic_mic);
publishRecorded_ns.attachVideo(myCam_cam);

5. Use [Link]( ) to record the stream on the server. The first parameter is the
name by which the stream should be known by the FlashCom application (when a client
wants to subscribe to it, for example). The second parameter is the recording mode. If
you specify "record" as the recording mode, any existing stream of the same name
(within the FlashCom application) is overwritten. If you specify "append" as the recording
mode, the new stream data is appended to any existing stream of the same name (a new
stream is created on the server if none exists).

publishRecorded_ns.publish("myFirstStream", "record");

6. Call [Link]( ) again with the value false when you want to stop recording the
stream:

publishRecorded_ns.publish(false);

Even if your FlashCom application permits multiple people to record to a single stream, only one
user at a time can record to that stream. Additionally, if a recorded stream is being played by a
subscribing client, FlashCom locks the stream, preventing you from recording to it. In both of
these scenarios, you can catch these errors with an onStatus( ) method. The net stream's

[Link] 2004-02-06
Strona 16 z 156

onStatus( ) method is invoked whenever there is a status update related to the stream. Flash
passes an info object parameter containing information about the status update to the onStatus
( ) handler. When another user is recording to a stream, the code property of the info object
has the value "[Link]"; when the stream is being played, the code
property has the value "[Link]".

Here is an example with an onStatus( ) handler that detects these error codes:

myConnection = new NetConnection( );


[Link]("rtmp:/myApplication/");
publishRecorded_ns = new NetStream(myConnection);

publishRecorded_ns.onStatus = function (infoObj) {


if ([Link] == "[Link]") {
trace("Someone is already recording to this stream");
} else if ([Link] == "[Link]") {
trace("Someone is playing this stream");
} else {
trace("Stream accessed successfully.");
}
};

myMic_mic = [Link]( );
myCam_cam = [Link]( );
publishRecorded_ns.attachAudio(myMic_mic);
publishRecorded_ns.attachVideo(myCam_cam);
publishRecorded_ns.publish("myFirstStream", "record");

14.9.4 See Also


Recipe 14.10. For more information on client-side NetStream info object codes, see
[Link]
[ Team LiB ]
[ Team LiB ]

Recipe 14.10 Publishing Live Content


14.10.1 Problem
You want to publish a live stream without recording it.

14.10.2 Solution
Create a net stream, attach the audio and/or video to the stream using attachAudio( ) and/or
attachVideo( ), then call the [Link]( ) method and specify "live" as the recording
mode.

14.10.3 Discussion
The procedure to publish a live stream is the same as that of publishing a recorded stream (see
Recipe 14.9) except that you specify the mode as "live" when you call the publish( ) method.
Live streams are not recorded and are available only as they are being published.

Here is an example that publishes a live stream called "livePresentation":

// Establish the net connection.


myConnection = new NetConnection( );

[Link] 2004-02-06
Strona 17 z 156

[Link]("rtmp:/myApplication/");

// Create a net stream using a net connection named myConnection.


publishLive_ns = new NetStream(myConnection);

// Attach the camera and microphone data to the net stream.


publishLive_ns.attachAudio([Link]( ));
publishLive_ns.attachVideo([Link]( ));

// Publish the stream as live content.


publishLive_ns.publish("livePresentation", "live");

If not specified, the recording mode defaults to "live". Therefore, the last line of the example
could also be written as:

publishLive_ns.publish("livePresentation");

14.10.4 See Also


To publish a live stream and archive it for subsequent playback, see Recipe 14.9.

[ Team LiB ]
[ Team LiB ]

Recipe 14.11 Pausing and Resuming a Net Stream


14.11.1 Problem
You want to control the playback (pause or resume) of audio and/or video that is being played
from a net stream.

14.11.2 Solution
Use the [Link]( ) method.

14.11.3 Discussion
You can use the [Link]( ) method to pause the playback of a net stream. For
example, if a net stream is playing, the following line of code pauses its playback:

[Link]( );

However, the pause( ) method, when used without a parameter, acts as a toggle between
pausing and resuming playback. If the net stream in the preceding example is already paused
at the time that the pause( ) method is invoked, the stream begins playing again at the point
where it left off.

Alternatively, you can specify a Boolean parameter to indicate whether the net stream should
pause or resume, regardless of its current state. A value of true causes a net stream to pause
if it is playing or to remain paused if it is already paused. On the other hand, a value of false
causes the net stream to resume playing if it is paused or to continue playing if it is already
playing. For example:

// Cause a net stream to pause.


[Link](true);

[Link] 2004-02-06
Strona 18 z 156

// Cause a net stream to play.


[Link](false);

The play( ) method should not be used to resume the playback of a net stream, nor should it be
used to fast-forward or rewind (see Recipe 14.12). The play( ) method retrieves a stream from
the FlashCom server and adds it to a client's net stream for playback. If you call play( ) more
than once, one of two things can happen:

z If you specify false for the flushPlayLists parameter, the stream is added to the
playlist, and it begins playing only after any existing streams in the playlist have finished.

z If flushPlayLists is undefined or true, the stream begins playing again from the
specified starting point. Technically, you could use this to try to seek the stream.
However, this would be very inefficient because each time play( ) is called, the stream is
requested from the server.

You can pause and resume any stream, even live streams. However, when you pause a live
stream, it resumes at the current (realtime) position, not at the point where the stream was
paused.

14.11.4 See Also


Recipe 13.3, Recipe 14.8, and Recipe 14.12
[ Team LiB ]
[ Team LiB ]

Recipe 14.12 Fast-Forwarding and Rewinding a Net


Stream
14.12.1 Problem
You want to fast-forward or rewind (also called seeking) a net stream.

14.12.2 Solution
Use the [Link]( ) method.

14.12.3 Discussion
You can fast-forward or rewind to any point within a net stream using the [Link]( )
method. This method requires you to specify the number of seconds from the beginning of the
net stream to which to seek. For example, to return to the beginning of a net stream, you can
use the value 0:

// Return to the beginning of a net stream.


[Link](0);

The preceding technique seeks to an absolute position within the stream. However, if you want
to seek to a position relative to the current playback position, such as five seconds forward or
backward, add or subtract the number of seconds from the net stream's time property (which
returns the current playback time in seconds from the beginning of the net stream):

// Seek six seconds ahead of the current time.


[Link]([Link] + 6);

[Link] 2004-02-06
Strona 19 z 156

Also, stream data may not contain keyframes at every frame, and the seek( ) method can seek
only to a keyframe. Therefore, by default, the seek( ) method moves to the keyframe that is
nearest to the frame you specify. For example, if you instruct a net stream to seek to 12
seconds, but the two nearest keyframes are at 10 and 15 seconds, the net stream seeks to 10
seconds. If you need more precision, you can adjust the [Link] file, which is found in a
subdirectory of the FlashCom installation (such as C:\Program Files\Macromedia\Flash
Communication Server MX\conf\_defaultRoot_\_defaultVHost_). You can change the
EnhancedSeek value to true to increase the seeking precision for all net streams. Here is the
snippet from the [Link] file that you should change:

. . .
<StreamManager>
<EnhancedSeek>true</EnhancedSeek>
</StreamManager>
. . .

With enhanced seeking, you can seek to any whole second within the stream. Be aware,
however, that this greatly increases the processing load on the server, so it should be used only
when necessary.

14.12.4 See Also


Recipe 14.11 and Recipe 14.13

[ Team LiB ]
[ Team LiB ]

Recipe 14.13 Seeking Relative to the Total Stream Length


14.13.1 Problem
You want to seek a stream based on the total length of the stream.

14.13.2 Solution
Get the stream length from the server and then use the seek( ) method.

14.13.3 Discussion
A problem arises when you want to seek within a stream relative to the total length of the stream
example, if you want to create a slider that a user can move to seek through a stream, you mus
determine the total length of a stream. There is no client-side property or method that returns th
information. However, you can write a server-side function to return a stream's length using the
[Link]( ) method. Here are the steps to return a stream's length from the server:

1. In the server-side .asc file (either the [Link] file or another .asc file that is loaded by
[Link]) create a method for client objects that takes the name of a stream and returns t
length of that stream (see Recipe 14.4). Most often, you assign methods to a client in the
[Link]( ) method.

// The [Link]( ) method is invoked automatically whenever a n


// client connects to the FlashCom application. The client object is created
// passed as a parameter to the method.
[Link] = function (newClient) {

[Link] 2004-02-06
Strona 20 z 156

// Create a custom getStreamLength( ) method for each client. The method


// the name of a stream as a parameter.
[Link] = function (streamName) {
// Return the length of the requested stream.
return [Link](streamName);
};
};

2. In the client-side ActionScript, connect to the FlashCom application:

myConnection = new NetConnection( );


[Link]("rtmp:/myApp");

3. Create a response object to handle the result from the call to getStreamLength( ). A respo
object is an object created from the Object class, for which you have defined an onResult(
method. When the onResult( ) method is invoked by a response from the FlashCom server
result is returned from FlashCom and passed to the method as a parameter:

myResponse = new Object( );


[Link] = function (result) {
_root.streamLength = result;
trace("the stream length is: " + result);
};

4. Once a connection is established and a response object is defined, call the server-side
getStreamLength( ) method using the call( ) method of the client-side connection object. S
that any response should be handled by the response object you created in the previous st
and pass along the name of the stream as a parameter.

[Link]("getStreamLength", myResponse, "myStreamName");

Once you have retrieved the total length of the stream, you can use that value in conjunction wi
slider controller to determine values to pass to the seek( ) method. Here is an example:

// Create the connection.


myConnection = new NetConnection( );
[Link]("rtmp:/myApp");

// Create the net stream. This example assumes that the stream is an audio-only
// stream for which we don't want to control the volume or panning, so we don't n
// to attach any audio or video.
subscribe_ns = new NetStream(myConnection);

// Create the response object.


myResponse = new Object( );
[Link] = function (result) {

// Assign the stream length to a variable on _root.


_root.streamLength = result;

// Begin the playback of the stream.


_root.subscribe_ns.play("myStreamName");

// Use an interval to call updateScroll( ) to continually update the scroll po


// so that it corresponds to the stream position.
setInterval(_root, "updateScroll", 100);
};

// Call the server-side getStreamLength( ) method. We're assuming you created th


// already in your [Link] file (or other loaded .asc).

[Link] 2004-02-06
Strona 21 z 156

[Link]("getStreamLength", myResponse, "myStreamName");

// Create an isMouseDown variable, and toggle its value depending on the mouse st
// We use this to determine whether the user is scrolling or if the scrollbar is
// being updated by the sound playback progress.
_root.onMouseDown = function ( ) {
_root.isMouseDown = true;
};
_root.onMouseUp = function ( ) {
_root.isMouseDown = false;
};

// Create a callback function for the scrollbar.


function onScroll (sb) {
// Perform the enclosed actions only if the user is scrolling, not if the scrol
// position is being updated due to the progress of sound playback.
if (_root.isMouseDown && scrollBar_sb.hitTest(_root._xmouse, _root._ymouse)) {

// Get the current scroll position of the scrollbar (from 0 to 100).


var percent = [Link]( );

// Calculate the seconds value that correspond to the percentage based on the
// total stream length.
var seconds = percent * streamLength / 100;

// Seek to the appropriate point in the stream.


subscribe_ns.seek(seconds);
}
}

// This function updates the position of the scrollbar based on the current playb
// position of the stream.
function updateScroll ( ) {
if (!_root.isMouseDown && !scrollBar_sb.hitTest(_root._xmouse, _root._ymouse)) {
var position = subscribe_ns.duration / streamLength * 100;
scrollBar_sb.setScrollPosition(position);
}
}

// Set the scrollbar properties and assign the change handler callback function.
// example assumes that you have created a
scrollbar instance on the Stage with a
// name of scrollBar_sb.
scrollBar_sb.setScrollProperties(10, 0, 100);
scrollBar_sb.setChangeHandler("onScroll");

14.13.4 See Also


Recipe 14.12, Recipe 14.14, and Recipe 14.16

[ Team LiB ]
[ Team LiB ]

Recipe 14.14 Implementing Server-Side ActionScript


14.14.1 Problem
You want to handle advanced FlashCom application needs with server-side functionality.

[Link] 2004-02-06
Strona 22 z 156

14.14.2 Solution
Place the server-side code in a [Link] or [registered_app_name].asc file in the FlashCom
application's root directory.

14.14.3 Discussion
Server-Side ActionScript must be placed within .asc files in the FlashCom application directory.
FlashCom always looks for an .asc file named [Link] or [registered_app_name].asc (such as
[Link]) in the application's root directory. FlashCom also detects files named [Link]
or [registered_app_name].js as alternatives to the files with .asc file extensions.

If none of these files exist, there is no custom server-side functionality. On the other hand, if
FlashCom does find one of these files, it automatically looks for special application event handler
methods when the corresponding events occur. Of these event handler methods, here are three
the most common:

[Link]( )

This method is automatically called when the application first starts. You should place all
your application initialization code within this method.

[Link]( )

This method is automatically called when a client connects to the application. FlashCom
creates a new client object on the server and passes a reference to that object to the
onConnect( ) method. Additionally, if the client-side connect( ) method includes any extra
parameters, those values are also passed to onConnect( ). The onConnect( ) method is the
place to do any client initialization, such as adding methods to the client object that can be
invoked from the client-side movie. Also, you must either accept or reject connecting clien
with the [Link]( ) or [Link]( ) methods.

[Link]( )

This method is automatically called when a client disconnects from the application.
FlashCom passes to this method a reference to the client object corresponding to the user
who has disconnected from the application. This is the place to clean up anything that need
to be taken care of when a client disconnects. For example, you might want to remove the
client from the list of connected users in a chat room.

Here is an example of a [Link] file, which can be placed in the FlashCom application's director
It defines the three most common handlers you'll use to detect FlashCom events.

[Link] = function ( ) {

// When the application starts up, create a server-side remote shared object an
// save it to an application-wide property named mySsRso.
[Link] = [Link]("mySharedObject");

// Create an application-wide property to keep track of the total number of use


// that have connected since the application started.
[Link] = 0;

// Create an application-wide property to keep track of the total number of use


// that have disconnected since the application started.
[Link] = 0;
};

[Link] 2004-02-06
Strona 23 z 156

[Link] = function (newClient) {

// Accept the connection to the new client.


[Link](newClient);

// Increment the number of connected users.


[Link] += 1;
};

[Link] = function (disconnectClient) {

// Increment the number of disconnected users.


[Link] += 1;
};

14.14.4 See Also


Recipe 14.16. For more information about Server-Side ActionScript for FlashCom, see:

[Link]

[ Team LiB ]
[ Team LiB ]

Recipe 14.15 Tracking Clients Connected to the Applicati


14.15.1 Problem
You want to keep track of all the clients connected to the FlashCom application.

14.15.2 Solution
Use the server-side [Link] property.

14.15.3 Discussion
Server-Side ActionScript (for FlashCom) includes a built-in, application-wide property named
[Link], which is an array of all the connected client objects.

This example defines a custom getUserInfo( ) method, for each client, which returns information
the clients connected to the application:

// Create an [Link]( ) method. This example assume that when the


// connects, a username parameter is passed along.
[Link] = function (newClient, username) {

// Accept the connection to the new client.


[Link](newClient);

// Add the username to the client object as a custom property.


[Link] = username;

// Define a custom getUserInfo( ) method for the client object. This method can
// be called from the client.
[Link] = function ( ) {

// Create a new array, loop through all the connected clients in

[Link] 2004-02-06
Strona 24 z 156

// [Link] and add the usernames to the array.


var clients_ar = new Array( );
for (var i = 0; i < [Link]; i++) {
clients_ar.push([Link][i].username);
}
// Return the array of usernames.
return clients_ar;
};
};

14.15.4 See Also


Recipe 14.14
[ Team LiB ]
[ Team LiB ]

Recipe 14.16 Invoking Server-Side Functions from the


Client Movie
14.16.1 Problem
You want to invoke a server-side function from a client movie.

14.16.2 Solution
In the .asc file ([Link] or another .asc file included in [Link]), define the server-side
function as a method of the client object. Then, from the client, invoke the call( ) method from
the net connection object.

14.16.3 Discussion
To invoke a server-side function from a client, you must first associate the server-side function
with the connected client object. You can do this by assigning the function as a method of the
client object. Generally, this is done within the onConnect( ) method, as follows:

[Link] = function (newClient) {


[Link](newClient);
[Link] = function ( ) {
// Do something here.
};
};

Then, from the client movie you can use the call( ) method to invoke the server-side client
method:

// Invoke the server-side ssFunction( ) method from a net connection object.


[Link]("ssFunction");

If you want the server-side method to return a value to the client, you must define a response
object on the client. A response object is an object derived from the Object class for which you
have defined an onResult( ) method. The value returned from the server-side method is passed
to the client-side onResult( ) method as a parameter.

myResponse = new Object( );


[Link] = function (result) {
// Do something with result here.
};

[Link] 2004-02-06
Strona 25 z 156

Once you have defined the response object on the client, you need to tell Flash to associate it
with the response for a given call to a server-side function. To do this, you should modify your
call( ) invocation by passing it a reference to the response object:

[Link]("ssFunction", myResponse);

Let's look at an example of how you might use this. First, in the .asc file you should define a
custom method for each of the connecting clients:

[Link] = function (newClient) {

// Accept the connection to the new client.


[Link](newClient);

// Define a custom getUserCount( ) method for the client object.


[Link] = function ( ) {

// Return the number of connected clients.


return [Link];
};
};

Then, from within the client code you can invoke the server-side getUserCount( ) method. Don't
forget to create a response object as well.

myResponse = new Object( );


[Link] = function (result) {
_root.numberOfUsers_txt.text = result;
};
[Link]("getUserCount", myResponse);

14.16.4 See Also


Recipe 14.14 and Recipe 14.17

[ Team LiB ]
[ Team LiB ]

Recipe 14.17 Invoking Client-Side Functions from the


Server
14.17.1 Problem
You want to invoke a client-side function from Server-Side ActionScript.

14.17.2 Solution
Define the client-side function as a method of the client-side net connection object, and then,
on the server, invoke the call( ) method on the client object.

14.17.3 Discussion
To invoke client-side functionality from the FlashCom server, you must first define the client-
side functionality within a method of the net connection object. Then you can invoke that
method from the FlashCom server using the call( ) method of the corresponding client object.

[Link] 2004-02-06
Strona 26 z 156

For example, here is a snippet of client-side code (meaning it should be in your Flash movie)
that defines a custom method for a net connection object:

[Link] = function ( ) {
trace("Method called from server!");
};

Then, in the Server-Side ActionScript (in the [Link] file or other .asc included in [Link]),
you can invoke the server-side function, in this case myCsFunction( ), using the call( ) method.
You should invoke the call( ) method on the client object that corresponds to the client on which
you want to invoke the function. For example, you can invoke the same method in all connected
clients using a for statement, as shown here:

for (var i = 0; i < [Link]; i++) {


[Link][i].call("myCsFunction");
}

14.17.4 See Also


Recipe 14.16 and Recipe 17.6. This chapter has barely scratched the surface of what can be
done with FlashCom server and Server-Side ActionScript. For more information on these topics,
see [Link] for a list of online
documentation and [Link] for a
list of online articles and tutorials.

[ Team LiB ]
[ Team LiB ]

Chapter 15. Loading Assets

Introduction

Recipe 15.1. Loading an External SWF

Recipe 15.2. Loading an External SWF from a Trusting Domain

Recipe 15.3. Loading an External JPEG Image

Recipe 15.4. Loading an External Image (All Formats)

Recipe 15.5. Loading an External MP3 Sound

Recipe 15.6. Loading Remote Content by Proxy

Recipe 15.7. Determining if an Asset Is Loaded

Recipe 15.8. Getting the Percentage of an Asset That Has Loaded

Recipe 15.9. Monitoring Load Progress Using a Progress Bar Component

Recipe 15.10. Monitoring Load Progress Without a Progress Bar Component

[Link] 2004-02-06
Strona 27 z 156

Recipe 15.11. Performing Actions When the Asset Is Loaded

Recipe 15.12. Hiding the Graphics and Text for a Progress Bar
[ Team LiB ]
[ Team LiB ]

Introduction
Loading external .swf files into a Flash movie has long been a part of ActionScript. Beginning
with Flash Player 6 it is also possible to load JPEG and MP3 files into a Flash movie at runtime.
The implications of this are rather important. There are many good reasons for loading external
content into your Flash movies at runtime, such as:

z It is easier and more convenient to manage external assets than to have to open the
Flash document, import new or modified assets, and re-export the .swf file each time you
want to make a change.

z In larger applications it makes sense to have different .swf files that are authored by
different people or teams that can be assembled at runtime by a main, loading movie.
This solves the bottleneck problems that can occur when trying to author a single .swf file
(since only one person at a time can edit a Flash document).

z By loading external assets, you can create applications in which users can load their own
assets into the Flash movie (for an example, see the jukebox application in Chapter 26).

z Loading external assets at runtime enables you to download assets to the client's
computer as they are requested. This is important for large applications. For example,
consider an application that allows a user to view photographs of a room in order to select
a paint color. The application might allow a user to select from 90 colors, and each color
selection might display a different version of the same photograph in which the walls have
been painted that color. It is likely that most users will not view all 90 images, so it
makes sense to avoid downloading all the images (which could create a significant
increase in initial download time). Instead, the appropriate images can be downloaded as
they are requested.

The preceding list includes just a few of the many benefits of loading external assets.
Undoubtedly, you can discover more as you create your own application.

In the recipes throughout this chapter, you can find solutions to all kinds of problems related to
loading external, binary assets at runtime. Binary assets include SWFs, JPEGs, and MP3s. For
more information about loading textual data see the recipes dealing with the LoadVars and XML
classes in Chapter 18 and Chapter 19.

[ Team LiB ]
[ Team LiB ]

Recipe 15.1 Loading an External SWF


15.1.1 Problem
You want to load an external SWF into your Flash movie.

15.1.2 Solution

[Link] 2004-02-06
Strona 28 z 156

Use the loadMovie( ) method.

15.1.3 Discussion
The loadMovie( ) method is the preferred technique for loading external .swf files into a Flash mo
You should call this method from the movie clip into which you want to load the .swf file and pas
the URL at which the .swf file can be found:

// Load a .swf file into myMovieClip using a relative URL to a file in the same
// directory as the loading Flash movie.
[Link]("[Link]");

// Load a .swf file into myMovieClip using a full URL to a file within the same
// domain as the loading Flash movie.
[Link]("[Link]

Flash's security sandbox prevents Flash movies from loading assets from different domains. In o
words, a Flash movie being served from [Link] can load a .swf file from
[Link] or any other server in the same domain (such as [Link]), b
cannot load a .swf file from [Link]. Recipe 15.2 describes how you can create
trusted relationships between domains using allowDomain( ). Recipe 15.6 shows a different
approach that uses a proxy script to load content from any domain.

The loadMovie( ) method replaces the timeline of the movie clip from which it is called with the
timeline of the specified .swf. All existing content within the movie clip is wiped out when loadMo
( ) is called. Therefore, it is convenient to use the createEmptyMovieClip( ) method to create a
container movie clip into which you load the .swf file (as opposed to creating instances at author
time). For example:

// Create myMovieClip using createEmptyMovieClip( ).


_root.createEmptyMovieClip("myMovieClip", 1);

// Load the .swf file into myMovieClip.


[Link]("[Link]");

The loadMovie( ) method works asynchronously. This means that when the ActionScript interpret
encounters a loadMovie( ) call, it initiates the request to load the external content, but it does no
wait for the content to load before proceeding to the next line of code. This can have very impor
ramifications. For example, if you want to resize a movie clip into which you have loaded a new .
file, you have to wait until the file has fully loaded. Here is some sample code to illustrate the
problem:

// Create myMovieClip using createEmptyMovieClip( ).


_root.createEmptyMovieClip("myMovieClip", 1);

// Load the .swf file into myMovieClip.


[Link]("[Link]");

// This displays 0 even if the width of the loaded .swf is greater than 0 because
// width of the movie clip is not properly set until the .swf file has fully load
trace(myMovieClip._width);

To solve this problem you should monitor the loading progress, as shown in Recipe 15.7 and Rec
15.8.

Although it is recommended that you use loadMovie( ) to load content into movie clips when load
external .swf files, there is an exception in which it is preferable to use a different technique. Wh
you load content using loadMovie( ), the content must be fully loaded before it can be viewed.
Therefore, in situations in which you want to stream the loaded .swf file you need to use a differ

[Link] 2004-02-06
Strona 29 z 156

technique. For loading streaming content you must load the .swf file into a level instead of a mov
clip.

The Flash Player can contain more than one level. The default level is level 0, and you can target
in ActionScript using _level0. In fact, you may have noticed that in your movies, _root and
_level0 are often synonymous (this is not necessarily the case when you start adding more leve
Additional levels can be created, and they are simply named level 1 (_level1), level 2 (_level2
etc. As the level number increases, so does the stacking order. In other words, content in level 1
appear in front of content in level 0. You can treat each level in the same way you treat _root. T
means you can move the contents of a level using the _x and _y properties, change the size usin
the _height and _width properties, etc.

You can load content into levels by using the loadMovieNum( ) global function. All you need to do
tell Flash the URL of the content and the level into which you want to load the content.

// Load [Link] into _level1.


loadMovieNum("[Link]", 1);

If the level does not yet exist, Flash first creates it, and then loads the content. If the level alrea
exists, Flash replaces the existing content with the loaded content.

If you load a new .swf file into _level0, you will lose all the contents of the
original movie as well as any other contents that have been loaded into other
levels. Likewise, be careful when using the global loadMovie( ) function. If
you specify a nonexistent path for the target, such as specifying _level1
when no _level1 object exists, loadMovie( ) replaces the current timeline
with the newly loaded movie. To avoid the problem altogether, specify the
target clip as a string, such as "_level1", or use the [Link]( )
method instead.

If you want to hide the contents of level 0 it is better to set the level's visibility to false:

_level0._visible = false;

You can unload levels (remove the contents and the level itself) using the unloadMovieNum( ) gl
function:

// Unload level 2.
unloadMovieNum(2);

The getURL( ) method is also worth noticing. It is useful (aside from opening new browser windo
when you have a Flash movie being played from an HTML page and you want to replace both
the .swf contents and the entire HTML page. This is important if, for example, you want to load a
new .swf file with different dimensions. You can call the getURL( ) method from any movie clip a
well. To replace the contents of the existing HTML page, use the value "_self" for the window
parameter, as follows.

_root.getURL("[Link]", "_self");

15.1.4 See Also


For a detailed discussion of document levels within the Player, see the section "The .swf Docume
`_level' Stack" in Chapter 13 of ActionScript for Flash MX: The Definitive Guide. This chapter is
freely downloadable from [Link]
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 30 z 156

Recipe 15.2 Loading an External SWF from a Trusting


Domain
15.2.1 Problem
You want to load an external .swf file into your Flash movie, but the .swf file resides within a
different domain.

15.2.2 Solution
Use the allowDomain( ) method from within the .swf file you want to load to create a trusting
relationship between the domains.

15.2.3 Discussion
The [Link]( ) method allows you to create trusting relationships
between .swf files in different domains. The allowDomain( ) method must be called from within
the .swf file you wish to load. Therefore, this technique is applicable only if you want to load .swf
files to which you can add the allowDomain( ) method call. Otherwise, see Recipe 15.6 to load
content by a proxy script.

Here is a scenario in which you can use allowDomain( ) to create a trusting relationship: you hav
two .swf files, [Link] and [Link]. The [Link] movie is on [Link], and [Link]
on [Link]. If you want [Link] to be able to load [Link], add the following line
[Link]:

[Link]("[Link]");

This line of code tells [Link] to allow itself to be loaded by any movies in the [Link]
domain. The [Link] movie can then load [Link] using a standard loadMovie( ) method call:

// Create myMovieClip using createEmptyMovieClip( ).


_root.createEmptyMovieClip("myMovieClip", 1);

// Load the trusting .swf file into myMovieClip.


[Link]("[Link]

If you want to allow [Link] to be loaded from more than one domain, you can specify them as
series of parameters passed to the allowDomain( ) method. For example:

[Link]("[Link]", "[Link]", "[Link]"

The parameters of the allowDomain( ) method can be in any of the following formats:

"[Link]"
"[Link]
"[Link]

15.2.4 See Also


Recipe 15.6 and Recipe 17.4

[ Team LiB ]

[Link] 2004-02-06
Strona 31 z 156

[ Team LiB ]

Recipe 15.3 Loading an External JPEG Image


15.3.1 Problem
You want to load a standard JPEG graphic into your Flash movie from a remote URL.

15.3.2 Solution
Use the [Link]( ) method.

15.3.3 Discussion
The loadMovie( ) method allows you to load not only .swf files but also JPEG images (.jpg
or .jpeg files) into your Flash movie. The syntax and usage of the method is similar whether
you are loading a .swf file or a JPEG:

// Load a JPEG into myMovieClip.


[Link]("[Link]");

You should load external JPEGs into a holder movie clip nested within another movie clip
because when you load any content into a movie clip using loadMovie( ), the movie clip's
timeline is replaced by that content. When you load a JPEG into a movie clip, therefore, the
movie clip's timeline is replaced by the image, and you can no longer control that object with
the properties and methods of a movie clip. However, if the movie clip into which you load the
JPEG is nested within a parent movie clip, then the parent can still be controlled as a movie clip
(and the nested image is controlled correspondingly).

Here is a good methodology to follow when loading JPEGs into Flash movies:

1. Begin with an existing movie clip or create a new movie clip using createEmptyMovieClip
( ). This clip will act as the parent clip.

2. Create a nested movie clip within the parent using createEmptyMovieClip( ). This is the
movie clip into which the JPEG should be loaded.

3. Call the loadMovie( ) method from the nested movie clip (not the parent clip).

4. Once the JPEG has loaded, control the image using the parent movie clip.

For example:

// Create a parent movie clip.


_root.createEmptyMovieClip("myMovieClip", 1);

// Create a nested movie clip.


[Link]("imageHolder", 1);

// Load the JPEG into the nested movie clip.


[Link]("[Link]");

JPEGs, like .swf files, are loaded into Flash movies asynchronously (see Recipe 15.1 for an
explanation). Therefore, the same need to monitor load progress applies. Furthermore,
loadMovie( ) loads only nonprogressive JPEG files; other formats are not directly supported.

[Link] 2004-02-06
Strona 32 z 156

15.3.4 See Also


The global loadMovie( ) and loadMovieNum( ) functions can also load JPEG files. See Recipe
15.4 for information on loading files in other image formats. See Recipe 15.1 for more
information on how to use the loadMovie( ) method. See Recipe 15.7 and Recipe 15.8 for more
information on monitoring load progress.

[ Team LiB ]
[ Team LiB ]

Recipe 15.4 Loading an External Image (All Formats)


15.4.1 Problem
You want to load image formats such as BMP, GIF, PNG, or progressive JPEG files into your Flash
movie.

15.4.2 Solution
Because [Link]( ) supports only nonprogressive JPEG files, convert the image into
proper JPEG format before calling loadMovie( ).

15.4.3 Discussion
The Flash Player supports runtime loading of only nonprogressive JPEG images. If you want to lo
images of another format, you have two basic options:

z Convert the image to JPEG format and save it to the server during authoring time.

z Use a server-side script to convert the image from the original format to a JPEG on the fly.

This recipe provides several different scripts depending on the server platform you are using.
ColdFusion, Perl, .NET, and PHP scripts are included.

The ColdFusion version of the script is Java-based, so it requires ColdFusion MX. Additionally, thi
script requires that the <cfcontent> tag is enabled on the server. Some server administrators
disable this tag for security reasons. You can download the necessary files, already compiled, fro
[Link]

Alternatively, if you want to write the code yourself, follow these steps:

1. To compile the Java classes, you need the Java 2 Standard Development Kit (J2SDK),
available from [Link]

2. Download and install the Java Advanced Imaging (JAI) API on your local computer. You ne
this so that you can compile the Java class. You can download the API from
[Link]

3. Make sure to include the JAI JAR files in your system's classpath.

4. Upload the jai_codec.jar and jai_core.jar files to the WEB-INF/lib/ directory on your
ColdFusion server under the web root.

[Link] 2004-02-06
Strona 33 z 156

5. Create a new Java document and save it as [Link] to your local computer.

6. Add the following code to the [Link] document:

package imaging;

import [Link].*;
import [Link].*;

public class ImageConverter {

private RenderedOp image = null;

// This method accepts the location of the image to convert and the locati
// where to save the new image file locally.
public void convertImage(String location, String fileSaveLocation)
throws Exception{
URL url = new URL(location);
image = [Link]("url", url);
[Link]("fileStore", image, fileSaveLocation, "JPEG", null);
}
}

7. Once you have added the code to the file, save it and compile it into [Link]
Keep the .class file in the same directory as the source.

8. Create a new directory named imaging within the WEB-INF/classes/ directory on your
ColdFusion server under the web root.

9. Upload [Link] to the imaging directory on the server.

10. Create a new ColdFusion page and name it [Link].

11. Add the following code to [Link]:

<cfobject type="java" name="ic" class="[Link]" action="creat


<cflock type="exclusive" timeout="5">
<cfscript>
tempImage = "#ExpandPath('./')#" & "[Link]";
[Link]([Link], tempImage);
</cfscript>
</cflock>
<cfcontent deletefile="yes" file="#tempImage#" type="image/jpeg">

12. Upload [Link] to your ColdFusion web root.

13. Refer to the testing instructions at the end of this recipe, following the alternative language
implementations.

If your server uses Perl, then follow these steps.

1. Make sure that the ImageMagick Perl module is installed. (This module is fairly common bu
not ubiquitous; ask your webmaster to install it if necessary.) ImageMagick is available fro
[Link]

2. Create a new document named [Link]. (Some servers use .pl in place of the .cgi
extension. If you are unsure, contact your webmaster.)

3. Add the following code to [Link]:

[Link] 2004-02-06
Strona 34 z 156

#!/usr/bin/perl

use CGI;
use HTTP::Request;
use LWP::UserAgent;
use Image::Magick;

my $cgi = CGI->new( );

# Get the image from the URL.


my $ua = LWP::UserAgent->new;
my $request = HTTP::Request->new(GET => $cgi->param('url'));
my $response = $ua->request($request);

# Read the image into ImageMagick.


my $image = Image::Magick->new( );
$image->BlobToImage($response->content);

# Print the header and JPEG image to STDOUT.


print $cgi->header(-type=>'image/jpeg');
$image->Write('jpg:-');

4. Save the document and upload it to your server in the directory for CGI scripts (usually cg
cgi-bin).

5. Make sure to correctly set the permissions on the file so that it can be executed (this applie
to Unix servers only). The correct permissions value is 755. You can set the permissions fr
most FTP programs or use chmod.

6. Refer to the testing instructions at the end of this recipe, following the alternative language
implementations.

If you are using a server that supports [Link], you can write an ASPX page that can convert
images to JPEGs on the fly. The .NET framework includes all the classes necessary to accomplish
this, and therefore you do not need to concern yourself with adding any additional assemblies.
Follow these steps to create the [Link] script:

1. Create a new ASPX document and name it [Link].

2. Add the following code to the document:

<!-- set the content type for the page to image/jpeg so that the
correct data format is returned -->
<%@ Page Language="C#" ContentType="image/jpeg" %>

<%
// Get the image from the URL passed to this ASPX page.
[Link] webRequest =
([Link])[Link](
(string)[Link]["url"]);
[Link] webResponse =
([Link])[Link]( );
[Link] inStream = [Link]( );

// Create an Image object using the stream obtained from the image URL.
[Link] img = [Link](inStream);

// Save the output the image in JPEG format to the output stream.
[Link]([Link], [Link]);
%>

[Link] 2004-02-06
Strona 35 z 156

3. Save the document and upload it to your server.

4. Refer to the testing instructions at the end of this recipe, following the alternative language
implementations.

If you want to use PHP to convert images on the fly, then you can use PHP's image functions.
However, be aware that the image functions require you to have the GD library installed for PHP
Most web hosts that offer PHP have this library installed, but if not, ask for it to be installed.
Complete the following steps to create a PHP script that converts JPEG, GIF, PNG, and BMP imag
to a JPEG format that Flash can read:

1. Create a new PHP document and name it [Link].

2. Add the following code to the document:

<?php
// The following line is needed only if gd wasn't compiled in.
if (!extension_loaded('php_gd.so')) exit; // Change to php_gd.dll on Windows
header('Content-type: image/jpeg');
$imgname = $_GET['url'];
switch(substr($imagename, -3)) {
case 'jpg':
case 'jpeg':
$im = imagecreatefromjpeg($imgname);
break;
case 'gif':
// This option is only available in gd1 or in the bundled gd library.
$im = imagecreatefromgif($imgname);
break;
case 'png':
$im = imagecreatefrompng($imgname);
break;
case 'bmp':
$im = imagecreatefromwbmp($imgname);
break;
}
imagejpeg($im, '', 100);
?>

3. Save the document and upload it to your server.

4. See the following testing instructions.

To test the script in your chosen language, run it from a web browser. Regardless of the languag
implementation, the script expects a parameter named url that specifies the location of the ima
to convert. Each implementation should display the image (as a JPEG) in the browser window.

For example, if the CFML script is being served at [Link] a


the image that you want to convert is at [Link] you can tes
the script in a browser using [Link]
url=[Link]

Similarly, you can test the Perl script in a browser using the following URL syntax:

[Link]
url=[Link]

You can test the [Link] script in a browser using:

[Link] 2004-02-06
Strona 36 z 156

[Link]
url=[Link]

You can test the PHP script in a browser using:

[Link]

Regardless of which language implementation you choose, you use it in the same manner. Once
you have one of the server-side scripts in place, you can call it from a Flash movie with
[Link]( ). For example, you can load a converted PNG into a movie with the followi
code:

[Link](
"[Link]

As an additional benefit, each of the conversion scripts also overcomes Flash's sandbox security
restrictions. This means that you can load images of any format from any domain.

15.4.4 See Also


Recipe 15.3 and Recipe 15.6
[ Team LiB ]
[ Team LiB ]

Recipe 15.5 Loading an External MP3 Sound


15.5.1 Problem
You want to load an MP3 sound file into your Flash movie at runtime.

15.5.2 Solution
Use the [Link]( ) method.

15.5.3 Discussion
You can use the [Link]( ) method to load MP3 sound files into your Flash movie at
runtime. The first step is to create a Sound object. For this purpose, use the custom
createNewSound( ) method from Recipe 13.1:

// Include the [Link] file from Chapter 13.


#include "[Link]"
mySound = [Link]( );

Next, call the loadSound( ) method from the Sound object. You should pass the loadSound( )
method two parameters: the URL where the MP3 can be found and a Boolean value indicating
whether the sound should be streamed. Be careful to select the correct option for streaming
because there is a major difference between streamed and nonstreamed sounds. Streaming
sounds begin playing automatically as soon as enough data has been buffered (i.e., enough of
the sound has loaded into the Flash movie). Nonstreamed sounds, on the other hand, must load
completely into the movie before you can instruct them to start playing. You can control a
nonstreamed sound using all the methods and properties of a Sound object. Streaming sounds,
however, cannot be controlled other than by stopping the playback:

// Load an MP3 from the same directory as the Flash movie. Set streaming to false
[Link]("myMP3.mp3", false);

[Link] 2004-02-06
Strona 37 z 156

You cannot start the playback of a nonstreamed sound until it has loaded completely, nor can
you retrieve any information about the sound (duration, position, etc.) until after the loading
is finalized. Therefore, you should monitor the loading of a sound and define a callback function
to be called once loading is complete. You can use the same techniques to monitor the loading o
an MP3 that you use to monitor the loading of a JPEG or SWF. See Recipe 15.7 and Recipe 15.8
for more information on these techniques.

15.5.4 See Also


Recipe 2.7, Recipe 13.2, and Recipe 14.5

[ Team LiB ]
[ Team LiB ]

Recipe 15.6 Loading Remote Content by Proxy


15.6.1 Problem
You want to load content into your Flash movie from another domain and are prevented from do
so by Flash's security sandbox.

15.6.2 Solution
Use a proxy script on the server to overcome the domain restrictions.

15.6.3 Discussion
Flash's security sandbox prevents you from loading content into your Flash movies from other
domains indiscriminately. You can load .swf files, JPEGs, and MP3s from the same domain (or
subdomain), and you can also load .swf files from a trusting domain. However, if you need to loa
content that is not in the same domain, and it is not a trusted .swf file, you have at least three
options:

z Copy the content to the same domain. This is the simplest solution if it is possible for you t
implement.

z Create a DNS entry within your own domain such that the remote server is mapped to a
machine name within the same domain. For example, you can create a DNS entry such tha
[Link] maps to [Link] within your own domain
This solution requires that you have administrator access and DNS knowledge.

z Create and use a proxy script that downloads the remote content to the local server (eithe
memory or to disk) on the fly and then returns the local copy to the Flash movie.

Included in this recipe are instructions for creating proxy scripts for ColdFusion, Perl, .NET, and P

If you use ColdFusion, you can write a proxy script with just a few lines of code. Be aware that fo
this to work, the <cfcontent> tag must be enabled on the server. Some web hosts disable this t
for security purposes. To create a ColdFusion proxy script, complete the following steps:

1. Create a new CFML document named [Link].

2. Add the following code to the document:

<!--- Make sure the code is single-threaded so that the requested content

[Link] 2004-02-06
Strona 38 z 156

from one client doesn't get sent to another by mistake. --->


<cflock timeout="10" type="exclusive">

<!--- Get the remote content and save it to the server --->
<cfhttp url="#[Link]#" method="get" path="#ExpandPath("./")#"></cfhttp>

<!--- Display the content that has been saved. Delete the file afterward.
<cfcontent file="#ExpandPath(GetFileFromPath([Link]))#" deletefile="yes"
type="#[Link]#">
</cflock>

3. Save the document and upload it to your server.

4. Refer to the testing instructions at the end of this recipe, following the alternative language
implementations.

If Perl is your choice for server-side scripting, you can create a proxy script by completing the
following steps:

1. Create a new Perl script document named [Link].

2. Add the following code to the document:

#!/usr/bin/perl

# Proxy for Flash to bypass sandbox security limitations


# Supports GET and POST CGI methods
# Arun Bhalla (bhalla@[Link]) 2002.07.15

use CGI qw/-oldstyle_urls/;


use HTTP::Request::Common;
use LWP::UserAgent;

my $cgi = new CGI;


my $ua = new LWP::UserAgent;
my $location;
my $method;

if ($cgi->param( )) {
# extract the location (URL) and method values from the parameter list
$location = $cgi->param('location');
$method = $cgi->param('method');
$cgi->delete('location');
$cgi->delete('method');
}

$method = uc($method) || 'GET';


my $res;

if ($method eq 'GET') {
if ($location =~ /\?/) {
$location .= '&' . $cgi->query_string;
} else {
$location .= '?' . $cgi->query_string;
}

$req = HTTP::Request->new(GET => $location);


} elsif ($method eq 'POST') {
$req = HTTP::Request->new(POST => $location);
$req->content_type($cgi->content_type( ) ||
'application/x-www-form-urlencoded');
$req->content($cgi->query_string);

[Link] 2004-02-06
Strona 39 z 156

my $res = $ua->request($req);
print $res->headers_as_string, "\n", $res->content;(

3. Save the document and upload it to your server.

4. Refer to the testing instructions at the end of this recipe, following the alternative language
implementations.

If you are using [Link], follow these steps:

1. Create a new ASPX document named [Link].

2. Add the following code to the document:

<%@ Page Language="C#" %>


<%

// First, get the content from the remote domain.


[Link] webRequest =
([Link])[Link](
(string)[Link]["url"]);
[Link] webResponse =
([Link])[Link]( );
[Link] inStream = [Link]( );

// Next, read the content into an array of bytes.


byte[] buffer = new byte[(int)[Link]];
[Link](buffer, 0, (int)[Link]);

// Next, set the appropriate content type and write the data as binary outpu
[Link] = [Link];
[Link](buffer);

// Finally, close everything that is still open.


[Link]( );
[Link]( );
%>

3. Save the document and upload it to your server.

4. Refer to the testing instructions at the end of this recipe, following the alternative language
implementations.

If you are using PHP, follow these steps:

1. Create a new PHP document named [Link].

2. Add the following code to the document:

<?php
// Stop Script from Timing Out.
set_time_limit(0);

// Check for variable "url" in querystring.


if ($_GET['url']) {
$fp = fopen ($_GET['url'], "r");
$file = "";
while (!feof($fp)) {

[Link] 2004-02-06
Strona 40 z 156

// Read Target File in chunks of 4 KB (4096 bytes).


$file .= fgets($fp, 4096);
}
fclose($fp);

// Send File Data to Flash


echo $file;
}
?>

3. See the following testing instructions.

To test the script in your chosen language, run it from a web browser. Regardless of the languag
implementation, the script expects a parameter named url that specifies the location of the data
copy to the proxy server.

For example, if the CFML script is being served at [Link]


and the content that you want to retrieve is at [Link] you
test the script in the browser using:

[Link]
url=[Link]

This should retrieve the MP3 and play it in the browser window (if you have the appropriate play

Similarly, you can test the Perl script in a browser using the following URL syntax:

[Link]
url=[Link]

You can test the [Link] script in a browser using the following URL syntax:

[Link]
url=[Link]

You can test the PHP script in a browser using the following URL syntax:

[Link]
url=[Link]

Regardless of which language implementation you choose, you use it in the same manner. When
you invoke the loadMovie( ) or loadSound( ) method from your Flash movie, instead of passing t
URL to the remote asset, pass the URL to the proxy script that is running on the same domain an
append the URL of the remote asset as a query string. For example:

[Link]("[Link]
url=[Link]

When you use a proxy script, be aware that the content can take even longer to load into the Pla
than it would if the content is loaded directly. The reason is that the asset has to first download t
the proxy server, then to the Player.

15.6.4 See Also


Recipe 15.2, Recipe 15.4, and Recipe 17.4
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 41 z 156

Recipe 15.7 Determining if an Asset Is Loaded


15.7.1 Problem
You want to know if an asset has completed loading into the Player.

15.7.2 Solution
Use the getBytesLoaded( ) and getBytesTotal( ) methods. Alternatively, define a custom isLoade
property for the Sound and MovieClip classes.

15.7.3 Discussion
There are three kinds of assets that can be loaded into a Flash movie using ActionScript: .swf file
JPEGs, and MP3s. The first two types are loaded into movie clips, and MP3s are loaded into Soun
objects. Both the MovieClip and Sound classes have methods named getBytesLoaded( ) and
getBytesTotal( ), which you can use to determine whether the asset is loaded. So, no matter wh
type of asset you are loading, the ActionScript is essentially the same.

The getBytesLoaded( ) method returns the number of bytes that are currently loaded into a mov
clip or Sound object, and the getBytesTotal( ) method returns the number of bytes that will be
loaded when the asset has been completely received. You know that the asset is completely load
into the Player once the values returned by the two methods are equal.

The getBytesLoaded( ) and getBytesTotal( ) methods use uncompressed file


sizes and thus are not necessarily accurate measurements of the
compressed asset sizes. However, their ratio yields a reasonable estimate of
what percentage of the asset has loaded.

There is a caveat when comparing the values returned by getBytesLoaded( ) and getBytesTotal(
Before an object retrieves the information about the file it is loading (such as the total file size),
both getBytesLoaded( ) and getBytesTotal( ) return 0. Thus, there is always a moment during
which the two values are equal to 0, but the asset has not been loaded. You can address this
problem by also checking whether the total bytes is also greater than zero. Therefore, we have:

if ( ([Link]() == [Link]( )) &&


([Link]( ) > 0) ) {
trace("The asset has completed loading");
}

If you load many assets into your Flash movies, consider adding a custom property to the Sound
and MovieClip classes that handles all this logic for you. The following code should be added to th
[Link] file created in Chapter 13:

// The getIsLoaded( ) method returns true if the asset has loaded completely;
// otherwise, it returns false.
[Link] = function ( ) {
return ( ([Link]() == [Link]( )) &&
([Link]( ) > 0) );
};

// The isLoaded property is created for all Sound objects by calling the
// addProperty( ) method from the Sound class's prototype. This code configures
// isLoaded property to automatically call the getIsLoaded( ) method.
[Link]("isLoaded", [Link], null);

[Link] 2004-02-06
Strona 42 z 156

You should add the following code to the [Link] file created in Chapter 7. This code is exac
the same as the preceding code, except that it is applied to the MovieClip class instead of the
Sound class.

[Link] = function ( ) {
return ( ([Link]() == [Link]( )) &&
([Link]( ) > 0) );
};
[Link]("isLoaded", [Link], null

Once you have defined the isLoaded property, you can check to see if an asset has loaded, as
follows:

if ([Link]) {
trace("The asset has completed loading");
}

In real-life scenarios, you monitor the load status repeatedly rather than simply checking it once
Monitoring the load status is demonstrated in Recipe 15.9 and Recipe 15.10.

15.7.4 See Also


Recipe 15.8. Also refer to Recipe 12.6 for an explanation of the techniques used in this recipe wi
regard to the addProperty( ) method and the isLoaded property. See Recipe 18.3 for details on
detecting the loading of text variables using the LoadVars class. See Recipe 19.11 for details on
monitoring XML loading progress.

[ Team LiB ]
[ Team LiB ]

Recipe 15.8 Getting the Percentage of an Asset That Has


Loaded
15.8.1 Problem
You want to determine what percentage of an asset has loaded.

15.8.2 Solution
Use the getBytesLoaded( ) and getBytesTotal( ) methods. Alternatively, you can create a custom
percentLoaded property for the Sound and MovieClip classes.

15.8.3 Discussion
You can use the getBytesLoaded( ) and getBytesTotal( ) methods to determine the percentage o
an asset that has loaded. The percentage is calculated by determining the ratio of the bytes load
to the total bytes:

ratio = [Link]() / [Link]( );

The ratio is always between 0 and 1. Therefore, to format the percentage for display, you can
multiply the ratio by 100. If you want the percentage to be a whole number, you can round the
result as well:

[Link] 2004-02-06
Strona 43 z 156

percentLoaded = [Link](ratio * 100);

The only caveat is that before any information about the asset has been retrieved (such as its fil
size), the getBytesLoaded( ) and getBytesTotal( ) methods both return 0, and 0 divided by 0 doe
not yield a valid number. Therefore, you should also use the isNaN( ) (is Not-a-Number) function
catch if the percentage is not a valid number and set it to 0 instead:

if (isNaN(percentLoaded)) {
percentLoaded = 0;
}

You can encapsulate this process by defining a custom percentLoaded property for the Sound an
MovieClip classes. The following code should be added to the [Link] file created in Chapter 13

// The getPercentLoaded( ) method returns the percent that has loaded as a whole
// number. It also makes sure that the returned value is always a valid number.
[Link] = function ( ) {
var pLoaded = [Link](([Link]() / [Link]( )) * 100
if (isNaN(pLoaded)) {
pLoaded = 0;
}
return pLoaded;
};

// The percentLoaded property is created for all Sound objects using the
// addProperty( ) method. This code configures the Sound class so that whenever
// percentLoaded property is accessed, the getPercentLoaded( ) method is called
// automatically.
[Link]("percentLoaded",
[Link], null);

You should add the following code to the [Link] file created in Chapter 7:

[Link] = function ( ) {
var pLoaded = [Link](([Link]() / [Link]( )) * 100
if (isNaN(pLoaded)) {
pLoaded = 0;
}
return pLoaded;
};
[Link]("percentLoaded",
[Link], null);

Once you have defined the percentLoaded property for the Sound and MovieClip classes, you ca
get the percentage that has loaded using a single call to the property:

trace([Link]);

In real-life scenarios, you monitor the load status repeatedly, rather than simply checking it once
Monitoring the load status is demonstrated in Recipe 15.9 and Recipe 15.10.

15.8.4 See Also


Recipe 15.7. Also refer to Recipe 12.6 for an explanation of the techniques used in this recipe wi
regard to the addProperty( ) method and the percentLoaded property. See Recipe 18.3 for deta
on detecting the loading of text variables using the LoadVars class. See Recipe 19.11 for details
monitoring XML loading progress.
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 44 z 156

Recipe 15.9 Monitoring Load Progress Using a Progress


Bar Component
15.9.1 Problem
You want to display the progress of an asset-loading operation using a progress bar.

15.9.2 Solution
Use the Progress Bar component.

15.9.3 Discussion
The Progress Bar component is included in the Flash UI Components Set 2. You can download th
components set for free from the Macromedia Flash Exchange at
[Link] The full URL is:

[Link]
view=sn110#loc=en_us&view=sn111&viewName=Flash%
20Extension&extID=365880&lc_id=55920

After downloading the components set, install it using the Macromedia Extension Manager (also a
free download from the Flash Exchange). You have to restart Flash to see the new components.
Once you restart Flash, you should see the Flash UI Components Set 2 option in the Components
panel menu.

You can add a progress bar to a movie manually at authoring time, or you can add the Progress
component to the movie's Library and use the attachMovie( ) method to add the component to t
movie at runtime:

_root.attachMovie("FProgressBarSymbol", "pBar", 1);

You should use the setLoadTarget( ) method of a progress bar instance to specify a movie clip or
sound that the component should monitor:

// This code configures a progress bar named soundPBar to monitor the load progre
// of a sound named mySound_sound.
[Link](mySound_sound);

// This code configures a progress bar named jpegPBar to monitor the load progres
// a JPEG into a movie clip named myMovieClip_mc.imageHolder_mc.
[Link](myMovieClip_mc.imageHolder_mc);

Here is a complete example showing how to monitor the loading of an MP3:

// Include the [Link] file from Chapter 13.


#include "[Link]"

// Create the Sound object.


mySound_sound = [Link]( );

// Load an MP3 from the same directory as the Flash movie. Set streaming to false
mySound_sound.loadSound("myMP3.mp3", false);

// Add the progress bar.

[Link] 2004-02-06
Strona 45 z 156

_root.attachMovie("FProgressBarSymbol", "pBar", 1);

// Set the progress bar to monitor the sound.


[Link](mySound_sound);

The progress bar indicates the load progress each time a new chuck of data is received. The
perceived smoothness of this depends on the connection speed and latency.

15.9.4 See Also


Recipe 15.11 and Recipe 15.12. See Recipe 15.10 for an alternative approach. You can also find
other third-party progress bars by going to the Macromedia Flash Exchange
([Link] and searching for "Progress Bar."
[ Team LiB ]
[ Team LiB ]

Recipe 15.10 Monitoring Load Progress Without a Progre


Bar Component
15.10.1 Problem
You want to monitor the load progress of an asset without having to use the Progress Bar compo

15.10.2 Solution
Create a movie clip and define an onEnterFrame( ) method that monitors the load progress of th
asset, or use an interval function. Alternatively, you can create a custom LoadMonitor class.

15.10.3 Discussion
The Progress Bar component is a handy, pre-built resource for graphically representing the progr
an operation, such as asset loading. However, the Progress Bar component is overkill if you mere
want to monitor the download progress in the background in order to trigger other actions when
appropriate.

You can use a movie clip with an onEnterFrame( ) method to monitor the load progress of an ass
Within the onEnterFrame( ) method, you should continually check the percentage of the asset th
loaded.

// Create a movie clip and begin loading a .swf file into it.
_root.createEmptyMovieClip("swfHolder", 1);
[Link]("[Link]");

// Create a movie clip to monitor the load progress.


_root.createEmptyMovieClip("loadMonitorMc", 2);

// Define an onEnterFrame( ) method for the monitor movie clip.


[Link] = function ( ) {

// Execute trace( ) once the .swf file has finished loading.


if (_root.[Link]) {
trace("SWF has loaded");
}
};

Or, you can achieve similar results using an interval function instead of a movie clip:

[Link] 2004-02-06
Strona 46 z 156

// Create a movie clip and begin loading a .swf file into it.
_root.createEmptyMovieClip("swfHolder", 1);
[Link]("[Link]");

// Define a function to call at an interval.


function monitor ( ) {
// Execute trace( ) once the .swf file has finished loading.
if ([Link]) {
trace("SWF has loaded");
}
};

// Invoke monitor( ) every 100 milliseconds to update the load status.


monitorInterval = setInterval("monitor", 100);

You can also write a custom LoadMonitor class that you can reuse in multiple movies. You can ad
following code to a [Link] file in your Flash Include directory for easy inclusion in other
projects:

// The LoadMonitor constructor accepts a reference to the object it should monito


// This object can be a Sound object or a movie clip.
_global.LoadMonitor = function (obj) {

// Create a property with the value of the monitored object.


[Link] = obj;

// Set the interval.


[Link] = setInterval(this, "monitor", 100);
};

// The setChangeHandler( ) method allows you to define a callback function that


// invoked automatically each time there is load progress. The method accepts a
// reference to the callback function.
[Link] = function (callback) {
[Link] = callback;
};

[Link] = function ( ) {

// If the percent loaded is greater than the last time monitor( ) was called,
// the callback function, passing it a reference to the monitored object. The
// parameter makes it convenient to reference the monitored object within the
// callback function.
var pLoaded = [Link];
if (pLoaded != [Link]) {
[Link]([Link]);
}
[Link] = pLoaded;

// If the monitored object is fully loaded, delete the interval so as to avoid


// unnecessarily calling this method again.
if ([Link]) {
clearInterval([Link]);
}
};

You can use the custom LoadMonitor class as follows:

#include "[Link]"

// Include the [Link] file from Chapter 13 for its createNewSound( ) method.
#include "[Link]"

[Link] 2004-02-06
Strona 47 z 156

// Define the callback function.


function playSound (obj) {
if ([Link]) {
[Link]( );
}
}

// Create the Sound object.


mySound_sound = [Link]( );

// Load an MP3 from the same directory as the Flash movie. Set streaming to false
mySound_sound.loadSound("myMP3.mp3", false);

// Create the LoadMonitor object to monitor the progress of mySound_sound and set
// callback function.
lm = new LoadMonitor(mySound_sound);
[Link](playSound);

Consider using the LoadMonitor class in situations in which you want to monitor the load progres
assets, but you don't want to visually represent this to the user. The resulting .swf will have a sl
smaller file size, and you can include the LoadMonitor class without having to add a component t
library or attach a movie clip to your movie.

15.10.4 See Also


Recipe 15.9 and Recipe 15.11
[ Team LiB ]
[ Team LiB ]

Recipe 15.11 Performing Actions When the Asset Is


Loaded
15.11.1 Problem
You want to define actions to occur when an asset has loaded completely.

15.11.2 Solution
For sounds, use the onLoad( ) event handler method. For all other datatypes, set a callback
function using the setChangeHandler( ) of the FProgressBar class or the custom LoadMonitor
class (depending on which technique you are using).

15.11.3 Discussion
The Sound class provides a convenient way to perform actions when the sound has loaded.
Simply define an onLoad( ) method for the Sound object and Flash automatically invokes that
method once the sound has loaded:

mySound_sound.onLoad = function ( ) {
// When the sound loads, tell it to start.
[Link]( );
};

Other types of assets require another way of determining when the asset has loaded. When
using the Progress Bar component, you can define a callback function that is called each time
there is progress in the loading of the asset. The setChangeHandler( ) method accepts the name
of a callback function as a string. It also accepts an optional, second parameter specifying a

[Link] 2004-02-06
Strona 48 z 156

reference to the object in which the function can be found. If the second parameter is omitted,
the timeline in which the Progress Bar component exists is assumed. The callback function is
automatically passed a reference to the Progress Bar component.

// Define the callback function.


function onProgress (cmpt) {
trace("onProgress( ) called for the progress bar " + cmpt);
}

// Set the callback function for updates on the progress bar named pBar.
[Link]("onProgress");

Note that the callback function is called every time there is an update in the load progress for
the asset being monitored. If you want to perform certain actions only when the loading has
completed, use the [Link]( ) method to check whether the loading
progress has reached 100%. For example:

function onProgress (cmpt) {


// Ensure that the asset has completed loading before performing the actions.
if ([Link]( ) == 100) {
trace("Loading complete");
}
}

If you are using the custom LoadMonitor class (see Recipe 15.10), you can set a callback
function for the LoadMonitor object using the setChangeHandler( ) method. Like the change
handler callback function for FProgressBar, the callback function for LoadMonitor is called each
time load progress is made. Therefore, within the callback function, you should first determine
whether the asset has completely loaded before performing any actions.

// Include the custom LoadMonitor class.


#include "[Link]"

// Define the callback function.


function resize (obj) {
if ([Link]) {
obj._xscale = 50;
obj._yscale = 50;
}
}

// Create a movie clip and load an external .swf file.


[Link]("myMovieClip_mc", 1);
myMovieClip_mc.loadMovie("[Link]");

// Create the LoadMonitor object to monitor the progress of myMovieClip_mc and se


// the callback function.
lm = new LoadMonitor(myMovieClip_mc);
[Link](resize);

15.11.4 See Also


Recipe 15.9 and Recipe 15.10
[ Team LiB ]
[ Team LiB ]

Recipe 15.12 Hiding the Graphics and Text for a Progress


Bar

[Link] 2004-02-06
Strona 49 z 156

15.12.1 Problem
You want to hide the graphics and/or text for a progress bar.

15.12.2 Solution
Set the _visible property of the progress bar instance to false, or use the setDisplayGraphics
( ) and/or setDisplayText( ) methods of the progress bar.

15.12.3 Discussion
Progress bars display the loading progress using both graphical and textual means. Although you
may want to show both at times, you may want to use the component to monitor the load
progress silently—that is, without displaying anything on the screen. Even if you want to display
the progress to the user, once the asset has loaded, you generally should turn off the display of
the progress bar. In either case, you can easily hide the visual components of the progress bar
by setting its _visible property to false:

pBar._visible = false;

Here is some sample code that hides a progress bar once the asset has completed loading:

function onProgress (cmpt) {


if ([Link]( ) == 100) {
cmpt._visible = false;
}
}

You can hide the graphics and text of the progress bar separately using the setDisplayGraphics
( ) and setDisplayText( ) methods. Both methods accept a Boolean parameter, in which true
shows the graphics or text and false hides them.

// Add the progress bar. (You must have already added the symbol to your library.
_root.attachMovie("FProgressBarSymbol", "pBar", 1);

// Hide the graphics.


[Link](false);

// Hide the text.


[Link](false);

15.12.4 See Also


Recipe 15.9

[ Team LiB ]
[ Team LiB ]

Chapter 16. Storing Persistent Information

Introduction

[Link] 2004-02-06
Strona 50 z 156

Recipe 16.1. Storing and Retrieving Locally Persistent Information

Recipe 16.2. Adding Data to a Client-Side Shared Object

Recipe 16.3. Reading Values from a Client-Side Shared Object

Recipe 16.4. Saving a Local Shared Object

Recipe 16.5. Sharing Information Between Movies Within the Same Domain

Recipe 16.6. Storing Persistent Data on the Server

Recipe 16.7. Saving Remote Shared Object Data

Recipe 16.8. Checking for Updates to Remote Shared Objects

Recipe 16.9. Reading Values from a Server-Side Shared Object

Recipe 16.10. Adding Data to a Server-Side Shared Object


[ Team LiB ]
[ Team LiB ]

Introduction
You already know how to work with data that is stored in memory as long as a Flash movie is
open. In fact, almost all data you work with in ActionScript is of this type. But what if you want
to be able to store data between Flash movie sessions? Or what if you want to be able to share
data between Flash movies running on the same client, or even on different clients? To
accomplish these feats, we'll need to look at how we can store and transmit data outside of the
Flash Player.

This chapter addresses both client-side and server-side data. First, we'll examine client-side
data. Prior to Flash 6, the only way to store persistent data on the client was with complex
techniques that invoked external JavaScript functions to read and write cookies. However, Flash
6 introduced local shared objects to ActionScript, making it possible to store and retrieve
persistent data on the client without the use of these other, complicated techniques.

We'll also take a closer look at server-side data. Prior to Flash 6, server-side data manipulation
was limited to invoking server-side scripts (using loadVariables( ) or an XML object) to read and
write data to text files and/or databases on the server. The problem was, of course, that this
functionality required that you had the necessary scripts on the server. While this kind of
functionality still has its place (although the LoadVars class supercedes the older loadVariables
( ) method), this chapter examines how remote shared objects, used in conjunction with
FlashCom, allow us to work with server-side data in a different way.

Shared objects come in several varieties. Local shared objects (LSOs) are similar to browser
cookies in that they are stored on the client's machine. Local shared objects are useful for
storing the same kinds of information for which cookies have traditionally been used, such as
the capability for a web site to remember a user so that the user does not have to manually log
in during each visit. But LSOs are more powerful than cookies because they can store more
data than cookies and store native ActionScript datatypes. Local shared objects are available to
any Flash 6 movie, and they do not require any additional software on the client or on the
server.

In contrast with local shared objects, remote shared objects (RSOs) are available only for
movies that are clients of a Flash Communication Server MX (FlashCom) application (see
Chapter 14). Remote shared objects allow you to store persistent data that is accessible by all

[Link] 2004-02-06
Strona 51 z 156

clients of that application. Remote shared objects can also be used for nonpersistent data
sharing between clients (see Recipe 16.5).

Local shared objects exist on the client only. On the other hand, remote shared objects must be
opened on both the client and the server. Throughout this chapter, we refer to shared objects
that are opened on the client (whether local or remote shared objects) as client-side shared
objects, and we refer to shared objects opened on the server (using code within an .asc file) as
server-side shared objects. This is an important distinction because the ActionScript that you
use in each case is different.
[ Team LiB ]
[ Team LiB ]

Recipe 16.1 Storing and Retrieving Locally Persistent


Information
16.1.1 Problem
You want to store information on a client's computer that persists between movies and
sessions.

16.1.2 Solution
Use a local shared object (LSO).

16.1.3 Discussion
Local shared objects are to Flash what cookies are to web browsers—but more so. They are
called "super cookies" by some developers because they allow you to store potentially large
amounts of data (if the user allows) and because they also allow you to store and retrieve many
native ActionScript datatypes (as well as objects created from custom classes). Local shared
object files are saved to the client computer. Flash movies within the same domain can write to
and read from these files by means of ActionScript's SharedObject class.

The static getLocal( ) method is the mechanism by which LSOs are both created and opened for
reading. The method requires at least one parameter—a string that specifies the name of the
shared object to create or open:

my_l_so = [Link]("myFirstLSO");

The getLocal( ) method attempts to first locate an existing LSO by the specified name stored on
the client computer. If none is found, Flash creates a new LSO with that name. In either case,
the existing or new LSO is opened. The getLocal( ) method returns a SharedObject instance. It
is used instead of the new operator to instantiate a new local shared object.

For a complete example of how to create, write to, and save a local shared object, see Recipe
16.4.

16.1.4 See Also


Recipe 16.5 discusses sharing data between movies. Also refer to Recipe 2.8 for details on
opening the Flash Player Settings dialog box to the Local Storage tab. See Recipe 16.2 for
important information on how to store data in a local shared object. See Recipe 16.3 for
important information on how to retrieve data from a local shared object. See Recipe 16.4 for
information on the [Link]( ) method, which is used to manually save data to a
shared object.

[Link] 2004-02-06
Strona 52 z 156

[ Team LiB ]
[ Team LiB ]

Recipe 16.2 Adding Data to a Client-Side Shared Object


16.2.1 Problem
You want to add data to a client-side shared object (whether it be a remote or local shared objec

16.2.2 Solution
Add the values as properties of the shared object's data object.

16.2.3 Discussion
Shared objects have a special built-in property named data. The data property is an object to w
you should add any information that you want to save to the shared object:

// Store a username value to the shared object.


[Link] = "Joey";

Properties attached directly to the shared object, rather than to its data property, are not writte
the shared object. Thus, attaching properties directly to the shared object is useful only for data
does not need to persist between sessions. Therefore, the following code is potentially an errone
attempt to save data:

[Link] = "this value is lost when the movie closes";

The correct approach is to attach the value to the data property, as follows:

[Link] = "this value is stored in the shared object";

You can store several native ActionScript datatypes to the shared object's data property, as follo

[Link] = new Array("a", "b", "c");


[Link] = new Date( );

However, you cannot store movie clips, buttons, text fields, or shared objects themselves to a
shared object's data property such that the data persists correctly between sessions.

You can also store objects created from custom classes to a shared object's data property.
However, for the object to be properly cast (interpreted as the intended datatype) when it is
retrieved in the next session, you must use the [Link]( ) method. If you do not
register your custom class, ActionScript won't know what to do with the data retrieved from the
shared object.

The conventional use of the registerClass( ) method is to associate a class to a symbol in the
Library. You should do this when you want to develop components with class definitions. Howeve
you can also use the registerClass( ) method to register a class to an arbitrary identifier:

function MyClass ( ) {}

[Link]("MyClassID", MyClass);

[Link] 2004-02-06
Strona 53 z 156

This code tricks the object into storing information about the class that created it. Therefore, wh
the data is retrieved from the shared object, Flash knows what kind of object it is.

The custom class must be defined and registered in all movies that retrieve
the custom object data from a shared object.

There is one additional consideration you must address to successfully recast custom datatypes
when they are retrieved from a shared object. When an object is retrieved from a shared object,
the properties are set and then the constructor for the class is called. This process will likely wipe
out some, if not all, of the properties of the object if your constructor includes any initialization
functionality. To understand this potential problem, consider the following example:

// Define a custom class. The constructor sets a property in the new object.
function MyClass(val) {
[Link] = val;
}

// Register the class.


[Link]("MyClassID", MyClass);

// Create or open a local shared object.


my_l_so = [Link]("myFirstLSO");

// If the shared object doesn't already have an objA object, create a new MyClass
// object and assign it to the shared object. Otherwise, if objA already exists
// (meaning that it was saved from a previous session), display the value of its
// property in the Output window.
if (my_l_so.[Link] == undefined) {
my_l_so.[Link] = new MyClass("yay");
my_l_so.flush( );
} else {
trace(my_l_so.[Link]);
}

In this example, the value of the object's val property is always undefined. The reason is that ev
though the object was initially saved with the value "yay", the constructor is called with an
undefined value each time the shared object data is retrieved. You can solve this problem by
enclosing all the initialization code within the constructor in an if statement, as follows, which
prevents the property from being erroneously reinitialized:

function MyClass(val) {
if (![Link]) {
[Link] = true;
[Link] = val;
}
}

When you change the constructor in this way, the original value for val is retained. When the
MyClass object is first created, the inited property does not exist. Therefore, the if statement's
condition is true. However, when the object is retrieved from the shared object, the inited prop
is true, and the initialization code does not execute. In this way the val property is not overwrit

16.2.4 See Also


Recipe 16.3, Recipe 16.10, and Recipe 17.2
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 54 z 156

Recipe 16.3 Reading Values from a Client-Side Shared


Object
16.3.1 Problem
You want to read values from a client-side shared object (either a remote or local shared
object).

16.3.2 Solution
Read the values from the properties of the shared object's data property.

16.3.3 Discussion
There is nothing difficult about reading the values from a client-side shared object. All
persistent values are stored in the shared object's data property, so you simply read the values
from the data property, as follows:

// Read the value of myProperty from the shared object, mySO, and write it to the
// Output window.
trace([Link]);

16.3.4 See Also


Recipe 16.2. Also refer to Recipe 16.9 for important differences when reading data from a
remote shared object.

[ Team LiB ]
[ Team LiB ]

Recipe 16.4 Saving a Local Shared Object


16.4.1 Problem
You want to save local shared object data to the client computer.

16.4.2 Solution
Use the [Link]( ) method in the Flash movie.

16.4.3 Discussion
Flash automatically attempts to save local shared object data to disk when the movie is unloaded
from the Player (such as when the Player closes). However, it is not a good practice to rely on th
automatic save functionality, as there are several reasons why the data might not save successfu
Instead, you should explicitly instruct the local shared object to write the data to disk using the
[Link]( ) method:

flushResult = my_l_so.flush( );

When the flush( ) method is invoked, it attempts to write the data to the client computer. The re

[Link] 2004-02-06
Strona 55 z 156

of a flush( ) invocation can be one of three possibilities:

z If the user set the local storage for the domain to "Never", the data is not saved and the
method returns false.

z If the amount of disk space required to save the local shared object's data is less than the
local storage setting for the domain, the data is written to disk and the method returns tru

z If the user has not allotted as much space as the shared object data requires, he is prompt
to allow enough space or to deny access to save the data. When this happens, the method
returns "pending". If the user chooses to grant access, the extra space is automatically
allotted and the data is saved.

In the third case, in which the flush( ) method returns "pending", there is an additional step you
take to determine whether the user grants or denies access to save the data. When the user ma
a selection from the automatic prompt, the onStatus( ) method of the shared object is automatic
invoked. It is up to you to define the method to handle the results in the way that is appropriate
your application. When the callback method is invoked, it is passed a parameter. The parameter
an object with a code property that is set to "[Link]" if the user granted
access or "[Link]" if the user denied access.

Here is an example that invokes flush( ) to save the data explicitly and then handles the possible
responses:

my_l_so = [Link]("myFirstLSO");
my_l_so.[Link] = "a value";
result = my_l_so.flush( );
// If the flush operation completes, check the result.
// If the operation is pending, the onStatus( ) method of the
// shared object is invoked before any result is returned.
if (result) {
// Saved successfully. Place any code here that you want to execute after the d
// was successfully saved.
}
else if (!result) {
// This means the user has the local storage settings to 'Never.' If it is
// important to save your data, you may want to alert the user here. Also, if y
// want to make it easy for the user to change his settings, you can open the l
// storage tab of the Player Settings dialog box with the following code:
// [Link](1);.
}

// Define the onStatus( ) method for the shared object.


// It is invoked automatically after the user makes a selection
// from the prompt that occurs when flush( ) returns "pending."
my_l_so.onStatus = function (infoObj) {
if ([Link] == "[Link]") {
// If the [Link] property is "[Link]", it means the
// user granted access. Place any code here that you want to execute when the
// user grants access.
} else if ([Link] == "[Link]") {
// If the [Link] property is "[Link]", it means the u
// denied access. Place any code here that you want to execute when the user
// denies access.
}
};

If you know in advance that a shared object is likely to continue to increase in size with each
session, it is prudent to request a larger amount of local storage space when the shared object is
created. Otherwise, each time the current allotted space is exceeded, the user is prompted again

[Link] 2004-02-06
Strona 56 z 156

accept or deny the storage request. Setting aside extra space avoids repeatedly asking the user
permission to store incrementally more data. You can request a specific amount of space when y
call the flush( ) method by passing it a number of bytes to set aside for the shared object:

// Request 500 KB of space for the shared object.


result = [Link](1024 * 500);

[ Team LiB ]
[ Team LiB ]

Recipe 16.5 Sharing Information Between Movies Within


the Same Domain
16.5.1 Problem
You want two movies within the same domain to have access to the same local shared object.

16.5.2 Solution
Specify a local path parameter when creating and opening the local shared object.

16.5.3 Discussion
By default, local shared objects are saved to a path on the client computer that is unique to the
domain, path, and name of the .swf file that is calling the getLocal( ) method. This prevents
name conflicts between local shared objects from different domains or even different movies on
the same domain. For example, on a system running Windows XP, if a movie named
[Link] served from [Link]/ascb/ writes a local shared object named
myFirstLSO, the data is saved to the following location:

D:\Documents and Settings\[UserName]\Application Data\Macromedia\Flash


Player\[Link]\ascb\[Link]\[Link]

The name of the .swf file is included in the path to which the LSO is saved so that it will not
conflict with an LSO named myFirstLSO created by another movie served from the same
domain and path. However, in some cases, you want two movies on the same domain to have
access to the same LSO. In these cases, you should use the optional local path parameter when
creating and opening the LSO using getLocal( ).

The local path parameter (the second parameter passed to getLocal( )) must be a string that
specifies the full or partial path to the .swf file that created the LSO. For example:

my_l_so = [Link]("myFirstLSO", "/");

If the preceding code exists in [Link], which is served from [Link]/ascb/,


the local shared object is stored at the following location:

D:\Documents and Settings\[UserName]\Application Data\Macromedia\Flash


Player\[Link]\[Link]

An LSO created in this way can be opened by any other Flash movie in the same domain with
the following line of ActionScript:

my_l_so = [Link]("myFirstLSO", "/");

[Link] 2004-02-06
Strona 57 z 156

It is important to understand that a movie can only create and/or open an LSO within the same
full or partial path. To understand this, consider an example with two Flash movies:
[Link] and [Link]. Both movies are served from the same domain—
[Link]—but at different paths. [Link] is served from
[Link]/ascb/firstGroup/, and [Link] is served from
[Link]/ascb/secondGroup/. In this scenario, [Link] can create and read
LSOs with any of the following local path values:

/
/ascb
/ascb/firstGroup

and [Link] can create and read LSOs with any of the following local path values:

/
/ascb
/ascb/secondGroup

Therefore, if you want both movies to be able to access a common LSO, you must specify one
of the two local paths that the movies share (/ or /ascb) when you invoke the getLocal( )
method.

To illustrate how you can share data between two (or more) Flash movies within the same
domain, let's take a look at an example. If the movies don't exist within the same directory, we
must specify a local path that is common to both of them in the directory hierarchy. Let's start
by looking at what happens if we fail to specify a common local path:

1. Create a new Flash document, and on the first frame of the main timeline add the
following code:

[Link]("message_txt", 1, 100, 100, 100, 20);


message_l_so = [Link]("messageLSO");
val = (message_l_so.[Link] == undefined) ? 0 : message_l_so.[Link];
message_l_so.[Link] = val;
message_txt.text = "movie A value: " + val;

2. Create a new directory somewhere on your computer. Name this directory LSOTest.

3. Create two subdirectories within LSOTest. Name these subdirectories movieAPath and
movieBPath.

4. Save the document as [Link] to the LSOTest/movieAPath directory.

5. Export the movie as [Link] to the LSOTest/movieAPath directory.

6. Create a new Flash document, and on the first frame of the main timeline add the
following code (it's almost identical to the code in [Link]):

[Link]("message_txt", 1, 100, 100, 100, 20);


message_l_so = [Link]("messageLSO");
val = (message_l_so.[Link] == undefined) ? 0 : message_l_so.[Link];
message_l_so.[Link] = val;
message_txt.text = "movie B value: " + val;

7. Save the document as [Link] to the LSOTest/movieBPath directory.

8. Export the movie as [Link] to the LSOTest/movieBPath directory.

[Link] 2004-02-06
Strona 58 z 156

9. Open [Link] in a web browser and reload the page several times. Each time you
reload, you should see the number increment by one.

10. Test [Link] in the same way you tested [Link]. You should see the number
increment by one with each reload as well. However, notice that the number starts at 0.
This is because, as it stands, [Link] and [Link] use different shared objects.
Even though the shared objects have the same name, they have different paths.

11. To cause both movies to use the same shared object, we must tell them to look in the
same path. Modify the line of code that opens the shared object so that it reads as follows
in both [Link] and [Link]:

message_l_so = [Link]("messageLSO", "/");

This causes both movies to look to a common path for the shared object, and hence use
the same file.

12. Save the Flash documents and re-export the .swf files.

13. Test the movies again as in steps 9 and 10. This time, notice that each update to one
movie also increments the value used by the other movie. This is because they are now
using the same shared object.

16.5.4 See Also


Chapter 17

[ Team LiB ]
[ Team LiB ]

Recipe 16.6 Storing Persistent Data on the Server


16.6.1 Problem
You want to store persistent data on the FlashCom server.

16.6.2 Solution
Use a remote shared object to store persistent data on the server and access it from one or mor
clients. Use the [Link]( ) method in the Server-Side ActionScript to create the shared
object, and then connect the client(s) to the shared object using the [Link]( )
connect( ) methods in the client-side ActionScript. Or, you can create the RSO entirely from the
client using the [Link]( ) method.

16.6.3 Discussion
To create persistent remote shared objects, you can use one of two approaches. Either you can
create the server-side shared object using the [Link]( ) method in an .asc file and the
connect to it from the Flash clients, or you can handle everything from the client. Creating the
shared object on the server side is preferable in most scenarios because it allows you to do more
things with the data on the server. (The server-side .asc file must exist within the FlashCom
application. See Recipe 14.1 and Recipe 14.14.) The get( ) method requires you to specify the na
of the shared object as well as a Boolean value indicating whether the shared object should be
persistent (true) or not (false). (In this case, you want to create a persistent object. See Recip

[Link] 2004-02-06
Strona 59 z 156

17.5 for information about creating nonpersistent shared objects.) If a shared object with the sa
name already exists on the server, then it is opened; otherwise, a new shared object is created.

// Create a new persistent shared object named myFirstRSO and


// assign that reference to a variable named myServer_r_so.
// This code belongs in an .asc file on the FlashCom server.
myServer_r_so = [Link]("myFirstRSO", true);

Once a shared object has been created on the server, you can connect Flash movie clients to it u
the client-side [Link]( ) and [Link]( ) methods. The getRemot
method requires three pieces of information: the name of the shared object (which must match t
name of the shared object that was created using the Server-Side ActionScript), the URI for the
connection to the FlashCom server (which can be retrieved using the uri property of the net
connection object), and a Boolean indicating whether the shared object is persistent (true) or no
(false). The getRemote( ) method returns a SharedObject instance. It is used instead of the ne
operator to instantiate a new remote shared object.

Additionally, you must also instruct the client-side shared object to connect to the FlashCom serv
to synchronize with the server-side object by using the connect( ) method. The connect( ) metho
requires that you specify which connection object to use to make the connection.

// Create a new, client-side, persistent RSO in the Flash movie. This code looks
// a server-side shared object named myFirstRSO on the server and application
// specified by the URI (returned by the [Link] property).
myClient_r_so = [Link]("myFirstRSO", [Link], true);
myClient_r_so.connect(myConnection);

When any changes are made to a remote shared object, the server automatically synchronizes th
client-side data and invokes the onSync( ) method of the client-side shared object for all clients.
Therefore, to catch any synchronization events, you should define an onSync( ) method for the
client-side shared object. For example:

myClient_r_so.onSync = function ( ) {
trace("RSO data has been updated/retrieved.");
};

It is important to understand that there will always be some degree of latency between the creat
and connection of the remote shared object and the retrieval of the data from the server-side
object. Therefore, you cannot rely on the data being available within the client immediately follow
the calls to getRemote( ) and connect( ). Consider, for example, that you want to use data from
remote shared object to initialize a list box in a client movie. It might seem natural enough to try
something like this:

myClient_r_so = [Link]("myFirstRSO", [Link], true);


myClient_r_so.connect(myConnection);

// Try to set the data provider of the list box to an array stored in the shared
// object (it doesn't work).
[Link](myClient_r_so.[Link]);

However, this will not work! The problem is that, most likely, the remote data has not yet been
loaded into the client. This is why it is important that you use the onSync( ) method to catch any
updates to the client-side data (which includes the initial retrieval of the server-side data). Here
the correct way to implement the preceding example:

myClient_r_so = [Link]("myFirstRSO", [Link], true);


myClient_r_so.connect(myConnection);

// Place the setDataProvider( ) call inside the onSync( ) method to ensure that

[Link] 2004-02-06
Strona 60 z 156

// server-side data has been retrieved before trying to use it.


myClient_r_so.onSync = function ( ) {
_root.[Link]([Link]);
};

16.6.4 See Also


See Recipe 16.10 for important information on how to store data in the remote shared object. Al
refer to Recipe 17.5.
[ Team LiB ]
[ Team LiB ]

Recipe 16.7 Saving Remote Shared Object Data


16.7.1 Problem
You want to save the data stored in a remote shared object.

16.7.2 Solution
Define an onAppStop( ) callback handler or use the [Link]( ) method on the
server.

16.7.3 Discussion
When you are working with RSOs, there are really two shared objects: the object as it exists on
the server and the object as it exists on the client. And in a scenario in which the shared
objects are persistent, there is both a persistent, disk-based version and a working version
stored in memory. As the client makes changes to the data in the client-side, memory-based
shared object, it is automatically sent to the server, and the data in the server-side, memory-
based shared object is synchronized with the client-side data. And once the server data is
updated, the onSync( ) method is invoked for all clients connected to the RSO.

// Create or open an RSO and connect to it.


my_r_so = [Link]("myFirstRSO", [Link]);
my_r_so.connect(myConnection);

// Add data to the RSO. The data is automatically sent to the server.
my_r_so.[Link] = "Your Value Here.";

However, even though the memory-based shared objects are kept in synch with one another,
the persistent, disk-based shared objects are not always kept in synch with the current data.
This is because file access is a relatively "expensive" task. It requires more time to read and
write to disk than to read and write to memory. As such, the default behavior for shared objects
is that the data is not written to disk until the memory-based object is about to be deleted. This
occurs on the client when the Flash movie is being closed, and it occurs on the server when the
application is being shut down. You can read more about how to save the client-side data to
disk in Recipe 16.4. For more information on storing server-side data to disk, keep reading.

The RSO data should need to be written to disk only if and when the FlashCom application
stops. To tell FlashCom server to write the RSO data to disk when the application stops, define
an [Link]( ) method that returns true in your [Link] file on the server, as
follows:

[Link] = function ( ) {
return true;
};

[Link] 2004-02-06
Strona 61 z 156

You can also invoke the flush( ) method on the server-side shared object (from the .asc file) at
any point to explicitly write the data to disk:

// Place this in your [Link] file or other included .asc file.


[Link]( );

16.7.4 See Also


Recipe 16.6

[ Team LiB ]
[ Team LiB ]

Recipe 16.8 Checking for Updates to Remote Shared


Objects
16.8.1 Problem
You want to be able to efficiently check to see what data has changed when a remote shared obj
is synchronized.

16.8.2 Solution
Use the list parameter that is automatically passed to the onSync( ) method. Loop through the
parameter to locate all the elements in which the code property has the value indicating the type
update you are interested in.

16.8.3 Discussion
When the onSync( ) method is invoked (see Recipe 16.6), it is automatically passed a parameter
that contains information about how the data has changed. The parameter is an array of objects
one for each property of the remote shared object. For example, if a remote shared object has tw
properties named myDate and myArray (meaning my_r_so.[Link] and
my_r_so.[Link]), then the list parameter passed to the onSync( ) method will have tw
elements. In turn, each element contains three properties: code, name, and oldVal. One elemen
will have its name property set to "myDate", and the other will have its name property set to
"myArray". The code property can have the following values for persistent shared objects:

"success"

The server-side value was successfully updated from a change made by this client.

"reject"

The client tried to make a change to the value, but it was rejected by the server—usually
because the same object was being updated by another client.

"change"

The client-side value has been updated based on a new value from the server.

"delete"

The property has been deleted.

[Link] 2004-02-06
Strona 62 z 156

"clear"

Either the object is not persistent on the server or on the client, or all the properties have
been deleted.

This change information is useful when you want to make changes in the client movie following a
synchronization event, and you need to determine what data changed and how. For example, in
whiteboard application, you might use an RSO to store the positions of various movie clips that c
be controlled by multiple users. Each movie clip's data can be stored as a different element in th
RSO. When one client updates the position of a movie clip, you want to update that movie clip's
position in all other connected clients. However, you don't want to unnecessarily update the
positions of other movie clips that have not been moved. The following client-side code defines a
onSync( ) method that determines which movie clips moved and updates their positions accordin

myClient_r_so = [Link]("myFirstRSO", [Link], true);


myClient_r_so.connect(myConnection);

myClient_r_so.onSync = function (list) {


for (var i = 0; i < [Link]; i++) {
// Synchronize changes to a movie clip's position across all connected client
// Check only for elements marked as "change" because the movie clip's proper
// will be changed locally already for those marked "success".
if (list[i].code == "change") {
// This code assumes that the name of the elements in the shared object
// correspond to the names of movie clips in _root.
_root[list[i].name]._x = [Link][list[i].name]._x;
_root[list[i].name]._y = [Link][list[i].name]._y;
}
}
};

Of course, the preceding code does not work in isolation. It requires that the client also contain c
that writes to the RSO each time a client moves a movie clip. For example:

myMovieClip_mc.onPress = function ( ) {
[Link]( );
};

myMovieClip_mc.onRelease = function ( ) {
[Link]( );

// Create a new property in the RSO with the same name as the movie clip. Set th
// property's value equal to an object with _x and _y properties.
_root.myClient_r_so.data["myMovieClip_mc"] = {_x: this._x, _y: this._y};

};

16.8.4 See Also


See Recipe 16.10 for important information on how to store data in the remote shared object. Al
refer to Recipe 17.6.

[ Team LiB ]
[ Team LiB ]

Recipe 16.9 Reading Values from a Server-Side Shared


Object

[Link] 2004-02-06
Strona 63 z 156

16.9.1 Problem
You want to read values from a server-side shared object.

16.9.2 Solution
Use the [Link]( ) method.

16.9.3 Discussion
You cannot read from a server-side shared object the same way you can from a client-side share
object. Use the [Link]( ) method to read from a server-side shared object. T
method requires that you specify the name of the property to read, and it returns the value of th
property.

// This code should be within an .asc file. It retrieves the value of myProperty
// assigns that value to the variable myPropertyVar.
myPropertyVar = my_r_so.getProperty("myProperty");

16.9.4 See Also


Recipe 16.3, Recipe 16.8, and Recipe 16.10

[ Team LiB ]
[ Team LiB ]

Recipe 16.10 Adding Data to a Server-Side Shared


Object
16.10.1 Problem
You want to add values to a server-side shared object.

16.10.2 Solution
Use the [Link]( ) method.

16.10.3 Discussion
If you try to add values to a server-side shared object in the same way that you add values to a
client-side shared object, you will receive an error. The correct way to add values (or modify
existing values) to a server-side shared object is to use the [Link]( )
method. The setProperty( ) method requires that you specify both the name of the property and
the value to assign to the property.

// This example code should appear in an .asc file. It adds a property named
// myProperty to a shared object and assigns the property the value of 6.
my_r_so.setProperty("myProperty", 6);

If you want to modify an existing property relative to its current value, you can use the
getProperty( ) method in conjunction with setProperty( ):

[Link] 2004-02-06
Strona 64 z 156

// Set the myProperty property to one more than its current value.
my_r_so.setProperty("myProperty", my_r_so.getProperty("myProperty") + 1);

16.10.4 See Also


See Recipe 16.9 for details on [Link]( ). See Recipe 16.2 for important
differences when setting data for a local shared object.

[ Team LiB ]
[ Team LiB ]

Chapter 17. Communicating with Other


Movies

Introduction

Recipe 17.1. Communicating with Other Movieson the Same Computer

Recipe 17.2. Sending Data Using Local Connections

Recipe 17.3. Validating Receipt of Communication Over a Local Connection

Recipe 17.4. Accepting Communications from Other Domains

Recipe 17.5. Communicating Between Movies on Different Computers

Recipe 17.6. Broadcasting Data to Remote Shared Object Clients


[ Team LiB ]
[ Team LiB ]

Introduction
When we speak of Flash movies interacting with one another, there are two types of
interactions. First, there are interactions between movies that are running on the same
computer. And second, there are interactions between movies on different computers.

Prior to Flash MX, the only way to communicate between movies on the same computer
(without using server-side functionality) was to use the fscommand( ) global function, which
executes JavaScript methods. Unfortunately, these methods were both cumbersome to use and
not always reliable (due to lack of support in various browsers). Also, these methods worked
only with Flash movies running in web browsers in which the browser windows had references
to one another. However, Flash MX introduced local connections—a means by which any Flash
movie can broadcast to and listen for broadcasts from any other movie on the same computer.
The advantages of local connections are:

z They are relatively simple to use.

z They are implemented entirely in ActionScript, and they work for movies running in Flash
Player 6 or later.

[Link] 2004-02-06
Strona 65 z 156

Additionally, prior to Flash MX, the only way to communicate between movies on different
clients was to use a socket connection via the XMLSocket class. While the XMLSocket class is
still a valuable and powerful technology, it has its disadvantages:

z Using sockets requires a server-side socket server. Although creating your own socket
server requires proficiency in a server-side programming, third-party socket servers, such
as Unity ([Link] are widely available and make working with the socket
server considerably easier.

z The XMLSocket class does not have a very robust feature set. XMLSocket objects allow
users to send data to a socket server and listen for data from a socket server. Additional
code is necessary to handle that data in a meaningful way.

Using Flash Player 6 in conjunction with Flash Communication Server MX (FlashCom), it is


possible to create communication between client movies without relying on the XMLSocket
class. The advantages of FlashCom are:

z It provides a standardized way of implementing communication between clients.

z It provides a rich API, and it automatically serializes and deserializes datatypes.

z It is capable of much more than just creating channels of communication between client
movies. You can also use FlashCom for audio and video recording, playback, and live
broadcasts. Socket servers don't offer this kind of functionality.

At the same time, you should take into consideration that there are possible disadvantages to
FlashCom:

z FlashCom costs money; however so do many professional socket servers. Furthermore, a


developer version of FlashCom server is freely available for testing, and the personal
edition of FlashCom server is bundled with Director MX.

z FlashCom may not perform as well as socket servers when it comes to a large number of
connections or high volumes of data transfer. Do some research and testing to determine
whether FlashCom's performance characteristics meet the needs of your application.

See Also
See Chapter 14 for FlashCom recipes. See [Link] for
more information on FlashCom and its pricing. Third-party socket servers of varying price and
capability include moockComm ([Link] Unity
([Link] aquaServer
([Link] Fortress
([Link] and ElectroServer
([Link]
[ Team LiB ]
[ Team LiB ]

Recipe 17.1 Communicating with Other Movieson the


Same Computer
17.1.1 Problem
You want to communicate from one Flash movie to one or more Flash movies playing on the

[Link] 2004-02-06
Strona 66 z 156

same client computer.

17.1.2 Solution
Use a LocalConnection object to invoke a function in the receiving movie from the sending
movie. Use [Link]( ) to listen for messages in the receiving movie and define
the function that will be invoked. Use [Link]( ) from the sending movie to invoke
a function on the remote computer. Both the sending and receiving movies must specify the
same named channel for communication.

17.1.3 Discussion
When two or more Flash movies are playing on the same client computer, they can
communicate with each other via a local connection created with the LocalConnection class. As
long as the movies are playing on the same computer, a connection can be made regardless of
the domains from which the movies are being served.

By default, movies accept communications from movies on the same


domain only. However, you can configure them to receive from other
domains as well. See Recipe 17.4.

To successfully communicate between multiple movies on the same computer you must do
three things:

1. Configure the receiving movie(s) to listen for communications.

2. Tell the receiving movie(s) what to do when messages are received.

3. Send messages to the receiving movie(s) from the sending movie.

A receiving movie must listen for messages over a specific, named local connection. To
establish this communication channel, you should create a local connection object in the
receiving movie and tell it to listen to a named connection using the connect( ) method:

// Create the local connection object in the receiving movie.


receiving_lc = new LocalConnection( );

// Instruct the local connection object to listen for messages sent over the
// "_myConnection" channel.
receiving_lc.connect("_myConnection");

As shown in the preceding example, the best practice is to name your communication channel
(not the local connection object) with an initial underscore (_). Naming your connections in this
way also simplifies communicating across domains.

All communications are targeted to specific, custom methods of the receiving local connection
object. For example, if the sending movie sends a communication that looks for a method
named myMethod( ), you should define myMethod( ) on the receiving movie's local connection
object, as follows:

receiving_lc.myMethod = function ( ) {
_root.output_txt.text = "communication received!";
};

To set up the sending movie, you must first create a local connection object:

[Link] 2004-02-06
Strona 67 z 156

// Create the local connection object in the sending movie.


sending_lc = new LocalConnection( );

Then use the [Link]( ) method to send a communication. The first parameter of
the send( ) method is a string that specifies the name of a connection over which to send the
communication, enabling you to create multiple discrete connections. The second parameter of
the send( ) method is a string specifying the name of the method to call in the receiving movie.
When a movie receives a message, it invokes the method of the same name on the receiving
local connection object. This example invokes a remote method:

// Send a communication across the "_myConnection" channel that invokes a method


// named myMethod( ) in the receiving movie.
sending_lc.send("_myConnection", "myMethod");

When a sending movie sends a communication over a connection, any movie listening to that
connection receives the communication. Therefore, you can have more than one receiving
movie for a single sending movie. Conversely, multiple movies can send messages to a single
receiving movie over a single named connection.

Note that both the sending and receiving movies have to be playing on the same computer at
the same time. To communicate between movies that play on the same computer at different
times, use a local shared object. See Recipe 16.1 and Recipe 16.5.

17.1.4 See Also


To send data over a local connection, see Recipe 17.2 and Recipe 17.5.
[ Team LiB ]
[ Team LiB ]

Recipe 17.2 Sending Data Using Local Connections


17.2.1 Problem
You want to send data to one or more movies playing on the same computer.

17.2.2 Solution
Pass the data as additional parameters to the send( ) method of the sending local connection obj

17.2.3 Discussion
You can send data to another movie using a local connection by passing the data as additional
parameters to the send( ) method of the sending local connection object. The send( ) method
requires at least two parameters: the name of the connection and the name of the method to inv
on the receiving movie. Any additional parameters passed to the send( ) method are automatica
passed as parameters to the receiving movie's method. Note that the name of the method you a
invoking on the receiving movie cannot be one of the built-in methods of the LocalConnection cla
such as send, connect, etc. For a complete list of the built-in methods of a local connection, see
"Language Reference" section of ActionScript for Flash MX: The Definitive Guide, or the Referenc
panel (Window Reference) in Flash.

You should define the receiving method so it accepts the parameters sent to it. In this example,
receiving movie contains a local connection with a method named myMethod( ). The method exp
three parameters: str, num, and bool.

receiving_lc = new LocalConnection( );

[Link] 2004-02-06
Strona 68 z 156

receiving_lc.connect("_myConnection");
receiving_lc.myMethod = function (str, num, bool) {
_root.output_txt.text = "The parameters are: " +
str + newline +
num + newline +
bool;
};

Here is a code snippet from a sending movie that calls myMethod( ) in the receiving movie and
passes it parameters, similar to invoking it as follows: myMethod( "a string", 6, true).

// Send three parameters to a receiving movie's local connection method named


// myMethod( ). The parameters happen to be a string, a number, and a Boolean.
sending_lc = new LocalConnection( );
sending_lc.send("_myConnection", "myMethod", "a string", 6, true);

You are not limited to sending primitive datatypes only. Specifically, you can send data of the
following complex types: Object, Array, Date, TextFormat, and XML.

Additionally, you can send objects of custom types. The process for doing so involves many of th
same steps as storing and retrieving custom object types from a shared object. You can find mo
details about the theory behind this in Recipe 16.2, but the required steps are:

1. Define the custom class in both the sending and receiving movies.

2. Register the class in the sending and receiving movies using the [Link]( )
method.

3. Define the constructor of the class such that any initialization code is enclosed in an if
statement that is executed only once.

The following is an example of the code in a sending movie and a receiving movie that passes da
of a custom object type from one to the other.

First, let's define the custom class in its own .as file so that we can easily include it in both the
sending and receiving movies. For the purposes of this example, name the file [Link].

// Define the custom class, MyClass.


function MyClass(val) {

// Since the constructor contains initialization code, enclose it in an if


// statement so that it is executed only when the object is first created and n
// on subsequent deserializations. The inited property is used to track whether
// data has been initialized.
if (![Link]) {
[Link] = true;
[Link] = val;
}
}

// Let's define a method for the class so that we can show that Flash allows us t
// invoke the method in the receiving movie.
[Link] = function ( ) {
return [Link];
};

// Register the class to an arbitrary ID.


[Link]("MyClassID", MyClass);

Here's the receiving movie code:

[Link] 2004-02-06
Strona 69 z 156

#include "[Link]"

// Define the local connection object to listen over "_myConnection".


receiving_lc = new LocalConnection( );
receiving_lc.connect("_myConnection");

receiving_lc.myMethod = function (param) {


// If the parameter is an instance of MyClass, display its val property by the
// getVal( ) method.
if (param instanceof MyClass) {
trace("Parameter value is " + [Link]( ));
} else {
trace("Param is not of the expected type.");
}
};

and here's the sending movie code:

#include "[Link]"

// Create the local connection and send a MyClass object to the receiving movie.
sending_lc = new LocalConnection( );
sending_lc.send("_myConnection", "myMethod", new MyClass("test"));

Regardless of what type of data is being sent, the same rules apply to any type of local connectio
communication. Both movies must be running on the same computer at the same time, and the
receiving movie must be listening on the same channel that the sending movie uses to send. Als
is worth noting that once you have defined a custom datatype in both the sending and receiving
movies, you can send that type of data in both directions (although you should use two separate
channels for bidirectional communication, as discussed in Recipe 17.3).

17.2.4 See Also


Recipe 17.1
[ Team LiB ]
[ Team LiB ]

Recipe 17.3 Validating Receipt of Communication Over a


Local Connection
17.3.1 Problem
You want a sending movie to receive confirmation that the communication was successfully
received.

17.3.2 Solution
Configure the receiving movie to return a receipt to the sending movie.

17.3.3 Discussion
If you need to confirm that a communication was received, you can have the receiving movie sen
a message back to the original sending movie. Here are the steps for confirming receipt of a
communication:

1. Set up the sending and receiving movies, as described in Recipe 17.1.

[Link] 2004-02-06
Strona 70 z 156

2. In addition to whatever code you include in the method that is invoked on the receiving
movie, write code to send a receipt over a new channel (e.g., "_myConnectionReceipt"). Yo
can use [Link]( ) within the local connection's method to send a receipt back to the
original sender.

3. In the sending movie, call the connect( ) method, passing it the name of the channel over
which the receiving movie sends the receipt (again, "_myConnectionReceipt").

4. In the sending movie, create a method on the local connection object to handle the receipt
communication. Make sure this method name matches the name of the method that the
receiving movie invokes when sending the receipt.

The following is an example of some code from a sending movie and a receiving movie.

First, the receiving movie code:

// Create the receiving code to listen on the "_myConnection" channel.


lc = new LocalConnection( );
[Link]("_myConnection");
[Link] = function ( ) {
// In addition to whatever other code goes in the
// receiving method, add this code to issue a receipt
// back to the sending movie over the "myConnectionReceipt" channel.
// The this keyword refers to the current local connection object.
[Link]("_myConnectionReceipt", "onReceipt");
};

Then, the sending movie code:

// Create the local connection object for sending over the "_myConnection" channe
sending_lc = new LocalConnection( );
sending_lc.send("_myConnection", "myMethod");

// Tell the local connection to listen on the "_myConnectionReceipt" channel for


// receipt broadcast by the receiving movie.
sending_lc.connect("_myConnectionReceipt");

// Define the onReceipt( ) method that gets called from the receiving movie.
sending_lc.onReceipt = function ( ) {
_root.output_txt.text = "received";
};

The key point is that the name of the channel on which a local connection object listens (using
connect( )) must be the same as the name of the channel over which another movie's local
connection object sends a message (using send( )). Also, notice that the two movies do not
communicate back and forth over the same channel. This is because a movie ignores any
broadcasts over any channel to which it is also listening. So we establish two one-way channels
because a single channel is not two-way in practice.

17.3.4 See Also


Recipe 17.1
[ Team LiB ]
[ Team LiB ]

Recipe 17.4 Accepting Communications from Other


Domains

[Link] 2004-02-06
Strona 71 z 156

17.4.1 Problem
You want a movie to accept local connection communications from movies served from other
domains.

17.4.2 Solution
Use the allowDomain( ) method of the receiving local connection object.

17.4.3 Discussion
By default, receiving movies accept communications from sending movies on the same domain o
However, you can use the allowDomain( ) method of a local connection object to allow or disallow
communications from any domains. You need to define the allowDomain( ) method for each
receiving local connection object for which you wish to define a custom list of domains to accept
deny.

If present, the allowDomain( ) method is automatically invoked when a local connection object
receives a communication. If allowDomain( ) returns true, the communication is accepted; if it
returns false, the communication is denied. Therefore, you can configure a local connection obj
to receive communications from any domain by having its allowDomain( ) method return true in
cases:

// Define the receiving local connection, and instruct it to listen to communicat


// over the "_myConnection" channel.
receiving_lc = new LocalConnection( );
receiving_lc.connect("_myConnection");

// Define the allowDomain( ) method for the receiving local connection object, wh
// is invoked automatically whenever a communication is received. This example alw
// returns true, so all communications are accepted.
receiving_lc.allowDomain = function (domain) {
return true;
};

However, it is generally not a good practice to allow communications from all domains, because
doing so allows any other movie to invoke an arbitrary method on your movie. It is better to spe
trusted domains from which to accept connections. The domain of the sending movie is passed t
the allowDomain( ) method as a parameter, and you can use this to determine whether the send
domain should be trusted. For example:

receiving_lc.allowDomain = function (domain) {


// If the domain of the sending movie is [Link], allow the communication.
// Otherwise, disallow it.
if (domain == "[Link]") {
return true;
} else {
return false;
}
};

Local connection objects also provide a convenient means of determining the domain of the
receiving movie. The domain( ) method can be invoked from any local connection object to revea
the domain from which the movie is being served. You can use this method within the allowDom
( ) method to allow communications from the same domain. For example:

receiving_lc.allowDomain = function (domain) {


// If the domain of the sending movie is the same as that of the receiving movi
// allow the communication. Otherwise, disallow it.

[Link] 2004-02-06
Strona 72 z 156

if (domain == [Link]( )) {
return true;
} else {
return false;
}
};

The preceding example accomplishes exactly the same thing as though you had not defined the
allowDomain( ) method at all—it allows communications from the same domain only. Normally,
therefore, you use domain( ) to allow communications from the same domain as well as
communications from other domains:

receiving_lc.allowDomain = function (domain) {


// If the domain of the sending movie is [Link] or the same domain as the
// receiving movie, allow the communication. Otherwise, disallow it.
if ( (domain == "[Link]") || (domain == [Link]( )) ) {
return true;
} else {
return false;
}
};

17.4.4 See Also


Recipe 15.2 and Recipe 15.6

[ Team LiB ]
[ Team LiB ]

Recipe 17.5 Communicating Between Movies on Different


Computers
17.5.1 Problem
You want to create a channel of communication between two or more movies on different
computers.

17.5.2 Solution
Create a remote shared object and connect each of the clients to it.

17.5.3 Discussion
LocalConnection objects can be used to communicate between movies on the same computer bu
not on different computers. Remote shared objects offer a way to create channels of
communication between multiple computers acting as clients of a FlashCom application. Remote
shared objects can be either persistent or nonpersistent. In most cases where you simply want t
create a channel of communication between FlashCom clients, there is no need to create a
persistent shared object (in which the data is stored to disk). On the other hand, if you want to
save the data between sessions or restarts of the FlashCom application, you should make the
shared object persistent, as discussed in Recipe 16.6.

Each client must create a client-side remote shared object using the [Link]( )
method and connect using the connect( ) method:

[Link] 2004-02-06
Strona 73 z 156

// Create a nonpersistent client-side RSO and connect it to the server. This examp
// assumes that you have an existing net connection object named myConnection.
myClient_r_so = [Link]("clientComRSO", [Link], false);
myClient_r_so.connect(myConnection);

17.5.4 See Also


See the Introduction to Chapter 16 as well as Recipe 16.6 for more information regarding remote
shared objects and the details of the get( ), getRemote( ), and connect( ) methods. See Recipe
17.6 for further information on using an RSO to communicate between connected FlashCom clien
Also refer to Recipe 14.15 and Recipe 17.1.
[ Team LiB ]
[ Team LiB ]

Recipe 17.6 Broadcasting Data to Remote Shared Object


Clients
17.6.1 Problem
You want to send data to all the clients of a FlashCom application that are connected to a remote
shared object.

17.6.2 Solution
Use the [Link]( ) method to catch synchronization events, and use the
[Link]( ) method to broadcast messages without writing to the RSO.

17.6.3 Discussion
Every time the server-side data for a remote shared object changes, FlashCom attempts to
synchronize the client-side data for all clients connected to that RSO. The [Link](
method for a client-side remote shared object is automatically invoked whenever a synchronizati
attempt has occurred, so you can monitor all synchronization events. (See more on this in Recip
16.8.) This is useful in situations in which a relatively small amount of data is shared among mu
clients. Whenever one client makes a change, all the other clients are automatically notified. The
onSync( ) method works the same regardless of whether the remote shared object is persistent.

You can also manually invoke a client-side method on all clients connected to a remote shared o
by using the [Link]( ) method, and you can call the send( ) method from either a se
side or client-side remote shared object. In either case, the send( ) method requires that you sp
the name of the method to invoke on the client(s). You should then ensure that the client movie
a method with that same name defined for the client-side remote shared object.

// Use send( ) to invoke a method named clientMethod( ) on all connected client


// send( ) method can be used on either a server-side or client-side RSO.
my_r_so.send("clientMethod");

// The following code should exist within the client movie. It defines a method n
// clientMethod( ) for the client-side RSO named my_r_so.
my_r_so.clientMethod = function ( ) {
trace("clientMethod( ) called");
};

Additionally, you can pass parameters to the client-side method, as follows:

// Invoke a method named clientMethod( ) on all connected clients and pass the m

[Link] 2004-02-06
Strona 74 z 156

// two parameters: 6 and six.


my_r_so.send("clientMethod", 6, "six");

// The following code should exist within the client movie. This time, the
// clientMethod( ) method expects two parameters.
my_r_so.clientMethod = function (numNum, numStr) {
trace("clientMethod( ) called with parameters: " + numNum + " " + numStr);
};

We've seen two ways to broadcast messages to connected clients using RSOs: either explicitly ca
the send( ) method or implicitly relying on the onSync( ) method. It is important to determine w
use which technique.

Generally, it is not a good idea to write a lot of data to a single property of a remote shared obje
the amount of data saved to a property of the RSO grows, so does the latency in the application
example, in a chat application it is not a good idea to write all the chat messages to a property o
RSO and rely on the onSync( ) method to update the clients. At first this will appear to work fine
after a few users have joined the chat room and more messages are sent, things will get slower
slower because the entire chat history is synchronized each time someone sends a new message
Instead, you should use the send( ) method to broadcast each new message to all the connected
clients. In that case, the new message is sent to all connected clients, but the chat history is not
only drawback is that the chat history is not available to new, connecting clients. What you can d
remedy this is to also use a call( ) method to send each new message to the server and append
new message to a chat history application variable. This way, the information is available when y
want to use it, but it is not automatically sent with each new message.

17.6.4 See Also


Recipe 14.15, Recipe 14.17, Recipe 16.6, and Recipe 16.8
[ Team LiB ]
[ Team LiB ]

Chapter 18. Sending and Loading


Variables

Introduction

Recipe 18.1. Loading Variables from a Text File

Recipe 18.2. Loading Variables from a Server-Side Script

Recipe 18.3. Checking Load Progress

Recipe 18.4. Sending Data to a Server-Side Script

Recipe 18.5. Sending Variables and Handling a Returned Result


[ Team LiB ]
[ Team LiB ]

Introduction
There are many reasons why you may want to send and load values from and to your Flash

[Link] 2004-02-06
Strona 75 z 156

movies. Here is a brief list of some of the possibilities:

z Send form values to a server-side script to store in a database

z Send values from an email form to a server-side script to send the email

z Load values from a text file (appropriate when the values are subject to change, such as
for the current weather or for a links page)

z Load values from a server-side script where the values are drawn from a database, such
as categories in an e-commerce application

z Send values to a server-side script for processing, and return a value to the Flash movie,
such as for a login process

When you are loading variables into Flash, it is important that you correctly format the values
to load. Flash can interpret data in URL-encoded format. URL-encoded variables follow these
rules:

z Each variable name is associated with a value using an equals sign, without spaces on
either side of the equals sign.

z Values loaded into Flash movies are always treated as strings; therefore, you should not
enclose any values in quotes, as you would within ActionScript. A proper example of this
is artist=Picasso.

z When there is more than one name/value pair, each pair is separated by an ampersand.
For example, artist=Picasso&type=painting.

z Spaces within the values should be replaced by plus signs, not %20, as in:
title=The+Old+Guitarist. (Spaces and %20 may also work, but stick with plus signs for
the greatest compatibility.)

z Any character that is not a digit, a Latin 1 letter (non-accented), or a space should be
converted to the hexadecimal escape sequence. For example, the value "L'Atelier" should
be encoded as L%27Atelier (%27 is the escape sequence for an apostrophe).

Here is an example of variables in URL-encoded format:

artist=Picasso&type=painting&title=Guernica&seller=Joey+Lott&room=L%27Atelier

Flash 6 introduced the LoadVars class, which provides a much more robust means of sending
and loading variables than the loadVariables( ) method, which was available previously.
Therefore, LoadVars is the preferred means of performing these actions, and it is used for the
recipes in this chapter. Refer to Recipe 11.13, Recipe 11.17, and Recipe 11.19, which
demonstrate how to transmit data associated with a form. There are, of course, other ways to
send and receive data in Flash. Chapter 19 covers data transmission using XML. Chapter 20
covers data transmission using Flash Remoting (see also Chapter 21 with regard to recordsets).

Loading values using a LoadVars object is subject to Flash's standard security sandbox. That is,
you can load variables from a text file or script only if it is within the same domain as the Flash
movie. For ways around the domain security limitation, see Recipe 17.4, Recipe 15.2, and
Recipe 15.6.

[Link] 2004-02-06
Strona 76 z 156

[ Team LiB ]
[ Team LiB ]

Recipe 18.1 Loading Variables from a Text File


18.1.1 Problem
You want to load variables into your Flash movie from an external text file.

18.1.2 Solution
Use the [Link]( ) method.

18.1.3 Discussion
You should use the [Link]( ) method when you want to load URL-encoded data into your
Flash movie from a text file. This technique allows your Flash movie to access values that change
frequently without modifying the Flash movie itself.

The load( ) method requires a URL where it can find the text file. The URL can be an absolute or
relative address.

// You must first create the LoadVars object.


myLoadVars = new LoadVars( );

// This example loads values from a text file at an absolute URL.


[Link]("[Link]

// This example loads values from a text file located at a relative URL, in this
// case, in the same directory as the Flash .swf file.
[Link]("[Link]");

Here is an example of what the text file might contain:

someText=testing&myVariable=123

Once you invoke the load( ) method of a LoadVars object, Flash attempts to load the values from
that URL into the same LoadVars object. After the data loads, Flash attempts to decode the value
and invokes the object's onLoad( ) method. It is up to you to define the onLoad( ) method so tha
the object can do something useful once the data is loaded and decoded.

You should define an onLoad( ) method that expects a Boolean parameter indicating whether Fla
was able to load the values. If Flash cannot load the values, such as if the file (or script) is
unreachable, the onLoad( ) method is passed a value of false.

When playing the movie in the Test Player, you will also receive an error
message in the Output window if the specified URL is unreachable.

Here is an example onLoad( ) handler:

[Link] = function (success) {


// If the data didn't load, write a message to a text field and assign some def
// values to the variables that would otherwise have been loaded.
if (!success) {
_root.output_txt.text = "Flash is using the default values.";
[Link] = "Loading Variables For Fun and Profit";

[Link] 2004-02-06
Strona 77 z 156

[Link] = "Anonymous";
[Link] = "You too can enjoy the fun of loading variables.";
} else {
// Process the loaded variables.
}
};

If loading is successful, Flash stores the loaded variables as properties of the LoadVars object an
invokes onLoad( ), passing it the value true. Here is an example that accesses a known property
assumes that there was a variable named title in the text file from which the data was loaded.

[Link] = function (success) {


// If the loaded data was decoded, display the title property.
if (success) {
trace([Link]);
} else {
trace("Unable to load the title.");
}
};

Here is what the text file might look like:

title=ActionScript+Cookbook

In most cases, you already know the names of the variables that are loaded from the external fil
script. For example, you might use a LoadVars object to load a daily article from a server-side sc
or file, and regardless of the article's content, you expect three variables—title, author, and
articleBody—to be loaded. You know which variables to expect because you wrote the server-s
script, work with the person who wrote the script, or have access to documentation on the script
However, there are some situations in which you might not know all the variables that are being
loaded. In such cases, you can use a for . . . in statement to enumerate all the variables that hav
loaded. Remember that a for . . . in statement loops through all the custom properties and meth
of an object, including the variables loaded from the script or file; the built-in methods and
properties of the LoadVars class don't show up. Since your onLoad( ) method is stored as a custo
property, that method shows up in a for . . . in statement. You can use an if statement to filter o
onLoad( ) from the display. This principle also holds true for other custom properties or methods
might have defined for the object that you don't want to appear (in order to display the loaded
variables only). For example:

[Link] = function (success) {


if (success) {
// Use a for . . . in statement to enumerate the elements of this, which is th
// LoadVars object, myLoadVars.
for (var prop in this) {
// Display the property only if it is not "onLoad". To omit other propertie
// methods, add them to the conditional expression with &&'s. For example:
// (prop != "onLoad" && prop != "aCustomProperty")
if (prop != "onLoad") {
_root.output_txt.text += prop + ": " + this[prop] + newline;
}
}
}
};

If the LoadVars object already has properties with the same names as any of the loaded variable
those properties are overwritten. Therefore, avoid variables with the same names as any of the
properties or methods of the LoadVars class (including the undocumented onData( ) and decode
methods). Here is an example of a text file that includes a variable named onLoad:

onLoad=test&myVariable=abc

[Link] 2004-02-06
Strona 78 z 156

If you load these values into a Flash movie, the onLoad( ) method is never invoked because once
the data is loaded and decoded, the onLoad( ) method is overwritten with the value "test".

18.1.4 See Also


Recipe 18.2. See Recipe 6.13 for details on for . . . in loops.

[ Team LiB ]
[ Team LiB ]

Recipe 18.2 Loading Variables from a Server-Side Script


18.2.1 Problem
You want to load variables into a Flash movie from a server-side script (ColdFusion, Perl, PHP,
etc.).

18.2.2 Solution
Use the [Link]( ) method.

18.2.3 Discussion
The ActionScript to load variables from a server-side script is exactly the same as that used to
load variables from a text file. When you call the load( ) method you should pass it the URL of
the script from which you want to load the variables, then handle the results with an onLoad( )
method, as shown in Recipe 18.1.

You should use server-side scripts as the source for variables loaded into Flash when the values
for the variables are generated from a database or another resource accessible only to the
server. The script that you use must output URL-encoded data only, beginning from the first
character. If you are writing a CGI script in Perl, the result is URL-encoded, so you do not need
to make any special adjustments. For example:

#!/usr/bin/perl

# In a more practical example this value would be retrieved


# from a database or other server-side resource.
$myVar = "test";

# Define the Content-Type header.


print "Content-Type: text/plain\n\n";

# Output the name-value pair.


print "myVar=$myVar";

However, when you use a ColdFusion page to load variables into Flash, you need to take steps
to ensure that it outputs URL-encoded data and that the output begins from the first character.
Otherwise, extra whitespace characters may precede the output of the URL-encoded data, in
which case Flash cannot properly decode the values.

Here is what you should do:

1. Include the <cfsetting enablecfoutputonly="yes"> tag at the beginning of the


document.

[Link] 2004-02-06
Strona 79 z 156

2. Make sure to enclose any values you want to output within a <cfoutput> element.

3. Place the whole document in a <cfprocessingdirective suppresswhitespace="yes">


tag. This ensures that no extra whitespace characters are output.

For example:

<cfsetting enablecfoutputonly="yes">
<cfprocessingdirective suppresswhitespace="yes">
<cfset myVar="test">
<cfoutput>
myVar=#myVar#
</cfoutput>
</cfprocessingdirective>

If you use PHP, perform output using echo or print from within a processing directive, such as:

<?php
$myVar = "test";
echo "myVar=$myVar";
?>

Other preprocessor markup languages may or may not require additional steps to ensure
proper output. JSP and ASP (.NET and classic) do not require any special considerations. One
trick that seems to work in most cases is to simply surround each name/value pair with
ampersands. This helps Flash know where to begin and end each variable pair. This solution
should work regardless of the language/platform you are using (ASP, JSP, CFML, etc.). For
example, if you don't want to use the <cfprocessingdirective> and <cfsetting> tags in your
ColdFusion page, you should be able to rewrite the preceding ColdFusion example as follows
(note the ampersands in bold):

<cfset myVar="test">
<cfoutput>&myVar=#myVar#&</cfoutput>

In each of the preceding examples, the server-side script returns a single variable, myVar, to
Flash. Here is an example of the ActionScript code that loads the variable from a script and
displays it in the movie:

// You must first create the LoadVars object.


myLoadVars = new LoadVars( );

// Then load the variable from the server-side script. This example
// uses the [Link] script, which is assumed to be in
// the /cgi-bin directory on the same server. To test this code,
// change the URL to point to an actual script on your server.
[Link]("/cgi-bin/[Link]");

[Link] = function (success) {


// If the data loads, display the variable in a
// text field named output_txt on _root.
if (success) {
_root.output_txt.text = [Link];
}
};

Of course, many of the same things apply to loading variables from a script as when loading
from a file. For example, if you don't know all the variable names, you can use a for...in
statement to list them and their values, as shown in Recipe 18.1.

[Link] 2004-02-06
Strona 80 z 156

18.2.4 See Also


Recipe 18.1 and Recipe 19.11

[ Team LiB ]
[ Team LiB ]

Recipe 18.3 Checking Load Progress


18.3.1 Problem
You want to know how much of the data has loaded.

18.3.2 Solution
Use the [Link]( ) and [Link]( ) methods. Alternatively, use
a progress bar (see Recipe 15.9).

18.3.3 Discussion
The LoadVars class's getBytesLoaded( ) and getBytesTotal( ) methods work in the same way as
the methods of the same name from the MovieClip, Sound, and XML classes. Prior to a request
to load external data, both methods return 0. Once Flash determines information about the
requested data, getBytesTotal( ) returns the total number of bytes to load. The value of
getBytesLoaded( ) changes each time there is load progress. All the data is loaded when
getBytesLoaded( ) is equal to getBytesTotal( ), provided they're not both 0. Typically, you
should use a movie clip with an onEnterFrame( ) method or an interval function to monitor the
load progress. For example:

myLoadVars = new LoadVars( );


[Link]("[Link]");
[Link] = function (success) {
// Process loaded data here
};

function monitorLV ( ) {

// Get the percentage by multiplying the loaded-to-total bytes ratio by 100.


var percent = [Link]([Link]( ) /
[Link]( ) * 100);

// If the percentage is not a number (no bytes have loaded), set it to 0.


percent = (isNaN(percent)) ? 0 : percent;

// Display the load percentage in the Output window.


trace(percent);
}

You can use a Progress Bar component to monitor the progress of data loaded with a LoadVars
object, just as you can monitor the progress of data loaded into a movie clip or Sound object.
You should set the progress bar's load target to the LoadVars object, as follows:.

_root.attachMovie("FProgressBarSymbol", "pBar", 1);


[Link](myLoadVars);

18.3.4 See Also

[Link] 2004-02-06
Strona 81 z 156

Recipe 15.9 offers more details on how to use the Progress Bar component. See Recipe 15.8,
Recipe 15.10, Recipe 15.11, and Recipe 19.11.

[ Team LiB ]
[ Team LiB ]

Recipe 18.4 Sending Data to a Server-Side Script


18.4.1 Problem
You want to send data from a Flash movie to a server-side script.

18.4.2 Solution
Use the [Link]( ) method. Use [Link]( ) if you expect a response.

18.4.3 Discussion
Use the [Link]( ) method to send data to a server-side script if there is no need to
process the result. For example, you might want to submit a web form's data to a server-side
script without displaying any result from the server-side processing. However, the send( ) metho
does not return any confirmation that the data was received, so it isn't practical in most cases.
Even if you just want to display a static message such as "Thank you for submitting the form,"
you need confirmation that the variables were successfully received on the server. Therefore, if
you want confirmation of receipt, use the sendAndLoad( ) method instead (see Recipe 18.5).

The send( ) method sends all the enumerable properties of a LoadVars object to the specified
URL. Enumerable properties are any properties that show up in a for . . . in statement for that
object. The built-in methods and properties of the LoadVars class do not show up in a for . . . in
statement, but custom properties do:

myLoadVars = new LoadVars( );

// Nothing is displayed in this example.


for (var prop in myLoadVars) {
trace(prop);
}

[Link] = "test";

// Now the myVar property shows up. When send( ) is invoked


// from myLoadVars, the myVar variable is sent to the specified URL.
for (var prop in myLoadVars) {
trace(prop);
}

The send( ) method requires at least one parameter: the URL to which to send the variables. The
URL can be absolute or relative:

// Send variables to a CGI script with an absolute URL.


[Link]("[Link]

// Send variables to a CGI script relative to the location of the Flash .swf file
[Link]("cgi-bin/[Link]");

[Link] 2004-02-06
Strona 82 z 156

The server's response to a send( ) method is disregarded by the Flash movie but can be sent to
browser window. If you want the response to be displayed in a web browser, specify a target
browser window as the second parameter. Use the target of "_blank" to display the response in
new browser window, or you can display the result in another, named window or frame that is
already open if you know the correct target name. Neither of these two options have any effect o
the browser window containing the Flash movie. However, specifying the target as "_self" or
"_parent" replaces the page contained the Flash movie.

// Send variables to a CGI script and display the script's output in a new browse
// window.
[Link]("cgi-bin/[Link]", "_blank");

If you want the response to be returned to the Flash movie, use the sendAndLoad( ) method, as
discussed in Recipe 18.5.

By default, the send( ) method uses the HTTP POST method to send data to the specified script.
the server-side script handles data sent using GET only, you can specify "GET" as the method
parameter (the third parameter) when calling send( ):

// Send variables to a CGI script using the GET method. The null value is specifi
// for the target parameter so that the response is disregarded.
[Link]("cgi-bin/[Link]", null, "GET");

Note that the method parameter must be the literal "GET" or "POST". Variables or expressions ar
not allowed for the method.

18.4.4 See Also


Recipe 18.5. See Recipe 6.13 for details on for . . . in loops. See Recipe 11.13, Recipe 11.17, and
Recipe 11.19 for ways to transmit data associated with a form.

[ Team LiB ]
[ Team LiB ]

Recipe 18.5 Sending Variables and Handling a Returned


Result
18.5.1 Problem
You want to send variables to a server-side script and handle the results of the server-side
processing.

18.5.2 Solution
Use the [Link]( ) method.

18.5.3 Discussion
You should use the sendAndLoad( ) method when you want to send variables to a server-side sc
and have the results returned to Flash. An example of such a scenario is a Flash storefront for a
product catalog that is stored in a database. Typically, items are categorized. When a user select
category, the Flash movie might send the selected category ID to a server-side script and expect
script to return all the items in the category.

[Link] 2004-02-06
Strona 83 z 156

The sendAndLoad( ) method sends variables to a server-side script in the same way that the sen
method does. Any enumerable properties of the LoadVars object are automatically sent to the sc
at the specified URL. The difference between send( ) and sendAndLoad( ) is that with the latter y
must also provide a reference to a LoadVars object that handles the loading of the results. You c
use the same LoadVars object to load the returned data as you used to send the variables. Howe
it is a best practice to specify a different object for loading the data. Using two LoadVars objects
avoids conflicts with variable names. Otherwise, if the sending object has a property with the sam
name as one of the loaded variables, the loaded variable overwrites the sending object's propert
Here is an example using two separate LoadVars objects (one for sending and one for receiving)

// Create an object to handle the data loading. Define an onLoad( ) method.


loadLV = new LoadVars( );
[Link] = function (success) {
if (success) {
trace("loaded results");
}
};

// Create an object to send the variables to the script.


sendLV = new LoadVars( );

// Define at least one variable to send.


[Link] = 42;

// Invoke the sendAndLoad( ) method specifying loadLV as the object that handles
// loading of the returned data.
[Link]("cgi-bin/[Link]", loadLV);

18.5.4 See Also


Recipe 18.4. Recipe 19.14 covers analogous functionality using XML. Also refer to Recipe 17.3.

[ Team LiB ]
[ Team LiB ]

Chapter 19. XML

Introduction

Recipe 19.1. Understanding XML Structure (Reading and Writing XML)

Recipe 19.2. Creating an XML Object

Recipe 19.3. Adding Elements to an XML Object

Recipe 19.4. Adding Text Nodes to an XML Object

Recipe 19.5. Creating an XML Object from an Array

Recipe 19.6. Adding Attributes to an XML Element

Recipe 19.7. Reading Elements in an XML Tree

Recipe 19.8. Finding Elements by Name

[Link] 2004-02-06
Strona 84 z 156

Recipe 19.9. Reading Text Nodes and Their Values

Recipe 19.10. Reading an Element's Attributes

Recipe 19.11. Loading XML

Recipe 19.12. Removing Extra Whitespace from XML Objects

Recipe 19.13. Sending XML

Recipe 19.14. Sending XML Data and Receiving a Response

Recipe 19.15. Searching XML

Recipe 19.16. Using XML Data to Initialize a Movie

[ Team LiB ]
[ Team LiB ]

Introduction
The capability to load, parse, and send XML data was added to Flash in version 5, and it marked
a significant advance in the ease of communicating complex data to and from a server script.
Prior to the introduction of the XML class to Flash, the only way to load and send text data was
to use URL-encoded values, as discussed in Chapter 18. XML is a superior alternative in many
cases because, unlike URL-encoded data, XML data is structured. URL encoding is fine for
passing simple data between Flash and server-side scripts, but for complex data or Unicode
characters, XML generally works much better. For example, if you want to load data from a text
file that represents a simple datatype such as a string, URL-encoded data, such as the
following, can be loaded using a LoadVars object:

myString=a+string+value

However, when you want to load data from an external source and use that data to create an
ActionScript object, you are presented with the problem of how to represent that data as a URL-
encoded string. You might try something like the following, in which each property-value pair is
separated by an asterisk (*), and each property is separated from its corresponding value by a
vertical pipe (|):

myObject=prop0|val0*prop1|val1*prop2|val2

Once the string value is returned for myObject, you could use [Link]( ) to recreate the
elements that make up the object. Although you can get by with this approach, it is often much
easier to represent complex values in XML. For example, the same object can be represented
by the following XML snippet:

<myObject>
<prop0>val0</prop0>
<prop1>val1</prop1>
<prop2>val2</prop2>
</myObject>

XML data offers several advantages over URL-encoded data, including:

z When creating XML manually (for a static XML document) or programmatically (from a
ColdFusion script, PHP script, etc.), it is much easier to represent complex data.

[Link] 2004-02-06
Strona 85 z 156

z Most server-side scripting languages offer built-in functionality for reading and generating
XML data.

z XML is a standard used for the transfer and storage of data across all kinds of applications
and platforms.

There are, of course, other ways to send and receive data in Flash. Chapter 17 covers ways to
transmit data using the LocalConnection and SharedObject classes. Chapter 20 covers data
transmission using Flash Remoting. However, this chapter focuses on XML, which doesn't
require a server-side installation (as does Flash Remoting) and works in Flash Player 5, unlike
the LocalConnection and SharedObject classes.

An XML tree generally has an object of the XML class as its top node and objects of the
XMLnode class as nodes below it. Many of the methods and properties discussed in this chapter
work with both XML and XMLNode objects, but some apply to objects of the XML class only. This
is because the XML class is a subclass of the XMLNode class.

The following methods and properties apply to all instances of the XMLNode and XML classes:

appendChild( )
attributes
childNodes
cloneNode( )
firstChild
hasChildNodes( )
insertBefore( )
lastChild
nextSibling
nodeName
nodeType
nodeValue
parentNode
previousSibling
removeNode( )
toString( )

The following methods, properties, and event handlers apply to instances of the XML class but
not to XMLNode instances:

contentType
createElement( )
createTextNode( )
docTypeDecl
getBytesTotal( )
getBytesLoaded( )
ignoreWhite
load( )
loaded
onData( )
onLoad( )
parseXML( )
send( )
sendAndLoad( )
status
xmlDecl

In this chapter, the following terminology is used:

[Link] 2004-02-06
Strona 86 z 156

XML document

A file containing XML, or the contents of that file. We also use this term to refer to XML
data that is being loaded or sent.

XML packet

An XML packet can be any snippet of XML—from an entire XML document to just a single
node—as long as it is represents a complete, well-formed piece of information in XML.

XML node

A node is the basic building block for XML. Nodes can be elements, text nodes, attributes,
and so on. We refer to elements and text nodes collectively as "nodes" when talking in
general terms.

XML element

The term "element" is often used interchangeably with the term "tag." More accurately,
however, an element contains tags. Elements must have an opening and closing tag
(<myElement></myElement>), or the opening and closing tags can be combined into one
when the element has no nested elements (<myElement />).

Root node

The root node is an element that is at the top of the XML hierarchy of elements.

Text node

A node containing text. Text nodes are generally nested within elements.

Attribute

An attribute is a node that is part of an element. Attributes are placed within the tags of
elements in name/value pair format, such as <myElement name="value">.

XML declaration

The declaration typically looks like this: <?xml version="1.0" ?>. It is a special tag that
the XML parser recognizes as containing information about the XML document, and it is
not parsed as an element.

XML tree

Also sometimes called the "data tree," an XML tree is the hierarchy of nodes in XML data.

[ Team LiB ]
[ Team LiB ]

Recipe 19.1 Understanding XML Structure (Reading and


Writing XML)

[Link] 2004-02-06
Strona 87 z 156

19.1.1 Problem
You want to understand how to read or write XML.

19.1.2 Solution
XML is tag-based and hierarchical. If you are familiar with HTML, learning the basics of XML isn't
very difficult.

19.1.3 Discussion
Although reading and writing good XML is not a skill that is specific to ActionScript, it is,
nonetheless, a skill from which your ActionScript can benefit. If you are not yet familiar with
XML, don't worry. This is going to be painless.

XML is a way of representing structured data. This means that you explicitly define the context
for the data. For example, without XML you might have a string of data such as:

Jerry,Carolyn,Laura

You can use XML to tell us who these people are:

<family>
<father>Jerry</father>
<mother>Carolyn</mother>
<sister>Laura</sister>
</family>

Now, as you can see, the XML tells us a lot more about the data. Here are a few other points to
notice about XML:

z XML is composed mainly of nodes. A node is a general term that can refer to many parts
within the XML. For example, <family> is a node in the preceding XML snippet. These
kinds of nodes are called elements. Also, the values such as Jerry, Carolyn, and Laura are
nodes. These kinds of nodes are called text nodes.

z Every XML element must have a matching opening and closing tag. The opening tag might
look like <family>, and the closing tag is identical except that it uses a forward slash to
indicate it is closing the element, as in </family>. The opening and closing tags can be
combined if the element does not contain any nested nodes. For example,
<emptyElement /> is an element that combines the opening and closing tags. Notice that
there is a space between the element name, emptyElement, and the forward slash.

z Elements can contain nested nodes (be they other elements or text nodes). There are
several examples of this in the <family> XML document shown earlier. The <family>
element, which is the root node in this example, contains three nested elements:
<father>, <mother>, and <sister>. These nested nodes are also called child nodes. Each
of these child nodes also contains a nested node. However, their nested nodes are text
nodes, and not elements. In either case, they are still treated as child nodes.

There is one other type of node that we want to look at here. An attribute is a special kind of
node that can be assigned to an element, and in many cases it can even be used as an
alternative to a nested node. If you've ever worked with HTML, you are already familiar with
attributes. Some common attributes in HTML include the href attribute of the <a> element and
the colspan attribute of the <td> element. Here is how we can rewrite the same XML document
we examined previously using attributes instead of nested nodes:

[Link] 2004-02-06
Strona 88 z 156

<family father="Jerry" mother="Carolyn" sister="Laura" />

Notice that we were able to eliminate the nested elements and write the same data all in one
element. Also notice that since <family> no longer contains any nested nodes, we can combine
the opening and closing tags.

You may be wondering when and why to use attributes versus nested nodes. This is often a
matter of preference. Sometimes it may appear easier or clearer to you to write the XML data
using attributes. Generally, attributes are a good idea when you want to represent a fairly small
number of values and when those values are relatively short. Also, the attributes' names need to
be unique within the element. When you want to represent larger quantities of data, when the
data is long (more than a few words), or when the names of the attributes would not be unique
within the element, use nested elements instead.

Also, you can use a combination of both attributes and nested nodes. Here is an example of an
<article> element that includes attributes for the title and author but uses a nested text
node to represent the article text. This is a good example of when one of the values (the article
text) is simply too long to reasonably be an attribute.

<article title="XML: It's Not Just for Geeks" author="Samuel R. Shimowitz">


My friends couldn't believe it when I started working with XML. I became an
outcast, confined to my dark office illuminated only by the glow of my trusty CRT
Blah blah blah.
</article>

You can create XML strings right in Flash for cases in which you are constructing XML data to
send to an external script. For example:

xmlStr = "<feedback name='Aham' comments='nice'>";

To construct a static XML document outside of Flash, use a simple text editor and save it as plain
text. To create an XML document dynamically using a server-side script, consult the appropriate
reference for the language you are using.

19.1.4 See Also


The preceding examples are very simple cases. XML can get much more complex, including
namespaces, document type declarations, etc. Most of these details are well beyond the scope o
this book and beyond what you need to know to work with XML and Flash. For more information
on XML in general, see [Link] For more information on XML data structures and
how to access them in Flash, refer to the XML, XMLnode, and XMLSocket classes in the
"Language Reference" section of ActionScript for Flash MX: The Definitive Guide.
[ Team LiB ]
[ Team LiB ]

Recipe 19.2 Creating an XML Object


19.2.1 Problem
You want to create an XML object complete with a tree structure and data.

19.2.2 Solution
A populated XML object contains data, just as a populated city contains people.

Use one of the following to create a populated XML object:

[Link] 2004-02-06
Strona 89 z 156

z Populate the XML object tree by passing an XML string to the constructor.

z Create a blank object and use the parseXML( ) method to populate the XML tree with an
XML string.

z Create a blank object and use the XML class's properties and methods to populate the
XML tree one node at a time.

z Create a blank object and load the XML data from an external source.

19.2.3 Discussion
There are many possibilities for creating a populated XML object in Flash. Each technique offers
its own advantages, and you should base your decision on the needs of your project.

The simplest way to create a populated XML object is to pass an XML string to the constructor:

my_xml = new XML("<abc><a>eh</a><b>bee</b><c>see</c>");

This first technique is appropriate when you already know the complete XML string before you
want to create the object. For example, if you want to create a simple XML object to send a
user's name and score to the server, you could easily construct the XML string using known
data and then construct the XML object, as follows:

xmlStr = "<gamescore><username>" + username +


"</username><score>" + score + "</score></gamescore>";
score_xml = new XML(xmlStr);

You can also use the parseXML( ) method to populate an XML object using an XML string. This
is useful in situations in which you already have an existing XML object, but you want to
populate it with new data. For example, you might already have an existing score_xml object in
which you stored the name and score of the last player. Instead of creating a new object, you
can simply repopulate the existing object using parseXML( ). For example:

xmlStr = "<gamescore><username>" + username + "</username><score>" +


score + "</score></gamescore>";
score_xml.parseXML(xmlStr);

There are other cases, however, in which you don't necessarily know all the data at one time.
You might want to build the XML object's tree over time. For example, if you use an XML object
to store a user's shopping cart information, you need to modify the object's data each time the
user modifies her shopping cart. In such cases, you should use the built-in properties and
methods that allow you to add and remove nodes, as described in Recipe 19.3 and Recipe 19.4.

And let's not forget about populating XML objects with data retrieved from an external source,
such as a static XML document or a script that generates dynamic XML. Up to this point we've
looked only at constructing XML objects send data to a server. But you can load data from the
server for all kinds of reasons—retrieving user data, catalog information, or movie initialization
information, just to name a few. For these scenarios, you should use either the load( ) or the
sendAndLoad( ) method, as discussed in Recipe 19.11 and Recipe 19.14.

19.2.4 See Also


See Recipe 19.3, Recipe 19.4, Recipe 19.5, and Recipe 19.6 for more information on
constructing an XML tree using the built-in properties and methods of the XML class. For more
information on loading information from external sources, see Recipe 19.11 and Recipe 19.14.

[Link] 2004-02-06
Strona 90 z 156

Also refer to Recipe 19.16 for an example of how to use loaded data to initialize a movie.

[ Team LiB ]
[ Team LiB ]

Recipe 19.3 Adding Elements to an XML Object


19.3.1 Problem
You want to construct an XML object and add elements to it.

19.3.2 Solution
Use the createElement( ) method to create the element, and then use the appendChild( ) or
insertBefore( ) method to add the element to the XML tree.

19.3.3 Discussion
You might want to add elements to an XML object to construct an XML data structure to pass to
another application. There are several ways to accomplish this, as discussed in Recipe 19.2.

You can create XML elements using the createElement( ) method of any XML object. You should
specify the element name as a string parameter passed to the createElement( ) method. The
method returns a reference to the new element (an XMLnode object).

my_xml = new XML( );

// Create a new element, <myFirstElement />.


myElement = my_xml.createElement("myFirstElement");

Although the createElement( ) method creates the new element, it doesn't insert it into the XML
tree hierarchy. Instead, you have to instruct Flash where to insert the new element using either
the appendChild( ) or insertBefore( ) method. The appendChild( ) method adds the element to
the end of the children elements of the XML object (or XMLNode object) from which the method
is called:

// Adds the myElement element to the my_xml object.


my_xml.appendChild(myElement);

trace(my_xml); // Displays: <myFirstElement />

// Create another element, <myFirstNestedElement />.


myNestedElement0 = my_xml.createElement("myFirstNestedElement");

// Add the new element as a child of myElement.


[Link](myNestedElement0);

// Displays: <myFirstElement><myFirstNestedElement /></myFirstElement>


trace(my_xml);

If you want to insert an element before another child element (instead of simply appending it to
the end of the list of child nodes), use the insertBefore( ) method. This method works just like
the appendChild( ) method except that you also need to supply a reference to the element
before which the new element is inserted.

// Create and append a third nested element (before adding the second).

[Link] 2004-02-06
Strona 91 z 156

myNestedElement2 = my_xml.createElement("myThirdNestedElement");
[Link](myNestedElement2);

// Displays: <myFirstElement><myFirstNestedElement />


// <myThirdNestedElement /></myFirstElement>
trace(my_xml);

// Create the second element and insert it before the third element.
myNestedElement1 = my_xml.createElement("mySecondNestedElement");
[Link](myNestedElement1, myNestedElement2);

// Displays: <myFirstElement><myFirstNestedElement />


// <mySecondNestedElement /><myThirdNestedElement /></myFirstElement>
trace(my_xml);

19.3.4 See Also


Recipe 19.4

[ Team LiB ]
[ Team LiB ]

Recipe 19.4 Adding Text Nodes to an XML Object


19.4.1 Problem
You want to add text nodes to an XML object.

19.4.2 Solution
Use the createTextNode( ) and appendChild( ) methods.

19.4.3 Discussion
The createTextNode( ) method is very similar to the createElement( ) method discussed in
Recipe 19.3, except that it creates a new text node instead of an XML element. As with the
createElement( ) method, the createTextNode( ) method does not insert the node into the XML
object hierarchy but returns a reference to the newly created node. You are responsible for
inserting the text node into the XML object using the appendChild( ) or insertBefore( ) method.

// Create an XML object.


my_xml = new XML( );

// Create an element to which to add the text node.


myElement = my_xml.createElement("myFirstElement");

// Create the next node with the value "this is some text".
myTextNode = my_xml.createTextNode("this is some text");

// Add the text node to the element.


[Link](myTextNode);

// Add the element to the XML object


my_xml.appendChild(myElement);

// Displays: <myFirstElement>this is some text</myFirstElement>


trace(my_xml);

[Link] 2004-02-06
Strona 92 z 156

Generally, text nodes are not of much use in isolation. Remember, XML data is structured. That
means that your text nodes should always be nested in XML elements such as the
<myFirstElement> node in the preceding example. Therefore, we first create the element using
the createElement( ) method (see Recipe 19.3). We then create the text node and assign it a
value of "this is some text"—both accomplished using the createTextNode( ) method. Neither of
the two nodes (the element nor the text node) has been added to the XML object, so we use the
appendChild( ) method to nest these nodes. We nest the text node in the element, and we nest
the element in the XML object as the root node.

The appendChild( ) method is valid for all XMLNode objects, including text
nodes. However, text nodes cannot contain nested nodes; therefore,
calling appendChild( ) from a text node has no effect.

It might seem strange to have to create a text node as a separate node from its parent element.
After all, you may wonder why we don't just assign the text value to a property of the element
instead. The answer is that XML can be more complex than that solution would allow. Consider,
for a moment, a mixed element, which is an element that contains not only a child text node or
child elements, but both. For example:

<article>
<title>XML: It's Not Just for Geeks</title>
<author>Samuel R. Shimowitz</author>
My friends couldn't believe it when I started working with XML. I became an
outcast, confined to my dark office illuminated only by the glow of my trusty CRT
Blah blah blah.
</article>

In this example, <article> is a mixed element because it contains not only nested elements
(<title> and <author>) but also a nested text node. This is perfectly valid XML, and for this
reason, we want to be able to treat each nested item as its own node.

When working with text nodes, it can be convenient to create the text node and the parent
element all in one step. You can automate this process with a short and simple custom method,
createElementWithText( ). Add this method to the XMLNode class by placing the following code
in a custom [Link] file that you place in your Flash Include directory for easy inclusion in future
projects. The method accepts two parameters: the name of the element and the value to add to
the nested text node.

[Link] = function (name, text) {

// Create the element.


var elem = [Link](name);

// Create the text node.


var txtNode = [Link](text);

// Add the text node to the element.


[Link](txtNode);

// Return the element.


return elem;
};

Here is an example of the createElementWithText( ) method in use:

#include "[Link]"

my_xml = new XML( );


myElement = my_xml.createElementWithText("where", "here");

[Link] 2004-02-06
Strona 93 z 156

my_xml.appendChild(myElement);
trace(my_xml); // Displays: <where>here</where>

Notice that the createElementWithText( ) method returns a new XMLNode object just like the
createElement( ) and createTextNode( ) methods. This means that you need to add it to an XML
object using a method such as appendChild( ).

19.4.4 See Also


Recipe 19.3
[ Team LiB ]
[ Team LiB ]

Recipe 19.5 Creating an XML Object from an Array


19.5.1 Problem
You want to create an XML object using an array to populate it.

19.5.2 Solution
Use a for statement to create and insert the elements and text nodes.

19.5.3 Discussion
Generating an XML object, especially a large one, can be somewhat tedious. In some cases you
can make your job a little easier by storing the text node values in an array and inserting them
into the XML object using a for loop.

Let's take a look at an example in which you want to create an XML object to represent a menu
and the menu items. This might be a useful example if, say, you are creating a Flash movie that
allows the user to configure a menu's items and save them to the server so he can retrieve them
later on.

// Include [Link] for the custom createElementWithText( ) method from Recipe 19.
#include "[Link]"

// Create the array of text node values.


menuItems = new Array("Springs", "Cogs", "Widgets", "Gizmos");

my_xml = new XML( );

// Create the root element for the XML object.


rootElement = my_xml.createElement("menu")

// Use a for statement to loop through all the elements in the array.
for (var i = 0; i < [Link]; i++) {

// Create an XML element for each arrangement.


menuItemElement = my_xml.createElementWithText("menuItem", menuItems[i]);
[Link](menuItemElement);
}

// Add the root element to the XML object.


my_xml.appendChild(rootElement);

// Displays: <menu><menuItem>Springs</menuItem><menuItem>Cogs</menuItem>
// <menuItem>Widgets</menuItem><menuItem>Gizmos</menuItem></menu>

[Link] 2004-02-06
Strona 94 z 156

trace(my_xml);

The first thing we did in this example was to create an array of options we'll add to the XML
object. Each element represents a menu item. In a real-world example the elements of the array
might be added one at a time by the user, but in our code snippet we're creating the array all at
once for the sake of demonstration. Next, we create a new XML object and a root element. Then
using a for statement, we loop through all the items in the array, create new elements and text
nodes one by one, and append them to the root element.

We can also create a custom method, populateFromArray( ), and add it to the XMLNode class.
This custom method automates the same process, so you can use it in many different situations,
not just when creating an XML packet to represent a menu. Add the following code to your custo
[Link] file in your Flash Include directory. The method accepts two parameters: the array of
values to add and the name of each nested element.

[Link] = function (items_ar, elementName) {


for (var i = 0; i < items_ar.length; i++) {
// Create an element.
itemElement = my_xml.createElementWithText(elementName, items_ar[i]);
[Link](itemElement);
}
};

Next, we'll use the method to accomplish the same thing we did earlier with the menu XML. The
uncommented lines of code are the same as before. The new code is shown in bold.

#include "[Link]"

menuItems = new Array("Springs", "Cogs", "Widgets", "Gizmos");


my_xml = new XML( );
rootElement = my_xml.createElement("menu")

// Populate the rootElement node with the contents of menuItems.


[Link](menuItems, "menuItem");

my_xml.appendChild(rootElement);

// Displays: <menu><menuItem>Springs</menuItem><menuItem>Cogs</menuItem>
// <menuItem>Widgets</menuItem><menuItem>Gizmos</menuItem></menu>

trace(my_xml);
[ Team LiB ]
[ Team LiB ]

Recipe 19.6 Adding Attributes to an XML Element


19.6.1 Problem
You want to add attributes to an XML element.

19.6.2 Solution
Assign properties to the attributes property of an XMLNode object representing an element.

19.6.3 Discussion
Every XMLNode object has a read/write attributes object that determines the attributes of the

[Link] 2004-02-06
Strona 95 z 156

element. You can assign properties to the attributes object, and those properties are
automatically added to the element's attributes. For example:

my_xml = new XML( );


myElement = my_xml.createElement("myFirstElement");

// Create attributes a, b, and c for myElement.


[Link].a = "eh";
[Link].b = "bee";
[Link].c = "see";

my_xml.appendChild(myElement);

// Displays: <myFirstElement c="see" b="bee" a="eh" />


trace(my_xml);

When you assign values to the attributes object, be careful not to overwrite the entire object,
as in:

[Link] = {a: "eh", b: "bee", c: "see"};

Using an object literal to add multiple properties will add extra, undesired attributes since the
new object also includes two hidden properties, _ _proto_ _ and constructor.

Now, let's talk briefly about some of the things you can and cannot do with attributes in Flash.
First of all, even though XML allows for attribute values containing newline characters, Flash
does not. Therefore, if your attribute will contain a newline, be sure to use a nested node
instead. Also, if you access attributes using dot syntax, as shown in the preceding example, you
must use valid Flash variable names for the names of the attributes. This means that the
attributes must have names consisting of numbers, letters, and underscores, and the first
character cannot be a number. However, XML attribute names can sometimes contain other
characters for good reasons. For example, though a full discussion of namespaces is beyond the
scope of this book, XML namespaces can require that an attribute's name contain characters
such as a colon (:). You can use array-access notation to create or read attributes whose
names contain invalid variable name characters. For example:

[Link]["xmlns:soap"] = "[Link]

19.6.4 See Also


For more information on using attributes in XML, see Recipe 19.2 and Recipe 19.10.
[ Team LiB ]
[ Team LiB ]

Recipe 19.7 Reading Elements in an XML Tree


19.7.1 Problem
You want to extract the child elements of an XML or XMLnode object.

19.7.2 Solution
Use the firstChild, lastChild, nextSibling, and previousSibling properties to read the ele
one at a time. Alternatively, use the childNodes property to extract all the child elements as an

19.7.3 Discussion

[Link] 2004-02-06
Strona 96 z 156

You'll often want to "walk" (traverse) an XML tree to extract or examine one or more elements. T
convenient for searching for a given element or processing elements in which you know (or don't
about) their precise order.

All the child elements of an XML or XMLnode object fall into one of three possible categories: the
child, the last child, or some other sibling. Working from this premise, you can extract the child
elements of a node using the firstChild, lastChild, nextSibling, and previousSibling prop
The firstChild and lastChild properties return the first and last child elements of an XML or
XMLnode object, respectively. Assuming that there are no whitespace nodes in an XML object, th
element is returned by the firstChild property of that object.

my_xml = new XML("<sections><a /><b /><c /><d /></sections>");

// Extract the root element (<sections>).


rootElement = my_xml.firstChild;

// Extract the first child element of the root element (<a>).


aElement = [Link];

// Extract the last child element of the root element (<d>).


dElement = [Link];

You can access a child element's sibling nodes using the nextSibling and previousSibling
properties:

// Extract the next sibling to the <a> element (<b>).


bElement = [Link];

// Extract the previous sibling to the <d> element (<c>).


cElement = [Link];

Another way to extract the child elements of an XML or XMLnode object is to work with them as
elements of an array. The childNodes property returns such an array:

children = [Link];

// Displays:
// <a />
// <b />
// <c />
// <d />
for (var i = 0; i < [Link]; i++) {
trace(children[i]);
}

Using the aforementioned properties, you can walk through an XML tree structure recursively, as
shown in the following example. The search( ) method we create here can be useful in simple
situations in which you want to find all the elements with a particular name regardless of their co
or location in the tree's hierarchy. For more advanced and complex searches, use XPath (see Rec
19.15).

We'll add the search( ) method to the XMLNode class, and the method should accept a single
parameter: the node name for which to search. You can add this custom function to your [Link]
for easy inclusion in other projects:

[Link] = function (searchName) {

// Create an array in which to store the matching elements.


var matches = new Array( );

[Link] 2004-02-06
Strona 97 z 156

// If the current node has children, loop through all elements to find matches.
if ([Link]( )) {
for (var i = 0; i < [Link]; i++) {
// For each child node, invoke the search( ) method recursively. Concatena
// results with the current matches array.
matches = [Link]([Link][i].search(searchName));
}
}

// If the current node is a match, add it to the matches array. For more
// information on the nodeName property, see Recipe 19.8.
if ([Link] == searchName) {
[Link](this);
}

// Return the matches.


return matches;
};

Here is an example of how you might use this method:

#include "[Link]"

// Set ignoreWhite to true for the XML class. See Recipe 19.12 for more informati
// on the ignoreWhite property.
[Link] = true;

// Create an XML object and load data from an external document named [Link].
my_xml = new XML( );
my_xml.load("[Link]");
my_xml.onLoad = function (success) {
if (success) {
// When the document loads, use the search( ) method to find all the <author
// elements. Display the results in the Output window. We join all the elemen
// of the resulting array together with a newline so that each one appears on
// own line.
trace([Link]("author").join("\n"));
}
};

19.7.4 See Also


Recipe 19.15, Recipe 19.12, and Recipe 19.8
[ Team LiB ]
[ Team LiB ]

Recipe 19.8 Finding Elements by Name


19.8.1 Problem
You want to identify an element by name rather than by its position in the XML hierarchy.

19.8.2 Solution
Use the nodeName property.

19.8.3 Discussion
You can use the nodeName property to make sure you are properly processing an XML object

[Link] 2004-02-06
Strona 98 z 156

when the exact order of elements is not known. The nodeName property returns the element
name:

// Create an XML object with a single element.


my_xml = new XML("<root />");

// Extract the root element.


rootElement = my_xml.firstChild;

// Displays: root
trace([Link]);

This example extracts elements based on their names. Note that it walks the XML tree using the
childNodes array to examine each element in sequence:

my_xml = new XML("<elements><a /><d /><b /><c /></element>");


rootElement = my_xml.firstChild;
children = [Link];

for (var i = 0; i < [Link]; i++) {


// Set the value of the proper variable depending on the value of nodeName. Thi
// technique works regardless of the order of the child elements.
switch (children[i].nodeName) {
case "a":
aElement = children[i];
break;
case "b":
bElement = children[i];
break;
case "c":
cElement = children[i];
break;
case "d":
dElement = children[i];
break;
}
}

19.8.4 See Also


For a more complex example using nodeName, see the search( ) method in Recipe 19.7.

[ Team LiB ]
[ Team LiB ]

Recipe 19.9 Reading Text Nodes and Their Values


19.9.1 Problem
You want to extract the value from a text node.

19.9.2 Solution
Extract the text node from its parent using the firstChild property. Then get the text node's
value using the nodeValue property.

19.9.3 Discussion

[Link] 2004-02-06
Strona 99 z 156

In Recipe 19.4 we looked at text nodes and how to create them in your XML objects. Now we
want to look at how to extract the value from a text node. You can use the firstChild,
lastChild, nextSibling, previousSibling, and childNodes properties discussed in Recipe
19.7 to get a reference to a text node the same way you would get a reference to any other
node (such as an element). Then, once you have a reference to the text node, use the
nodeValue property to retrieve that node's text value.

An example will help illustrate. Let's consider the following XML packet:

<book>
<title>ActionScript Cookbook</title>
</book>

In this XML packet, the root node is <book>, which contains a child element, <title>. The
<title> element, in turn, contains a nested node—a text node with a value of "ActionScript
Cookbook".

If we parse this XML packet into an XML object, we can get the root node by using the
firstChild property. Then we can get the <title> element with the firstChild property of
the root node. And finally, we can get the text node with the firstChild property of the
<title> element.

my_xml = new XML("<book><title>ActionScript Cookbook</title></book>");

// Extract the root node (<book>).


bookElement = my_xml.firstChild;

// Extract the <title> element.


titleElement = [Link];

// Extract the text node (ActionScript Cookbook).


titleTextNode = [Link];

Once you have extracted a text node, you can read its value using the nodeValue property:

value = [Link];

trace(value); // Displays: ActionScript Cookbook

19.9.4 See Also


Recipe 19.4

[ Team LiB ]
[ Team LiB ]

Recipe 19.10 Reading an Element's Attributes


19.10.1 Problem
You want to extract the attributes of an element.

19.10.2 Solution
Use the attributes property.

[Link] 2004-02-06
Strona 100 z 156

19.10.3 Discussion
You should use the attributes property to return an associative array of an element's attributes
The keys of the associative array are the names of the attributes, and the values corresponding
those keys match up with the attribute values:

my_xml = new XML("<myElement a='eh' b='bee' c='see' />");


attribs = my_xml.[Link];

// Use a for...in statement to loop through the


// keys and values of the associative array. This displays:
// a = eh
// b = bee
// c = see
for (var key in attribs) {
trace(key + " = " + attribs[key]);
}

The attributes property is very important in any situation in which you want to retrieve XML da
stored in attributes. For example, suppose you want to display information about an article to th
user, including the title and author, both of which are stored in an element as attributes. Here is
example of such an XML packet:

<article title="XML: It's Not Just for Geeks" author="Samuel R. Shimowitz" />

Once you have loaded this content into an XML object, you can display the values to the user wit
just a few lines of code:

// Create the XML object and set ignoreWhite to true since we're loading the data
// from an external source.
article_xml = new XML( );
article_xml.ignoreWhite = true;

// Define the onLoad( ) method. When the data is loaded, display the title and au
// data in their respective text fields on the main timeline.
article_xml.onLoad = function ( ) {
var articleElement = [Link];
_root.title_txt.text = [Link];
_root.author_txt.text = [Link];
};

// Load the external data from a file named [Link].


article_xml.load("[Link]");

19.10.4 See Also


For more information on the attributes property, see Recipe 19.6.
[ Team LiB ]
[ Team LiB ]

Recipe 19.11 Loading XML


19.11.1 Problem
You want to load XML data from an XML document or a server-side script that generates XML.

19.11.2 Solution

[Link] 2004-02-06
Strona 101 z 156

Use the [Link]( ) method to load the data. Define an onLoad( ) method to handle the results.

19.11.3 Discussion
The load( ) method loads XML data from the URL that you specify:

my_xml = new XML( );

// Initiate the loading of an XML document in the same directory as the .swf file
my_xml.load("[Link]");

To meaningfully process the XML data that loads, you must define an onLoad( ) method for the
XML object on which you called load( ). The onLoad( ) method is automatically invoked when the
data loads, and it is passed a Boolean value indicating whether the XML data was successfully
parsed. It is up to you to define the onLoad( ) method so that it processes the XML data in the
way that you want it to.

my_xml.onLoad = function (success) {


if (success) {
trace("The XML was successfully parsed. Here are the contents: ");
trace(this);
} else {
trace("There was an error parsing the XML data");
}
};

You should, almost without exception, set the XML object's ignoreWhite property to true before
you load any XML data into a Flash movie. Otherwise, the ActionScript XML parser creates
whitespace nodes for any space, tab, carriage return, newline, or form feed character. The
resulting data is much more difficult to handle because you have to contend with excess
whitespace nodes. Setting the ignoreWhite property to true causes the parser to automatically
ignore whitespace and not create separate whitespace elements in the data. You must set the
property to true prior to the parsing of the data. In practical terms this means you should set
the property to true before you call the load( ) method. See a more detailed discussion in Recipe
19.12.

my_xml.ignoreWhite = true;

Setting ignoreWhite on the prototype sets it for all XML objects:

[Link] = true;

19.11.4 See Also


Recipe 19.12

[ Team LiB ]
[ Team LiB ]

Recipe 19.12 Removing Extra Whitespace from XML Obje


19.12.1 Problem
You want to remove whitespace nodes from your XML object because they are interfering with yo
programmatically extract the values you want.

[Link] 2004-02-06
Strona 102 z 156

19.12.2 Solution
Set the ignoreWhite property to true before loading external data into an XML object or before
string into an XML object, or use a custom removeWhitespace( ) method to remove whitespace n
loading data or for legacy purposes.

19.12.3 Discussion
XML documents are often written with extra whitespace characters for the purposes of formatting
easier for humans to read. Most of the time, each element appears on its own line (meaning that
carriage return, or form feed characters have been added), and child nodes are sometimes inden
spaces. In what follows, you can see the same XML data formatted in two ways. The first is the d
without the whitespace characters, and the second is the same data with formatting. Which is ea

<book><title>ActionScript Cookbook</title><authors><author name="Joey Lott" /></au

or:

<book>
<title>ActionScript Cookbook</title>
<authors>
<author name="Joey Lott" />
</authors>
</book>

While the formatted XML data may be easier for you, as a human, to read, the unformatted XML
suited for Flash. Flash counts all of the whitespaces as their own text nodes, which means that F
newline or tab character into its own node. The result is that you might have a more difficult tim
accurately locate the data you want.

Fortunately, when you load data from an external source, such as a static file or a server-side sc
parse any string into an XML object, you can tell Flash to ignore whitespace nodes when parsing
ignoreWhite property is set to false by default, which means that Flash parses whitespace nod
you set the ignoreWhite property to true prior to parsing any data into the object, Flash will no
whitespace nodes. This means that you should set the ignoreWhite property for an XML object t
calling the load( ) method or sendAndLoad( ) method, or before using the parseXML( ) method t

// Create the XML object.


my_xml = new XML( );

// Set ignoreWhite to true to ignore whitespace during parsing.


my_xml.ignoreWhite = true;

// Define an onLoad( ) method and then load the data. (See Recipe 19.11.)
my_xml.onLoad = function (success) {
// Actions to perform on load
};
my_xml.load("[Link]");

Since, in most cases, you want to set ignoreWhite to true, you can set the value of the ignore
for the entire XML class. This means that you only have to set the property once and all XML obj
that value. For most Flash developers, this is a common practice.

[Link] = true;

You can even add the preceding line to your [Link] file. Just be aware that you've effectively ch
behavior for all XML parsing operations.

[Link] 2004-02-06
Strona 103 z 156

Now, if you want to remove whitespace nodes from an XML document after the data has been lo
parsed, you need to create a custom method. Such a method is necessarily recursive, meaning t
to make sure that all the nodes are checked. Here, then, is our recursive whitespace-stripping fu
for inclusion in your [Link] file, if not for framing:

[Link] = function ( ) {
var cNode, hasNonWhitespace;

// Loop through all the child nodes.


for (var i = 0; i < [Link]; i++) {
cNode = [Link][i];

// If the node is a text node . . .


if ([Link] == 3) {

// Check to see if any of the characters it contains are non-whitespace


// characters. If so, set hasNonWhitespace to true and break out of the for
// loop since all it takes is one non-whitespace character to mean the node
// a non-whitespace node.
for (var j = 0; j < [Link]; j++) {
if ([Link](j) > 32) {
hasNonWhitespace = true;
break;
}
}

// If the node is a whitespace node, remove it with the removeNode( ) meth


// decrement i so that we don't skip over a node now that we've removed one
if (!hasNonWhitespace) {
[Link]( );
i--;
}
} else {
// Otherwise, it is not a text node; invoke removeWhitespace( ) recursivel
[Link]( );
}
}
};

Here is an example in which we use the removeWhitespace( ) method to remove whitespace afte
the data:

#include "[Link]"
my_xml = new XML( );
my_xml.load("[Link]");
my_xml.onLoad = function (success) {
if (success) {
// Run the removeWhitespace( ) method and display the results.
[Link]( );
trace(this);
}
};
[ Team LiB ]
[ Team LiB ]

Recipe 19.13 Sending XML


19.13.1 Problem
You want to send XML data to a server-side script.

[Link] 2004-02-06
Strona 104 z 156

19.13.2 Solution
In almost all cases, use [Link]( ), as discussed in Recipe 19.14. In some isolated
cases, use the [Link]( ) method.

19.13.3 Discussion
XML is normally used to transfer data to and from applications, and in this case, Flash movies.
Therefore, it is quite unusual that you would want to create XML objects in your Flash movies
for use within Flash alone. Instead, you generally load XML data from another source, create
XML data in Flash for the purposes of sending to another application, or both. In this recipe, we
want to look at sending XML data from Flash to another application. There are lots of reasons to
do this. For example, in a Flash game you might want to use XML to send the user's name and
score to the server. At other times, you might want to send XML packets to a server-side script
to invoke server-side functionality. This is a process that is sometimes called a remote
procedure call (RPC), and it can use XML to send the function invocation information (function
name, parameters, etc.) to the server. So as you can see, the possibilities for sending XML to
the server are quite diverse.

ActionScript actually includes two methods for sending XML data: send( ) and sendAndLoad( ).
The original idea was that send( ) would be for sending data in which no response was
necessarily expected or in which the response would be opened in a new browser window. The
sendAndLoad( ) method was intended for those times when you wanted to send data and also
receive a response from the server. However, the send( ) method seems to function other than
expected. The send( ) method relies on a mechanism in the web browser to work. However,
only one browser supports that functionality. Therefore, the send( ) method will not work in the
Test Player, the Standalone Player, Projectors, or in many web browsers. For these reasons, it
is suggested that you use the sendAndLoad( ) method even when you don't expect a server
response.

19.13.4 See Also


Recipe 19.14
[ Team LiB ]
[ Team LiB ]

Recipe 19.14 Sending XML Data and Receiving a Respons


19.14.1 Problem
You want to send XML data to a server-side script and load the response into the same Flash mo

19.14.2 Solution
Use the [Link]( ) method.

19.14.3 Discussion
The sendAndLoad( ) method does just what its name suggests—it sends XML data to a server-sid
script and loads the response back into Flash. It is ideal when you want to send data to the serve
processing and then receive the results back in the Flash movie. Additionally, since there are
limitations in the [Link]( ) method, the sendAndLoad( ) method is the best choice even in
situations in which you don't expect any response from the server.

The sendAndLoad( ) method sends the XML data from the object on which it is called. You must

[Link] 2004-02-06
Strona 105 z 156

specify a target XML object into which the response is loaded. You can use the same XML object
load the data as you use to send it, but generally it is better to create a separate XML object for
loaded data, since the loaded response replaces any existing data in the target XML object. Ther
in most cases, you should create two XML objects—one for sending and one for loading. For exam

// Create the loading object.


myLoadXML = new XML( );

// As when loading data using the load( ) method, set ignoreWhite to true.
[Link] = true;

// Define the onLoad( ) method. (See Recipe 19.11.)


[Link] = function (success) {
if (success) {
trace("The XML was successfully parsed. Here are the contents: ");
trace(this);
} else {
trace("There was an error parsing the XML data");
}
};

// Create the sending object.


mySendXML = new XML("<sendingData>this is some data</sendingData>");

// Set the content type and XML declaration. See the Recipe 19.14.3 for details.
[Link] = "text/xml";
[Link] = "<?xml version=\"1.0\" ?>";

// Call the sendAndLoad( ) method. This sends the data to a script and tells Fla
// load the server response into myLoadXML.
[Link]("cgi-bin/[Link]", myLoadXML);

In the preceding example, you may notice that we also set the contentType and xmlDecl prope
of the sending XML object. Though not always necessary, it is a good idea to set these properties
before sending the data. Some scripts/languages require them, but defining them doesn't hurt e
they are not required. By default, the contentType property is set to "application/x-www-form-
urlencoded", and the xmlDecl property has no defined value. In most cases you'll want to set
contentType to "text/xml" and set xmlDecl to "<?xml version=\"1.0\" ?>".

Now let's take a look at a complete working example. In what follows, we'll first create the neces
client-side ActionScript code, and then you should choose from one of the server-side solutions.
Choose the one that is supported by your server (or your personal computer if you use that as y
server). There are server-side scripts in Perl, PHP, and ColdFusion.

The first thing you should do is create a Flash movie and add the PushButton component to the
Library by dragging an instance from the Components panel to the Stage. Then add the following
code on the first frame of the main timeline:

function init( ) {

// Create an XML object to handle the response from the server.


result_xml = new XML( );
result_xml.onLoad = function ( ) {
if ([Link] == "1") {
_root.message_txt.text = "Saved successfully."
} else {
_root.message_txt.text = "Error.";
}
};

// Create two text fields. One for messages to the user, one for the username.

[Link] 2004-02-06
Strona 106 z 156

[Link]("message_txt", 1, 100, 100, 100, 20);


[Link]("username_txt", 2, 100, 150, 100, 20);

// Create a push button instance.


[Link]("FPushButtonSymbol", "saveScore_pb", 3, {_x: 100, _y: 180});

message_txt.text = "Enter a username and click the button";


message_txt.autoSize = true;
username_txt.border = true;
username_txt.type = "input";
saveScore_pb.setClickHandler("saveScore");
saveScore_pb.setLabel("Save");
}

// A function to generate a random score from 500 to 1000


function generateScore ( ) {
return [Link]([Link]( ) * 500) + 500;
}

function saveScore ( ) {

// Create a score.
var score = generateScore( );

// Create the XML packet string.


var xmlStr = "<gamescore><username>" + username_txt.text +
"</username><score>" + score + "</score></gamescore>";

// Create an XML object using the XML string.


var score_xml = new XML(xmlStr);

// Set the content type and XML declaration. Then send the XML data to the serv
// script. This example uses a URL for a ColdFusion page on the localhost. Use
// correct URL for your script. Also, when invoking the sendAndLoad( ) method,
// Flash to return the results to the result_xml object.
score_xml.contentType = "text/xml";
score_xml.xmlDecl = "<?xml version=\"1.0\" ?>";
score_xml.sendAndLoad("[Link] result_xml);

// Tell the user that the score is being sent to the server.
message_txt.text = "Score (" + score + ") sent!";
}

// Call the init( ) function to get things started.


init( );

Obviously, in a real-world example the user's score would be generated based on his performanc
a game. In this example we just want to demonstrate sending and receiving XML, so we generat
score randomly.

The next step is to create the server-side script. First, here's the Perl option. If you use this optio
place the following code in a text file named [Link] (or [Link]) in a directory on
web server that has CGI access enabled (usually cgi or cgi-bin).

#!/usr/bin/perl

# Flash/Perl+CGI XML interaction demo


# Arun Bhalla (arun@[Link])

use strict;
use XML::Simple;
use CGI;

[Link] 2004-02-06
Strona 107 z 156

my $ScoreFile = "[Link]";

# Here we assume that this CGI script is receiving XML in text/xml form
# via POST. Because of this, the XML will appear to the script via STDIN.
my $input = XMLin(join('',<STDIN>));

# Write out the HTTP header


print CGI::header('text/xml');

# Try to open score file for writing, or return an error message.


open(SCORES, ">> $ScoreFile") || (printMessage(0) &&
die "Error opening $ScoreFile");

# Save the score in a pipe-delimited text file.


print SCORES join('|', $input->{username}, $input->{score}), "\n";

# Return the result in XML.


printMessage(1);

# Subroutine to output the result in XML.


sub printMessage {
my $value = shift;
my $message = {};
$message->{success} = $value;
print XMLout($message, keeproot => 1, rootname => 'result');
}

If you are using ColdFusion, a sample ColdFusion script is provided in the following code block. P
this code in a ColdFusion page named [Link] within a directory on your web server tha
run ColdFusion pages.

<cfsilent>
<cfsetting enablecfoutputonly="Yes">
<cfset success = 0>
<cftry>
<!--- XML packet sent by Flash MX. // --->
<cfset scores_xml = XmlParse( getHTTPRequestData( ).content ) >

<!--- Parse out the XML packet sent from Flash MX. // --->

<!--- Grab the username and score from the XML document and save as
local variables so they are easier to work with. // --->
<cfset username = scores_xml.[Link] >
<cfset score = scores_xml.[Link] >

<!--- Append the latest score to our scores file. This could also be
stored in the database or another XML document. // --->
<cffile action="APPEND" file="#ExpandPath( '[Link]' )#"
output="#username#|#score#|#getHTTPRequestData( ).content#" addnewline="Yes">
<cfset success = 1 >
<cfcatch type="Any">
<cfset success = 0 >
<cffile action="APPEND" file="#ExpandPath( '[Link]' )#" output="ERROR"
addnewline="Yes">
</cfcatch>
</cftry>
</cfsilent>
<cfcontent type="text/xml">
<cfoutput><?xml version="1.0" ?><success>#success#</success></cfoutput>
<cfsetting showdebugoutput="no" enablecfoutputonly="No">

If you are using PHP on your server, a sample PHP script follows. Place this code in a PHP page
named [Link] on your web server in a directory that allows PHP access.

[Link] 2004-02-06
Strona 108 z 156

<?php

// Read In XML from Raw Post Data.


$xml = $GLOBALS['HTTP_RAW_POST_DATA'];

// Process XML using DOM PHP extension.


$document = xmldoc($xml);

// Read root element <gameinfo>.


$rootElement = $document->root( );

// Read child nodes <username> and <score>.


$childNodes = $rootElement->children( );

$data = "";

// Loop through child nodes and place in array.


foreach($childNodes as $childNode){
// Add data to array;
$name = $childNode->tagName( );
$value = $childNode->get_content( );
$data[$name] = $value;
}

// Append data to [Link] ( format: username|score )


$fp = fopen("[Link]","a+");
$dataString = $data['username'] . "|" . $data['score'] . "\n";
fputs($fp,$dataString,strlen($dataString));
fclose($fp);

// Return success code to Flash


echo "<success>1</success>";
?>

Once you have the Flash movie and the server-side script in place, you need only to run the mov
and click on the button to test it. The movie should get a successful response, and if you check i
directory on the server in which the script has been created, you should find a [Link] file
containing the data you just entered via the Flash movie.
[ Team LiB ]
[ Team LiB ]

Recipe 19.15 Searching XML


19.15.1 Problem
You want to search an XML object for nodes based on keywords and other criteria such as node
hierarchy.

19.15.2 Solution
Use the third-party XPath class from [Link].

19.15.3 Discussion
Thus far in this chapter you've read recipes on how to work with XML objects using the DOM, or
Document Object Model. This means that if you want to locate a particular node in the XML tree,
need to know the relationship of that node to the whole (i.e., first child, next sibling, etc.). Howe
when you want a more flexible way of looking for nodes, the DOM can become tedious.

[Link] 2004-02-06
Strona 109 z 156

XPath is a language that allows you a much more intuitive and flexible way to find nodes within a
object. XPath is a W3C standard (see [Link] that is supported on many
platforms, but it is not natively supported in Flash. However, Neeld Tanksley of [Link]
created an ActionScript XPath class that you can download from
[Link] You should download the .zip file and e
all the .as files into your Flash Include directory (make sure they are extracted into the Include
directory, and not into subdirectories).

Once you have downloaded and installed the custom XPath class, you can include it in your Flash
movies and begin using XPath to work with XML, as follows:

#include "[Link]"

XPath uses path expressions to denote the node or nodes you want to find. For example, if the r
node in your XML object is named books, then you can find that root node using:

/books

If books contains child nodes named book, then you can return all the book nodes using:

/books/book

If you don't know or care about the full path from the root node to the node or nodes for which y
searching, you can use a double slash to indicate that you want to locate all matching nodes at a
level in the XML tree. For example, the following returns all author nodes regardless of their hier

//author

An asterisk (*) is a wildcard. For example, the following matches all author nodes that are childr
any nodes that are, in turn, children of the books node:

/books/*/author

You can also use square brackets ([]) to indicate criteria that the nodes must match. For exampl
can match all book nodes that contain author nodes with the following:

//book[author]

Notice that the preceding is different from //book/author in that the former returns book nodes a
latter returns author nodes.

You can also use expressions with equality operators such as the following, which returns all boo
containing a child title node with a text value of "ActionScript Cookbook":

//book[title='ActionScript Cookbook']

The @ sign can be used to signify an attribute. The following example matches all author nodes
containing a name attribute:

//author[@name]

There are also many other built-in functions, operators, and keywords in XPath that you can read
in the documentation.

The XPath class has one method that we are interested in for this recipe. The [Link]
method is a static method, which means you invoke it from the class itself, not from an instance
XPath. The method takes two parameters—the XMLNode object to search and the XPath express

[Link] 2004-02-06
Strona 110 z 156

use—and returns an array of matching nodes:

matches = [Link](my_xml, "/books/book");

Now let's take a look at an example of XPath in use. For this example you should make sure that
have installed all the .as files for the XPath class.

1. Create an XML document using a simple text editor such as WordPad. Add the following XM
to the document, and then save it as [Link]:

<books>
<book>
<title>ActionScript Cookbook</title>
<authors>
<author name="Joey Lott" />
</authors>
</book>
<book>
<title>Flash Cookbook</title>
<authors>
<author name="Joey Lott" />
<author name="Jeffrey Bardzell" />
</authors>
</book>
<book>
<title>Flash Remoting: The Definitive Guide</title>
<authors>
<author name="Tom Muck" />
</authors>
</book>
<book>
<title>ActionScript for Flash MX: The Definitive Guide</title>
<authors>
<author name="Colin Moock" />
</authors>
</book>
</books>

2. Open a new Flash document. Copy the PushButton component symbol into the Library by
dragging an instance from the Components panel onto the Stage and then deleting the ins
The symbol remains in the Library. Then add the following code to the first frame of the m
timeline:

#include "[Link]"

function doSearch ( ) {
// Use the selectNodes( ) method to find all matches to the XPath express
// user has entered into xpath_txt. Display the results in output_txt. Use
// newline character between each match.
var results = [Link](my_xml, xpath_txt.text);
output_txt.text = [Link]("\n");
}

// Create the input text field for the user to enter the XPath expression.
[Link]("xpath_txt", 1, 20, 20, 300, 20);
xpath_txt.border = true;
xpath_txt.type = "input";

// Create the text field for the output of the results.


[Link]("output_txt", 2, 20, 70, 300, 300);
output_txt.border = true;
output_txt.multiline = true;

[Link] 2004-02-06
Strona 111 z 156

output_txt.wordWrap = true;

// Add the push button instance so the user can perform the search.
[Link]("FPushButtonSymbol", "search_pb", 3, {_x: 20, _y: 45});
search_pb.setClickHandler("doSearch");
search_pb.setLabel("Find Matches");

// Set the ignoreWhite property to true for all XML objects.


[Link] = true;

// Create an XML object and load [Link] into it.


my_xml = new XML( );
my_xml.load("[Link]");

3. Save the Flash document to the same directory as the [Link] file. Test the movie. Try e
the following XPath expressions, and you should get the results as shown:

//book/title

<title>ActionScript Cookbook</title>

<title>Flash Cookbook</title>

<title>Flash Remoting: The Definitive Guide</title>

<title>ActionScript for Flash MX: The Definitive Guide</title>

//book[contains(title, `ActionScript')]/title

<title>ActionScript Cookbook</title>

<title>ActionScript for Flash MX: The Definitive Guide</title>

//book[authors[author[@name='Joey Lott']]]/title

<title>ActionScript Cookbook</title>

<title>Flash Cookbook</title>

19.15.4 See Also


XPath is too large a subject to cover in detail in this book. For more information, refer to the onli
tutorial at [Link] or check out the book XPath and XPoint
John E. Simpson (O'Reilly).

[ Team LiB ]
[ Team LiB ]

Recipe 19.16 Using XML Data to Initialize a Movie


19.16.1 Problem
You want to use XML data from an external source to initialize certain elements of your Flash
movie.

[Link] 2004-02-06
Strona 112 z 156

19.16.2 Solution
Use the [Link]( ) method to load the XML data, and then initialize the movie's elements
within the onLoad( ) method.

19.16.3 Discussion
One of the many uses for XML within your Flash movie is initializing elements such as menus,
navigation elements, news, headlines, and so on. By loading the data from an external source,
you can more easily update the values or even generate them from a database. This is
extremely important for applications in which the content changes frequently, such as product
catalogs, news sites, and message boards. To initialize your movie using XML data, load the
data using the [Link]( ) method, and then perform the initialization within the onLoad( )
method (or within a function called by onLoad( )):

my_xml = new XML( );


my_xml.load("[Link]");
my_xml.onLoad = function ( ) {

/* This example populates a list box with message titles and


IDs from XML data that might look something like this:
<messages>
<message>
<title>XML fun</title>
<id>0</id>
</message>
<message>
<title>more XML fun</title>
<id>1</id>
</message>
<message>
<title>XML fun yet</title>
<id>2</id>
</message>
</messages>
*/
var messages = [Link];
for (var i = 0; i < [Link]; i++) {
_root.[Link](messages[i].[Link],
messages[i].[Link]);
}
};

19.16.4 See Also


See Recipe 16.1 for information on using local shared objects to store client-side information
between sessions.

[ Team LiB ]
[ Team LiB ]

Chapter 20. Flash Remoting


Introduction

Recipe 20.1. Establishing a Connection via Flash Remoting

[Link] 2004-02-06
Strona 113 z 156

Recipe 20.2. Configuring Flash Remoting for ColdFusion

Recipe 20.3. Configuring Flash Remoting for .NET

Recipe 20.4. Configuring Flash Remoting for J2EE

Recipe 20.5. Configuring Flash Remoting for PHP or Perl

Recipe 20.6. Invoking a Remote Function on a Service

Recipe 20.7. Handling Flash Remoting Results

Recipe 20.8. Distinguishing Among Results from Multiple Calls to a Single Service

Recipe 20.9. Calling [Link] Functions from Flash

Recipe 20.10. Calling ColdFusion Functions from Flash

Recipe 20.11. Passing Named Parameters to ColdFusion Component Methods

Recipe 20.12. Passing Complex Parameters to ColdFusion Component Methods

Recipe 20.13. Calling Java or JSP Functions from Flash

Recipe 20.14. Transmitting Custom Datatypes to a Flash Remoting Back End

Recipe 20.15. Receiving Typed Objects with ColdFusion

Recipe 20.16. Receiving Typed Objects with [Link]

Recipe 20.17. Receiving Typed Objects with Java

Recipe 20.18. Returning Typed Objects from ColdFusion

Recipe 20.19. Returning Typed Objects from [Link]

Recipe 20.20. Returning Typed Objects from Java

Recipe 20.21. Writing Server-Side Functions in ActionScript

Recipe 20.22. Querying a Database with Server-Side ActionScript

Recipe 20.23. Making HTTP Requests with Server-Side ActionScript

Recipe 20.24. Consuming Web Services with Flash Remoting for .NET or ColdFusion
[ Team LiB ]
[ Team LiB ]

Introduction
Flash Remoting is a technology that enables you to easily connect your Flash movie to a server-
side back end. Prior to Flash Remoting, the only options for transferring data were through XML
or URL-encoded data strings, both of which require you, the developer, to serialize and
deserialize the data on both the client and server. The terms serialize and deserialize refer to
the process of formatting complex data so that it can be transferred between programs, and

[Link] 2004-02-06
Strona 114 z 156

then translating the serialized data back into the appropriate datatype. For example, consider
the scenario in which you want to pass an ActionScript array from Flash to a server-side script
using LoadVars. To do this, you have to first convert the array into a string (serialization). Here
is an example of a serialized array in which vertical pipes are used as delimiters between
elements:

myArray=val0|val1|val2

Not only do you have to serialize the data before sending it, but you have to deserialize it in the
server-side script to convert it back into an array. Furthermore, the data may not be correctly
interpreted when it is deserialized. XML and URL-encoded strings allow you to send string data
only. So, for example, if your array has Boolean values and you serialize it as follows:

myArray=true|false|true

when you deserialize it, the resulting array will be an array of strings with the values "true",
"false", and "true". Furthermore, although arrays are fairly simple to serialize, other datatypes,
such as recordsets, can become much more complex.

The problem of transferring data between applications is not unique to Flash, and many
different solutions exist. One such solution is WDDX, an XML-based language that describes
both simple and complex datatypes. WDDX is supported in many programming languages,
including ActionScript (if you download Branden Hall's WDDX ActionScript file available from
[Link] However, the downside with WDDX is
that, although it handles the nitty-gritty details of serialization/deserialization, you still have to
manually instruct your application to perform these tasks.

Flash Remoting is Macromedia's answer to this dilemma. Flash Remoting is supported for
ColdFusion MX, .NET, and J2EE. For details, see
[Link] Additionally, there are third-party
implementations: AMFPHP for PHP ([Link] FLAP for Perl
([Link] and OpenAMF for Java ([Link] Whatever the
server-side implementation, I refer to it as the Flash Remoting gateway, or simply the gateway.

Flash Remoting uses a proprietary message format called action message format (AMF) for
sending data between Flash movies and server-side applications. As a developer, you never
have to see the AMF messages, since the Flash Player (on the client) and the Flash Remoting
gateway (on the server) perform the serialization and deserialization for you, automatically and
transparently. Flash Remoting knows how to convert between ActionScript datatypes and the
appropriate server-side datatypes. This chapter necessarily assumes you have some familiarity
with the server-side platform on which you intend to use Flash Remoting. For more information,
refer to the resources cited in appropriate recipes.

In addition to the server-side implementation of Flash Remoting, you'll also want to download
and install the free Flash Remoting Components available from Macromedia at
[Link] The term
"Components" is slightly misleading. You will not be installing any Flash components, but rather
a handful of .as files in the Flash Include directory. These files define important ActionScript
classes you'll need to harness the power of Flash Remoting. Simply download the file
appropriate for your platform and unzip it. You should automatically be prompted to save
the .as files to the correct directory (the Include directory in the Flash installation).

Whenever you use Flash Remoting, you must include the [Link]
library file, which contains the code necessary to connect to a Flash
Remoting-enabled server from Flash. If you do not include
[Link], the example does not work; however, you do not receive
any errors within the authoring environment.

[Link] 2004-02-06
Strona 115 z 156

Once you have the server-side software (a valid gateway) and client-side software (the Flash
Remoting Components) set up, you are ready to get started with Flash Remoting. Of course,
you'll also want to have a general idea of how Flash Remoting works. The Flash Player has a
built-in class called NetConnection, which enables the Flash Player to send and receive AMF
messages. Therefore, it is possible for the Flash Player to send messages to a server-side script
that is configured to listen for and accept messages (the gateway) and for the server to send
messages back to Flash. Using AMF messages, you can actually instruct the gateway to invoke
server-side functions and return the value (if any) to Flash. When invoking server-side
functionality with Flash Remoting, there are two terms you should be aware of:

Service

A service contains the functionality you want to invoke. For example, a ColdFusion
Component (CFC), a JavaBean, or a .NET assembly can contain methods that you want to
invoke from Flash Remoting. Therefore, these constructs are referred to as services.

Service function

A service function is the remote function that you want to invoke from Flash. For
example, the methods within a CFC, a JavaBean, or a .NET assembly are all service
functions.

Table 20-1 shows the types of services and corresponding service functions for ColdFusion,
J2EE, and .NET.

Table 20-1. Flash Remoting services and service functions


Server Service Service function
ColdFusion Directory ColdFusion page
ColdFusion CFC CFC method
ColdFusion Web service Web method
J2EE Context JSP or servlet
J2EE Java class Public method
J2EE JNDI for EJB Public method
J2EE MBean MBean method
.NET Directory [Link] page
.NET Assembly Public method
.NET Web service Web method
[ Team LiB ]
[ Team LiB ]

Recipe 20.1 Establishing a Connection via Flash


Remoting
20.1.1 Problem
You want to establish a connection to the Flash Remoting gateway on your chosen server.

20.1.2 Solution

[Link] 2004-02-06
Strona 116 z 156

Pass the URL of the Flash Remoting gateway to [Link]( ).


Alternatively, set a default gateway URL using [Link]( ). As an
optional, additional step, specify the gateway URL in the FlashVars attribute within the HTML
page in which the Flash movie is embedded (when applicable).

20.1.3 Discussion
A Flash movie accesses Flash Remoting functionality on a sever via a connection object that
communicates via a gateway. A connection object is obtained by invoking
[Link]( ). A gateway is simply specified as a URL to the
appropriate server.

Within ActionScript, you have two options for setting the Flash Remoting gateway URL that a
connection object uses. The exact URL varies depending on the location of your remote
application and the server model in use, as described in Recipe 20.2, Recipe 20.3, Recipe 20.4,
and Recipe 20.5. The most direct route is to specify the URL as a parameter when you invoke th
[Link]( ) method. For example:

#include "[Link]"

myConnection =
[Link]("[Link]

There is technically nothing wrong with this direct approach. However, a better, more flexible
approach is to set a default gateway URL with [Link]( [Link])
and not specify any parameter when creating the connection object. For example:

#include "[Link]"

// First, set the default gateway URL.


[Link]("../localhost/flashservices/gateway/[Link]"

// Then, create the connection object without passing a parameter to the method.
myConnection = [Link]( );

This technique may not seem advantageous, because it requires two lines of code to do what can
essentially be accomplished in one line. However, to understand the benefit of this technique, it
helpful to know how Flash decides which gateway URL to use. Flash determines the gateway as
follows:

1. If a value is passed to createGatewayConnection( ), Flash uses that gateway, no matter


what.

2. Otherwise, if the variable gatewayUrl was passed to the Flash movie by way of the
FlashVars attribute, Flash uses the gatewayUrl variable's value.

3. Otherwise, Flash uses the default gateway specified via setDefaultGatewayURL( ).

Using setDefaultGatewayURL( ) is particularly valuable when, as in most scenarios, the gateway


URL changes between the development phase and the release of your application. If you hardcod
the gateway URL into your Flash movie (by specifying the value in the createGatewayConnection
( ) method), you must change that value and reexport the Flash movie before publishing it to the
production environment.

The recommended scenario is to set the default gateway URL in your Flash movie to the server
that you use for development and/or testing, and do not specify a URL parameter for the
createGatewayConnection( ) method. This way, you can easily test your movie during
development. For example:

[Link] 2004-02-06
Strona 117 z 156

#include "[Link]"

[Link](
"[Link]
myConnection = [Link]( );

Then, in the HTML page in which the Flash movie is embedded for production, set a gatewayUrl
variable with the production server's URL. Use the FlashVars attribute to accomplish this (shown
in bold in the following code listing). In this way, you do not have to make changes to the Flash
movie between the development and production environments.

<OBJECT
classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
codebase="[Link]
cabs/flash/[Link]#version=6,0,0,0"
WIDTH="100%"
HEIGHT="100%"
id="myMovie">
<PARAM
NAME=flashvars
VALUE="gatewayUrl=[Link]
<PARAM NAME=movie VALUE="[Link]">
<PARAM NAME=quality VALUE=high>
<PARAM NAME=bgcolor VALUE=#FFFFFF>
<EMBED src="[Link]"
FLASHVARS="gatewayUrl=[Link]
quality=high
bgcolor=#FFFFFF
WIDTH="100%"
HEIGHT="100%"
NAME="myMovie"
TYPE="application/x-shockwave-flash"
PLUGINSPAGE="[Link]
</EMBED>
</OBJECT>

20.1.4 See Also


Recipe 20.2 discusses the proper gateway URL for ColdFusion applications. Recipe 20.3 discusses
the proper gateway URL for .NET applications. Recipe 20.4 discusses the proper gateway URL for
J2EE applications. Recipe 20.5 gives pointers to resources for using Flash Remoting with PHP and
Perl.

[ Team LiB ]
[ Team LiB ]

Recipe 20.2 Configuring Flash Remoting for ColdFusion


20.2.1 Problem
You want to configure Flash Remoting for ColdFusion MX.

20.2.2 Solution
Flash Remoting comes preinstalled as part of ColdFusion MX.

[Link] 2004-02-06
Strona 118 z 156

20.2.3 Discussion
Flash Remoting comes preinstalled and preconfigured for ColdFusion MX. You do not need to
make any modifications. If you are using CFMX with Updater 3, you must configure your
[Link] file to turn on access for Flash Remoting to work with web services. Prior to Updater 3,
this feature was on by default.

When you use Flash Remoting for ColdFusion MX, the gateway URL, as discussed in Recipe
20.1, should be of the form:

[Link]

in which coldFusionServerNameOrIP is the domain name or IP address to the ColdFusion MX


server. If the ColdFusion MX server is running on a port other than 80, then you also need to
specify the port. For example, if the ColdFusion server is running on localhost at port 8500,
then the correct gateway URL is:

[Link]

20.2.4 See Also


Recipe 20.1, Recipe 20.10, Recipe 20.11, Recipe 20.12, Recipe 20.15, Recipe 20.18, and Recipe
20.24
[ Team LiB ]
[ Team LiB ]

Recipe 20.3 Configuring Flash Remoting for .NET


20.3.1 Problem
You want to configure Flash Remoting for .NET.

20.3.2 Solution
Add the [Link] file to the .NET web application's bin directory and create a blank
[Link] file in the web application's root directory. The file and folder permissions must
be set to allow access to .NET web services. For example, web services won't work if the
[Link] user doesn't have permission to run [Link]. To support all Flash Remoting features,
install the entire .NET SDK on the server, and not just the limited .NET Framework.

20.3.3 Discussion
Flash Remoting for .NET allows Flash clients to communicate with Windows .NET applications.
The Flash Remoting for .NET gateway is sold separately by Macromedia (see
[Link] You must
download and install either the trial version or full version before proceeding.

To use Flash Remoting for .NET, two things must be properly configured on the server: the
Flash Remoting assembly ([Link]), which implements Flash Remoting support on the
server, and an ASPX page, which is used as the Flash Remoting gateway. If you have installed
Flash Remoting for .NET, the default installation creates a flashremoting directory within the IIS
web root (usually C:\Inetput\wwwroot). The [Link] assembly file can be found within
the bin directory of that directory, and you should copy it to the bin directory of your own .NET
web application. Additionally, you should create a blank ASPX page within your web
application's root directory. For Flash to connect to the Flash Remoting gateway, it needs a URL

[Link] 2004-02-06
Strona 119 z 156

to a valid ASPX page in the web application's root. While it is not absolutely necessary that you
create an ASPX page specifically for this purpose (since any ASPX page will do), it is generally a
good idea to keep things simple and standardized. Likewise, it is a good idea to name the ASPX
file [Link], although it is not absolutely necessary.

The gateway URL that you should use in your Flash movies to connect to the Flash Remoting
gateway for .NET applications (as discussed in Recipe 20.1) should be of the form:

[Link]

in which dotNetServerNameOrIP is the domain name or IP address of the .NET server.


applicationContext should be the context of the .NET web application as it has been set in
the IIS snap-in. For example, the correct gateway URL for a web application on localhost with a
context of /myApplication is:

[Link]

20.3.4 See Also


Recipe 20.1, Recipe 20.9, Recipe 20.16, Recipe 20.19, and Recipe 20.24

[ Team LiB ]
[ Team LiB ]

Recipe 20.4 Configuring Flash Remoting for J2EE


20.4.1 Problem
You want to configure Flash Remoting for J2EE.

20.4.2 Solution
Copy the [Link] file into the web application's WEB-INF/lib directory and configure a
servlet and servlet-mapping in the [Link] file. If you use JRun 4 as your J2EE server, Flash
Remoting comes preinstalled.

20.4.3 Discussion
If you are using JRun 4 as your J2EE server, Flash Remoting comes preinstalled and you do not
need to purchase or configure any additional software. If you use a third-party J2EE server,
such as IBM WebSphere or BEA Weblogic, the Flash Remoting for J2EE gateway, sold separately
by Macromedia, allows Flash clients to communicate with Enterprise Java applications (see
[Link] If you are
not using JRun 4, you must download and install either the trial version or full version before
proceeding. (As an open source alternative, see the OpenAMF project at
[Link]

All the necessary code for Flash Remoting for J2EE is contained within the [Link] file.
You can find this file in the Flash Remoting installation (on Windows machines this is typically
under C:\Program Files\Macromedia\Flash Remoting MX). You can either extract the .jar file
from the .war file ([Link]) or copy it from the sample application (within
the sample\WEB-INF\lib subdirectory). You should copy this .jar file to the WEB-INF/lib
directory of your own J2EE web application.

[Link] 2004-02-06
Strona 120 z 156

Once you have copied the .jar file into your web application, you only need to configure a
servlet and servlet-mapping for the J2EE Flash Remoting gateway servlet that is contained
within the .jar file. You should map the servlet to the URL pattern /gateway (the
name /gateway is not required, but it is a logical choice). The following shows an example
[Link] file with the necessary <servlet> and <servlet-mapping> tags:

<?xml version="1.0" encoding="UTF-8"?>


<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"[Link]
<web-app>
<display-name>My Application</display-name>
<servlet>
<servlet-name>FlashGatewayServlet</servlet-name>
<servlet-class>
[Link]
</servlet-class>
<init-param>
<param-name>LOG_LEVEL</param-name>
<param-value>Error</param-value>
</init-param>
<init-param>
<param-name>DISABLE_JAVA_ADAPTERS</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FlashGatewayServlet</servlet-name>
<url-pattern>/gateway</url-pattern>
</servlet-mapping>
</web-app>

The correct gateway URL to use within your Flash movies to connect to Flash Remoting for J2EE
(as discussed in Recipe 20.1) should be of the form:

[Link]

in which j2eeServerNameOrIP is the domain name or IP address of the J2EE application server,
and applicationContext is the context for the web application as it is configured by the
application server. For example, the correct gateway URL for web application on localhost (port
80) with a context of /myApplication is:

[Link]

20.4.4 See Also


Recipe 20.1, Recipe 20.13, Recipe 20.17, and Recipe 20.20. "Flash Remoting for J2EE
Developers" at [Link] and The
OpenAMF Java Flash Remoting project ([Link] a free, open source
alternative to Macromedia's Flash Remoting for J2EE. Information on JRun can be found at
[Link]

[ Team LiB ]
[ Team LiB ]

Recipe 20.5 Configuring Flash Remoting for PHP or Perl

[Link] 2004-02-06
Strona 121 z 156

20.5.1 Problem
You want to use Flash Remoting with something other than ColdFusion MX, .NET, or J2EE,
which are the only three server-side platforms officially supported by Macromedia.

20.5.2 Solution
Use a third-party solution such as AMFPHP ([Link] which offers PHP support, or
FLAP ([Link] which offers Perl support.

20.5.3 Discussion
Although the AMF format is not open or documented, it turns out that it is the same format
used by remote shared objects. An enterprising individual, Wolfgang Hamann, reverse-
engineered the AMF format, allowing developers to build open source libraries to support Flash
Remoting in server-side languages not supported by Macromedia.

The AMFPHP project implements a Flash Remoting library for PHP. The Flash Remoting in Perl
project (FLAP) implements a Flash Remoting library for Perl. Refer to their web sites
([Link] and [Link] respectively) for installation,
configuration, and support information.

20.5.4 See Also


OpenAMF for Java ([Link]

[ Team LiB ]
[ Team LiB ]

Recipe 20.6 Invoking a Remote Function on a Service


20.6.1 Problem
You want to call a service function from a Flash movie (see Table 20-1 for Flash Remoting servic
function types).

20.6.2 Solution
Create a service object by invoking the getService( ) method from the connection object returne
createGatewayConnection( ). The service object is the object returned by getService( ). Then ca
the service function (any function defined for the service) as a method of the service object.

20.6.3 Discussion
The first step in calling a service function from a Flash movie is to create a connection object (se
the Introduction and Recipe 20.1 for more information about this). For example:

#include "[Link]"
[Link]("[Link]
myConnection = [Link]( );

Once you have created the connection object, you can create an object that maps to any service
available through the specified gateway using the getService( ) method. There are two variations

[Link] 2004-02-06
Strona 122 z 156

when using the getService( ) method. In the first variation, you specify a single response object
handle responses from all functions invoked on the object. In the second variation, instead of
specifying the response object when invoking getService( ), you specify it as the first parameter
each time you invoke a service function.

Both variations require that you provide the name of the service as it is known to the Flash Rem
gateway. A service is known by the URL that accesses the service. See the Introduction and Rec
20.2, Recipe 20.3, and Recipe 20.4 for more details on determining the correct service name.

In the first variation, you also need to provide a reference to an object that will handle the respo
by default (called the response object). In this variation, the response object should be an objec
with named result functions (see Recipe 20.7 for details on how to handle results using named re
functions). By convention, the object is the same object within which the service object is define
(see also Recipe 20.7 for more details on service objects).

// Create a service object that maps to a service named serviceName and set this
// same object within which the service object is defined) to handle the response
myService = [Link]("serviceName", this);

Notice that the service object is returned by getService( ), and the response object is optionally
passed to getService( ) to handle the response. After obtaining the service object with getServic
based on the service name, you can invoke any service function as a method of the service objec
the service function expects parameters, you can pass the parameters to it just as with any othe
method invocation.

// Call a service function named serviceFunction0 with no parameters.


myService.serviceFunction0( );

// Call a service function named serviceFunction1 with


// two parameters: a string and a Boolean.
myService.serviceFuncton1("param value", true);

In the alternative approach, when calling the getService( ) method, you don't specify a response
object to handle the response. Instead, with this variation, you specify the response object as th
first parameter when invoking a service function. However, the response object parameter is not
sent to the service function.

// Create the service object without specifying the response object.


myService = [Link]("serviceName");

// Call serviceFunction0, specifying that myResponseObject should handle the resu


// but do not pass any additional parameters. myResponseObject is not sent to the
// service function.
myService.serviceFunction0(myResponseObject);

// Call serviceFunction1, specifying that myResponseObject should handle the resu


// and pass two additional parameters. myResponseObject is not sent to the servic
// function, but the string and Boolean values are sent.
myService.serviceFunction1(myResponseObject, "param value", true);

In this variation, the response object should contain an onResult( ) method, as described in Reci
20.7.

In most cases, the second variation—in which the response object is specified as the first parame
when calling a service function—is the better choice. This option enables you to specify different
response objects for each service function call (something you cannot do using the first variation
which the service object is set when calling getService( )).

It is worth noting that you must always declare a response object, either when calling the getSer
( ) method or in the service function invocation. This is true even if the service function does not

[Link] 2004-02-06
Strona 123 z 156

return any result. Also, you should never try to define a response object in both places. Doing so
only create errors.

20.6.4 See Also


Recipe 20.7, Recipe 20.9, Recipe 20.10, and Recipe 20.13
[ Team LiB ]
[ Team LiB ]

Recipe 20.7 Handling Flash Remoting Results


20.7.1 Problem
You want to handle the results from calling a service function using Flash Remoting.

20.7.2 Solution
Use a named result function or, as a better alternative, use a response object.

20.7.3 Discussion
Unlike invocations of functions and methods that are defined within the Flash movie, Flash
Remoting service function invocations are asynchronous. This means that when the ActionScript
interpreter encounters a remote service function call within the code, it initiates the call but does
not wait for a response before continuing to the next line of ActionScript code. The same thing is
true when loading any external content, be it by means of an XML object, a LoadVars object, or a
movie clip using loadMovie( ). This asynchronous behavior allows the Flash movie to continue to
run without waiting on something that has the potential for high latency. Therefore, if a remote
service function returns a value, you cannot use that value within your ActionScript code
immediately, as you would the return value of a native ActionScript function or method.

// myVariable is undefined because the service function


// does not return a value immediately.
myVariable = [Link](responseObject);

Instead, you have to use a response object with callback functions or methods to obtain the resu
There are two ways to handle results from a Flash Remoting service function, and they correspon
to the two variations of invoking the service function (see Recipe 20.6).

The first technique, named result functions, is used, by convention, when you have specified a
default response object for a service object (that is, when you've passed a response object to th
getService( ) method). Named result functions are functions that must be named with the patter
serviceFunctionName_Result( ). For example, if the service function is named getUserList( ), th
the named result function should be named getUserList_Result( ). The Flash Remoting gateway
automatically passes any returned value to the result function as a parameter. For example:

// Define the named result function getUserList_Result( ) to handle the result f


// the service function getUserList( ).
function getUserList_Result (result) {
trace(result);
}

// Create a service object from a connection object. Define the default response
// object as this, since this is where the result function is defined.
myService = [Link]("serviceName", this);

// Invoke getUserList( ).

[Link] 2004-02-06
Strona 124 z 156

[Link]( );

The second technique is to use a response object that declares an onResult( ) method. By
convention, you should use this technique when you are defining a response object for each serv
function invocation. This technique is much more flexible than the first, in that you can define
different response objects for each service function invocation, or you can share a single respons
object among multiple service function invocations. Note that this, which represents the current
object, is passed as the response object in the preceding example.

To create a response object, define an onResult( ) method on it. The onResult( ) method is
automatically invoked when a result is returned to the Flash movie from the service function, and
any returned value is passed to the onResult( ) method as a parameter. For example:

// Create an object and define an onResult( ) method for it.


myResponseObject = new Object( );
[Link] = function (result) {
trace(result);
};

// Create the service object from a connection object. Do not set


// a default response object.
myService = [Link]("serviceName");

// Call the getUserList( ) service function and specify that the response should
// handled by the myResponseObject response object.
[Link](myResponseObject);

20.7.4 See Also


Recipe 20.6 and Recipe 20.8
[ Team LiB ]
[ Team LiB ]

Recipe 20.8 Distinguishing Among Results from Multiple


Calls to a Single Service
20.8.1 Problem
You want to be able to distinguish among results from multiple calls to a single service function.

20.8.2 Solution
Use instances of a custom response class for each response object.

20.8.3 Discussion
When a result is returned from a Flash Remoting service function, Flash doesn't automatically te
you which service function invocation generated the result. Furthermore, due to the
asynchronous nature of Flash Remoting, you cannot rely on results being returned to Flash in the
same order in which the functions were invoked. However, you can distinguish between the
results from calls to multiple service functions by using a separate instance of a custom class for
each response object. Simply attach a custom property to each response object instance and
check its value when the result from the service function is returned.

This solution uses a custom response class that takes an id parameter in the constructor. This
allows you to create multiple instances of the class—one for each function invocation—and assign

[Link] 2004-02-06
Strona 125 z 156

each one a unique id. Then, when the result comes back, you can distinguish between the
results using the id property of the response object. For example:

// Define a constructor for a custom response object class that assigns an id


// property to each instance.
function MyResponseObject(id) {
[Link] = id;
}

// Define the onResult( ) method for the response object class.


[Link] = function (result) {
// Process the result differently depending on the value of the id property.
trace("The response came from the call with id " + [Link]);
};

// Call the same service function multiple times. In this example the service
// function, getUserInfo( ), which is not shown, retrieves user information for
// different users based on a username parameter. In each case, use an instance o
// the MyResponseObject class as the response object. Assign each instance an id
// corresponding to the username so that you can distinguish between them when th
// results are returned.
[Link](new MyResponseObject("Heather"), "Heather");
[Link](new MyResponseObject("Julie"), "Julie");
[Link](new MyResponseObject("Sarah"), "Sarah");

20.8.4 See Also


Recipe 20.7

[ Team LiB ]
[ Team LiB ]

Recipe 20.9 Calling [Link] Functions from Flash


20.9.1 Problem
You want to call [Link] service functions using Flash Remoting.

20.9.2 Solution
Use [Link] pages (which requires modifying the code) or, preferably, use public DLL methods
requires no modification).

20.9.3 Discussion
You can call [Link] pages or public DLL methods as service functions from a Flash movie using
Remoting.

Ideally, DLL methods should be used in place of [Link] pages when it comes to Flash Remoting
some people may have hesitancy about writing/using DLLs because they require compilation, the
more advantageous than using [Link] pages. Any public method of an [Link] DLL can be ca
Flash Remoting without any modification to the DLL code. Furthermore, using DLLs allows you to
your business logic from your presentation logic, so the same DLL can be used by multiple interf
movies, [Link] pages, Windows Forms, etc.). Using DLLs for your business logic is a best pract
regardless of whether you are using Flash Remoting.

[Link] 2004-02-06
Strona 126 z 156

To call a DLL method from a Flash movie, you should place that DLL in the web application's bin
and create a service object that maps to the class within it. The correct service name to use is th
qualified class name, which must include the namespace. For example:

// Create a service object that maps to the Book class in the [Link] namesp
myService = [Link]("[Link]");

// Call the getTitle( ) method of the Book class and specify a response object,
// shown in Recipe 20.7, to handle the results.
[Link](myResponseObject);

If you must use an [Link] page, there are several modifications you must make to access it vi
Remoting.

You must create a [Link] object in the page. By convention, this object is named f
can accomplish this one of two ways:

Tag-based

Use the Register directive to register a tag prefix to the FlashGateway namespace and the
flashgateway assembly. Then create an instance of the Flash class with a <Macromedia:Fl
The ID attribute should be the name of the object, and the runat attribute should always b
For example:

<%@ Register TagPrefix="Macromedia" Namespace="FlashGateway" Assembly="flash


<Macromedia:Flash ID="flash" runat="server" />

Code block

Whether in a render block in the [Link] page or in a code-behind class, you can create a
of the [Link] class using the constructor. The following example is in C#:

[Link] flash = new [Link]( );

If you want to accept parameters, you must extract them from the [Link] object's
property. Params implements IList, and the parameters are stored as elements with integer inde

string myFirstParam = [Link][0];


string mySecondParam = [Link][1];

If you want to return a value to the Flash movie, you must assign that value to the FlashGatewa
object's Result property. The standard output of the [Link] page is disregarded by Flash Rem
the value of the Result property is returned to Flash.

When you want to call an [Link] page as a service function, the service name is the path to th
within the web application's hierarchy. Slashes should be replaced by dots. For example:

// Create a service object that maps to the /aspx/users directory


// of the web application.
myService = [Link]("[Link]");

// Create a service object that maps to the root directory of the web application
myService = [Link]("");

The service function name should be the same as the filename of the [Link] page. For exampl

// Invoke the page called [Link].


[Link](myResponseObject);

[Link] 2004-02-06
Strona 127 z 156

[ Team LiB ]
[ Team LiB ]

Recipe 20.10 Calling ColdFusion Functions from Flash


20.10.1 Problem
You want to invoke ColdFusion service functions using Flash Remoting.

20.10.2 Solution
Preferably use a ColdFusion Component (CFC). Alternatively, you can invoke a ColdFusion page,
with some modifications.

20.10.3 Discussion
When you are using ColdFusion, there are two types of Flash Remoting service functions:
ColdFusion pages and CFC methods.

Preferably, you should use CFC methods instead of ColdFusion pages for all of your Flash
Remoting needs. Calling a CFC method from Flash Remoting does not require any special code
changes to the CFC. The only consideration is that you must make sure that the CFC method
can be accessed remotely by setting the <cffunction> tag's access attribute to "remote", as
follows:

<cffunction name="myCFCMethod" access="remote">


<!--- method body --->
</cffunction>

When you want to call a CFC method from a Flash movie, you should create a service object
that maps to the CFC by specifying the fully qualified CFC name in the getService( ) method.
The fully qualified CFC name includes any packages in which the CFC has been placed. For
example:

// Create a service object that maps to a CFC named MyCFC in the root of the
// ColdFusion application.
myService = [Link]("MyCFC");

// Or, if the CFC is in a package, create a service object including the package
// name. This example maps to a CFC named MyCFC in the [Link] package.
myService = [Link]("[Link]");

If you must use a ColdFusion page with Flash Remoting, you must make changes to the code
within the page. Any values that the page receives or returns are contained within the
ColdFusion FLASH scope. All parameters that you send to the ColdFusion page via the service
function invocation are stored in the params array within the FLASH scope. If you want to return
a value to the Flash movie from the ColdFusion page, you should assign that value to the
result variable within the FLASH scope. You can return only one value per ColdFusion page.
The last value assigned to the [Link] variable when the page has been processed is the
value that is returned.

<!--- Set two variables within the ColdFusion page to the values of the first
two parameters passed to the page from the Flash movie that called it --->
<CFSET myFirstParam = [Link][1]>
<CFSET mySecondParam = [Link][2]>

[Link] 2004-02-06
Strona 128 z 156

<!--- Return a value to the Flash movie --->


<CFSET [Link] = "a return value">

20.10.4 See Also


Recipe 20.11

[ Team LiB ]
[ Team LiB ]

Recipe 20.11 Passing Named Parameters to ColdFusion


Component Methods
20.11.1 Problem
You want to pass named parameters to a ColdFusion Component (CFC) method using Flash
Remoting.

20.11.2 Solution
Create an ActionScript object with properties corresponding to the expected CFC method
parameters, and pass that object as a single parameter to the CFC method.

20.11.3 Discussion
You can pass named parameters (as opposed to positional parameters) to a CFC method from a
Flash movie. To accomplish this, you should pass a single object parameter to the CFC method
in which the property names correspond to the names you have given to the parameters in the
CFC method. ColdFusion automatically matches up the object's property values to the CFC
method's parameters with the same names. For example:

<!--- A sample CFC method definition --->


<cffunction name="getCarInfo" access="remote">
<cfargument name="make" type="string">
<cfargument name="model" type="string">
<!--- rest of method body --->
<cffunction>

// The corresponding ActionScript snippet . . .

// Create an object with properties named make and model.


params = new Object( );
[Link] = "Honda";
[Link] = "Accord";

// Call the getCarInfo( ) service function and pass it the params object.
[Link](myResponseObject, params);

20.11.4 See Also


Recipe 20.12

[ Team LiB ]

[Link] 2004-02-06
Strona 129 z 156

[ Team LiB ]

Recipe 20.12 Passing Complex Parameters to


ColdFusion Component Methods
20.12.1 Problem
You want to pass an object parameter to a ColdFusion Component (CFC) method using Flash
Remoting.

20.12.2 Solution
Pass the value as a named parameter.

20.12.3 Discussion
ColdFusion automatically attempts to convert object parameters into named parameters (see
Recipe 20.11). This is problematic if you want to pass an ActionScript object to a CFC method
as a parameter. Consider the following example:

<!--- A sample CFC method definition --->


<cffunction name="insertCatalogItem" access="remote">
<cfargument name="catalogItem" type="struct">
<!--- rest of method body --->
<cffunction>

// The corresponding ActionScript snippet . . .

// Create an item object with name, price, and description properties.


item = new Object( );
[Link] = "Widget";
[Link] = 9.99;
[Link] = "Widgets available in green, blue, and gold";

// Call the insertCatalogItem( ) service function and pass it the item object.
[Link](myResponseObject, item);

In the preceding example, we want to pass the item object from Flash to the CFC method as a
parameter that should be converted into a struct (a complex datatype). However, ColdFusion
attempts to convert the properties of the item object into named parameters, and it tries to
match up the name, price, and description properties to corresponding parameters within the
CFC method. This prevents it from successfully passing a complex parameter, such as an object
datatype.

You should pass the object parameter as a named parameter instead of as a positional
parameter. Here, the item object is passed as the named parameter catalogItem:

[Link](myResponseObject, {catalogItem: item});

Note that using named parameters prevents ColdFusion from trying to convert the subitems
into individual parameters within the CFC method, thus allowing complex datatypes to be
passed.

20.12.4 See Also


Recipe 20.11

[Link] 2004-02-06
Strona 130 z 156

[ Team LiB ]
[ Team LiB ]

Recipe 20.13 Calling Java or JSP Functions from Flash


20.13.1 Problem
You want to invoke J2EE service functions using Flash Remoting.

20.13.2 Solution
Use public Java class methods. Alternatively, use servlets/JSPs that have been modified to work
with Flash Remoting.

20.13.3 Discussion
Although Flash Remoting for J2EE enables you to invoke public methods of Java classes or
servlets/JSPs, invoking Jave class methods is the preferable option. You can invoke any public
method of a Java class without having to modify the Java code at all. This is an obvious
advantage. To access the methods of a Java class from Flash Remoting, you should create an
ActionScript service object that maps to the fully qualified Java class name:

// Create a service that maps to the Book class in the [Link] package.
myService = [Link]("[Link]");

If you must use servlets/JSPs instead of invoking Java class methods, you need to modify the
servlet/JSP code for it to work properly with Flash Remoting. Values are passed to and from the
servlet/JSP by way of attributes of the request object. Parameters are passed from Flash to the
servlet/JSP using the attribute named [Link]. You can retrieve the attribute value using
the getAttribute( ) method of the request object. For example:

Object myFlashParams = [Link]("[Link]");

The value of the [Link] attribute is a List object. However, if you want the servlet/JSP
to be accessible from a web browser as well as from Flash Remoting, you should cast the value
only after checking to make sure it actually is an instance of List. Otherwise, when the
servlet/JSP is accessed from a web browser (and no [Link] attribute exists), the
attempt to cast the object to List will cause an error. Therefore, you should use:

if (myFlashParams instanceof List) {


List myParamsList = (List) myFlashParams;
// Process the parameters here.
}

You can return a value to a Flash movie from a servlet/JSP using the [Link] attribute of
the request object. Assign the value you want to return to Flash to the attribute using the
setAttribute( ) method. The setAttribute( ) method accepts complex datatypes only. You cannot
set the attribute value to a primitive datatype such as float. If you want to return a primitive
datatype, use a wrapper class instead, such as:

// Return a floating-point number to Flash using the Float class.


[Link]("[Link]", new Float(6.6));
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 131 z 156

Recipe 20.14 Transmitting Custom Datatypes to a Flash


Remoting Back End
20.14.1 Problem
You want to create an object of a custom type to send to a Flash Remoting back end.

20.14.2 Solution
On the client-side, create a custom class in the ActionScript code, and then use [Link]
( ) to register it.

20.14.3 Discussion
Flash Remoting enables you to send and receive many different kinds of data—even custom
datatypes—to and from a Flash Remoting back end. However, Flash has to know about the datat
for it to be properly transmitted. The [Link]( ) method takes care of registering
custom classes with the Flash movie so that it knows how to handle objects of those types when
sending and receiving them via Flash Remoting. When you use the registerClass( ) method for th
purpose, the first parameter should be an arbitrary name by which Flash should know the dataty
and the second parameter should be a reference to the class that you want to register.
Conventionally, the name to which you register the class should match the class name.

// Create a custom class.


function MyClass( ){}

// Register the class so that Flash knows it by the ID MyClass.


[Link]("MyClass", MyClass);

Note that for the properties of a custom class to be correctly interpreted by Flash, when using th
technique, you must declare the properties as members of the class's prototype:

// Create a custom class. The constructor accepts two parameters and sets two
// properties. Normally this would be all that is necessary.
function MyClass(a, b) {
this.a = a;
this.b = b;
}

// When you want to register a custom class you should also declare the propertie
// members of the class's prototype.
[Link].a;
[Link].b;

[ Team LiB ]
[ Team LiB ]

Recipe 20.15 Receiving Typed Objects with ColdFusion


20.15.1 Problem
You want to be able to handle typed object parameters (i.e., properties of an object that store da
to a ColdFusion back end (a CFC function or a CFM page).

[Link] 2004-02-06
Strona 132 z 156

20.15.2 Solution
Work with the parameter as an ASObject. Use the get( ) method to get the values for the proper
( ) method to get the registered class name.

20.15.3 Discussion
Typed objects are automatically converted to ASObject datatypes in ColdFusion. ASObject is a Ja
[Link] class (meaning all the properties and methods of a HashMap will also work for
adds the getType( ) and setType( ) methods. If you are not yet familiar with how to work with Ja
you can read more about this in the ColdFusion documentation here:

[Link]

Here is an example of a snippet of ActionScript code that creates a typed object and sends it to a
function:

function MyClass(a, b) {
this.a = a;
this.b = b;
}
[Link].a;
[Link].b;

[Link]("MyClass", MyClass);

// Send a new MyClass object to a ColdFusion service function.


[Link](myResponseObj, {param: new MyClass("eh", "bee")});

Here is an example of a ColdFusion snippet that extracts the values from properties named a and
named param and also retrieves the object's type:

<!--- aVal = eh, bVal = bee, objTypeName = MyClass --->


<cfset aVal = [Link]("a")>
<cfset bVal = [Link]("b")>
<cfset objTypeName = [Link]( )>

20.15.4 See Also


Recipe 20.14 and Recipe 20.18

[ Team LiB ]
[ Team LiB ]

Recipe 20.16 Receiving Typed Objects with [Link]


20.16.1 Problem
You want to be able to handle typed object parameters sent to a .NET back end (an ASPX page,
DLL method, or web service method).

20.16.2 Solution
Create a .NET class with the same name as the name to which the ActionScript class is
registered. The .NET Flash Remoting gateway automatically converts the ActionScript typed

[Link] 2004-02-06
Strona 133 z 156

object to an object of the corresponding .NET type.

20.16.3 Discussion
When you pass a typed object to a .NET back end from a Flash movie, the .NET Flash Remoting
gateway looks for a .NET class with the same name as the registered class name of the
ActionScript object and tries to convert the ActionScript object to that .NET type. Therefore, you
need to create a .NET type to correspond to each of the registered ActionScript classes.

For example, in the Flash movie, define the following ActionScript class and register it using the
name "MyClass":

function MyClass(a, b) {
this.a = a;
this.b = b;
}
[Link].a;
[Link].b;

[Link]("MyClass", MyClass);

Then create a .NET class with the same name and the same properties. The .NET class should
have a constructor that requires no parameters.

public class MyClass {

public string a;
public string b;

public MyClass( ){}


}

Then, so long as the .NET MyClass is accessible to the .NET service function (be it a DLL
method, an ASPX page, or a web service method), the service function can accept a parameter
of type MyClass. Here is an example of a DLL method that accepts a MyClass parameter:

public string returnPropertiesString(MyClass myCls) {


return myCls.a + " " + myCls.b;
}

20.16.4 See Also


Recipe 20.19
[ Team LiB ]
[ Team LiB ]

Recipe 20.17 Receiving Typed Objects with Java


20.17.1 Problem
You want to handle typed objects using a J2EE service function.

20.17.2 Solution
Use JavaBean classes that correspond to the ActionScript classes, and use the third-party
ASTranslator class to perform conversions between datatypes.

[Link] 2004-02-06
Strona 134 z 156

20.17.3 Discussion
Unlike the .NET version, Flash Remoting for J2EE does not automatically convert typed ActionScr
objects into corresponding custom Java types. Instead, the gateway converts the ActionScript
objects into ASObject types. The [Link] class (included in [Link]) is
subclass of [Link] to which two additional methods (getType( ) and setType( )) have
been added. The properties of the ActionScript object are stored as key/value pairs in the ASObj
You can create your own proprietary code to convert between an ASObject and the appropriate J
datatype. However, Carbon Five has already created an excellent utility named ASTranslator to d
this for you.

ASTranslator is freely available for download from [Link]


[Link]. It converts between ASObject and custom Java datatypes using JavaBean-style
introspection. Therefore, it requires that your custom Java datatypes adhere to the JavaBean rul
(namely, they must implement [Link], use get/set methods for all publicly accessibl
properties, and have a constructor that requires no parameters).

Here is how to use ASTranslator to receive typed objects and convert them to custom Java types

1. Download the [Link] file and place it in your web application's WEB-INF/lib direct

2. Create a JavaBean class that corresponds to the registered ActionScript class. The JavaBea
class should have the same name as the name to which the ActionScript class is registered
Also, the JavaBean class should have get and set methods for each property in the
ActionScript class. Here is an example ActionScript class:

function MyClass(a, b) {
this.a = a;
this.b = b;
}
[Link].a;
[Link].b;

[Link]("MyClass", MyClass);

Here is the code for the corresponding JavaBean class:

public class MyClass implements [Link]{

private string _a;


private string _b;

public MyClass( ) {}

public void setA(string a) {


this._a = a;
}

public void setB(string b) {


this._b = b;
}

public string getA( ) {


return this._a;
}

public string getB( ) {


return this._b;
}

[Link] 2004-02-06
Strona 135 z 156

3. The Flash Remoting service function (servlet/JSP, Java class, etc.) should accept a parame
of type ASObject. Then you can convert the ASObject into a JavaBean using the
fromActionScript( ) method of an ASTranslator object. Here is an example of a Java class
method that does this:

public void myMethod(ASObject aso) {

// Create the ASTranslator instance.


[Link] ast = new
[Link](

// Convert and cast the ASObject to a MyClass object using the


// fromActionScript( ) method.
MyClass myCls = (MyClass) [Link](aso);

// Now you can do something with the properly cast Java object.
}

20.17.4 See Also


Recipe 20.20

[ Team LiB ]
[ Team LiB ]

Recipe 20.18 Returning Typed Objects from ColdFusion


20.18.1 Problem
You want to return a typed object from ColdFusion to a Flash movie.

20.18.2 Solution
Create an ASObject and set the type to match the name of a registered ActionScript class.

20.18.3 Discussion
When an ASObject is returned to a Flash movie, Flash attempts to find a registered class in whic
matches the name of the type that has been set for the ASObject via the setType( ) method. You
within ColdFusion using the ColdFusion techniques for creating Java objects. If you are not famili
you can read more about it in the ColdFusion documentation available here:

[Link]

Here is an example of a ColdFusion Component method that creates and returns an ASObject:

<cffunction name="getTypedObject" access="remote">

<!--- Create a Java object from the [Link] class.


Add the name attribute with the arbitrary name ASObjectClass so
that you can initialize the object properly. --->
<cfobject type="Java" class="[Link]"
name="ASObjectClass"
action="create" >

[Link] 2004-02-06
Strona 136 z 156

<!--- The init( ) method returns a proper ASObject. --->


<cfset aso = [Link]( )>

<!--- Set the type of the ASObject to match the name


of a registered ActionScript class --->
<cfset [Link]("MyClass")>
<cfset [Link]("a", "eh")>
<cfset [Link]("b", "bee")>
<cfreturn aso>
</cffunction>

Here is an example snippet of ActionScript code that corresponds to the preceding ColdFusion m

function MyClass(a, b) {
this.a = a;
this.b = b;
}
[Link].a;
[Link].b;

// Add a method that just writes all the properties to the Output window. This
// example simply illustrates that the ASObject is correctly converted into a MyC
// object when returned to Flash.
[Link] = function ( ) {
for (var prop in this) {
trace(prop + " = " + this[prop];
}
};

[Link]("MyClass", MyClass);

// Create a response object.


myResponse = new Object( );
[Link] = function (result) {
// The result parameter is correctly converted to a MyClass object. You can prov
// this because the traceProperties( ) method gets called.
[Link]( );
};

// Call the service function.


[Link](myResponse);

20.18.4 See Also


Recipe 20.15
[ Team LiB ]
[ Team LiB ]

Recipe 20.19 Returning Typed Objects from [Link]


20.19.1 Problem
You want to return a typed object to Flash from a .NET back end.

20.19.2 Solution
Use an ASObject and set the ASType property to match the name of the ActionScript class as it is
registered in Flash.

[Link] 2004-02-06
Strona 137 z 156

20.19.3 Discussion
Flash automatically attempts to convert any returned ASObject values into ActionScript
datatypes. It tries to find an ActionScript class that is registered within Flash with the same
name as the value of the ASObject's ASType property. Therefore, you can return a typed object
to a Flash movie from a .NET back end by using an ASObject and setting its ASType property to
the name of an ActionScript class as it is registered in the Flash movie. Use the Add( ) method to
add properties to the ASObject. Here is an example of a .NET DLL method that returns an
ASObject:

public [Link] getTypedObject( ) {

// Create an ASObject.
[Link] aso = new [Link]( );

// Add properties to the object.


[Link]("a", "eh");
[Link]("b", "bee");

// Set the ASType of the ASObject to match the name


// of a registered ActionScript class.
[Link] = "MyClass";

// Return the ASObject.


return aso;
}

Here is an example snippet of ActionScript code that handles the ASObject that the preceding
code returns:

function MyClass(a, b) {
this.a = a;
this.b = b;
}
[Link].a;
[Link].b;

// Add a method that just writes all the properties to the Output window.
// This example simply illustrates that the ASObject is correctly converted into
// MyClass object when returned to Flash.
[Link] = function ( ) {
for (var prop in this) {
trace(prop + " = " + this[prop];
}
};

[Link]("MyClass", MyClass);

// Create a response object.


myResponse = new Object( );
[Link] = function (result) {
// The result parameter is correctly converted to a MyClass object.
// You can prove this because the traceProperties( ) method iss called.
[Link]( );
};

// Call the service function.


[Link](myResponse);

.NET web services do not allow you to return ASObject data. This is a
limitation of .NET web services, and it prevents you from returning typed

[Link] 2004-02-06
Strona 138 z 156

objects from them.

20.19.4 See Also


Recipe 20.16

[ Team LiB ]
[ Team LiB ]

Recipe 20.20 Returning Typed Objects from Java


20.20.1 Problem
You want to return a typed object from a J2EE back end.

20.20.2 Solution
Use the third-party ASTranslator class to convert a JavaBean object to an ASObject, and return t
ASObject.

20.20.3 Discussion
For details on obtaining ASTranslator, see Recipe 20.17.

Once you have ASTranslator included in your web application, you can use the toActionScript( )
method of an ASTranslator object to convert a JavaBean object into an ASObject that can be
returned to Flash. Here is an example of a Java method that creates a JavaBean object, converts
into an ASObject, and returns the ASObject:

public [Link] getTypedObject( ) {

// Create a MyClass JavaBean object. See Recipe 20.14 for the MyClass code.
MyClass myCls = new MyClass( );

// Set the properties of the object.


[Link]("eh");
[Link]("bee");

// Create an ASTranslator object.


[Link] ast = new [Link](

// Convert the MyClass object to an ASObject.


[Link] aso = [Link](myCls);

// Return the ASObject.


return aso;

Here is an example ActionScript snippet that handles the returned ASObject:

function MyClass(a, b) {
this.a = a;

[Link] 2004-02-06
Strona 139 z 156

this.b = b;
}
[Link].a;
[Link].b;

// Add a method that just writes all the properties to the Output window. This
// example illustrates that the ASObject is correctly converted into a MyClass ob
// when returned to Flash.
[Link] = function ( ) {
for (var prop in this) {
trace(prop + " = " + this[prop];
}
};

[Link]("MyClass", MyClass);

// Create a response object.


myResponse = new Object( );
[Link] = function (result) {
// The result parameter is correctly converted to a MyClass object. You can prov
// this because the traceProperties( ) method is called.
[Link]( );
};

// Call the service function.


[Link](myResponse);

20.20.4 See Also


Recipe 20.14 and Recipe 20.17
[ Team LiB ]
[ Team LiB ]

Recipe 20.21 Writing Server-Side Functions in


ActionScript
20.21.1 Problem
You want to write Sever-Side ActionScript (SSAS) that you can call from a Flash Remoting
movie.

20.21.2 Solution
Create an ASR file as part of your ColdFusion or JRun web application.

20.21.3 Discussion
SSAS is available only for ColdFusion and JRun, and it allows you to create server-side functions
that you can call from Flash Remoting. The advantage is that with SSAS, you can perform some
basic server-side functionality (such as database queries and HTTP requests) using familiar
ActionScript syntax. The disadvantage of SSAS is that it is quite limited in its (documented)
functionality.

Flash Remoting SSAS (as opposed to the FlashCom SSAS in Recipe


14.14) is based on Java, and the standard Java API is available in a
modified, ActionScript-like form. This might hold some appeal to Java
developers. The syntax is entirely ActionScript-like, and it also allows for

[Link] 2004-02-06
Strona 140 z 156

the less strict declaration and typing of ActionScript variables. However,


you can create objects of all Java types. For example:

myFile = new File("[Link]");

To write SSAS, all you need to do is create an ASR file within the web application. The file must
end with the .asr extension and should contain functions, which can be written with any plain-
text editor. For example, an ASR file could contain the following:

function mySSASFunction ( ) {
return "This is a value from SSAS";
}

Within your Flash movie, you can create a service object that maps to an ASR file using the
getService( ) method and specifying the fully qualified path and name of the ASR file (minus
the .asr extension):

// Create a service object that maps to [Link] where the ASR file is within a
// directory called ssasFiles.
mySSASSrv = [Link]("[Link]");

20.21.4 See Also


Recipe 20.22 and Recipe 20.23

[ Team LiB ]
[ Team LiB ]

Recipe 20.22 Querying a Database with Server-Side


ActionScript
20.22.1 Problem
You want to use Server-Side ActionScript (SSAS) to query a database.

20.22.2 Solution
Make sure the database is registered as a datasource with ColdFusion or JRun, and then use the
query( ) method of the SSAS CF object.

20.22.3 Discussion
Server-Side ActionScript allows you to query any registered datasource by way of the [Link]
( ) method. The first step is to make sure that your database has been properly registered with
ColdFusion or JRun. If you are not sure how to do this, then you should consult your ColdFusion
or JRun documentation.

The [Link]( ) method allows you to pass it the following arguments as either positional or
named parameters:

datasource

The name of the datasource as registered with ColdFusion or JRun.

[Link] 2004-02-06
Strona 141 z 156

sql

The SQL command.

username

If the datasource requires a username, you can specify it with this parameter.

password

If the datasource requires a password, you can specify it with this parameter.

maxrows

You can optionally specify the maximum number of records to return.

timeout

An optional number of milliseconds to wait before timing out.

When you use the [Link]( ) method with positional parameters, you can specify one or more
optional parameters in the following orders:

[Link](datasource, sql);
[Link](datasource, sql, maxrows);
[Link](datasource, sql, username, password);
[Link](datasource, sql, username, password, maxrows);

You can use the timeout parameter only if you used named parameters. Otherwise, you can
use named parameters by passing the method an object in which the properties match the
parameter names. For example:

[Link]({datasource: "myDatabase", sql: "SELECT * FROM MyTable"});

The [Link]( ) method returns a recordset that you can return to the Flash movie. For
example, here is an SSAS function that accepts a parameter and performs a database query to
find records containing that value. The function then returns the recordset to the Flash movie.

function getUsers (city) {


return [Link]("MyDatabase", "SELECT * FROM UsersTable WHERE city = " + city);
}

However, the recordset that the [Link]( ) method returns is not an ActionScript RecordSet
object in SSAS (although it is converted into a RecordSet object in the Flash movie). This is
because the ActionScript RecordSet class is available only in client-side ActionScript, not in
SSAS. The object that [Link]( ) returns is actually a [Link] object. The
[Link] class subclasses [Link], so if you absolutely want to work
with the recordset on the server before returning the data to Flash, you can use the methods of
the RowSet class.

20.22.4 See Also


Recipe 20.21. Also, Chapter 21 covers recordsets in detail. For more information on RowSet
objects, see the Java API at
[Link]

[ Team LiB ]

[Link] 2004-02-06
Strona 142 z 156

[ Team LiB ]

Recipe 20.23 Making HTTP Requests with Server-Side


ActionScript
20.23.1 Problem
You want to make HTTP requests using Server-Side ActionScript (SSAS).

20.23.2 Solution
Use the [Link]( ) method.

20.23.3 Discussion
The Server-Side ActionScript [Link]( ) method enables you to make HTTP requests to any
resource available on the Web. This is a convenient way to run server-side scripts on remote
domains and return those values to Flash.

The [Link]( ) method accepts the following parameters:

url

The URL to the requested resource (e.g.,


"[Link] or the protocol and server
address/domain name (e.g., "[Link] You may also need to
specify values for the path and file parameters when you use the second format.

method

Either "post" or "get", corresponding to the HTTP method (POST or GET) used to send any
parameters.

path

If you do not include the logical path and filename in the url parameter, then you should
indicate the logical path with the path parameter. For example, if the resource is
"[Link] and you use the value
"[Link] for the url parameter, then you should set path to
"/logicalPath".

file

The filename of the requested resource when you do not include it in the url parameter.
For example, if the resource is "[Link] and
you use the value "[Link] for the url parameter, then you
should set file to "[Link]".

username

The username, if required.

password

[Link] 2004-02-06
Strona 143 z 156

The password, if required.

resolveurl

A value of either "yes" or "no". If "yes", then relative links and references within the remot
document are converted to absolute references. The values of the url and path parameter
are used for this purpose. For example, if the url parameter is
"[Link] and the path parameter is "/logicalPath", then an HTML
tag in the remote document such as <a href="pages/[Link]"> is converted to <a
href="[Link] in the returne
value.

params

The params parameter contains the values (if any) that are sent to the resource, and it
should be in the form of an indexed array of objects with the following properties:

name

The variable name.

value

The variable value.

type

The scope for the variable. The possible values for type are "URL", "FormField",
"Cookie", and "CGI".

You can pass the arguments to [Link]( ) as either positional or named parameters. If you use
positional parameters, the following is a list of possible parameter orders:

[Link](url);
[Link](method, url);
[Link](method, url, username, password);
[Link](method, url, params, username, password);

Notice that path, file, and resolveurl are not available when using positional parameters. If
you want to use these parameters, you must use named parameters.

The [Link]( ) method returns an associative array with the following properties:

Header

The raw response header.

Charset

The character the requested resource uses.

Mimetype

The mime type of the requested resource.

[Link] 2004-02-06
Strona 144 z 156

Statuscode

The status code, such as "200 OK" or "500 Internal Server Error".

Responseheader

The response header as an associative array. Each response header key (such as Date,
Server, etc.) is an element of the associative array.

Text

A Boolean value. The value is true if the requested resource contains text, and false
otherwise.

FileContent

The contents of the requested resource.

Most of the time, you should return the result of a [Link]( ) to the Flash movie, and you can
process the result as a normal ActionScript associative array within the client movie. However, if
you want to process the result using SSAS, be aware that on the server, the object is actually an
ASObject. Therefore, you must use the ASObject methods (ASObject subclasses
[Link]) to process the result of a [Link]( ) method invocation within SSAS:

// Get the contents of [Link] and assign the result to httpResul


var httpResult = [Link]("[Link]

// Extract the value of the FileContent element using the get( ) method.
var fContent = [Link]("FileContent");

20.23.4 See Also


Recipe 20.21

[ Team LiB ]
[ Team LiB ]

Recipe 20.24 Consuming Web Services with Flash


Remoting for .NET or ColdFusion
20.24.1 Problem
You want to consume a web service using Flash Remoting for .NET or ColdFusion.

20.24.2 Solution
Use a Flash Remoting service object that maps to the .wsdl file for the web service and invoke
the web service method from the service object.

20.24.3 Discussion
One of the great features of Flash Remoting for .NET and ColdFusion is that you can use it to
invoke web service methods directly from the Flash movie. You don't have to create any server-

[Link] 2004-02-06
Strona 145 z 156

side scripts to consume the web service and return the results to Flash.

The process for calling a web service method from a Flash movie is the same as calling any
service function using Flash Remoting. You need to create a connection object, a service object,
and a response object. Then, you only need to invoke the service function from the service
object and tell it to return the results to the response object. The only change when working
with web services (versus service functions) is that you should map the service object to the
web service's WSDL (Web Services Description Language) document. You should still use your
local application server as the Flash Remoting gateway, regardless of the domain of the web
service. Your local application server actually proxies the request for you. For example:

// Create a connection object that uses your local application server


// as the Flash Remoting gateway.
gwURL = "[Link]
[Link](gwURL);
myConnection = [Link]( );

// Create a service object that maps to the WSDL document for the web service. In
// this example it exists at [Link]
wsSrv = [Link](
"[Link]

// Create a response object.


myResponse = new Object( );
[Link] = function (result) {
// Do something with the result here.
};

// Invoke a web service method named myWebMethod( ).


[Link](myResponse);

20.24.4 See Also


See Recipe 20.2 and Recipe 20.3 for important configuration information to allow Flash
Remoting access to web services with ColdFusion MX Updater 3 and [Link]. For more
information, see [Link]

[ Team LiB ]
[ Team LiB ]

Chapter 21. Recordsets

Introduction

Recipe 21.1. Creating Recordsets

Recipe 21.2. Reading Recordsets

Recipe 21.3. Filtering Recordsets

Recipe 21.4. Sorting Recordsets by a Single Column

Recipe 21.5. Populating Menu Components

[Link] 2004-02-06
Strona 146 z 156

Recipe 21.6. Using Recordsets with DataGrids


[ Team LiB ]
[ Team LiB ]

Introduction
Databases are an integral part of many applications, and Flash Remoting helps to integrate
databases into your Flash applications easily. Data in a database is stored in tables as records.
You can think of a database table as a grid and a record as being a single row in that table
containing data for each column. When data is retrieved from a database it is stored as a
recordset. ColdFusion recordsets are called Query objects, Java recordsets are called ResultSet
objects, and .NET recordsets are called DataTable objects. But regardless of what the datatype
is called in the server-side application, when it is returned to your Flash movie by way of Flash
Remoting it is automatically converted into a RecordSet object. See Recipe 20.22 for more
information on retrieving a recordset.

The RecordSet class is part of the Flash Remoting Components installation. To include the class
in your Flash movies, you should include the [Link] file:

#include "[Link]"

If you are using client-side recordsets only, it is sufficient to include [Link] instead of
[Link].
[ Team LiB ]
[ Team LiB ]

Recipe 21.1 Creating Recordsets


21.1.1 Problem
You want to create and populate a recordset.

21.1.2 Solution
Return a recordset from Flash Remoting, or create a recordset using the constructor and populat
using the [Link]( ) or [Link]( ) methods.

21.1.3 Discussion
Most often, recordsets are returned to Flash movies from Flash Remoting. Recordsets cannot be
meaningfully passed back to an application server portion of a Flash Remoting application.
Therefore, it is rarely necessary to create recordsets with ActionScript. However, recordsets can
convenient way of storing data, and with the DataGlue class, the data within a recordset can be
used to populate many UI components.

The constructor function for recordsets requires an array of column names as a parameter, as
follows:

// Make sure to include [Link] whenever you work with recordsets, although
// for this simple example, including [Link] would suffice.
#include "[Link]"

// Create a new recordset with three columns: COL0, COL1, and COL2.
rs = new RecordSet(["COL0", "COL1", "COL2"]);

Once you have created a recordset using the constructor, you can add records to it using the

[Link] 2004-02-06
Strona 147 z 156

addItem( ) and addItemAt( ) methods. Both methods require a record object parameter. A recor
object is merely an object (an instance of the Object class) in which the property names match t
column names of the recordset. The addItemAt( ) method differs from the addItem( ) method in
that addItemAt( ) inserts a new record at a specific index within the recordset, while addItem( )
appends a record to the end of the recordset. Note that recordset indexes are zero-relative.

// Appends a record to the recordset.


[Link]({COL0: "val0", COL1: "val1", COL2: "val2"});

// Adds a record to the recordset at index 5.


[Link](5, {COL0: "val0", COL1: "val1", COL2: "val2"});

Additionally, you can use the [Link]( ) method to alter a single column of an existing
record within a recordset. The method takes three parameters: the index of the record, the colum
name, and the new value to assign.

#include "[Link]"

// Create a new recordset and fill it with three records.


rs = new RecordSet(["SHAPE", "COLOR"]);
[Link]({SHAPE: "square", COLOR: 0x00FF00});
[Link]({SHAPE: "circle", COLOR: 0xFF00FF});
[Link]({SHAPE: "triangle", COLOR: 0x0000FF});

// Change the COLOR column to 0x000000 in the record with index 2 (the third reco
[Link](2, "COLOR", 0x000000);
[ Team LiB ]
[ Team LiB ]

Recipe 21.2 Reading Recordsets


21.2.1 Problem
You want to read the data from a recordset.

21.2.2 Solution
Use the [Link]( ) method to get a record at a particular index. Use
[Link]( ) to get an array of the column names.

21.2.3 Discussion
You can think of recordsets as being composed of rows and columns, like a grid. Each row
represents a single record, and each column represents a field within each record. All recordsets
have a getItemAt( ) method that returns a record object at a given row index.

#include "[Link]"

// Create a recordset and populate it using addItem( ) (see Recipe 21.1).


rs = new RecordSet(["COL0", "COL1", "COL2"]);
[Link]({COL0: "a", COL1: "b", COL2: "c"});
[Link]({COL0: "d", COL1: "e", COL2: "f"});
[Link]({COL0: "g", COL1: "h", COL2: "i"});

// Get a single record from the recordset using getItemAt( ).


record1 = [Link](1);

// Output the values from the record object. Displays: d e f

[Link] 2004-02-06
Strona 148 z 156

trace(record1.COL0 + " " + record1.COL1 + " " + record1.COL2);

The getItemAt( ) method is all you need, as long as you know the column names. However, if
you do not already know the column names (remember, recordsets are often retrieved from the
server), use the getColumnNames( ) method to get an array of the recordset's column names.
You can then use that array to loop through all the columns of a given recordset.

// Get a record from the recordset.


record1 = [Link](1);

// Retrieve the column names.


columnNames = [Link]( );

/* Loop through all the columns and output the values for the record. Outputs:
COL0: d
COL1: e
COL2: f
*/
for (var i = 0; i < [Link]; i++) {
trace(columnNames[i] + ": " + record1[columnNames[i]]);
}

You can also use the [Link]( ) method to determine the number of records that a
recordset contains. Using this value, you can loop through all the records.

// Get the column names.


columnNames = [Link]( );

// Loop through each record in the recordset from 0 to the length, as returned by
// getLength( ).
for (var i = 0; i < [Link]( ); i++) {

// Display the record number.


trace("record " + i);

/* Loop through each column for each record and output the values. Displays:
record 0
COL0: a
COL1: b
COL2: c
record 1
COL0: d
COL1: e
COL2: f
record 2
COL0: g
COL1: h
COL2: i
*/
for (var j = 0; j < [Link]; j++) {
trace(" " + columnNames[j] + ": " + [Link](i)[columnNames[j]]);
}
}

You can create a custom method that allows you to write the recordset data to the Output
window during testing. The [Link]( ) method, as shown in the following code block,
uses the same logic as the preceding code:

[Link] = function ( ) {
var columnNames = [Link]( );
for (var i = 0; i < [Link]( ); i++) {
trace("record " + i);

[Link] 2004-02-06
Strona 149 z 156

for (var j = 0; j < [Link]; j++) {


trace(" " + columnNames[j] + ": " + [Link](i)[columnNames[j]]);
}
}
};
// Example usage (if rs contains a recordset):
[Link]( );
[ Team LiB ]
[ Team LiB ]

Recipe 21.3 Filtering Recordsets


21.3.1 Problem
You want to create a new recordset that contains a subset of the original.

21.3.2 Solution
Use the [Link]( ) method.

21.3.3 Discussion
You can manually create a new RecordSet object by reading the records from a recordset and
writing selected ones to a new recordset. However, this is unnecessarily laborious. Instead, you
should use the filter( ) method, which handles almost all of the processing for you. Simply provid
filter function that contains the logic to decide which records to use. The filter( ) method expects
reference to a filter function as a parameter, and it accepts a second parameter that is automatic
passed to the filter function. The second parameter lets you filter records based on different crite
The filter( ) method returns a new RecordSet object.

filteredRs = [Link](filterFunction, context);

The filter function is called once for each record. Each time it is called, it is passed the next recor
object, and if a context parameter was specified in the filter( ) method call, that value is also pas
along to the filter function. Within the filter function you should place the logic to determine whe
the record should be included in the filtered recordset. The optional context parameter, if used,
the filter function filter based on different criteria. If the filter function returns true, then the rec
is included in the filtered recordset; if it returns false, the record is not included.

function filterFunction (record, context) {


// Determine whether to include the record and return true or false.
}

Here is an example in which a recordset is filtered twice to create two new recordsets, filtered
and filteredRs1, based on different criteria:

#include "[Link]"

// Create and populate a new recordset.


rs = new RecordSet(["ID", "NAME"]);
[Link]({ID: 24, NAME: "a"});
[Link]({ID: 42, NAME: "b"});
[Link]({ID: 66, NAME: "c"});
[Link]({ID: 93, NAME: "d"});
[Link]({ID: 33, NAME: "e"});

// Define the filter function.


function filterer (record, context) {

[Link] 2004-02-06
Strona 150 z 156

// If the record's ID value is greater than the context value, return true to
// include the record. Otherwise, return false and exclude the record.
if ([Link] > context) {
return true;
}
return false;
}

// Use the filter( ) method to create two new recordsets. The first one is popul
// with all records in which the ID is greater than 51. The second is populated w
// all records in which the ID is greater than 36.
filteredRs0 = [Link](filterer, 51);
filteredRs1 = [Link](filterer, 36);

/* Output the values in filteredRs0. Displays:


record 0
ID: 66
NAME: c
record 1
ID: 93
NAME: d
*/
[Link]( );

/* Output the values in filteredRs1. Displays:


record 0
ID: 42
NAME: b
record 1
ID: 66
NAME: c
record 2
ID: 93
NAME: d
*/
[Link]( );

It is not necessary to use the context parameter with the filter( ) method. You can also define a
very specific filter function without it:

// Use a filter function that does not require a context parameter. Filter for
// records with ID values between 39 and 72.
function filterer (record) {
if ([Link] > 39 && [Link] < 72) {
return true;
}
return false;
}

filteredRs = [Link](filterer);

/* Output the values from filteredRs:


record 0
ID: 42
NAME: b
record 1
ID: 66
NAME: c
*/
[Link]( );

[Link] 2004-02-06
Strona 151 z 156

Filtered recordsets contain references to the records of the original recordset.


This is important to keep in mind because it means that if you make changes
to a record in a filtered recordset, the same change is reflected in the original
as well as in any filtered versions of the original.

21.3.4 See Also


See Recipe 6.8 for a discussion of complex datatypes and copying them by reference. To create
independent copy of one or more records in a recordset, copy data manually into a new recordse
using the methods of the RecordSet class, as described in Recipe 21.1 and Recipe 21.2.
[ Team LiB ]
[ Team LiB ]

Recipe 21.4 Sorting Recordsets by a Single Column


21.4.1 Problem
You want to sort the records in a recordset, ordering them by the values in a single column.

21.4.2 Solution
Use the [Link]( ) method.

21.4.3 Discussion
If you want to do a simple sort (sorting according to one column) on a recordset, use the
sortItemsBy( ) method. This method takes the name of the column on which to sort as a
parameter, and it sorts the records within the original recordset (no copy is made). Passing the
value "DESC" to the method as a second parameter sorts the records in descending order.
Otherwise, the sort order is ascending.

#include "[Link]"

rs = new RecordSet(["ID", "NAME"]);


[Link]({ID: 24, NAME: "a"});
[Link]({ID: 42, NAME: "b"});
[Link]({ID: 66, NAME: "c"});
[Link]({ID: 93, NAME: "d"});
[Link]({ID: 33, NAME: "e"});

// Sort the recordset by ID values in ascending order.


[Link]("ID");

/* Outputs:
record 0
ID: 24
NAME: a
record 1
ID: 33
NAME: e
record 2
ID: 42
NAME: b
record 3
ID: 66
NAME: c
record 4

[Link] 2004-02-06
Strona 152 z 156

ID: 93
NAME: d
*/
[Link]( );

// Sort the recordset by the NAME column in descending order.


[Link]("NAME", "DESC");

/* Outputs:
record 0
ID: 33
NAME: e
record 1
ID: 93
NAME: d
record 2
ID: 66
NAME: c
record 3
ID: 42
NAME: b
record 4
ID: 24
NAME: a
*/
[Link]( );

[ Team LiB ]
[ Team LiB ]

Recipe 21.5 Populating Menu Components


21.5.1 Problem
You want to use a recordset to populate a menu component instance.

21.5.2 Solution
Use the setDataProvider( ) method for the menu component of interest. For more complex
scenarios, use the DataGlue class.

21.5.3 Discussion
List boxes and combo boxes can both be populated by recordsets without much effort by employ
one of two techniques. The first technique is to use the setDataProvider( ) method of the list box
combo box. You can pass a recordset reference to the setDataProvider( ) method, and it will kno
how to process it. The result is that the menu displays one item per record, with each item show
the record's column values in a comma-delimited list. The order in which the column values appe
within each item is determined by the order of the columns within the recordset column names
array.

#include "[Link]"

rs = new RecordSet(["ID", "NAME"]);


[Link]({ID: 24, NAME: "a"});
[Link]({ID: 42, NAME: "b"});
[Link]({ID: 66, NAME: "c"});
[Link]({ID: 93, NAME: "d"});

[Link] 2004-02-06
Strona 153 z 156

[Link]({ID: 33, NAME: "e"});

// Populate a list box and a combo box with the values from the recordset.
[Link](rs);
[Link](rs);

Additionally, when you use a recordset as a data provider for a menu, the object(s) returned by
getItemAt( ), getSelectedItem( ), and getSelectedItems( ) methods are record objects instead o
the conventional objects with data and label properties. This also means that the getValue( )
method of a menu populated by a recordset returns undefined. Another interesting effect of
populating a menu with a recordset as a data provider is that changes made to the recordset cau
the menu to be automatically updated.

#include "[Link]"

rs = new RecordSet(["ID", "NAME"]);


[Link]({ID: 24, NAME: "a"});

// Populate the list box with the recordset.


[Link](rs);

// Add an item to the recordset after having already set the data provider for th
// list box. The list box is automatically updated!
[Link]({ID: 99, NAME: "z"});

While using a recordset with the setDataProvider( ) method offers certain advantages, such as e
of use and automatic updating of menus, it also has its disadvantages. The main disadvantage o
the technique is that you can't control the display of the data within the menu to any great degre
The other problem is that it doesn't conform to the standard way in which menus work—namely,
using objects with data and label properties. The DataGlue class offers functionality that solves
both of these problems.

The DataGlue class has two static methods for populating UI components with recordset data. Le
look at the first of these, the bindFormatStrings( ) method. This method requires four parameter

dataConsumer

A reference to the UI component, such as a combo box or a list box.

dataProvider

A reference to the recordset.

labelString

A string that is used for the menu labels. Any values in the string between pound signs (#)
dynamically evaluated as column names from the recordset.

dataString

A string that is used for the menu items' data properties. Any values in the string between
pound signs (#) are dynamically evaluated as column names from the recordset.

The bindFormatStrings( ) method populates the UI component with the values from the recordse
and allows you to control the label and data values. By placing column names within pound sig
(#) in the label and data string parameters, those values are evaluated when the menu is
populated. For example, #COL0# is evaluated for the value of column COL0 for each record.

[Link] 2004-02-06
Strona 154 z 156

#include "[Link]"

// You must include the [Link] file whenever you use DataGlue methods.
#include "[Link]"

rs = new RecordSet(["ID", "NAME"]);


[Link]({ID: 24, NAME: "a"});
[Link]({ID: 42, NAME: "b"});
[Link]({ID: 66, NAME: "c"});
[Link]({ID: 93, NAME: "d"});
[Link]({ID: 33, NAME: "e"});

// Populate a combo box with the records from rs. The labels of each item are in
// format NAME (ID). For example, a (24). The data properties for each item are th
// string equivalents of the ID values.
[Link](myComboBox, rs, "#NAME# (#ID#)", "#ID#");

// Components populated using DataGlue methods are automatically updated as well!


[Link]({ID: 99, NAME: "f"});

The bindFormatStrings( ) method has a few shortcomings. One limitation is that it populates
components only with strings, although you may want to populate menus in which the data prop
is a number, Boolean, or object. Another limitation is that it doesn't let you manipulate the colum
values. For example, if a recordset has a column PERSON_NAME with values that are in the format
FirstName LastName, you might want to display that in the menu as LastName, FirstName. With
bindFormatStrings( ), this is not possible. However, the bindFormatFunction( ) method takes car
both of these problems.

The bindFormatFunction( ) method functions similarly to the bindFormatStrings( ) method. It tak


the same first two parameters—the data consumer and data provider—but the third parameter f
bindFormatFunction( ) is a reference to a format function. The format function is automatically
passed a record, and it should return an object with data and label properties.

#include "[Link]"

// Define the format function. The function should expect a record object.
function formatter (record) {

// Create the object to which the label and data properties are added.
var obj = new Object( );

// Define the label property. This example displays the NAME column of each rec
// in uppercase, demonstrating the flexibility of the format function technique
[Link] = [Link]( ) + " (" + [Link] + ")";

// Define the data property. This example assigns the value of the record itsel
// the data property. This is useful in many cases.
[Link] = record;

// Return the object with label and data properties defined.


return obj;
}

// Populate a combo box with the values from a recordset (rs).


[Link](myComboBox, rs, formatter);

[ Team LiB ]
[ Team LiB ]

Recipe 21.6 Using Recordsets with DataGrids

[Link] 2004-02-06
Strona 155 z 156

21.6.1 Problem
You want to display recordset data in a grid.

21.6.2 Solution
Use the DataGrid component.

21.6.3 Discussion
The DataGrid component is available for purchase from Macromedia as part of the DevNet Resou
Kit (DRK) Volume 1, available from
[Link] It is invalua
for displaying complex data sets such as recordsets. Normal menus, such as list boxes, work gre
for showing one-dimensional lists of values, but when it comes to displaying two-dimensional (ro
and column) data, they are not up to par. The DataGrid component allows you to display data as
rows and columns, and with the [Link]( ) method or the DataGlue class, you
populate a data grid with recordset data in short order.

The setDataProvider( ) method populates the data grid with data from a recordset. If the data gr
has no defined columns, columns that match the columns of the recordset in name and order are
automatically created.

#include "[Link]"

// Create a new recordset with columns ID, FIRST, LAST.


rs = new RecordSet(["ID", "FIRST", "LAST"]);
[Link]({ID: 24, FIRST: "Bob", LAST: "Bobson"});
[Link]({ID: 42, FIRST: "Sarah", LAST: "Littlefoot"});
[Link]({ID: 66, FIRST: "George", LAST: "Georgeman"});

// Set the data grid to alternate row colors between white and light blue.
[Link](0xFFFFFF, 0xC1E0FD);

// Populate the data grid with the recordset data. It creates


// three columns: ID, FIRST, and LAST, in that order, which is the
// same order as the columns from the recordset.
[Link](rs);

If you want to change the order of the columns as they are displayed in the data grid, add colum
to the data grid before calling the setDataProvider( ) method. The easiest way to add columns to
data grid is with the setColumns( ) method. You should call this method and pass it the names o
the columns to add in the order you want them to be displayed. When you use this technique wit
setDataProvider( ), you must make sure the columns you add to the data grid match the column
names of the recordset.

[Link]("ID", "LAST", "FIRST");


[Link](rs);

The setDataProvider( ) method works in many cases—especially when you want to display the ra
data from a recordset without modification, except, perhaps, the column order. If you want to ha
more control over how the recordset data is displayed, use the DataGlue class.

The DataGlue class has two static methods, as described in Recipe 21.5. The bindFormatStrings(
method is not of much use with data grids, but you can use the bindFormatFunction( ) method w
a format function to preprocess the recordset data and populate the data grid with custom objec
The format function should create and return objects with properties corresponding to the colum
you want to display in the data grid. For example:

[Link] 2004-02-06
Strona 156 z 156

#include "[Link]"

// Define the format function.


function formatter (record) {

// Define the object that is used to populate a row of the data grid.
var obj = new Object( );

// Add a Name property to the object that is in the format of LAST, FIRST.
[Link] = [Link] + ", " + [Link];

// Add an ID property to the object with the value of the record's ID column.
[Link] = [Link];

// Return the object.


return obj;
}

// Populate the data grid with the recordset using the format function. The resul
// a data grid that has columns Name and ID.
[Link](myDataGrid, rs, formatter);

Remember that you must always include the [Link] file when you use
the DataGlue class.

When you use the format function technique and you have not previously defined the columns
within the data grid, the order of the columns is not under your control. However, if you add
columns to the data grid before populating it, you can control the order in which they display. Th
column names you add to the data grid and the properties of the object returned by the format
function must match.

#include "[Link]"

function formatter (record) {


var obj = new Object( );
[Link] = [Link] + ", " + [Link];
[Link] = [Link];
return obj;
}

// Set the column names and order within the data grid before
// populating it. The column names match those of the properties
// of the object returned by the format function.
[Link]("Name", "ID");
[Link](myDataGrid, rs, formatter);

21.6.4 See Also


Macromedia sells additional components as part of the DRKs available at
[Link] Third-party vendors also sell components. For
example, B-Line Express ([Link] sells a package of charting components that
provide a flexible and extensible library of charting and graphics functions for displaying data as
charts, bar charts, and much, much more.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 180

[ Team LiB ]

Part III: Applications


Chapter 22

Chapter 23

Chapter 24

Chapter 25

Chapter 26

Chapter 27

Chapter 28
[ Team LiB ]
[ Team LiB ]

Chapter 22. Building a Flash Paint


Application
Flash MX offers many possibilities for dynamically generating vector graphics and text using
ActionScript. In this chapter, you will create a Flash Paint application. This application builds on
many of the recipes from earlier chapters. Specifically, this application utilizes the following
skills:

z Drawing and filling shapes with the Drawing API

z Developing and using custom components

z Extending classes

z Swapping depths of movie clips and text fields

z Using mouse and key listeners

z Changing colors of movie clips

z Getting and setting the current focus and selection

The Flash Paint application is designed to allow for the following functionality:

z Drawing lines, rectangles, and ellipses

z Adding text fields

z Rearranging existing shapes and text

[Link] 2004-02-06
Strona 2 z 180

z Moving existing shapes and text forward or backward to adjust the overlap with other
objects

z Selecting a color to apply to new text and with which to draw shapes

z Filling existing shapes with solid color and applying color to existing text

z Deleting existing shapes and text

[ Team LiB ]
[ Team LiB ]

22.1 Planning the Application


You should begin designing the Flash Paint application by determining the necessary elements
and mapping out how they should work together. The list of desired functionality in the previous
section gives you a good idea of which elements you will need to develop. Essentially, there are
four basic elements:

Toolbar buttons

These are the buttons that allow the user to select which type of action to take, be it
selecting items, drawing shapes, colorizing shapes, etc.

Color selector

The color selector is the means by which a user can select the current color to use for
drawing shapes, adding text, or filling or colorizing shapes or text.

Shapes

Shapes can be drawn by the user. A shape can be a line, a rectangle, or an ellipse.

Text

Text items, like shapes, are units that are added and modified by the user.

All of these elements can be contained within a single Flash document. At this point you should
open a new Flash document and save it as [Link]. This is the document to which you
should add all of the code and other modifications that are contained within the following
sections. The final version is available at [Link]
[ Team LiB ]
[ Team LiB ]

22.2 Building the Components


The four elements described in the previous section are all good candidates for components. In e
case, the elements are discrete constituents that can be abstracted. For example, the toolbar is
composed of multiple buttons that are responsible for different tasks. However, each of the butto
shares basic, core functionality. Likewise, each of the shape units that are drawn by the user ma
different—different outline shapes, sizes, and fill colors—but the basic functionality of all shapes
same. And it is the same with the text units as well. While there is only one color selector instan
application, it is nonetheless a good candidate for a component because it is a distinct unit that c
be reused in another application.

[Link] 2004-02-06
Strona 3 z 180

22.2.1 Creating the Superclass for the Components


The toolbar buttons, shape units, and text units all share some common functionality. Each of th
three types of components needs to have the capability to define callback methods, including cal
methods for when the component is selected, deselected, pressed, and released. While it is certa
possible to define the same methods individually in each of the component classes, you can also
single superclass from which the component classes can inherit. This is advantageous because y
define the methods in one location.

Complete the following steps to define the custom PaintBase class from which three of the comp
inherit their methods:

1. In your [Link] document, create a new movie clip symbol named PaintBaseClass.

2. On the first frame of the default layer of the PaintBaseClass symbol, add the following code

// Enclose the code using #initclip so that it executes before the rest of th
// code in the movie.
#initclip 0

function PaintBase ( ) {}

// PaintBase needs to inherit from MovieClip because the classes that extend
// PaintBase are all component classes.
[Link] = new MovieClip( );

// The following four methods define callback functions for instances of the
// class. Each method requires at least one parameter: the name of the callb
// function as a string. Also, each accepts a second, optional parameter
// indicating the path to the function. If no path is supplied, the timeline
// which the component instance exists is used.
[Link] = function (functionName, path) {
if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link] = function (functionName, path) {


if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link] = function (functionName, path) {


if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link] = function (functionName, path) {


if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link] 2004-02-06
Strona 4 z 180

// Several of the components should automatically call the onPress( ) callb


// function when a press event occurs.
[Link] = function ( ) {
[Link][[Link]](this);
};

// Likewise, several of the components should automatically call the onRelea


// and onSelect( ) callback methods when the release event occurs. Addition
// a selected property is set to true so that the component can keep track o
// current state.
[Link] = function ( ) {
[Link][[Link]](this);
[Link] = true;
[Link][[Link]](this);
};

// Several of the components include a deselect( ) method that calls the


// onDeselect( ) callback function.
[Link] = function ( ) {
[Link][[Link]](this);
};

#endinitclip

Congratulations! You have just created a superclass that defines some common functionality for
class that extends it. Now, perhaps a little explanation is due.

A class such as PaintBase is sometimes referred to as an abstract class because it contains abstr
functionality that can be used by any class that extends it. By itself, PaintBase doesn't do very m
it does save you from having to define the same methods in multiple classes later on. Instead, e
the component classes inherits from PaintBase in the following way:

[Link] = new PaintBase( );

When you create a class that inherits from PaintBase, all the methods of PaintBase are available
instances of that class. For example, if myObj is an instance of MyComponentClass, and
MyComponentClass inherits from PaintBase, then you can call any of the PaintBase methods from

// Define an onRelease( ) callback function for myObj. Now,


// whenever myObj is clicked and released the function
// myOnReleaseCallback( ) defined on _root is invoked automatically.
[Link]("myOnReleaseCallback", _root);

Now, let's look a little more closely at each of the methods of the PaintBase class.

The first line of code is an #initclip directive. In this case, you should follow the directive with
value 0, which instructs the Flash movie to execute all the code contained between the #initcli
#endinitclip directives before anything else, even before other code within #initclip directive
processes all #initclip code before any code on the frame in which the component first exists.
component is an exported symbol, the #initclip code is processed before the first frame of the
timeline. However, in this case you need to ensure that PaintBase is defined before any of the co
classes are defined. You can specify the precedence for processing the #initclip code using the
order parameter. Code with lower order parameters is processed first, and code with order param
processed before code without order parameters. Therefore, we use:

#initclip 0

The PaintBase class is an abstract class for component classes. All components must extend Mov
directly or indirectly. Therefore, PaintBase must extend MovieClip (which is not to say that all ab

[Link] 2004-02-06
Strona 5 z 180

classes must extend MovieClip):

[Link] = new MovieClip( );

The setOnPress( ), setOnRelease( ), setOnSelect( ), and setOnDeselect( ) methods all work in th


manner. The concept is to closely mimic the functionality of the callback-setting methods of man
predefined ActionScript classes and the UI components. Each of these four methods, therefore, a
string specifying the name of the callback function. Additionally, you can specify a path to the fu
no path is specified (that is, if the path parameter is undefined), the value of the timeline on wh
component instance resides (given by this._parent) is used. In each case, the function name a
to the function are stored in unique properties, such as onPressCB and onPressPath:

[Link] = function (functionName, path) {


if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

There is a very good reason to use callback functions instead of allowing each component to defi
onPress( ) and onRelease( ) methods directly. If onPress( ) or onRelease( ) methods were to be
for the component instances, the prototype methods would be wiped out. Working with callback
allows you to define actions to occur on these events for the instances while not overwriting the
functionality defined for the prototype. The PaintBase class's onPress( ) and onRelease( ) method
demonstrate how the callback function is invoked without wiping out any functionality that is alre
defined within the prototype. ActionScript treats functions as properties (Function datatypes) of t
timeline in which they are defined. You can use this fact to invoke the callback functions using ar
access notation. In each case, the callback function is passed a reference to the component doin
callback. This is both convenient and in keeping with the conventions of callback functions as the
used throughout ActionScript.

[Link] = function ( ) {
[Link][[Link]](this);
};

[Link] = function ( ) {
[Link][[Link]](this);
[Link] = true;
[Link][[Link]](this);
};

22.2.2 Creating the Toolbar Button Component


The toolbar comprises eight tools/buttons:

Select

Allows existing shape and text items to be selected and moved.

Line

Allows the user to draw a line.

Rectangle

Allows the user to draw a rectangular outline.

[Link] 2004-02-06
Strona 6 z 180

Ellipse

Allows the user to draw an elliptical outline.

Text

Lets the user add text.

Fill

Adds a fill to any shape that is clicked or applies a new color to any text that is clicked. The
color from the color selector is used in both cases.

Back

Moves the selected shape or text back by one depth.

Forward

Moves the selected shape or text forward by one depth.

Each of the toolbar buttons shares common functionality with every other toolbar button, namely

z The buttons are drawn using the Drawing API. Each toolbar button is composed of the sam
button that is labeled with a symbol or text (a line on the line tool, "abc" on the text tool, e

z Each button has a selected and deselected state. When the button is selected, it appears to
pressed in. When the button is deselected, it appears to be raised.

Additionally, there are two kinds of toolbar buttons. The first six buttons are called "stick" button
because when they are pressed, they remain selected until otherwise deselected. The remaining
buttons (the back and forward buttons) are called "spring" buttons because they spring back afte
have been clicked and released.

Perform the following steps to construct the ToolbarButton component:

1. Create a new movie clip symbol named ToolbarButton.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to ToolbarButtonSymbol.

5. Click OK.

6. Edit the new symbol.

7. Rename the default layer as superclass and add a new layer named toolbarButtonClass.

8. On the superclass layer, drag an instance of the PaintBaseClass symbol. This ensures that
superclass is defined so that ToolbarButtonClass can extend it.

9. On the toolbarButtonClass layer, add the following code to the first frame:

[Link] 2004-02-06
Strona 7 z 180

#initclip

// Initialize the component instance to its unselected state.


function ToolbarButtonClass( ) {
[Link] = false;
}

// This class extends PaintBase. Because PaintBase extends MovieClip,


// ToolbarButtonClass is still a valid component class.
[Link] = new PaintBase( );

// The create( ) method draws the button. The method requires a name that
// indicates what kind of symbol to draw (line, rectangle, etc.). It also
// requires a type parameter specifying if the button is a "stick" or "sprin
// button type.
[Link] = function (name, type) {

// Set the type as a property. This is used later to determine how the but
// responds to presses and releases.
[Link] = type;

// Create a movie clip in which the button is drawn. Within this movie clip
// three nested movie clips: one for the highlight, one for the button's
// center, and one for the shadow.
[Link]("btn", [Link]( ));
[Link]("btnHighlight", [Link]( ));
[Link]("btnShadow", [Link]( ));
[Link]("btnCenter", [Link]( ));

// The width and height value of 42 and 21 are hardcoded for this applicat
// You could also choose to make this more abstract.
var w = 42;
var h = 21;

// Draw the button highlight, center, and shadow.


with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0xECECEC, 100);
drawRectangle(w + 2, h + 2);
endFill( );
}
with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0, 100);
drawRectangle(w + 2, h + 2);
endFill( );
_x += 1;
_y += 1;
}
with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0xDFDFDF, 100);
drawRectangle(w, h);
endFill( );
}

// Create the movie clip into which the button's symbol or label is added.
[Link]("symbol", [Link]( ));

// Add the appropriate symbol based on the name value. If "select", draw an
// arrow. If "line", draw a line. If "rectangle", draw a rectangle. If
// "ellipse", draw an ellipse. If "text", label the button "abc". If "fill
// "back", or "forward", label the button with "fill", "back", or "forward
switch (name) {

[Link] 2004-02-06
Strona 8 z 180

case "select":
with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0, 100);
drawRectangle(w/4, h/9);
drawTriangle(2 * h/3, 2 * h/3, 60, 30, -w/4);
endFill( );
_rotation += 30;
_x += w/8;
_y += h/8;
}
break;
case "line":
with ([Link]) {
lineStyle(1, 0x000000, 100);
moveTo(-(w/2) + 6, -(h/2) + 6);
lineTo((w/2) - 6, (h/2) - 6);
}
break;
case "rectangle":
with ([Link]) {
lineStyle(0, 0x000000, 100);
drawRectangle(w - 12, h - 6);
}
break;
case "ellipse":
with ([Link]) {
lineStyle(0, 0x000000, 100);
drawEllipse((w - 12)/2, (h - 6)/2);
}
break;
case "text":
[Link]("label", [Link]( ),
-w/2, -h/2, w, h);
[Link] = "abc";
var tf = new TextFormat( );
[Link] = "center";
[Link](tf);
break;
case "fill":
[Link]("label", [Link]( ),
-w/2, -h/2, w, h);
[Link] = "fill";
var tf = new TextFormat( );
[Link] = "center";
[Link](tf);
break;
case "back":
[Link]("label", [Link]( ),
-w/2, -h/2, w, h);
[Link] = "back";
var tf = new TextFormat( );
[Link] = "center";
[Link](tf);
break;
case "forward":
[Link]("label", [Link]( ),
-w/2, -h/2, w, h);
[Link] = "forward";
var tf = new TextFormat( );
[Link] = "center";
[Link](tf);
}

[Link] 2004-02-06
Strona 9 z 180

// The drawing methods draw the buttons with the registration point at the
// center of the shapes. To move the shapes so that the registration point
// the component is at the upper-left corner, adjust everything down and t
// right by half.
var shiftx = this._height / 2;
var shifty = this._width / 2;
[Link]._y += shiftx;
[Link]._x += shifty;
[Link]._y += shiftx;
[Link]._x += shifty;
};

// The select( ) method adjusts the elements of the button to give the effe
// the button being pushed in.
[Link] = function ( ) {
[Link]._width += 1;
[Link]._height += 1;
[Link]._width -= 1;
[Link]._height -= 1;
this._x += 1;
this._y += 1;
[Link]._visible = false;
[Link]._visible = false;
};

// The deselect( ) method reverses the effects of the select( ) method.


[Link] = function ( ) {
[Link]._width -= 1;
[Link]._height -= 1;
[Link]._width += 1;
[Link]._height += 1;
this._x -= 1;
this._y -= 1;
[Link]._visible = true;
[Link]._visible = true;
};

// The toggleDeslect( ) method deselects the button if it is selected.


[Link] = function ( ) {
if ([Link]) {
[Link]( );
[Link] = ![Link];
}
};

// When the button is pressed, if it is not already selected, call the selec
// method and the onSelect( ) callback function.
[Link] = function ( ) {
if (![Link]) {
[Link]( );
[Link][[Link]](this);
}
};

// When the button is released, call the deselect( ) method if the


// button is already selected or if it is a "spring" button. Whenever
// a "spring" button is released, it automatically springs back to the
// original position. "Stick" buttons are deselected only if they had
// previously been selected. Additionally, toggle the selected status of
// the button if it is a "stick" button.
[Link] = function ( ) {
if ([Link] || [Link] == "spring") {
[Link]( );
}

[Link] 2004-02-06
Strona 10 z 180

if ([Link] == "stick") {
[Link] = ![Link];
}
};

// You should register the component class to the name that you gave to the
// component symbol.
[Link]("ToolbarButtonSymbol", ToolbarButtonClass);

#endinitclip

The ToolbarButtonClass class is not an overly complex class, yet there are still some parts of it th
warrant further examination. Let's take a closer look at parts of the class.

The create( ) method is a long method, but it doesn't need to be intimidating. Most of the code i
method is comprised of drawing methods. First, you create a movie clip for the basic button port
the component. This movie clip contains three nested movie clips for the button's highlight, cent
shadow. Generally, it is a good idea to create different movie clips for any parts that you draw us
drawing API. Additionally, you should group together any related movie clips into a parent clip, a
here:

[Link]("btn", [Link]( ));


[Link]("btnHighlight", [Link]( ));
[Link]("btnShadow", [Link]( ));
[Link]("btnCenter", [Link]( ));

Next, the highlight, shadow, and center of the button are drawn using the drawRectangle( ) met
Recipe 4.4. The center of the button is a gray rectangle. This movie clip has a greater depth than
other two, and so it appears above them. The other two movie clips are also rectangles. Each is
slightly larger than the button's center, and the shadow is offset by one pixel in the x and y direc
The size, positioning, and depths of the three movie clips give the illusion of three dimensions to
button.

with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0xECECEC, 100);
drawRectangle(w + 2, h + 2);
endFill( );
}
with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0, 100);
drawRectangle(w + 2, h + 2);
endFill( );
_x += 1;
_y += 1;
}
with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0xDFDFDF, 100);
drawRectangle(w, h);
endFill( );
}

You create the symbol movie clip above the basic button clip and add the appropriate symbol or
it. You use a switch statement to determine which symbol or label to add, depending on the valu
name parameter that is passed to the create( ) method. If the value is "select", you should draw
(for the selection tool). You can draw an arrow by placing a rectangle and a triangle end to end.
this example, the entire symbol movie clip is rotated 30 degrees so that the arrow is angled.

case "select":

[Link] 2004-02-06
Strona 11 z 180

with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0, 100);
drawRectangle(w/4, h/9);
drawTriangle(2 * h/3, 2 * h/3, 60, 30, -w/4);
endFill( );
_rotation += 30;
_x += w/8;
_y += h/8;
}
break;

You can draw a line, rectangle, or ellipse using the basic drawing techniques covered in Chapter

When the value of name is "text", "fill", "back", or "forward", you should add a text field to the sy
movie clip instead of drawing anything in it. If you create the text field with the width and height
button, you can format the text so that it is aligned to the center of the button.

case "text":
[Link]("label", [Link]( ),
-w/2, -h/2, w, h);
[Link] = "abc";
var tf = new TextFormat( );
[Link] = "center";
[Link](tf);
break;

Finally, because the custom drawing methods such as drawRectangle( ) and drawEllipse( ) draw
with the registration point at the center, you should shift everything in the toolbar button down a
the right by half the height and half the width such that the component's registration point is in t
upper-left corner:

var shiftx = this._height / 2;


var shifty = this._width / 2;
[Link]._y += shiftx;
[Link]._x += shifty;
[Link]._y += shiftx;
[Link]._x += shifty;

The select( ) and deselect( ) methods do the reverse of one another. The select( ) method gives
appearance of the button being in a pressed state. You accomplish this by increasing the dimens
the basic button movie clip while hiding the highlight and shadow. Additionally, the width and he
the symbol are decreased (since we want to give the appearance of it being slightly further away
when deselect( ) is called, these actions are all reversed.

[Link] = function ( ) {
[Link]._width += 1;
[Link]._height += 1;
[Link]._width -= 1;
[Link]._height -= 1;
this._x += 1;
this._y += 1;
[Link]._visible = false;
[Link]._visible = false;
};

[Link] = function ( ) {
[Link]._width -= 1;
[Link]._height -= 1;
[Link]._width += 1;
[Link]._height += 1;

[Link] 2004-02-06
Strona 12 z 180

this._x -= 1;
this._y -= 1;
[Link]._visible = true;
[Link]._visible = true;
};

The onPress( ) method needs to check to make sure the button is not selected. If it is selected, t
nothing needs to be done. But if the button is not already selected, the select( ) method needs t
called (to make the button look like it is being pressed), and the onSelect( ) callback method sho
called as well:

[Link] = function ( ) {
if (![Link]) {
[Link]( );
[Link][[Link]](this);
}
};

When the button is released, the type of actions depends on the type of button—"stick" or "sprin
spring button should always appear deselected once it is released. Therefore, if the button is a s
button, call the deselect( ) method. On the other hand, if the button is a stick button, there are m
decisions that need to be made. If the button had been previously selected, then the deselect( )
should be called. Also, spring buttons cannot maintain a selected/deselected state—they are sele
only when pressed—but stick buttons maintain a selected/deselected state. Therefore, each time
release event occurs on a stick button, the selected state should be toggled:

[Link] = function ( ) {
if ([Link] || [Link] == "spring") {
[Link]( );
}
if ([Link] == "stick") {
[Link] = ![Link];
}
};

22.2.3 Creating the Shape Component


The Flash Paint application uses shapes as one of the two kinds of units that can be drawn by the
(the other being text units). Here is a list of some of the basic characteristics of a shape:

z A shape can be a line, a rectangle, or an ellipse.

z Shapes can be drawn given a width and a height.

z Once a shape exists, it can be moved.

z Shapes can be filled (applies to rectangles and ellipses only).

Complete the following steps to create the shape component:

1. Create a new movie clip symbol named Shape.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to ShapeSymbol.

[Link] 2004-02-06
Strona 13 z 180

5. Click OK.

6. Edit the new symbol.

7. Rename the default layer as superclass and add a new layer named shapeClass.

8. On the superclass layer, drag an instance of the PaintBaseClass symbol. This ensures that
superclass is defined so that ShapeClass can extend it.

9. On the shapeClass layer, add the following code to the first frame:

#initclip

// When the component is first instantiated, add fill_mc and outline_mc movi
// clips to it. Also, create Color objects to target each movie clip.
function ShapeClass( ) {
[Link]("fill_mc", [Link]( ));
[Link]("outline_mc", [Link]( ));
this.outline_mc.col = new Color(this.outline_mc);
this.fill_mc.col = new Color(this.fill_mc);
}

// ShapeClass extends PaintBase.


[Link] = new PaintBase( );

// The create( ) method creates the shape. The method requires a width and
// height, plus an RGB value for the outline and the name of the shape to dr
[Link] = function (w, h, outlineRGB, shape) {
[Link] = shape;
this.w = w;
this.h = h;
this.outline_mc.rgb = outlineRGB;

// Draw the outline without a fill.


[Link](this.outline_mc, false);

// If the shape is not a line, then also add a fill. If no color has been
// assigned to the fill, it is made transparent.
if ([Link] != "line") {
[Link](this.fill_mc, true);
}
};

// The draw( ) method draws the shape within the specified movie clip (eith
// outline_mc or fill_mc).
[Link] = function (mc, doFill) {

// Make sure to clear anything that might already be drawn in the movie cl
[Link]( );

// If doFill is true, set the outline to be transparent. If the fill color


// been assigned, then use that color; otherwise, make the fill transparen
// well. If doFill is not true, then set the outline to the color that has
// assigned to the outline.
if (doFill) {
[Link](0, 0x000000, 0);
if (this.fill_mc.rgb == undefined) {
[Link](0, 0);
} else {
[Link](this.fill_mc.rgb, 100);
}
} else {
[Link](0, this.outline_mc.rgb, 100);

[Link] 2004-02-06
Strona 14 z 180

// If the shape is a line, just draw a line and break out of the method.
if ([Link] == "line") {
[Link](this.w, this.h);
return;
}

// The drawEllipse( ) and drawRectangle( ) methods don't handle negative


// numbers. So if the width and height are negative, use the absolute valu
drawW = [Link](this.w);
drawH = [Link](this.h);

// Draw the appropriate shape. These require the [Link] methods


// defined in Chapter 4.
switch ([Link]) {
case "ellipse":
[Link](drawW/2, drawH/2);
break;
case "rectangle":
[Link](drawW, drawH);
}

// If the method was drawing a fill, make sure to end the fill.
if (doFill) {
[Link]( );
}

// Offset the shape by half the width and height since the custom drawing
// methods place the registration point at the center. Also, if the width
// and/or height were negative, this corrects the offset as necessary.
mc._x = this.w/2;
mc._y = this.h/2;
};

// The toggleShowOutline( ) method reverses the visibility of the outline.


[Link] = function ( ) {
this.outline_mc._visible = !this.outline_mc._visible;
};

// The doFill( ) method assigns a value to the RGB color for the
// fill movie clips. Then, when the draw( ) method is called, the
// fill is drawn with that color.
[Link] = function (rgb) {
this.fill_mc.rgb = rgb;
[Link](this.fill_mc, true);
};

// When the component is deselected, set the outline color back to the origin
// (the color is highlighted blue when it is selected.) Also, set selected t
// false so the component knows about its current state, and call the desele
// method of the superclass, PaintBase.
[Link] = function ( ) {
this.outline_mc.[Link](this.outline_mc.rgb);
[Link] = false;
[Link]( );
};

// When the user mouses over the component, set the outline color to blue.
[Link] = function ( ) {
this.outline_mc.[Link](0x0000FF);
};

// When the user mouses out of the component, reset the outline color to the

[Link] 2004-02-06
Strona 15 z 180

// original value if the component instance is not selected.


[Link] = function ( ) {
if (![Link]) {
this.outline_mc.[Link](this.outline_mc.rgb);
}
};

// When the component is pressed, call the onPress( ) method of the supercl
// and highlight the outline blue if it is not already.
[Link] = function ( ) {
[Link]( );
if (![Link]) {
this.outline_mc.[Link](0x0000FF);
}
};

// Register the class to the linkage identifier for the symbol.


[Link]("ShapeSymbol", ShapeClass);

#endinitclip

Now that you've had a chance to see the ShapeClass component class, let's delve into it a little m
closely.

The create( ) method directs the drawing of a new shape. To create the shape, the method requ
width and height of the shape, the color to use for the outline, and the type of shape to draw. Th
possible values for the shape parameter are "line", "rectangle", and "ellipse". The method then p
to call the draw( ) method to draw the outline of the shape. If the shape is not a line, the draw(
is also called to draw a fill. At this point, no color has been defined for the fill, so the fill is transp
This might seem to be a bit pointless, but a shape filled with a transparent fill is much easier to s
than a shape that is only an outline.

[Link] = function (w, h, outlineRGB, shape) {


[Link] = shape;
this.w = w;
this.h = h;
this.outline_mc.rgb = outlineRGB;
[Link](this.outline_mc, false);
if ([Link] != "line") {
[Link](this.fill_mc, true);
}
};

The draw( ) method contains the core drawing functionality for the shape component. Before the
method is invoked, the component should already have at least three properties defined by the c
create( ): shape ("line", "rectangle", or "ellipse"), w (the width), and h (the height). Therefore, th
( ) method does not require any of these values to be passed to it as parameters. However, the
method does need to know into which movie clip it should draw (outline_mc or fill_mc) and w
apply a fill:

[Link] = function (mc, doFill) {


. . .
};

The draw( ) method can be invoked multiple times for each shape, so it is important that any ex
contents in the movie clip are cleared:

[Link]( );

Next, the method needs to determine what line styles and what kind of fill (if any) to apply. If th

[Link] 2004-02-06
Strona 16 z 180

parameter is true, then the outline should be transparent. If the fill color has been defined, it sh
used for the fill. Otherwise, the fill should be transparent. On the other hand, if doFill is not tru
outline should be the color that has been set for the rgb property of the outline movie clip.

if (doFill) {
[Link](0, 0x000000, 0);
if (this.fill_mc.rgb == undefined) {
[Link](0, 0);
} else {
[Link](this.fill_mc.rgb, 100);
}
} else {
[Link](0, this.outline_mc.rgb, 100);
}

The next part of the draw( ) method uses a little programming trick with a return statement. If t
is a line, then all you need to do is call lineTo( ) once. Therefore, you can use a return statement
immediately following that to end the processing of the method.

if ([Link] == "line") {
[Link](this.w, this.h);
return;
}

Finally, if the shape is an ellipse or rectangle, the appropriate drawing method is called. These m
don't accept negative values, so it is important that you convert the width and height of the shap
their absolute values.

drawW = [Link](this.w);
drawH = [Link](this.h);
switch ([Link]) {
case "ellipse":
[Link](drawW/2, drawH/2);
break;
case "rectangle":
[Link](drawW, drawH);
}

The doFill( ) method accomplishes a fill by defining the rgb property of the fill_mc movie clip a
calling the draw( ) method. Once the rgb property is defined, the draw( ) method draws the sha
that fill color.

[Link] = function (rgb) {


this.fill_mc.rgb = rgb;
[Link](this.fill_mc, true);
};

22.2.4 Creating the Text Component


Text units are the other kind of unit (in addition to shapes) that users can create using Flash Pai
units behave similarly to shape units in many ways. Here is a list of the functionality of the text
component:

z Text units can be drawn given a width and a height.

z Existing text units can be moved.

z You can apply a new color to existing text.

[Link] 2004-02-06
Strona 17 z 180

Complete the following steps to create the text component:

1. Create a new movie clip symbol named Text.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to TextSymbol.

5. Click OK.

6. Edit the new symbol.

7. Rename the default layer as superclass and add a new layer named textClass.

8. On the superclass layer, drag an instance of the PaintBaseClass symbol. This ensures that
superclass is defined so that TextClass can extend it.

9. On the textClass layer, add the following code to the first frame:

#initclip

function TextClass ( ) {}

// TextClass extends PaintBase.


[Link] = new PaintBase( );

// The create( ) method creates the text field within the component instanc
// method takes three parameters: the width, height, and color of the text.
[Link] = function (w, h, rgb) {

// Create the text field. The text field should be a multiline, input text
// field with a white background and a blue border.
[Link]("input_txt", 1, 0, 0, w, h);
this.input_txt.type = "input";
this.input_txt.multiline = true;
this.input_txt.textColor = rgb;
this.input_txt.border = true;
this.input_txt.background = true;
this.input_txt.borderColor = 0x0000FF;

// When the user adds new text for the first time, set the text to autosiz
this.input_txt.onChanged = function ( ) {
[Link] = true;
delete [Link];
}

// Call the deselect( ) method of the component whenever focus is lost (m


// the user clicked outside of the text field).
this.input_txt.onKillFocus = function ( ) {
if ([Link] != "") {
this._parent.deselect( );
}
}

// Set the focus to the text field.


[Link](this.input_txt);
[Link] = true;

// Invoke the checkEditing( ) method at an interval to constantly see if

[Link] 2004-02-06
Strona 18 z 180

// text is being edited.


[Link] = setInterval(this, "checkEditing", 100);
};

// The deselect( ) method first calls the superclass's deselect( ) method.


// sets other properties of the component so it can keep track of its state.
// Additionally, the border and background of the text field are turned off.
[Link] = function ( ) {
[Link]( );
[Link] = false;
this.input_txt.background = false;
this.input_txt.border = false;
[Link] = false;
};

// The doFill( ) method sets the text color.


[Link] = function (rgb) {
this.input_txt.textColor = rgb;
};

// When the component instance is moused over,


// turn on the border only (not the background).
[Link] = function ( ) {
if (![Link]) {
this.input_txt.border = true;
}
};

// When the user mouses out of the component instance, if it is not otherwis
// selected, turn off the border.
[Link] = function ( ) {
if (![Link]) {
this.input_txt.border = false;
}
};

// When the component instance is pressed, check whether the component has b
// double-clicked. If so, turn on editing and set the focus to the text fiel
// Otherwise, call the onPress( ) method of the superclass.
[Link] = function ( ) {
this.input_txt.border = true;
[Link] = getTimer( );
if ([Link] - [Link] < 500) {
[Link] = true;
this.input_txt.background = true;
[Link]( );
[Link](this.input_txt);
[Link](this.input_txt.length, this.input_txt.length);
} else {
[Link]( );
}
[Link] = [Link];
};

// The checkEditing( ) method continually checks to see if the text field i


// being edited. If so, and if the focus of the text field has been lost, th
// reset the focus to the text field.
[Link] = function ( ) {
if ([Link] && !([Link]( ) != String(this.input_txt))) {
[Link](this.input_txt);
[Link](this.input_txt.[Link],
this.input_txt.[Link]);
}
};

[Link] 2004-02-06
Strona 19 z 180

// Call the superclass's onRelease( ) method if the user is not editing the
[Link] = function ( ) {
if (![Link]) {
[Link]( );
}
};

// Register the class to the symbol's linkage identifier.


[Link]("TextSymbol", TextClass);

#endinitclip

The TextClass component class is not overly complex, but it is also not without its own intricacie
are worth exploring in a little more depth.

First of all, notice that both the ShapeClass and the TextClass classes have create( ) methods.
Furthermore, the create( ) method of each class accepts similar parameters in a similar order (a
[Link]( ) accepts one additional parameter). This similarity is intentional and allows
and shape components to be treated more or less identically by the Flash Paint application.

The [Link]( ) method includes several points of interest. First of all, notice that the tex
always created with a depth of 1. The create( ) method can be called multiple times for the same
unit, and creating the text field with the same depth each time ensures that any previous text fie
overwritten:

[Link]("input_txt", 1, 0, 0, w, h);

Additionally, the create( ) method employs an interesting technique using the onChanged( ) eve
handler method for the text field. When the text field is first created, it is designed to have a spe
width and height. The width and height are maintained until the user first enters text into the tex
At that point the onChanged( ) method is automatically invoked, and the text field is set to autos
onChanged( ) method has done its job at that point, so it can delete itself:

this.input_txt.onChanged = function ( ) {
[Link] = true;
delete [Link];
};

The onKillFocus( ) event handler method of the text field is also used. This method calls the dese
method of the component instance. The purpose of this is to deselect the unit when the user clic
outside of the text field.

this.input_txt.onKillFocus = function ( ) {
if ([Link] != "") {
this._parent.deselect( );
}
};

When a text unit is deselected, set the self-describing editing and selected properties to false
Additionally, the border and background of the text field should be hidden. The [Link]
method calls the deselect( ) method of the superclass, which ultimately invokes the onDeselect(
callback function to perform necessary housekeeping, such as recording which tool is selected:

[Link] = function ( ) {
[Link]( );
[Link] = false;
this.input_txt.background = false;
this.input_txt.border = false;
[Link] = false;

[Link] 2004-02-06
Strona 20 z 180

};

The [Link]( ) method shares its name with the [Link]( ) method for the sam
reasons that the create( ) methods are named the same. The doFill( ) method of the TextClass c
assigns the new color to the text field:

[Link] = function (rgb) {


this.input_txt.textColor = rgb;
};

The onPress( ) and checkEditing( ) methods are designed to overcome a specific problem that ca
when you place a text field within a movie clip. Normally, when you use the mouse to click on an
text field, Flash brings focus to that text field, and you can type in it. However, if the text field is
within a movie clip, and the movie clip handles button events (press, release, etc.), the movie cl
event handling takes precedence over the event handling of the nested text field. Therefore, the
a convenient way to select a text field when it is nested within a movie clip that handles button e
(such as with the TextClass component class). To work around this issue, the onPress( ) method
for double-clicks versus single-clicks. When the component is clicked, the value returned by getT
is recorded. Then that time is compared with the value of the previous click. If the difference is l
half a second, it constitutes a double-click. Otherwise, it is a single-click. When a double-click oc
[Link]( ) and [Link]( ) methods are used to bring focus to the text fi
to move the cursor to the end of the existing text. When a single-click occurs, the onPress( ) me
the superclass is invoked to allow the default handling for single-clicks, namely dragging the item
paint canvas.

[Link] = function ( ) {
this.input_txt.border = true;
[Link] = getTimer( );
if ([Link] - [Link] < 500) {
[Link] = true;
this.input_txt.background = true;
[Link]( );
[Link](this.input_txt);
[Link](this.input_txt.length, this.input_txt.length);
} else {
[Link]( );
}
[Link] = [Link];
};

You might think that the code in the onPress( ) method should be enough to successfully keep th
on the text field when it is double-clicked. However, the button events keep causing the text field
focus. As a workaround for this, the checkEditing( ) method continually brings focus back to the
if editing is on. You'll need to refer back to the create( ) method to see where we set the interva
which this method is called. The only drawback to this technique is that it does not allow you to d
kind of editing to the text field other than append text or delete text from the end.

[Link] = function ( ) {
if ([Link] && !([Link]( ) != String(this.input_txt))) {
[Link](this.input_txt);
[Link](this.input_txt.[Link], this.input_txt.[Link]
}
};

22.2.5 Creating the Color Selector Component


You should use the color selector component that you created in Recipe 12.13. If you followed th
complete instructions for that program, then the color selector component should be available fro
Components panel. If not, download the code from [Link] and install it
according to the instructions in Chapter 12.

[Link] 2004-02-06
Strona 21 z 180

When you have created or installed the color selector, create a copy of the component symbol in
[Link] Library. You can do this by dragging an instance of the component from the Compo
panel onto the Stage. Then delete the instance from the Stage. The symbol remains in the Librar
[ Team LiB ]
[ Team LiB ]

22.3 Assembling the Flash Paint Application


You have now created all the necessary components for the Flash Paint application. The only rem
step is to create the main routine that puts them all together. To accomplish this, add the follow
code to the first frame of the main timeline of the [Link] document:

// Include [Link] from Chapter 7, [Link] from Chapter 4, and


// [Link] from Chapter 11 for their custom methods.
#include "[Link]"
#include "[Link]"
#include "[Link]"

// Initialize the movie.


function init( ) {
selectedShape = null;
selectedTool = null;
shapes = new Array( );
makeTools( );
}

// The makeTools( ) method creates the toolbar buttons.


function makeTools ( ) {

// Define an array of the first six tool names. These names match the accepted
// values within the toolbar button component.
toolbarBtnNames = ["select", "line", "rectangle", "ellipse", "text", "fill"];

// Create an array to hold references to the "stick" toolbar button instances.


toolbarBtns = new Array( );

// Create a toolbar movie clip to contain all the tools (including


// the color selector) and a nested btns movie clip to specifically
// contain the toolbar buttons.
_root.createEmptyMovieClip("toolbar", _root.getNewDepth( ));
[Link]("btns", [Link]( ));
var name, btn;

// Create a table for holding the buttons. Set the row spacing to three pixels.
t = new Table(3);

// Loop through all the names of the buttons.


for (var i = 0; i < [Link]; i++) {
name = toolbarBtnNames[i];

// Create the toolbar button instance.


btn = [Link]("ToolbarButtonSymbol",
name + "Btn",
[Link]( ),
{val: name});
// Call the create( ) method for each of the "stick" buttons. Set the onSele
// callback function for each.
[Link](name, "stick");
[Link]("onSelectTool", _root);

// Add the button to the array and to a table column and row.
[Link](btn);

[Link] 2004-02-06
Strona 22 z 180

[Link](new TableRow(0, new TableColumn(0, btn)));


}

// Create the back and forward buttons, which are "spring" buttons.
[Link]("ToolbarButtonSymbol", "backBtn",
[Link]( ), {val: name});
[Link]("ToolbarButtonSymbol", "forwardBtn",
[Link]( ), {val: name});
[Link]("back", "spring");
[Link]("moveBack", _root);
[Link]("forward", "spring");
[Link]("bringForward", _root);

// Add the two buttons to the table.


[Link](new TableRow(0, new TableColumn(0, [Link])));
[Link](new TableRow(0, new TableColumn(0, [Link])));

// Add the color selector to the toolbar and to the table. Then render the tabl
[Link]("ColorSelectorSymbol", "colSelect", [Link]( )
[Link](new TableRow(0, new TableColumn(0, [Link])));
[Link](true);
}

// This function is called when one of the first six tools is selected.
// The function loops through the toolbarBtns array elements and deselects
// any other buttons in case they are selected. It also sets the selectedTool
// variable to the current tool.
function onSelectTool (cmpt) {
for (var i = 0; i < [Link]; i++) {
if (toolbarBtns[i] != cmpt) {
toolbarBtns[i].toggleDeselect( );
}
}
_root.selectedTool = [Link];
}

// bringForward( ) moves the selected shape or text forward by one depth.


function bringForward ( ) {

// Loop through the existing shapes to find the index of the selected shape.
for (var i = 0; i < [Link]; i++) {
if (shapes[i] == selectedShape) {
break;
}
}

// If the shape is already at the front, then exit the function.


if (i == [Link] - 1) {
return;
}

// Swap the depths and positions of the shape (or text unit) with the shape (or
// text unit) that has a index within the array that is one greater than the
// selected shape (or text unit).
[Link](shapes[i + 1]);
shapes[i] = shapes[i + 1];
shapes[i + 1] = selectedShape;
}

// The moveBack( ) function moves the selected shape or text back by one depth.
// function uses the same logic as the bringForward( ) function.
function moveBack ( ) {
for (var i = 0; i < [Link]; i++) {
if (shapes[i] == selectedShape) {

[Link] 2004-02-06
Strona 23 z 180

break;
}
}
if (i == 0) {
return;
}
[Link](shapes[i - 1]);
shapes[i] = shapes[i - 1];
shapes[i - 1] = selectedShape;
}

// If the mouse is currently over any shapes or text units, the isMouseOverShapes
// method returns true. Otherwise, it returns false.
function isMouseOverShapes ( ) {
var isOver = false;

// Loop through all the existing shapes (or text units) and use hitTest( ) to
// determine if the mouse is currently over any of them.
for (var i = 0; i < [Link]; i++) {
if (shapes[i].hitTest(_root._xmouse, _root._ymouse, true)) {
isOver = true;
break;
}
}
return isOver;
}

// This function deselects all the existing shapes except for the one specified b
// the keepSelectedShape parameter (if any).
function deselectShapes (keepSelectedShape) {
for (var i = 0; i < [Link]; i++) {
if (shapes[i] != keepSelectedShape) {
shapes[i].deselect( );
}
}
selectedShape = keepSelectedShape;
}

// The removeSelected( ) function removes the selected shape (or text unit) from
// movie and from the shapes array.
function removeSelected ( ) {
for (var i = 0; i < [Link]; i++) {
if (shapes[i] == selectedShape) {
[Link](i, 1);
break;
}
}
[Link]( );
}

// When the shape or text unit is pressed, this callback function is invoked
// automatically. Check to see which tool is selected. If it is the select tool, m
// the unit draggable. If it is the fill tool, call the doFill( ) method of the
// with the selected color.
function onPressShape (cmpt) {
if (selectedTool == "select") {
[Link]( );
}
if (selectedTool == "fill") {
[Link]([Link]( ));
}
}

// When the shape or text unit is released, this callback function is invoked

[Link] 2004-02-06
Strona 24 z 180

// automatically. Make sure the unit is no longer being dragged.


function onReleaseShape (cmpt) {
[Link]( );
}

// When a shape or text unit is selected, this callback function is invoked


// automatically. Deselect any other shapes that are already selected and set the
// selected shape to current shape or text unit.
function onSelectShape (cmpt) {
if (selectedShape != undefined) {
deselectShapes(cmpt);
}
selectedShape = cmpt;
}

// When a shape is deselected, set the selected shape to null.


function onDeselectShape (cmpt) {
selectedShape = null;
}

// The checkIfMouseDown( ) function continually checks to see if the mouseDown


// variable is true. If so, and if the selected tool is a rectangle, ellipse, lin
// or text, the doShapeDraw( ) function is called.
function checkIfMouseDown ( ) {
if (mouseDown) {
switch(selectedTool) {
case "rectangle":
case "ellipse":
case "line":
case "text":
doShapeDraw( );
}
}
}

mouseCheckIntervalID = setInterval(checkIfMouseDown, 100);

// The doShapeDraw( ) function calculates the width and height of the shape to d
// based on the difference between the starting coordinates and the current
// coordinates of the mouse. It then calls the create( ) method of the current sh
function doShapeDraw ( ) {
var w = _root._xmouse - startX;
var h = _root._ymouse - startY;
[Link](w, h, [Link]( ), selectedTool);
}

// The mouseListener object is a listener object applied to the mouse to listen f


// mouseDown and mouseUp events.
mouseListener = new Object( );

[Link] = function ( ) {

// Perform the following actions only if the mouse is not over the toolbar butt
// This prevents the user from accidentally drawing shapes when trying to selec
// buttons on the toolbar.
if (!_root.[Link](_root._xmouse, _root._ymouse)) {

// Set the mouseDown variable to true. This alerts the code in the
// checkIfMouseDown( ) function to be processed.
_root.mouseDown = true;

// Set the starting coordinates of the shape/text unit


// to the current mouse position.
_root.startX = _root._xmouse;

[Link] 2004-02-06
Strona 25 z 180

_root.startY = _root._ymouse;

// Get a new unique depth and name of the shape or text unit.
var uniqueVal = _root.getNewDepth( );

// Perform the correct actions depending on the selected tool.


switch (_root.selectedTool) {
case "select":
// If the mouse is clicked, but not over an existing shape/text unit,
// deselect any selected units and set selectedShape to null.
if (!_root.isMouseOverShapes( )) {
_root.deSelectShapes( );
[Link] = null;
}
break;
case "line":
case "rectangle":
case "ellipse":
// If the line, rectangle, or ellipse tools are selected, create a new sh
// component instance and set the callback functions for each.
_root.newShape = _root.attachMovie("ShapeSymbol", "shape" + uniqueVal,
uniqueVal, {_x: _root.startX, _y: _root.startY});
_root.[Link]("onPressShape", _root);
_root.[Link]("onReleaseShape", _root);
_root.[Link]("onSelectShape", _root);
_root.[Link]("onDeselectShape", _root);
_root.[Link](_root.newShape);
break;
case "text":
// If the text tool is selected, create a new text component and set the
// callback functions for each.
_root.newShape = _root.attachMovie("TextSymbol", "shape" + uniqueVal,
uniqueVal, {_x: _root.startX, _y: _root.startY});
_root.[Link]("onPressShape", _root);
_root.[Link]("onReleaseShape", _root);
_root.[Link]("onSelectShape", _root);
_root.[Link]("onDeselectShape", _root);
_root.[Link](_root.newShape);
}
}
};

// When the mouse is released, set mouseDown to false so the checkIfMouseDown( )


// stops executing.
[Link] = function ( ) {
_root.mouseDown = false;
};

[Link](mouseListener);

// The key listener checks to see if the delete key is pressed. If so, call the
// removeSelected( ) function. Note: The delete key has a special meaning when y
// the "Test Movie" feature in Flash. If you want to test your movie in this way,
// make sure you select the Disable Keyboard Shortcuts option from the Control men
// in the Test Player.
keyListener = new Object( );
[Link] = function ( ) {
if ([Link]( ) == [Link]) {
_root.removeSelected( );
}
};

[Link](keyListener);

[Link] 2004-02-06
Strona 26 z 180

// Call the init( ) function to get everything started.


init( );

The main routine of the Flash Paint application is a long one, but don't let that intimidate you. It
is not that scary. Let's take a closer look at some of the code.

The init( ) function is pretty straightforward. The selectedShape variable is used throughout the
application to keep track of the shape (or text) unit that is currently selected. Because no shape
selected (or yet created) at the beginning of the application, initialize this variable to null. Likewi
selectedTool variable is used to keep track of the tool that is selected. The shapes array is use
keep track of which shapes have been created, and the relative depths of each of them.

function init( ) {
selectedShape = null;
selectedTool = null;
shapes = new Array( );
makeTools( );
}

The makeTools( ) function creates all the toolbar elements. There are two types of buttons: "stic
buttons and "spring" buttons. The stick buttons need to be associated with one another because
one can be selected at a time. Therefore, create an array into which the buttons are stored:

toolbarBtns = new Array( );

To create the stick buttons, for each element of the toolbarBtnNames array the function creates
toolbar button component instance and attaches it to the [Link] movie clip. Using the
toolbarBtnNames array is not essential, but it is a convenient way to create the component insta
By using the array of names, you can create all the component instances within a single for state
instead of having to type the attachMovie( ), create( ), setOnSelect( ), push( ), and addRow( ) m
separately for each toolbar button:

for (var i = 0; i < [Link]; i++) {


name = toolbarBtnNames[i];
btn = [Link]("ToolbarButtonSymbol",
name + "Btn",
[Link]( ),
{val: name});
[Link](name, "stick");
[Link]("onSelectTool", _root);
[Link](btn);
[Link](new TableRow(0, new TableColumn(0, btn)));
}

Next, we want to create the Back and Forward buttons. Each of these buttons are spring buttons
they invoke different callback functions from the stick buttons, so we create these instances sepa
from the stick buttons:

[Link]("ToolbarButtonSymbol", "backBtn",
[Link]( ), {val: name});
[Link]("ToolbarButtonSymbol", "forwardBtn",
[Link]( ), {val: name});
[Link]("back", "spring");
[Link]("moveBack", _root);
[Link]("forward", "spring");
[Link]("bringForward", _root);

The bringForward( ) and moveBack( ) functions are almost identical. The difference is in which s
are swapped. All the shapes and text units are stored in the shapes array in the order of their de
The higher the index of an element in the shapes array, the higher it appears in the stacking ord

[Link] 2004-02-06
Strona 27 z 180

Therefore, to bring a shape forward, you should swap its depth with the element in the shapes a
that has an index that is one higher. Then, to keep the order of the elements in the shapes array
corresponding to the order of depths, also switch the positions of the two elements in the array.
process for the moveBack( ) function works the same, except the shape swaps depths with the e
that has an index of one less.

function bringForward( ) {
for (var i = 0; i < [Link]; i++) {
if (shapes[i] == selectedShape) {
break;
}
}
if (i == [Link] - 1) {
return;
}
[Link](shapes[i + 1]);
shapes[i] = shapes[i + 1];
shapes[i + 1] = selectedShape;
}

function moveBack( ) {
for (var i = 0; i < [Link]; i++) {
if (shapes[i] == selectedShape) {
break;
}
}
if (i == 0) {
return;
}
[Link](shapes[i - 1]);
shapes[i] = shapes[i - 1];
shapes[i - 1] = selectedShape;
}

The removeSelected( ) function removes the selected shape or text unit from the movie, with th
removeMovieClip( ) method, and it updates the shapes array so that the array contains existing
only. Therefore, the function loops through all of the elements of the shapes array until it finds t
selected shape. It then calls the splice( ) method to remove that item from the array.

function removeSelected( ) {
for (var i = 0; i < [Link]; i++) {
if (shapes[i] == selectedShape) {
[Link](i, 1);
break;
}
}
[Link]( );
}

The checkIfMouseDown( ) interval function continually checks to see if the mouseDown variable is
(The variable is set to true in the onMouseDown( ) method applied to the mouse listener.) Then
selected tool is a rectangle, ellipse, line, or text, the application draws a shape or text unit using
doShapeDraw( ) function. As long as the mouse is down, the mouseDown variable is set to true.
Therefore, the doShapeDraw( ) method is called continuously to draw, and redraw, a shape.

function checkIfMouseDown ( ) {
if (mouseDown) {
switch(selectedTool) {
case "rectangle":
case "ellipse":
case "line":

[Link] 2004-02-06
Strona 28 z 180

case "text":
doShapeDraw( );
}
}
}

mouseCheckIntervalID = setInterval(checkIfMouseDown, 100);

The doShapeDraw( ) method calls the create( ) method of the newly created shape (or text) unit
width and height are calculated based on the x and y coordinates of the mouse when it was first
and the current x and y coordinates of the mouse. Therefore, when the user presses the mouse
holds it, and drags the mouse, the width and height change. The create( ) method is also supplie
the color value that is selected from the color selector and the name of the current tool. The nam
the tool is either "line", "rectangle", "ellipse", or "text". The ShapeClass component class's create
method uses this value to determine which shape to draw. The TextClass component class's crea
method does not need this parameter, so it is extraneous when the new shape happens to be a t
unit.

function doShapeDraw ( ) {
var w = _root._xmouse - startX;
var h = _root._ymouse - startY;
[Link](w, h, [Link]( ), selectedTool);
}

The onMouseDown( ) method of the mouse listener does several very important things. First, the
method sets the mouseDown variable to true, which activates the checkIfMouseDown( ) code tha
calling doShapeDraw( ):

_root.mouseDown = true;

The onMouseDown( ) method also sets the startX and startY variables to the position of the m
the time the mouse button is pressed:

_root.startX = _root._xmouse;
_root.startY = _root._ymouse;

Next, the method determines the appropriate course of action depending on the selected tool. If
selected tool is a line, rectangle, ellipse, or text, the method creates a new component instance
positioned at the mouse pointer and adds that component to the shapes array. The new compon
instance is assigned to the newShape variable so that regardless of the instance name, the
doShapeDraw( ) method always calls the create( ) method from the newly created component in

switch (_root.selectedTool) {
case "select":
if (!_root.isMouseOverShapes( )) {
_root.deSelectShapes( );
[Link] = null;
}
break;
case "line":
case "rectangle":
case "ellipse":
_root.newShape = _root.attachMovie("ShapeSymbol", "shape" + uniqueVal,
uniqueVal, {_x: _root.startX, _y: _root.startY});
_root.[Link]("onPressShape", _root);
_root.[Link]("onReleaseShape", _root);
_root.[Link]("onSelectShape", _root);
_root.[Link]("onDeselectShape", _root);
_root.[Link](_root.newShape);
break;
case "text":

[Link] 2004-02-06
Strona 29 z 180

_root.newShape = _root.attachMovie("TextSymbol", "shape" + uniqueVal,


uniqueVal, {_x: _root.startX, _y: _root.startY});
_root.[Link]("onPressShape", _root);
_root.[Link]("onReleaseShape", _root);
_root.[Link]("onSelectShape", _root);
_root.[Link]("onDeselectShape", _root);
_root.[Link](_root.newShape);

}
[ Team LiB ]
[ Team LiB ]

22.4 Using Flash Paint


Once you have completed the Flash Paint application you will undoubtedly want to impress your
friends and enemies with your mighty design prowess. However, while Flash Paint is not without
its charm, it is possible that you may want to do more with it. While I won't go into any more
detail in this chapter, here are some suggestions for how to extend this application:

z Add more types of shapes that can be drawn (stars, triangles, etc.).

z Use a SharedObject to store the shapes, including their colors, positions, sizes, and any
text content. Then retrieve the SharedObject data when the application is re-opened and
lay out the contents as they existed when the application was last run.

z Use FlashCom to enable multiple users to interact with Flash Paint so that all connected
users can see the same thing. Flash Paint then resembles a whiteboard application.

[ Team LiB ]
[ Team LiB ]

Chapter 23. Creating a Simple Animation


in Stages
Although some ActionScript projects may appear a bit daunting at first glance, each one is quite
manageable when you can break it down into parts. While it is true that applications must
function as a whole, it is also true that a well-designed application can be developed in stages
and in discrete units. The creative spark that sets a project in motion is typically in the form of
a gestalt—a unified vision or idea for the entire application. However, the execution of the idea
generally involves analysis—breaking apart the whole into smaller, more manageable parts.

In this chapter, you will create a simple Flash movie using ActionScript alone to create all the
elements (in other words, no author-time movie clips or buttons are placed on the Stage). To
get a feel for how to analyze a task, you will create this movie in five stages:

1. Create a single circle that moves around and bounces off the sides of a rectangle.

2. Use the same algorithms from the first stage, but create a component that bounces
around within a rectangle.

3. Create a movie clip on the main timeline and fill that movie clip with multiple instances of
the circle component. Also, modify the circle component so that the circles bounce not
only off the sides of the rectangle but also off each other.

[Link] 2004-02-06
Strona 30 z 180

4. Create a button component and add instances of the button component to the movie so
that the user can control the position, scale, and rotation of the movie clip containing the
circle component instances. Use a table to position the buttons.

5. Create an XML document and load that document into the Flash movie. Use the data from
the XML document to determine the movie's parameters, such as the number of circles;
the size, color, and velocity of the circles; and the size of the rectangle within which the
circles bounce.

Admittedly, it is unlikely that you will add this Flash movie to your own web site to show off
your ActionScript prowess. However, in this chapter, you get the opportunity to become familiar
with some of the concepts and techniques that are used throughout some of the recipes in the
rest of this book, including:

z Creating custom components

z Using the getNewDepth( ) method to ensure that graphical object instances have unique
depths

z Working with the custom drawing methods

z Using runtime-generated text

z Loading XML data

The completed files can be downloaded from [Link]

[ Team LiB ]
[ Team LiB ]

23.1 Stage One


In the first stage of the application, begin with the most fundamental and central task of the
application. In an application that involves bouncing circles, the most natural place to begin is to
create a single circle that bounces within the boundaries of a rectangle. Here are the steps to com
this stage of the application:

1. Create a new Flash document named [Link] and open it.

2. Add the following code to the first frame of the default layer of the main timeline:

// Include [Link] from Chapter 4 for its custom drawing methods.


#include "[Link]"

// Include [Link] from Chapter 7 for its getNewDepth( ) method.


#include "[Link]"

// Create the circle movie clip and draw a circle of radius 10 within it.
_root.createEmptyMovieClip("circle", _root.getNewDepth( ));
with (circle) {
lineStyle(0, 0x000000, 0);
beginFill(0, 100);
drawCircle(10);
endFill( );
}

[Link] 2004-02-06
Strona 31 z 180

// Create a movie clip for the box and draw a rectangular outline within it.
_root.createEmptyMovieClip("border", _root.getNewDepth( ));
with (border) {
lineStyle(0, 0, 100);

// Draw a 200 x 200 rectangle.


drawRectangle(200, 200);
_x += 100;
_y += 100;
}

// Create some properties for the circle that define the area within which i
// movement should be constrained.
[Link] = 0;
[Link] = 0;
[Link] = 200;
[Link] = 200;

// Initialize the circle at a random coordinate within the acceptable range.


circle._x = [Link]( ) * [Link];
circle._y = [Link]( ) * [Link];

// Set the velocity of the circle to six pixels per frame.


[Link] = 6;

// Set a property that determines the direction or angle of the initial movem
// of the circle. The value needs to be in radians, so generate a random valu
// between 0 and 2π (which is 360 in degrees).
[Link] = [Link]( ) * [Link] * 2;

// Define an onEnterFrame( ) method for the circle so that it continually up


// its position.
[Link] = function ( ) {

// Calculate the new x and y coordinates for the circle.


// Using basic trigonometric formulas, we can derive the x
// and y components of a vector, a line that has both direction
// (an angle) and velocity. See Recipe 5.12 and Recipe 5.14.
this._x += [Link]([Link]) * [Link];
this._y += [Link]([Link]) * [Link];

// If the circle is touching the rectangle or is outside its boundaries, ch


// the direction of the circle.
if ((this._x + this._width/2) >= [Link]) {
this._x = [Link] - this._width/2 - 1;
[Link] += 2 * ([Link]/2 - [Link]);
}
else if ((this._x - this._width/2)<= [Link]) {
this._x = [Link] + this._width/2 + 1;
[Link] += 2 * ([Link]/2 - [Link]);
}
if ((this._y + this._height/2) >= [Link]) {
this._y = [Link] - this._height/2 - 1;
[Link] -= 2*[Link];
}
else if ((this._y - this._height/2) <= [Link]) {
this._y = [Link] + this._height/2 + 1;
[Link] -= 2*[Link];
}
};

That is all there is to stage one of the application. If you save and test your movie, you should se
single circle that bounces around within the boundaries of a rectangle. The majority of the code

[Link] 2004-02-06
Strona 32 z 180

stage is nothing new or complicated. Some of the mathematics involved in the calculations migh
new to you, but the ActionScript syntax is familiar. The only portion of the code that really needs
further explanation is a snippet from the onEnterFrame( ) method. Within this method you want
update the position of the circle according to the direction and velocity for the circle. There are tw
basic trigonometric formulas that you can employ here to find the new x and y coordinates based
the information you do know (the direction/angle and the velocity).

Trigonometry says that the x coordinate can be found by multiplying the cosine of the angle by t
velocity, and the y coordinate can be found by multiplying the sine of the angle by the velocity. N
the circle was only going to move along a straight line forever, then this would be all that is need
However, you want the circle to bounce when it hits the sides of the rectangle. Therefore, you ne
use a series of if statements. In each case, you must calculate whether the edge of the circle is
touching or outside of the rectangle. You can find the edge of the circle by adding or subtracting
radius from the center of the circle (the radius is half of either the width or the height). When th
circle needs to bounce, you must do two things. First, set the circle so that it is within the bound
of the rectangle by at least one pixel. Otherwise, the circle can get trapped along the side of the
rectangle. The other thing you should do is assign the circle a new direction. The new direction is
determined either by adding twice the difference between π/2 and the current direction (in the ca
the circle bouncing off the left or right walls) or by subtracting twice the current direction (in the
of the circle bouncing off the top or bottom walls), as shown in the preceding onEnterFrame( ) h

[ Team LiB ]
[ Team LiB ]

23.2 Stage Two


In stage two, we want to use the calculations that were worked out in the first stage to create a
component. The advantage of a component, in this case, is that you can create the component c
once and then create as many instances as you want. Here are the steps you should complete to
create the second stage of the application:

1. Open [Link] and save it as [Link].

2. Delete all the code from the first frame of the main timeline. You may want to select it and
it because you will use much of the same code within the component.

3. Create a new movie clip symbol named Circle.

4. Open the linkage properties for the Circle movie clip symbol.

5. Select the Export for ActionScript and Export on First Frame options and give the symbol a
linkage identifier of CircleSymbol.

6. Click OK to close the linkage properties dialog box.

7. Edit Circle.

8. On the first frame of the default layer within the Circle symbol, add the following code:

// Component class code should always be enclosed within #initclip/#endinitc


#initclip

function CircleClass ( ) {}

[Link] = new MovieClip( );

[Link] 2004-02-06
Strona 33 z 180

[Link] = function (minX, minY, maxX, maxY, vel,


col, radius) {

// Draw the circle with radius specified by the radius parameter.


with (this) {
lineStyle(0, 0x000000, 0);
beginFill(0, 100);
drawCircle(radius);
endFill( );
}

// Define the area within which the circle moves.


[Link] = minX;
[Link] = minY;
[Link] = maxX;
[Link] = maxY;

// Assign a random coordinate at which the circle initializes.


this._x = [Link]( ) * [Link];
this._y = [Link]( ) * [Link];

// If the string "random" is passed to the method as the vel parameter,


// generate a random velocity between 3 and 9. Otherwise, use the value of
if (vel == "random") {
[Link] = [Link]( ) * 6 + 3;
} else {
[Link] = vel;
}

// Generate a random direction in which the circle should initially move.


[Link] = [Link]( ) * [Link] * 2;

// Create a color object to control the color of the circle.


[Link] = new Color(this);

// If the col parameter is "random", generate a random color value.


if (col == "random") {
col = [Link]( ) * 255 * 255 * 255;
}

// Set the color.


[Link](col);
};

// The onEnterFrame( ) method here is the same as from stage one.


[Link] = function ( ) {
this._x += [Link]([Link]) * [Link];
this._y += [Link]([Link]) * [Link];
if ((this._x + this._width/2) >= [Link]) {
this._x = [Link] - this._width/2 - 1;
[Link] += 2 * ([Link]/2 - [Link]);
}
else if ((this._x - this._width/2)<= [Link]) {
this._x = [Link] + this._width/2 + 1;
[Link] += 2 * ([Link]/2 - [Link]);
}
if ((this._y + this._height/2) >= [Link]) {
this._y = [Link] - this._height/2 - 1;
[Link] -= 2*[Link];
}
else if ((this._y - this._height/2) <= [Link]) {
this._y = [Link] + this._height/2 + 1;
[Link] -= 2*[Link];
}

[Link] 2004-02-06
Strona 34 z 180

};

// Register the class to the linkage identifier CircleSymbol.


[Link]("CircleSymbol", CircleClass);

#endinitclip

9. On the first frame of the default layer of the main timeline, add the following code:

// Include [Link] from Chapter 4 and [Link] from Chapter 7.


#include "[Link]"
#include "[Link]"

// Create an instance of the circle component. Call the init( ) method to d


// the initialization parameters.
_root.attachMovie("CircleSymbol", "circle" + i, 1);
[Link](0, 0, 200, 200, "random", "random", 10);

_root.createEmptyMovieClip("border", 2);
with (border) {
lineStyle(0, 0, 100);
drawRectangle(200, 200, 0, 0, 100, 100);
}

When you save and test your movie in stage two, you should see pretty much the same thing yo
saw in stage one. The only apparent difference is that the color and velocity are randomized. But
have accomplished much more than just what is apparent from this simple example. You have
successfully created a component that can be used to make more circle instances, as you will se
the next stage.

Most of the code in stage two of this application is the same as the code from stage one. Howeve
there is one thing that is worth mentioning with regards to the colorization. In this example you
created a color object to control the color of the circle. Technically, this is not necessary since yo
could easily set the color in the beginFill( ) method when drawing the circle. However, if at a late
point you want to alter the color of the circle, it is much simpler to use a color object. For examp
you could extend this application a little by assigning a new, random color to the circle every tim
bounces.

[ Team LiB ]
[ Team LiB ]

23.3 Stage Three


In the third stage you want to add more circle instances to the movie. In and of itself, this is qui
easy now that you have created a component. However, adding more circles adds another level
complexity. The circles should bounce off each other as well as the sides of the rectangle. To
accomplish this, you need to devise a way in which each circle knows about all the other circles.
not as difficult as it may sound. Here are the steps to complete the third stage of the application

1. Open [Link] and save it as [Link].

2. Open the Circle symbol and modify the code as follows (changes are in bold):

#initclip

function CircleClass( ) {}

[Link] = new MovieClip( );

[Link] 2004-02-06
Strona 35 z 180

[Link] = function (minX, minY, maxX, maxY, vel, col, rad


with (this) {
lineStyle(0, 0x000000, 0);
beginFill(0, 100);
drawCircle(radius);
endFill( );
}
[Link] = minX;
[Link] = minY;
[Link] = maxX;
[Link] = maxY;
this._x = [Link]( ) * [Link];
this._y = [Link]( ) * [Link];
if (vel == "random") {
[Link] = [Link]( ) * 6 + 3;
} else {
[Link] = vel;
}
[Link] = [Link]( ) * [Link] * 2;
[Link] = new Color(this);
if (col == "random") {
col = [Link]( ) * 255 * 255 * 255;
}
[Link](col);
};

// The setCircleArray( ) method allows you to define an array of reference


// all the circle instances.
[Link] = function (cirAr) {
[Link] = cirAr;
};

[Link] = function ( ) {
this._x += [Link]([Link]) * [Link];
this._y += [Link]([Link]) * [Link];
if ((this._x + this._width/2) >= [Link]) {
this._x = [Link] - this._width/2 - 1;
[Link] += 2 * ([Link]/2 - [Link]);
}
else if ((this._x - this._width/2)<= [Link]) {
this._x = [Link] + this._width/2 + 1;
[Link] += 2 * ([Link]/2 - [Link]);
}
if ((this._y + this._height/2) >= [Link]) {
this._y = [Link] - this._height/2 - 1;
[Link] -= 2*[Link];
}
else if ((this._y - this._height/2) <= [Link]) {
this._y = [Link] + this._height/2 + 1;
[Link] -= 2*[Link];
}

// Loop through all the elements in the circle array and determine if the
// circle has collided with any of them.
for (var i = 0; i < [Link]; i++) {
cirB = [Link][i];

// Get the distance between the two circles using the Pythagorean theorem
// (see Recipe 5.13).
var dx = [Link](this._x - cirB._x);
var dy = [Link](this._y - cirB._y);
var dist = [Link]([Link](dx, 2) + [Link](dy, 2));

[Link] 2004-02-06
Strona 36 z 180

// If the distance between the two circles is less than the sum of their
// radii, they have collided. Also, make sure the current circle is not
// because you don't want to detect a circle's collision with itself!
if(dist <= (this._width/2 + cirB._width/2) && cirB != this) {

// This code sets the new directions for the colliding circles.
var circlesAngle = Math.atan2(this._y - cirB._y, this._x - cirB._x) +
[Link]/2;
[Link] = (circlesAngle + [Link])/2;
[Link] = (-circlesAngle + [Link])/2;
}
}
};

[Link]("CircleSymbol", CircleClass);

#endinitclip

3. Modify the code on the first frame of the main timeline as follows (changes in bold):

#include "[Link]"

// Place the code in a function to keep things organized as you add more
// functionality in the subsequent stages.
function initCircles( ) {

// Create a movie clip to contain all the circle instances.


_root.createEmptyMovieClip("circleHolder", _root.getNewDepth( ));

// Create an array to hold the references to all the circles.


circles = new Array( );

// Create 10 circle instances. For each circle, store a reference to the c


// array and then add the circle instance to the array. This enables each
// circle to reference all the other circles.
for (i = 0; i < 10; i++) {
cir = _root. [Link]("CircleSymbol", "circle" + i, i);
[Link](0, 0, 200, 200, "random", "random", 10);
[Link](circles);
[Link](cir);
}

// Draw the border within the circle holder movie clip instead of on _root
[Link]("border", i);
with ([Link]) {
lineStyle(0, 0, 100);
drawRectangle(200, 200, 0, 0, 100, 100);
}
}

initCircles( );

The additional code in this stage of the application is rather straightforward. By creating an array
references to all of the circle instances, and assigning that array as a property of each circle, eac
circle can know about all the other instances. Then, within the onEnterFrame( ) method, you che
whether the circle has collided with the rectangle boundaries or with any of the other circles. The
thing that might be unclear in this new code is the mathematics involved. Refer to Chapter 4 and
Chapter 5, and draw a diagram of two colliding circles, and that will help to clarify the math. Sav
test your movie to see how it works.

[ Team LiB ]

[Link] 2004-02-06
Strona 37 z 180

[ Team LiB ]

23.4 Stage Four


In the fourth stage of the application we want to add some buttons that allow the user to move t
circles, as a group, as they bounce around. Also, we'll let the user scale and rotate the circles as
group. Here are the steps to follow to complete stage four:

1. Open [Link] and save it as [Link].

2. Create a new movie clip symbol named RectangleButton.

3. Open the linkage properties for RectangleButton.

4. Check the Export for ActionScript and Export in First Frame options and add a linkage iden
of RectangleButtonSymbol.

5. Click OK to close the linkage properties.

6. Edit RectangleButtonSymbol.

7. Add the following code to the first frame of the default layer:

#initclip

function RectangleButtonClass ( ) {}

[Link] = new MovieClip( );

[Link] = function (label, col, w, h) {

// If w (width) is undefined, default to 100; if h (height) is undefined,


// default to 30; if col (for color) is undefined, default to 0xDFDFDF (li
// gray); and if label is undefined default to "submit".
w = (w == undefined) ? 100 : w;
h = (h == undefined) ? 30 : h;
col = (col == undefined) ? 0xDFDFDF : col;
label = (label == undefined) ? "submit" : label;

// Create a movie clip into which to draw the rectangle button.


[Link]("btn", 1);

// Create a label text field. Use createAutoTextField( ) to make the text


// autosize. (You will include [Link] from Chapter 8 on the main
// timeline.)
[Link]("label", 2);

// Make the text field nonselectable so it doesn't interfere with the butt
[Link] = false;
[Link] = label;

// If the label text field's width is greater than w, reassign a value to


// accommodate the text field.
w = ([Link]._width > w) ? [Link]._width + 6 : w;

// Draw a rectangle in the btn movie clip. (You will include [Link]
// Chapter 7 on the main timeline.)
with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(col);

[Link] 2004-02-06
Strona 38 z 180

drawRectangle(w, h);
endFill( );
_x += w/2;
_y += h/2;
}

// Center the label within the button.


[Link]._x = this._width/2 - [Link]._width/2;
[Link]._y = this._height/2 - [Link]._height/2;

// When the btn clip is pressed and released, scale it to create an effect
[Link] = function ( ) {
this._xscale = 96;
this._yscale = 96;
}
[Link] = function ( ) {
this._xscale = 100;
this._yscale = 100;
}
};

[Link]("RectangleButtonSymbol", RectangleButtonClass);

#endinitclip

8. Edit the code on the first frame of the main timeline and make the modifications shown he
(changes are in bold):

// Include [Link] from Chapter 4 and [Link] from Chapter


#include "[Link]"
#include "[Link]"

// Include the libraries for the table and text field custom methods.
// [Link] is from Chapter 8, and [Link] is from Chapter 11.
#include "[Link]"
#include "[Link]"

function initCircles ( ) {
circles = new Array( );
_root.createEmptyMovieClip("circleHolder", _root.getNewDepth( ));
for (var i = 0; i < 10; i++) {
cir = [Link]("CircleSymbol", "circle" + i,
[Link]( ));
[Link](0, 0, 200, 200, [Link]( ) * 3 + 3, "random", 10);
[Link](circles);
[Link](cir);
}
[Link]("border", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 100);
drawRectangle(200, 200, 0, 0, 100, 100);
}

// Add an onEnterFrame( ) method to circleHolder that makes changes to


// movie clip's properties depending on what button has been pressed.
[Link] = function ( ) {
var vel = 5;
switch ([Link]) {
case "up":
this._y -= vel;
break;
case "down":
this._y += vel;
break;

[Link] 2004-02-06
Strona 39 z 180

case "left":
this._x -= vel;
break;
case "right":
this._x += vel;
break;
case "rotateCW":
this._rotation += vel;
break;
case "rotateCCW":
this._rotation -= vel;
break;
case "scaleUp":
this._xscale += vel;
this._yscale += vel;
break;
case "scaleDown":
this._xscale -= vel;
this._yscale -= vel;
}
};
}

function initButtons ( ) {

// Create an array of names for the buttons.


btnNamesAr = new Array("left", "right", "up", "down",
"scaleUp", "scaleDown", "rotateCW", "rotateCCW");

// For each button in the array, create instances of the button component.
for (var i = 0; i < [Link]; i++) {
btn = _root.attachMovie("RectangleButtonSymbol", btnNamesAr[i] + "Btn",
_root.getNewDepth( ));
[Link](btnNamesAr[i]);

// Assign the button name to the action property for that button. This v
// corresponds to the possible action values for the circleHolder within
// switch statement in its onEnterFrame( ) method.
[Link] = btnNamesAr[i];

// Assign onPress( ) and onRelease( ) functions for each button.


[Link] = pressBtn;
[Link] = releaseBtn;
}
}

// This function is assigned to the onPress( ) event handler method for al


// buttons; it sets the action property of the circleHolder to the action
// property value for the button being pressed. This causes the correspondin
// action to take place for circleHolder .
function pressBtn ( ) {
_root.[Link] = [Link];
}

// When a button is released, set the circleHolder 's action property to nu


// stop the current action.
function releaseBtn ( ) {
_root.[Link] = null;
}

// Create a table to position all the elements on the stage.


function createTable ( ) {
tr0 = new TableRow(5, new TableColumn(5, circleHolder));
tr1 = new TableRow(5, new TableColumn(5, leftBtn, rightBtn, upBtn, downBtn

[Link] 2004-02-06
Strona 40 z 180

new TableColumn(5, scaleUpBtn, scaleDownBtn),


new TableColumn(5, rotateCWBtn, rotateCCWBtn));
t = new Table(5, 0, 0, tr0, tr1);
}

initCircles( );
initButtons( );
createTable( );

There is a lot of new code added in stage four. Save the file, and let's take a look at some of the
snippets that could use a little more explanation.

Within the initCircles( ) function, we added an onEnterFrame( ) method for the circleHolder m
clip. This is a technique you can use to create continuous changes to the movie clip's properties
a button is pressed. When you assign actions to an onPress( ) method for a button (or button mo
clip in this case), that action occurs only once each time the button is pressed. For example, if yo
place code to increment a movie clip's _x property within the onPress( ) method, you have to clic
button repeatedly to move the movie clip. If you want to use the button to continuously change
object's properties, then you need to come up with a different solution. In this case, you can use
onEnterFrame( ) method on circleHolder (the movie clip whose properties you want to change
Each button assigns a different value to the circleHolder's action property; using a switch
statement, you can determine which property or properties to update for the movie clip.

[Link] = function ( ) {
var vel = 5;
switch ([Link]) {
case "up":
this._y -= vel;
break;
case "down":
this._y += vel;
break;
case "left":
this._x -= vel;
break;
case "right":
this._x += vel;
break;
case "rotateCW":
this._rotation += vel;
break;
case "rotateCCW":
this._rotation -= vel;
break;
case "scaleUp":
this._xscale += vel;
this._yscale += vel;
break;
case "scaleDown":
this._xscale -= vel;
this._yscale -= vel;
}
};

Next, you should create all the buttons. You can create the rectangle button component instance
at a time, or, more conveniently, you can use a for statement to take care of this for you. In this
example, we use an array to store all the button names and then loop through that array and cre
component instance for each element. We pass the init( ) method the button name as a paramet
that its value shows up in the button's label. You also should assign the name to the action para
of the component instance. You use this property value in the onPress( ) method to determine w
value to assign to the action property of the circleHolder movie clip:

[Link] 2004-02-06
Strona 41 z 180

function initButtons( ) {
btnNamesAr = new Array("left", "right", "up", "down",
"scaleUp", "scaleDown", "rotateCW", "rotateCCW");
for (var i = 0; i < [Link]; i++) {
btn = _root.attachMovie("RectangleButtonSymbol", btnNamesAr[i] + "Btn",
_root.getNewDepth( ));
[Link](btnNamesAr[i]);
[Link] = btnNamesAr[i];
[Link] = pressBtn;
[Link] = releaseBtn;
}
}

When a button is pressed, assign that button's action property value to the circleHolder movi
clip's action property. This sets into motion the corresponding actions. On the other hand, when
button is released, set the circleHolder movie clip's action property to null to stop the action

function pressBtn ( ) {
_root.[Link] = [Link];
}
function releaseBtn ( ) {
_root.[Link] = null;
}

[ Team LiB ]
[ Team LiB ]

23.5 Stage Five


The final stage in this application is to use an XML file to load initialization data. Up to this point,
hardcoded the number of circles as well as their properties (color, radius, and velocity) into the F
document. However, you can create an XML document from which you can load all that data at r
that you can make changes to the movie without having to reexport the .swf file. Here are the st
should complete to finish the fifth stage of the application:

1. Open a new text document and save it as [Link] in the same directory as where you a
your Flash documents.

2. Add the following content to your XML document and save it:

<collisionMovieData>
<!-- Define the dimensions of the rectangle within which circles can move.
<bounceArea width="200" height="200" />

<!-- Define the circles that should be created. In this example you create
circles with random velocities and colors and with various radii. -->
<circles>
<circle radius="30" vel="random" col="random" />
<circle radius="5" vel="random" col="random" />
<circle radius="24" vel="random" col="random" />
<circle radius="10" vel="random" col="random" />
</circles>
</collisionMovieData>

3. Open [Link] and save it as [Link].

4. Modify the code on the main timeline, as shown here (changes are in bold):

[Link] 2004-02-06
Strona 42 z 180

#include "[Link]"
#include "[Link]"
#include "[Link]"
#include "[Link]"

function loadData ( ) {
var myXML = new XML( );
[Link] = true;

// Load the XML data from the XML document.


[Link]("[Link]");
[Link] = function ( ) {

// Get the width and height attribute values from the <bounceArea> el
var bounceAreaWidth = [Link];
var bounceAreaHeight = [Link];

// Create an array to hold the values for each of the circles.


var circlesInfo = new Array( );

// Get the array of the <circle> elements.


var circlesNodes = [Link];
var atr;

// Loop through all the <circle> elements, and add the


// attributes object for each to the circlesInfo array.
// The properties of the attributes object are vel , col , and dir .
for (var i = 0; i < [Link]; i++) {
[Link](circlesNodes[i].attributes);
}

// Call the initCircles( ) , initButtons( ) , and createTable( ) fun


// once the data has loaded. Pass initCircles( ) the width and height
// bounce area, and also pass it the circlesInfo array.
_root.initCircles(bounceAreaWidth, bounceAreaHeight, circlesInfo);
_root.initButtons( );
_root.createTable( );
};
}

// Modify initCircles( ) to accept parameters that define the width and he


// of the rectangle as well as an array of circle information objects.
function initCircles(baW, baH, circlesInfo) {
circles = new Array( );
_root.createEmptyMovieClip("circleHolder", _root.getNewDepth( ));
var cInfo;

// Loop through all the circle information objects, and use those
// values to create the circle component instances.
for (var i = 0; i < [Link]; i++) {
cInfo = circlesInfo[i];
cir = [Link]("CircleSymbol", "circle" + i,
[Link]( ));
[Link](0, 0, baW, baH, [Link], [Link], [Link]);
[Link](circles);
[Link](cir);
}
[Link]("border", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 100);
drawRectangle(baW, baH, 0, 0, baW/2, baH/2);
}
[Link] = function ( ) {
var vel = 5;

[Link] 2004-02-06
Strona 43 z 180

switch ([Link]) {
case "up":
this._y -= vel;
break;
case "down":
this._y += vel;
break;
case "left":
this._x -= vel;
break;
case "right":
this._x += vel;
break;
case "rotateCW":
this._rotation += vel;
break;
case "rotateCCW":
this._rotation -= vel;
break;
case "scaleUp":
this._xscale += vel;
this._yscale += vel;
break;
case "scaleDown":
this._xscale -= vel;
this._yscale -= vel;
}
};
}

function initButtons ( ) {
btnNamesAr = new Array("left", "right", "up", "down",
"scaleUp", "scaleDown", "rotateCW", "rotateCCW");
for (var i = 0; i < [Link]; i++) {
btn = _root.attachMovie("RectangleButtonSymbol", btnNamesAr[i] + "Btn",
_root.getNewDepth( ));
[Link](btnNamesAr[i]);
[Link] = btnNamesAr[i];
[Link] = pressBtn;
[Link] = releaseBtn;
}
}

function pressBtn ( ) {
_root.[Link] = [Link];
}

function releaseBtn ( ) {
_root.[Link] = null;
}

function createTable ( ) {
tr0 = new TableRow(5, new TableColumn(5, circleHolder));
tr1 = new TableRow(5, new TableColumn(5, leftBtn, rightBtn, upBtn, downBtn
new TableColumn(5, scaleUpBtn, scaleDownBtn),
new TableColumn(5, rotateCWBtn, rotateCCWBtn));
t = new Table(5, 0, 0, tr0, tr1);
}

// Call loadData( ) instead of the other three functions. The other


// functions are now called once the data has loaded.
loadData( );

[Link] 2004-02-06
Strona 44 z 180

Save the file and test it. There are really only two changes that we have made in this stage of th
application. First of all, rather than hardcode the data into the Flash document, we use an XML o
the data into the movie at runtime. You don't want to initialize the movie until after the data has
you should invoke the initCircles( ), initButtons( ), and createTable( ) functions from within the X
onLoad( ) method. Additionally, you want to extract values from the XML data and pass those va
to the initCircles( ) function. The bounceAreaWidth and bounceAreaHeight values are rather sel
The circlesInfo array, on the other hand, might not be immediately clear. You want to constru
in which each element represents the properties (color, velocity, and radius) of the circles that a
the XML document. This is rather convenient because each of the <circle> elements contains an
array with those three properties (the attributes of the XML element). So you can use the push(
append the value of each <circle> element's attributes property to the circlesInfo array.

[Link] = function ( ) {
var bounceAreaWidth = [Link];
var bounceAreaHeight = [Link];
var circlesInfo = new Array( );
var circlesNodes = [Link];
var atr;
for (var i = 0; i < [Link]; i++) {
[Link](circlesNodes[i].attributes);
}
_root.initCircles(bounceAreaWidth, bounceAreaHeight, circlesInfo);
_root.initButtons( );
_root.createTable( );
};

Then you need to make a small modification to the initCircles( ) function so that it accepts and u
parameters loaded from the XML data. Not much needs to change. The hardcoded values for eac
color, velocity, and radius should be replaced by the values from the circlesInfo array, and the
values that previously defined the width and height of the bounce area rectangle should be repla
parameters baW and baH:

for (var i = 0; i < [Link]; i++) {


cInfo = circlesInfo[i];
cir = [Link]("CircleSymbol", "circle" + i,
[Link]( ));
[Link](0, 0, baW, baH, [Link], [Link], [Link]);
[Link](circles);
[Link](cir);
}
[Link]("border", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 100);
drawRectangle(baW, baH, 0, 0, baW/2, baH/2);
}
[ Team LiB ]
[ Team LiB ]

23.6 Conclusion
We've seen a realistic example of the multistage process characteristic of software
development. At each stage, we added features and refined existing features, building on our
prior work. As we developed the application, you may have identified features you'd like to add
or other applications in which you can use similar techniques. This chapter introduced you to an
approach that can serve you well in future software development, which is usually an iterative
process. Think of it as an example of how to break down a large problem into smaller problems.
Practice the techniques the chapter demonstrates, such as the ability to create components,
reuse code, etc.
[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 45 z 180

Chapter 24. Video Chat/Message Center


Flash Communication Server MX is a technology that enables real-time communication between
Flash clients and the FlashCom server. Equally important is that FlashCom enables Flash clients
to publish (record or send a live stream) and subscribe to audio and video streams. The video
chat/message center application in this chapter takes advantage of a wide range of FlashCom
functionality. It builds on the FlashCom-related recipes of Chapter 14.

The video chat/message center application is a video version of a telephone/voice mail system.
In this application a user (the administrator) can open up a client to the FlashCom application
and receive live video calls from calling clients. Additionally, if the administrator is offline,
currently on another call, or otherwise unavailable, the calling client has the option to record a
video message, which the administrator can retrieve and play back at a later time.

The completes files can be downloaded from [Link]

[ Team LiB ]
[ Team LiB ]

24.1 Developing the Application Overview


The video chat/message center application, perhaps more than any of the others in this book,
requires good planning. This is because this application requires three parts that must work in
tight coordination:

The administrator client

The administrator is the person who receives incoming calls and can listen to messages
that callers have left. You need to create a client that can accept incoming calls and
retrieve messages from the FlashCom server.

The caller 4client

Many callers can attempt to call the one administrator. The caller client should enable a
caller to place a call to the administrator. If the administrator is not logged in, is on
another call, or simply doesn't accept the incoming call, the caller should be able to leave
a video message.

The server-side application file

The server-side part of the FlashCom application has a lot of work to do as well. It is
responsible for keeping track of who is connected, who is calling, who is leaving
messages, etc. This portion of the application is the main switchboard that keeps
everything in order. In this application, we keep all the server-side code in a single ASC
file.
[ Team LiB ]
[ Team LiB ]

24.2 Creating the Server-Side Application


The server-side portion of the video chat/message center application is contained within a single
file, [Link]. Here are the steps necessary to create the server-side part of this application:

[Link] 2004-02-06
Strona 46 z 180

1. Create a new FlashCom application named CommunicationCenterApp. You can do this by m


a new directory with this name inside of the FlashCom server's applications directory.

2. Create a new file inside of the CommunicationCenterApp directory. Name the file [Link]

3. Add the following code to [Link]:

// The [Link]( ) method is invoked each time a new client con


// to the application. The newClient parameter is a FlashCom-generated refer
// to the new client. The username parameter is a value passed to the method
// the Flash client.
[Link] = function (newClient, username, password) {

[Link] = username;
[Link] = password;

// The acceptClient( ) function accepts the client and assigns methods to


// client object according to the user type.
acceptClient(newClient);
};

// The [Link]( ) method is automatically invoked whenever


// client disconnects from the application.
[Link] = function (disconnectClient) {

// If the client who has disconnected was on a call with the administrator
// release that connection by setting the callingClient property to null. A
// if the administrator is still online, invoke the incomingCallEnd( ) me
// in the administrator client to clean up things on that end.
if ([Link] == disconnectClient) {
[Link] = null;
if ([Link] != undefined) {
[Link]("incomingCallEnd", null);
}
}

// If the client who has disconnected happens to be the administrator, tak


// care of the necessary loose ends.
if ([Link] = disconnectClient) {

// If the administrator was on a call, alert the calling client that the
// has ended.
if ([Link] != null) {
[Link]("adminEndCall", null);
}

// Set callingClient to null and delete the admin property since there a
// more active calls and no administrator logged in.
[Link] = null;
delete [Link];
}
};

// When the application first starts, this method is invoked automatically t


// initialize necessary application properties.
[Link] = function ( ) {

// The callingClient property is assigned a reference to the client object


// a client is on a call with the administrator.
[Link] = null;

// The messages shared object is a remote shared object (RSO) that contain
// the messages that have been left for the administrator.

[Link] 2004-02-06
Strona 47 z 180

[Link] = [Link]("messages", true);

// The messageNum property of the shared object is used to guarantee uniqu


// message numbers. If the property doesn't exist, initialize it to 0.
if ([Link]("messageNum") == undefined) {
[Link]("messageNum", 0);
}
};

// The acceptClient( ) function is invoked from


// the [Link]( ) method.
function acceptClient(newClient) {

// Make sure a calling client doesn't try to log in with the username "adm
if ([Link] == "admin" && [Link] != "adminPass") {
[Link](newClient);
return;
}

// Accept all other client connections.


[Link](newClient);

// Set the methods available to the client objects.


[Link] = recordMessageInfo;
[Link] = getMessageID;
[Link] = placeCall;
[Link] = endCall;

// If the client is the administrator client,


// then take care of a few other tasks.
if ([Link] == "admin") {
// Set the admin property to a reference to the connecting client object
[Link] = newClient;

// Set a few administrator-only client methods.


[Link] = acceptIncomingCall;
[Link] = sendToLeaveMessage;
[Link] = removeMessage;
}
}

// This function is a method assigned to the administrator client. When this


// method is invoked, the administrator has accepted an incoming call, and th
// acceptCall( ) method on the calling client is invoked.
function acceptIncomingCall ( ) {
[Link]("acceptCall", null);
}

// This function is a method assigned to the administrator client. It is inv


// when the administrator does not answer an incoming call.
function sendToLeaveMessage ( ) {
[Link]("notAvailable", null);
[Link] = null;
}

// The placeCall( ) function is assigned to clients as a method. This metho


// invoked when a client attempts to call the administrator.
function placeCall ( ) {

// If the administrator is logged in and if there is not already another c


// in place, invoke the onIncomingCall( ) method on the administrator cli
// alert the user that there is an incoming call. Otherwise, alert the cal
// client that the administrator is not available for a call right now.
if ([Link] != undefined && [Link] == null) {

[Link] 2004-02-06
Strona 48 z 180

[Link]("onIncomingCall", null, [Link]);


[Link] = this;
} else {
[Link]("notAvailable", null);
}
}

// The endCall( ) function is a method of all clients,


// and it ends the current call by invoking methods on
// both the calling client and the administrator client.
function endCall ( ) {
[Link]("adminEndCall", null);
[Link] = null;
[Link]("incomingCallEnd", null);
}

// The recordMessageInfo( ) function is a method of the calling clients, an


// is invoked when the user records a new message so that the information ab
// the message (such as username and time and date) can be stored as well.
function recordMessageInfo(id) {

// Get the messages array property of the messages shared object. If the
// property is undefined, create the array.
var messages = [Link]("messages");
if (messages == undefined) {
messages = new Array( );
}

// Create an object that contains the message id, username, and the date wh
// the message was recorded. Add this object to the messages array.
var infoObj = {id: id, username: [Link], dateTime: new Date( )};
[Link](infoObj);

// Add the updated info back to the shared object and write the data to di
[Link]("messages", messages);
[Link]( );
}

// The removeMessage( ) function is a method of the administrator client, an


// allows the administrator to remove recorded messages.
function removeMessage(streamName) {

// Use the [Link]( ) method to get a reference to the stream on the s


var messageStream = [Link](streamName);

// Use the clear( ) method to delete the stream.


[Link]( );

// Get the messages property of the shared object, and loop through it to
// the information for the stream that has been deleted. When the message
// information is found, delete it with a splice( ) method.
var messages = [Link]("messages");
for (var i = 0; i < [Link]; i++) {
if ("message" + messages[i].id == streamName) {
[Link](i, 1);
break;
}
}

// Update the shared object and write it to disk.


[Link]("messages", messages);
[Link]( );
}

[Link] 2004-02-06
Strona 49 z 180

// The getMessageID( ) function returns a unique message number.


function getMessageID ( ) {
// Get the messageNum property from the shared object and increment the va
// stored in the shared object.
var messageNum = [Link]("messageNum");
[Link]("messageNum", messageNum + 1);
[Link]( );
return messageNum;
}

The ASC file contains a lot of code, but fortunately, most of it is not very complex once you fami
yourself with it. Let's look at some of the code with a little more explanation as to what is going
why.

The onConnect( ) method is a method that FlashCom automatically invokes when a new client co
to the application. FlashCom creates a new client object and passes a reference to the object as
parameter to the onConnect( ) method. In addition, we pass two custom parameters: a usernam
password. The username is important because it helps the application keep track of what kinds o
are logged in (calling clients or an administrator). The password is null in the case of all calling c
but it has the value of "adminPass" in the case of the administrator (this is something you will ha
into the administrator client movie). The password is important because it helps to prevent a cal
client from logging in with the username of "admin" (which is the username we use for the
administrator).

[Link] = function (newClient, username, password) {


[Link] = username;
[Link] = password;
acceptClient(newClient);
};

Analogous to the onConnect( ) method, the onDisconnect( ) method is invoked each time a clien
disconnects from the application. As with the onConnect( ) method, FlashCom passes a paramet
onDisconnect( ) that references the client who has just disconnected. The onDisconnect( ) metho
important in this application only when the disconnecting client was on a live call. The server nee
know that the call has ended so it can alert the other client that the call has ended and can set t
correct values for properties it uses to track the call status. When the administrator is online, the
application keeps track of the administrator using a property named admin. And when a call is in
progress, the calling client object is stored as a property named callingClient. See the acceptC
function for more on these two properties.

Therefore, if the disconnecting client happens to be the same client that is stored as the calling c
set the calling client to null (since there is no longer a calling client connected) and invoke a met
the administrator client to tell it that the call has ended. Alternatively, if the disconnecting client
administrator, invoke a method on any calling client to let it know that the call has ended. Also,
important to set callingClient to null and delete the admin property so that the application kno
who is logged in and what the call status is.

[Link] = function (disconnectClient) {


if ([Link] == disconnectClient) {
[Link] = null;
if ([Link] != undefined) {
[Link]("incomingCallEnd", null);
}
}
if ([Link] = disconnectClient) {
if ([Link] != null) {
[Link]("adminEndCall", null);
}
[Link] = null;
delete [Link];

[Link] 2004-02-06
Strona 50 z 180

}
};

The onAppStart( ) method is automatically invoked once when the application starts. So this is w
you should place actions that should occur only once. Initialize the callingClient property to n
(since there is no call in progress when the application is first started) and get a remote shared o
for the application to store information about messages left for the administrator. If the shared o
has not been created (the first time the application runs), initialize a messageNum property, which
application uses to make sure each message is assigned a unique ID.

[Link] = function ( ) {
[Link] = null;
[Link] = [Link]("messages", true);
if ([Link]("messageNum") == undefined) {
[Link]("messageNum", 0);
}
};

The acceptClient( ) function is invoked by the onConnect( ) method every time a new client conn
the application. You should make sure that the client has not tried to log in with the username "a
unless it is accompanied by the password "adminPass". The calling clients are not asked for a pa
when they log in, so the password will be undefined. The administrator password is hardcoded in
administrator client movie. So if a calling client tries to log in with the username "admin", reject
connection and exit from the function. Otherwise, accept the connection and assign methods to t
client object. If the client is the administrator, also assign some administrator-only methods to t
object and assign the client object reference to the admin property. The admin property does two
things. First, it lets the application know that the administrator is online (so users can place calls
Second, it gives you a convenient way to invoke methods on the administrator client throughout
server-side code.

function acceptClient (newClient) {


if ([Link] == "admin" && [Link] != "adminPass") {
[Link](newClient);
return;
}
[Link](newClient);
[Link] = recordMessageInfo;
[Link] = getMessageID;
[Link] = placeCall;
[Link] = endCall;
if ([Link] == "admin") {
[Link] = newClient;
[Link] = acceptIncomingCall;
[Link] = sendToLeaveMessage;
[Link] = removeMessage;
}
}

The recordMessageInfo( ) function is assigned as a method of client objects, and it is invoked wh


calling client leaves a message. Although the video and audio portion of the message is publishe
completely from the client, this server-side function records information about the message into
remote shared object. This is important because it allows the administrator to retrieve a list of
messages. Each entry in the shared object contains the unique message ID, the username of the
leaving the message, and the date and time when the message was recorded.

function recordMessageInfo (id) {


var messages = [Link]("messages");
if (messages == undefined) {
messages = new Array( );
}
var infoObj = {id: id, username: [Link], dateTime: new Date( )};

[Link] 2004-02-06
Strona 51 z 180

[Link](infoObj);
[Link]("messages", messages);
[Link]( );
}

The removeMessage( ) function is a method of the administrator client object, and it removes a
stream (the FLV file) from the server when the administrator chooses that option. The client pas
name of the stream to this method and, using the [Link]( ) method, retrieves a server-side
reference to the stream. Then the clear( ) method removes the stream from the server. Howeve
also need to remove the message information from the shared object so that it no longer appear
administrator's list of messages. Do this by looping through all the elements of the messages arr
you find the matching entry. Then use the splice( ) method to remove that element, break out o
loop, and save the updated information back to the shared object.

function removeMessage (streamName) {


var messageStream = [Link](streamName);
[Link]( );
var messages = [Link]("messages");
for (var i = 0; i < [Link]; i++) {
if ("message" + messages[i].id == streamName) {
[Link](i, 1);
break;
}
}
[Link]("messages", messages);
[Link]( );
}
[ Team LiB ]
[ Team LiB ]

24.3 Creating the Calling Client


The calling client should include the following functionality:

z The user should be asked to log in before connecting to the FlashCom application and bein
allowed to place a call.

z When the client first connects, the user should be presented with a button that enables him
place a call.

z If the administrator is not online or otherwise cannot accept the call, the Place Call button
should change to a Record button to allow the user to record a message.

z If the user's call is accepted by the administrator, the client should display the video from
the calling client and the administrator client. Additionally, the client should play the audio
the administrator. The button should change to an End Call button, so the user can end the

Here are the steps to follow to create the calling client:

1. Create a new Flash document named [Link] and open it.

2. Add the PushButton component symbol to the Library. You can do this by dragging an insta
of the push button to the Stage and then deleting it.

3. Add a new video symbol by choosing New Video from the Library panel's pop-up Options m

4. Create a new movie clip symbol named videoMc.

[Link] 2004-02-06
Strona 52 z 180

5. Edit videoMc, and create an instance of the video symbol within it so that the video symbo
instance is placed at (0,0).

6. Name the video symbol instance vid.

7. Open the linkage properties for videoMc.

8. Select the Export for ActionScript and Export on First Frame options and set the linkage
identifier to VideoMcSymbol.

9. Add the following code to the first frame of the main timeline:

// [Link] is part of Flash Remoting.


#include "[Link]"

// Include [Link] from Chapter 7 and [Link] from Chapter 8.


#include "[Link]"
#include "[Link]"

// Include [Link] and [Link] from Chapter 11.


#include "[Link]"
#include "[Link]"

// Create the login form.


function createLoginForm ( ) {

// Create a login button.


_root.attachMovie("FPushButtonSymbol", "loginBtn", _root.getNewDepth( ));
[Link]("login");

// When the user clicks the button to log in, connect the user to the Flash
// application.
[Link] = function ( ) {
var un = _root.[Link];
_root.username = un;

// Connect the user only if she has entered a username.


if (un != "") {
[Link]("rtmp:/communicationCenterApp/", un);
}
};

// Create a label for the input text field.


_root.createAutoTextField("usernameLabel", _root.getNewDepth( ));
[Link] = "username: ";

// Create the input text field for the user to enter a username.
_root.createInputTextField("usernameField", _root.getNewDepth( ));

// Use a table to position the elements.


f0tr0 = new TableRow(5, new TableColumn(5, usernameLabel),
new TableColumn(5, usernameField));
f0tr1 = new TableRow(5, new TableColumn(5, loginBtn));
form0Table = new Table(5, 100, 100, f0tr0, f0tr1);

// Add the elements to a form.


form0 = new Form( );
[Link](usernameLabel);
[Link](usernameField);
[Link](loginBtn);
}

[Link] 2004-02-06
Strona 53 z 180

// Create the form for the main screen.


function createMainForm ( ) {

// Create clips for displaying the calling client's and administrator's vi


_root.attachMovie("videoMcSymbol", "localVideoMc", _root.getNewDepth( ));
_root.attachMovie("videoMcSymbol", "remoteVideoMc", _root.getNewDepth( ))

// Create a button so the user can place a call.


_root.attachMovie("FPushButtonSymbol", "makeCallBtn", _root.getNewDepth(
[Link]("place call");
[Link]("placeCall");

// Position the items using a table.


f1tr0 = new TableRow(5, new TableColumn(5, makeCallBtn));
f1tr1 = new TableRow(5, new TableColumn(5, localVideoMc),
new TableColumn(5, remoteVideoMc));
form1Table = new Table(5, 100, 100, f1tr0, f1tr1);

// Add the elements to a form.


form1 = new Form( );
[Link](makeCallBtn);
[Link](localVideoMc);
[Link](remoteVideoMc);
}

// Create a multipage form so that only the login screen or the main applica
// screen is visible at once.
function createMultiForm ( ) {
mForm = new MultiPageForm(form0, form1);
[Link](1);
}

myConnection = new NetConnection( );

// When the user successfully connects to the FlashCom application, display


// main application form.
[Link] = function (infoObject) {
if ([Link] == "[Link]") {
_root.[Link](2);
}
};

// The acceptCall( ) method is invoked by the FlashCom application when the


// administrator accepts a call.
[Link] = function ( ) {
_root.doLiveCall( );
};

// The adminEndCall( ) method is invoked when the administrator disconnects


// otherwise ends the call.
[Link] = function ( ) {
_root.endCall( );
};

// The notAvailable( ) method is invoked when the


// administrator does not accept the call. When this occurs,
// modify the button so that the user can record a message.
[Link] = function ( ) {
_root.[Link]("start recording");
_root.[Link]("startRecord");
};

// The placeCall( ) function is one of the


// button's callback functions. It invokes the

[Link] 2004-02-06
Strona 54 z 180

// server-side placeCall( ) method to place a call to the administrator.


function placeCall ( ) {
[Link]("placeCall", null);
}

// The response object for when the FlashCom application returns a message I
messageIDRes = new Object( );
[Link] = function (result) {
_root.doRecord(result);
};

// The startRecord( ) function is one of the callback functions for the but
// and it initiates the process for recording a message. The getMessageID(
// method on the server is invoked to get a new message ID, and the result i
// returned to the messageIDRes response object.
function startRecord(pb) {
[Link]("stop recording");
[Link]("stopRecord");
[Link]("getMessageID", messageIDRes);
}

// The doLiveCall( ) function is invoked when the administrator accepts a c


function doLiveCall ( ) {

// Create the net stream to publish the calling client's video and audio.
livePublishNs = new NetStream(myConnection);
[Link]([Link]( ));
[Link]([Link]( ));
[Link](username + "live", "live");

// Create the net stream to subscribe to the administrator's video and aud
liveSubscribeNs = new NetStream(myConnection);
[Link]("adminLive");

// Add the local video to the local video object and add the administrator
// video to the remote video object.
[Link]([Link]( ));
[Link](liveSubscribeNs);

// Modify the button behavior to allow the user to end the call.
[Link]("end call");
[Link]("endCall");
}

// The endCall( ) function is invoked when a call is ended by either the ca


// client or the administrator.
function endCall ( ) {

// Reset the button so the user can place another call.


[Link]("place call");
[Link]("placeCall");

// Clear all the videos and net streams.


[Link](false);
[Link]( );
[Link]( );
[Link](null);
[Link](null);
[Link]( );
[Link]( );

// Call the endCall( ) method on the server.


[Link]("endCall", null);
}

[Link] 2004-02-06
Strona 55 z 180

// The doRecord( ) function is invoked after a new message ID is returned f


// the server.
function doRecord(id) {

// Create a net stream and attach the audio and video to it.
publishNs = new NetStream(myConnection);
[Link]([Link]( ));
[Link]([Link]( ));

// Publish a recorded stream.


[Link]("message" + id, "record");

// Invoke the recordMessageInfo( ) method on the server.


[Link]("recordMessageInfo", null, id);

// Display the camera locally so the user can see what is being recorded.
[Link]([Link]( ));
}

// The stopRecord( ) function is invoked when the user clicks on the button
// stop recording a message.
function stopRecord(pb) {

// Clear the video and net stream.


[Link](null);
[Link](false);
[Link]( );
[Link]("placeCall");
[Link]("placeCall");
[Link](null);
[Link]( );
}

createLoginForm( );
createMainForm( );
createMultiForm( );

Although there is a lot of code in the client, most of it is nothing you haven't seen before. There
two forms that you create and between which you can switch using a multipage form object. The
remainder of the code is primarily user-initiated invocations of server-side functions.
[ Team LiB ]
[ Team LiB ]

24.4 Creating the Administrator Client


The administrator client should include the following functionality:

z The administrator should be able to view a list of available messages that have been left.

z The administrator should be able to play and/or delete messages.

z The administrator should be alerted when there is an incoming call and should be able to a
or ignore the call.

z If the administrator accepts a call, she should be able to end the call by clicking on a butto

Complete the following steps to make the administrator client movie:

[Link] 2004-02-06
Strona 56 z 180

1. Create a new Flash document named [Link] and open it.

2. Add the PushButton and ListBox component symbols to the Library by creating an instance
each on the Stage and then deleting them.

3. Add a new video symbol by choosing New Video from the Library panel's pop-up Options m

4. Create a new movie clip symbol named videoMc.

5. Edit videoMc and create an instance of the video symbol within it so that the video symbol
instance is placed at (0,0).

6. Name the video symbol instance vid.

7. Open the linkage properties for videoMc.

8. Select the Export for ActionScript and Export on First Frame options and set the linkage
identifier to VideoMcSymbol.

9. Add the following code to the first frame of the main timeline:

// [Link] is part of Flash Remoting.


#include "[Link]"

// Include [Link] from Chapter 7 and [Link] from Chapter 10.


#include "[Link]"
#include "[Link]"

// Include [Link] and [Link] from Chapter 11.


#include "[Link]"
#include "[Link]"

// Include [Link] from Chapter 4.


#include "[Link]"

function init ( ) {

// Create a net connection and connect to the FlashCom application as the


// administrator (username = "admin", password = "adminPass").
myConnection = new NetConnection( );
[Link]("rtmp:/communicationCenterApp/", "admin", "adminPass"

// Once the connection has been made successfully, invoke initMessagesList


[Link] = function (infoObject) {
if ([Link] == "[Link]") {
_root.initMessagesList(this);
}
};

// The onIncomingCall( ) method is invoked from the server when a calling


// client places a call to the administrator.
[Link] = function (username) {
_root.incomingCallUser = username;

// Make the incoming call button visible.


_root.incomingCallBtn._visible = true;

// Create an interval on which the notifyIncomingCall( ) function is ca


_root.incomingCallInterval = setInterval(_root, "notifyIncomingCall",
1000, getTimer( ));
}

[Link] 2004-02-06
Strona 57 z 180

// This method is invoked when a call is ended.


[Link] = function ( ) {
// Delete the incoming caller name and clear the videos and net streams.
delete _root.incomingCallUser;
_root.[Link](false);
_root.[Link]( );
_root.[Link]( );
_root.[Link](null);
_root.[Link](null);
_root.[Link]( );
_root.[Link]( );
};

createForm( );
}

// The createForm( ) function creates all the movie clip/component/symbol


// instances and positions them.
function createForm ( ) {

// Add the list box to display the list of available messages.


_root.attachMovie("FListBoxSymbol", "messageList", _root.getNewDepth( ));

// Create the video instance for the incoming calls.


_root.attachMovie("videoMcSymbol", "incomingVideoMc", _root.getNewDepth(

// Add a push button instance that enables the administrator to accept an


// incoming call.
_root.attachMovie("FPushButtonSymbol", "incomingCallBtn",
_root.getNewDepth( ));

// Create a movie clip containing a small, green circle used to alert the
// administrator of an incoming call.
_root.createEmptyMovieClip("callLight", _root.getNewDepth( ));
with (callLight) {
lineStyle(0, 0, 0);
beginFill(0x00FF00, 100);
drawCircle(5);
endFill( );
}
callLight._visible = false;

[Link]("incoming call");
incomingCallBtn._visible = false;
[Link]("acceptCall");

// Create the videos for displaying the messages


// and for monitoring the local camera stream during calls.
_root.attachMovie("videoMcSymbol", "messageVideoMc", _root.getNewDepth( )
_root.attachMovie("videoMcSymbol", "localVideoMc", _root.getNewDepth( ));

// Create buttons for playing and removing messages.


_root.attachMovie("FPushButtonSymbol", "playBtn", _root.getNewDepth( ));
_root.attachMovie("FPushButtonSymbol", "removeBtn", _root.getNewDepth( ))
[Link]("viewMessage");
[Link]("play message");
[Link]("removeMessage");
[Link]("remove message");

// Use a table to position all the elements.


tr0 = new TableRow(5, new TableColumn(5, messagelist, playBtn, removeBtn),
new TableColumn(5, messageVideoMc));
tr1 = new TableRow(5, new TableColumn(5, incomingVideoMc),

[Link] 2004-02-06
Strona 58 z 180

new TableColumn(5, localVideoMc));


tr2 = new TableRow(5, new TableColumn(0, callLight, incomingCallBtn));
t = new Table(5, 0, 0, tr0, tr1, tr2);
}

// The startLiveCall( ) function is invoked when the administrator accepts


// incoming call.
function startLiveCall ( ) {

// Create a net stream for subscribing to the calling client's audio and v
liveSubscribeNs = new NetStream(myConnection);
[Link](incomingCallUser + "live");

// Attach the local and remote video streams to the respective video instan
[Link]([Link]( ));
[Link](liveSubscribeNs);

// Create a net stream for publishing the administrator's audio and video.
livePublishNs = new NetStream(myConnection);
[Link]([Link]( ));
[Link]([Link]( ));
[Link]("adminLive", "live");
}

// The notifyIncomingCall( ) function is invoked at


// one-second intervals when an incoming call is being placed.
function notifyIncomingCall(startTime) {

// Toggle the visibility of the call light. This creates a blinking effect
callLight._visible = !callLight._visible;

// If the user has been trying to place the call for more than ten seconds
// clear the interval, make the call button and call light invisible, and
// the incoming caller to record a message instead.
if (getTimer( ) - startTime > 10000) {
clearInterval(incomingCallInterval);
callLight._visible = false;
incomingCallBtn._visible = false;
[Link]("sendToLeaveMessage", null, incomingUser);
delete incomingCallUser;
}
}

// The acceptCall( ) function is invoked when the administrator accepts an


// incoming call.
function acceptCall( ) {

// Once the call is accepted, turn off the call light.


callLight._visible = false;

// Modify the button so that it allows the administrator to end the call.
[Link]("end call");
[Link]("endCall");

// If there is an incoming caller, clear the interval that blinks the call
// light and call the server-side acceptIncomingCall( ) method.
if (incomingCallUser != undefined) {
clearInterval(incomingCallInterval);
[Link]("acceptIncomingCall", null);
startLiveCall( );
}
}

// The endCall( ) function is invoked when

[Link] 2004-02-06
Strona 59 z 180

// the administrator clicks the End Call button.


function endCall ( ) {

// Reset the button behavior so that when another call comes in, the
// administrator can accept it.
[Link]("incoming call");
incomingCallBtn._visible = false;
[Link]("acceptCall");

// Invoke the endCall( ) method on the server


// to take care of all the loose ends.
[Link]("endCall", null);
}

// The viewMessage( ) function is invoked when the


// administrator clicks on the button to play a message.
function viewMessage ( ) {
[Link]("message" + [Link]( ).data);
_root.[Link](subscribeNs);
}

// The initMessagesList( ) function is invoked when the administrator has


// connected to the FlashCom application. It retrieves the list of messages
// the server and populates the list box.
function initMessagesList ( ) {

// Create a remote shared object to retrieve the message data from the serv
messagesSO = [Link]("messages", [Link], true);
[Link](myConnection);

// When there is an update to the messages


// information, repopulate the list box.
[Link] = function ( ) {

// First, remove all existing items from the list box.


_root.[Link]( );
var mssg, un, dt;

// Loop through all the items in the messages list from the server and a
// item to the list box for each.
for (var i = 0; i < [Link]; i++) {
mssg = [Link][i];
un = [Link];
dt = [Link];
_root.[Link](un + " " + [Link]("MM-dd-yyyy") + " " +
[Link]("hh:mm a"), [Link]);
_root.[Link]( );
_root.[Link](true);
}
};

// Create the net stream over which each video message can be retrieved.
subscribeNs = new NetStream(myConnection);
}

// The removeMessage( ) function is invoked when the administrator clicks th


// Remove Message button. It invokes the removeMessage( ) method on the serv
function removeMessage( ) {
var messageName = "message" + [Link]( ).data;
[Link]("removeMessage", null, messageName);
}

init( );

[Link] 2004-02-06
Strona 60 z 180

Now that you have completed the administrator client, save the file, and let's take a closer look a
some elements of the code.

The init( ) function creates the net connection and connects the administrator to the FlashCom
application. Notice that the username and password are hardcoded into the connect( ) method
invocation. This is not a secure approach, although for this example application it should not pos
problem. If you need to ensure security, the username and password should be entered by the
administrator via a login screen.

myConnection = new NetConnection( );


[Link]("rtmp:/communicationCenterApp/", "admin", "adminPass");

You want to invoke the initMessagesList( ) function once the connection is completed. Do this by
invoking it from within the connection object's onStatus( ) method:

[Link] = function (infoObject) {


if ([Link] == "[Link]") {
_root.initMessagesList(this);
}
};

When the administrator accepts an incoming call, you want to start a live call. To do this, follow
steps:

1. Create a net stream, subscribe to the stream that the calling client is publishing, and displa
that video.

2. Display the video that the administrator is publishing so that she can monitor what the call
client is seeing.

3. Publish the camera and microphone data so that the calling client can subscribe to it.

For example:

function startLiveCall ( ) {
liveSubscribeNs = new NetStream(myConnection);
[Link](incomingCallUser + "live");
[Link]([Link]( ));
[Link](liveSubscribeNs);
livePublishNs = new NetStream(myConnection);
[Link]([Link]( ));
[Link]([Link]( ));
[Link]("adminLive", "live");
}

When a calling client places a call to the administrator, blink the call light by creating an interval
invokes notifyIncomingCall( ) once per second. Each time the function is invoked, the visibility of
call light is toggled. Additionally, you want the call to time out after 10 seconds. To do this, comp
the current timer value with the timer value from when the interval began (which is passed to th
function as the startTime parameter). Once the call request has timed out, clear the interval an
reset the rest of the values that changed when the call request came in.

function notifyIncomingCall(startTime) {
callLight._visible = !callLight._visible;
if (getTimer( ) - startTime > 10000) {
clearInterval(incomingCallInterval);
callLight._visible = false;
incomingCallBtn._visible = false;
[Link]("sendToLeaveMessage", null, incomingUser);

[Link] 2004-02-06
Strona 61 z 180

delete incomingCallUser;
}
}

The initMessagesList( ) function is invoked when the connection to the FlashCom application is m
successfully. This function creates a remote shared object and uses it to populate the list box wit
available messages. By placing the code to populate the list box within the onSync( ) method, th
box is updated every time a new message is left.

function initMessagesList ( ) {
messagesSO = [Link]("messages", [Link], true);
[Link](myConnection);
[Link] = function ( ) {
_root.[Link]( );
var mssg, un, dt;
for (var i = 0; i < [Link]; i++) {
mssg = [Link][i];
un = [Link];
dt = [Link];
_root.[Link](un + " " + [Link]("MM-dd-yyyy") + " " +
[Link]("hh:mm a"), [Link]);
_root.[Link]( );
_root.[Link](true);
}
};
subscribeNs = new NetStream(myConnection);
}
[ Team LiB ]
[ Team LiB ]

24.5 Putting It All Together


You have now completed the entire video chat/message center application. The only thing that
remains is to test it. To do this, you will need to enlist the help of friends. You need to publish
your application to a publicly accessible FlashCom server so that others can connect to it. You
can publish the calling client movie to an HTML page on the server as well, and send that URL
to your friends. Then simply open the administrator client movie in the Standalone Player on
your own computer and wait for incoming calls and messages. Refer to Chapter 14 for more
information on FlashCom.

[ Team LiB ]
[ Team LiB ]

Chapter 25. Image Viewer/Slideshow


Loading images at runtime is a powerful feature of Flash, and it is not a particularly difficult skill
to acquire. However, there remain several subtleties that practice and experience will help you
to master. This chapter is devoted to the creation of an image viewer/slideshow application that
highlights some of the following key skills:

z Loading JPEG content at runtime

z Monitoring the download of the content using a progress bar

z Loading XML data and using it to populate a UI component

[Link] 2004-02-06
Strona 62 z 180

z Extensive use of the drawing API

z Creating drag-and-drop functionality within a menu

z Using intervals to automate timed tasks

z Watching for keyboard activity

The application that you create in this chapter includes the following features:

z URLs to available images are loaded from an XML document.

z Each image has a title as well as a thumbnail, low resolution, and full image URL.

z The titles of available images are used to populate a list box.

z Users can preview the low-resolution images in a preview pane. Each preview image
opens up in its own window that can be moved and resized within the preview pane.

z Users can add images to a sequence of thumbnails, the order of which can be rearranged
by dragging and dropping the thumbnail images.

z The full versions of the images can be played back in the same order as the thumbnails.

[ Team LiB ]
[ Team LiB ]

25.1 Planning the Application Design


The first thing you should do when developing any application is formulate a blueprint. That is,
you should decide what elements you need, and how those elements fit together to make a
whole. There is never only one correct way to create an application, so creating a plan helps
you ensure that the way you are approaching the development includes the necessary features
and that everything fits together.

The image viewer/slideshow application has four main parts:

Low-resolution preview pane

This portion of the application allows the user to view the low-resolution versions of the
images. Each image is loaded into its own viewer that can be resized and moved.

Sequencer

The user can add thumbnails to the sequencer. Dragging and dropping the images into
different positions changes the order of the thumbnails.

Sequence viewer

The full images can be played back in the same order as the thumbnails in the sequencer.

Menu

[Link] 2004-02-06
Strona 63 z 180

The menu consists of a list box of available images as well as buttons for previewing the
images, adding images to the sequencer, and starting the sequence playback.
[ Team LiB ]
[ Team LiB ]

25.2 Beginning the Application


The image viewer/slideshow application should be created using a single FLA document.
Therefore, you should create a new Flash document named [Link] and use it for all of
the subsequent steps in this chapter. The final version of the application is available at
[Link]

Additionally, the application relies on several UI components, so make sure you include the
following components in the Library of the Flash document:

ListBox
ProgressBar (Flash UI Components Set 2)
PushButton
ScrollBar
ScrollPane

You also need to make sure that you have the following ActionScript library files (also available
from the preceding URL) available to the Flash document:

[Link] (from Chapter 7)


[Link] (from Chapter 4)
[Link] (from Chapter 11)
[Link] (from Chapter 11)
[Link] (from Chapter 8)
[ Team LiB ]
[ Team LiB ]

25.3 Creating the Components


Components are incredibly useful for developing well-constructed, object-oriented Flash applicat
You should consider making elements of a movie into components when they meet either of thes
criteria:

z The same elements, or similar elements, are used multiple times throughout a movie.

z The element has complex behaviors and can be treated as a discrete unit.

For the image viewer/slideshow application, we will make seven components:

Image

A component that loads images given a URL

ImageViewPane

A component that allows an image to be moved and resized

PreviewPane

A component into which image view panes are added

[Link] 2004-02-06
Strona 64 z 180

SequenceViewer

The viewer for the full image slideshow sequence

SequencerItem

One of the thumbnail items that can be added to the sequencer

Sequencer

The component into which thumbnails are added and ordered

Menu

The menu for the application

25.3.1 Designing the Image Element


The preview pane, sequencer, and sequence viewer all include elements that load images. Rathe
reinvent the wheel with each, it makes sense to create a single Image component that can be ut
in each case. The Image component should have basic functionality that includes:

z Loading an image from a given URL

z Monitoring load progress with a progress bar

z Invoking a callback function when loading is complete

z Resizing itself to scale (maintaining the aspect ratio) to fit within specific dimensions

To create the Image component, complete the following steps:

1. Create a new movie clip symbol named Image.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to ImageSymbol.

5. Click OK.

6. Edit the new symbol.

7. On the default layer, add the following code to the first frame:

#initclip 0

// The constructor creates a new movie clip into which the image is loaded.
function Image ( ) {
[Link]("imageHolder", [Link]( ));
}

[Link] = new MovieClip( );

// The load( ) method loads an image from a URL into the image holder.

[Link] 2004-02-06
Strona 65 z 180

[Link] = function (url, w, h) {

// Attach an instance of the progress bar, which monitors the load progres
[Link]("FProgressBarSymbol", "pBar", [Link]( ));

// Load the image into imageHolder.


[Link](url);

// Set the target for the progress bar and specify


// the load progress callback method.
[Link]([Link]);
[Link]("onLoadProgress", this);

// Center the progress bar.


[Link]._x = w/2 - [Link]._width/2;
[Link]._y = h/2 - [Link]._height/2;
};

// The onLoadProgress( ) method is invoked automatically by the progress ba


// time there is some load progress.
[Link] = function ( ) {

// Check to see if the progress is 100%.


if ([Link]( ) == 100) {

// Make the progress bar invisible.


[Link]._visible = false;

// Get the height and width of the image as it is when it is originally


// loaded. This is used to properly scale the image later.
[Link] = this._height;
[Link] = this._width;

// If an onLoad callback is defined for the image component, invoke it.


[Link][[Link]](this);
}
};

// The scale( ) method resizes the image to fit within a specified width an
// height while maintaining the aspect ratio. If fill is true, the image fil
// the specified area even at the expense of cutting off one of the sides.
// Otherwise, the image is resized to fit entirely within the boundaries,
// leaving space on the sides if necessary.
[Link] = function (w, h, fill) {
var iw = [Link];
var ih = [Link];
var scale = 1;

// The scale ratios in the x and y directions are obtained


// by dividing the width and height of the boundaries by the
// width and height of the original image.
var xscale = w/iw;
var yscale = h/ih;

if (fill) {
// Set scale to the larger of xscale and yscale.
scale = (xscale > yscale) ? xscale : yscale;
} else {
// set scale to the smaller of xscale and yscale.
scale = (xscale > yscale) ? yscale : xscale;
}

// Set the _xscale and _yscale values of the component to the value of sca
// times 100 (which converts the scale ratio to a percentage).

[Link] 2004-02-06
Strona 66 z 180

this._xscale = scale * 100;


this._yscale = scale * 100;
};

// Set the onLoad callback where the function name is given as a string and
// path is an optional parameter indicating the path to the callback function
[Link] = function (functionName, path) {
if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link]("ImageSymbol", Image);

#endinitclip

The Image component is short and uncomplicated. Each of the methods is straightforward. Let's
at each of them more closely to review what each one does and how.

The load( ) method initiates the loading of the image into the image holder movie clip with the
loadMovie( ) method. Additionally, the load( ) method creates a progress bar and sets it to moni
load progress of the image using the setLoadTarget( ) method. Also, using the setChangeHandle
method, we tell the progress bar to call the onLoadProgress( ) method of the image component
whenever there is any load progress. We refer to the Image component using the keyword this
refers to the component from which the load( ) method was invoked in the first place.

[Link] = function (url, w, h) {


[Link]("FProgressBarSymbol", "pBar", [Link]( ));
[Link](url);
[Link]([Link]);
[Link]("onLoadProgress", this);
[Link]._x = w/2 - [Link]._width/2;
[Link]._y = h/2 - [Link]._height/2;
};

The onLoadProgress( ) method is invoked automatically by the progress bar whenever there is a
progress made with the loading image. We want to wait until the image is completely loaded, so
use the getPercentComplete( ) method to check whether the percentage loaded is equal to 100.
then we make the progress bar invisible (the loaded image is visible, and we don't want the prog
bar to obscure it). Additionally, we get the original height and width of the image, which is neces
for proper scaling. And finally, if there is an onLoad callback defined for the component, we invok

[Link] = function ( ) {
if ([Link]( ) == 100) {
[Link]._visible = false;
[Link] = this._height;
[Link] = this._width;
[Link][[Link]](this);
}
};

The scale( ) method is the most complex of all the methods of the Image component. But even s
probably looks scarier than it really is. The w and h parameters tell the method the dimensions o
area into which we want the image to fit. Given these dimensions, and the dimensions of the orig
image size, we can find the scale ratios in the x and y directions by dividing the width and height
the new area by the width and height of the original image size.

For example, if the new area's dimensions are 120 x 60, and the original image size is 240 x 120
xscale and yscale ratios are both 1/2. In other words, we want to scale the image to 50% of th

[Link] 2004-02-06
Strona 67 z 180

original size. Now, if we didn't care about the aspect ratio of the image, we wouldn't need to per
any further calculations. However, we want to make sure that the image doesn't end up looking
squished. For example, if the original image dimensions are 240 x 120, but the new area's dimen
are 120 x 90, the xscale and yscale ratios are not equal, and the image would be squished. We
to use the same ratio to set the scale properties in both the x and y directions. Therefore, we ne
determine which of the ratios to use. If the fill parameter is true, we want use the larger of th
ratios so that the image fills the entire area, even though some of the image might extend beyon
boundaries. Otherwise, we use the smaller ratio, since that will ensure that the entire image fits
the boundaries. Then, once we have determined the correct ratio, we set the _xscale and _ysca
properties of the component to the ratio times 100 to create a percentage.

[Link] = function (w, h, fill) {


var iw = [Link];
var ih = [Link];
var scale = 1;
var xscale = w/iw;
var yscale = h/ih;
if (fill) {
scale = (xscale > yscale) ? xscale : yscale;
} else {
scale = (xscale > yscale) ? yscale : xscale;
}
this._xscale = scale * 100;
this._yscale = scale * 100;
};

The setOnLoad( ) method enables you to specify an onLoad callback function. When you call this
method you must provide it a string name of a function. Optionally, you can also specify the path
which the function can be found. If no path is specified, the component looks to the parent timel

[Link] = function (functionName, path) {


if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

25.3.2 Designing the Image View Pane


Before we get to the preview pane, we need to first look at its subelements, the Image View Pan
components. The image view panes load the low-resolution images so that they can be viewed,
dragged, resized, collapsed/expanded, and closed. Figure 25-1 shows an example of a preview p
with two opened image viewers.

Figure 25-1. A preview pane with two opened image viewers

[Link] 2004-02-06
Strona 68 z 180

The Image Viewer component includes a title bar, a close button, a frame/outline, a resize butto
an Image component. The image viewer should have the following functionality:

z Loading an image using the Image component

z Automatically sizing itself to match the dimensions of the loaded image

z Displaying the image title in the title bar

z Becoming draggable when the title bar is clicked

z Collapsing/expanding when the title bar is double-clicked

z Closing when the close button is clicked

z Resizing when the resize button is dragged

Follow these steps to create the image view pane component:

1. Create a new movie clip symbol named ImageViewPane.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to ImageViewPaneSymbol.

5. Click OK.

6. Edit the new symbol.

7. On the default layer, add the following code to the first frame:

#initclip 1

// The constructor creates title bar and view pane movie clips, and within
// the view pane movie clip, it loads an instance of the image component.
function ImageViewPane ( ) {
[Link]("titleBar", [Link]( ));
[Link]("viewPane", [Link]( ));

[Link] 2004-02-06
Strona 69 z 180

[Link]("ImageSymbol", "img", [Link](


}

[Link] = new MovieClip( );

// The load( ) method loads the image from the specified


// URL and sets the title text.
[Link] = function (url, title) {

// Draw the title bar and the view pane.


[Link](title, 200, 20);
[Link](200, 100);

// Call the load( ) method of the image component.


[Link](url, 200, 100);

// Set the onLoad callback function of the Image component to the


// onImageLoad( ) method of the image view pane.
[Link]("onImageLoad", this);
};

// The open( ) method opens the component if it has otherwise been closed.
[Link] = function ( ) {

// Make sure everything is visible.


[Link]._visible = true;
this._visible = true;

// Call the onSelect callback if it is defined.


[Link][[Link]](this);
};

// The makeViewPane( ) method draws the view pane portion of the component,
// the width and height.
[Link] = function (w, h) {

// If the view pane frame is undefined, create the movie clip. If the fram
// a greater depth than the Image component instance, swap depths so that
// image is not hidden.
if ([Link] == undefined) {
[Link]("frame", [Link](
if ([Link]() > [Link]( )) {
[Link]([Link]);
}
}

// Clear any existing frame and draw a rectangle to the specified dimension
with ([Link]) {
clear( );
lineStyle(0, 0x000000, 100);
beginFill(0xFFFFFF, 100);
drawRectangle(w, h);
endFill( );
_x = w/2;
_y = h/2 + 21;
}

// If the resize button is undefined, create it.


if ([Link] == undefined) {
[Link]("resizeBtn",
[Link]( ));

// Draw a triangle.
with ([Link]) {

[Link] 2004-02-06
Strona 70 z 180

lineStyle(0, 0x000000, 100);


beginFill(0xFFFFFF, 100);
drawTriangle(10, 10, 90, 180, -5, 15);
endFill( );
}

// When the resize button is pressed, make it draggable and set the
// isBeingResized property of the image view pane instance to true.
[Link] = function ( ) {
var viewer = this._parent._parent;
[Link] = true;
[Link]( );
};

// When the resize button is released, do the opposite of onPress( ).


[Link] = function ( ) {
var viewer = this._parent._parent;
[Link] = false;
[Link]( );
};
}

// Position the resize button in the lower-right corner of the image view p
[Link]._x = w;
[Link]._y = h;
};

// Create the title bar, given the title and the width and height.
[Link] = function (title, w, h) {

// If the bar portion of the title bar is undefined, create it.


if ([Link] == undefined) {
[Link]("bar", [Link]( ))

[Link] = function ( ) {
var viewer = this._parent._parent;

// Invoke the onSelect callback function.


[Link][[Link]](viewer);

// If the user double-clicked the title bar, expand/collapse the view p


// Otherwise, it's a single click, so make the image view pane draggab
var currentTime = getTimer( );
if (currentTime - [Link] < 500) {
[Link]._visible = ![Link]._visible;
} else {
[Link]( );
}
[Link] = currentTime;
};

// When the bar is released, stop dragging the image view pane and invok
// onUpdate( ) callback function.
[Link] = function ( ) {
viewer = this._parent._parent;
[Link]( );
[Link][[Link]](viewer);
}
}

// Draw (or redraw) the rectangle.


with ([Link]) {
clear( );
lineStyle(0, 0, 100);

[Link] 2004-02-06
Strona 71 z 180

beginFill(0xDFDFDF, 100);
drawRectangle(w, h);
endFill( );
_x = w/2;
_y = h/2;
}

// If the title text field is not yet defined, create it.


if ([Link] == undefined) {
[Link]("title", [Link]( ),
0, 0, 0, 0);
[Link] = false;
}

// Set the width of the title to the width of the image view pane.
[Link]._width = w;
[Link]._height = h;

// Assign the title to the title text field.


[Link] = title;

// If the close button is not yet defined, create it.


if ([Link] == undefined) {
[Link]("closeBtn", [Link]

// Draw a 10 x 10 square.
with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0xE7E7E7, 100);
drawRectangle(10, 10);
endFill( );
}

// When the close button is released, hide the entire image view pane.
[Link] = function ( ) {
this._parent._parent._visible = false;
};
}

// Position the close button at the upper-right corner of the image view p
[Link]._x = w - 10;
[Link]._y = 10;
};

// The onImageLoad( ) method is invoked automatically when the image has


// completed loading.
[Link] = function ( ) {
var img = [Link];

// Create the title bar and view pane to fit the loaded image.
[Link]([Link], img._width, 20);
[Link](img._width, img._height);

// Move the image down by 21 pixels so it does not cover the title bar.
img._y += 21;

// Call the onUpdate callback function.


[Link][[Link]](this);
};

// The onEnterFrame( ) method continually checks to see if the view pane is


// resized. If so, it calls the resize( ) method with the coordinates of th
// resize button.
[Link] = function ( ) {

[Link] 2004-02-06
Strona 72 z 180

if ([Link]) {
[Link]([Link]._x, [Link]._y);
}
};

// The resize( ) method resizes the image, the view pane, and the title bar
// the specified width and height.
[Link] = function (w, h) {

// Call makeViewPane( ) and makeTitleBar( ) to


// redraw the view pane and title bar.
[Link](w, h);
[Link]([Link], w, 20);

// Call the scale( ) method of the image component to resize the loaded im
[Link](w, h);

// Reposition the Image component so that it is centered in the view pane.


[Link]._x = w/2 - [Link]._width/2;
[Link]._y = h/2 - [Link]._height/2 + 21;
};

// Set the onSelect and onUpdate callback functions.


[Link] = function (functionName, path) {
if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link] = function (functionName, path) {


if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link]("ImageViewPaneSymbol", ImageViewPane);

#endinitclip

Much of the Image View Pane component is quite straightforward. However, there are some part
deserve closer examination. Undoubtedly, it will be much clearer once we look at what is going o

The constructor instantiates several of the main parts of the component. The title bar movie clip
rectangle that appears at the top of the image and displays the title. The view pane is the remain
portion of the image view pane that appears below the title bar and contains the image itself.

function ImageViewPane ( ) {
[Link]("titleBar", [Link]( ));
[Link]("viewPane", [Link]( ));
[Link]("ImageSymbol", "img", [Link]( ));
}

To avoid unnecessarily reloading images, the close button merely makes the image view pane
invisible. Therefore, the open( ) method can open a closed image view pane by setting the _visi
properties to true. Additionally, the open( ) method invokes the onSelect callback function beca
when an image view pane is opened, it should be selected as well.

[Link] = function ( ) {

[Link] 2004-02-06
Strona 73 z 180

[Link]._visible = true;
this._visible = true;
[Link][[Link]](this);
};

The makeViewPane( ) method is long, but it is not very complicated when you look at it more clo

First, we create the frame movie clip if it has not been created. The frame is the outline and
background in which the image appears. Therefore, if the frame is created such that the depth is
greater than the Image component, we swap their depths so that the image is not hidden behind
frame:

if ([Link] == undefined) {
[Link]("frame", [Link]( ));
if ([Link]() > [Link]( )) {
[Link]([Link]);
}
}

Once we are sure the frame exists, we draw a filled rectangle within it. The clear( ) method clear
any content that might have previously existed within the movie clip. Then we position the frame
correctly. Since the drawRectangle( ) method draws a rectangle with its center at (0,0), we mov
rectangle down and to the right by half its height and width. The frame is moved down another 2
pixels to accommodate the title bar (which is 20 pixels high).

with ([Link]) {
clear( );
lineStyle(0, 0x000000, 100);
beginFill(0xFFFFFF, 100);
drawRectangle(w, h);
endFill( );
_x = w/2;
_y = h/2 + 21;
}

Next, we create the resize button and draw a triangle in it, if it doesn't exist. Also, we assign eve
handler methods to the button so that it is draggable when pressed. The isBeingResized prope
tells the image view pane whether the image is being resized.

if ([Link] == undefined) {
[Link]("resizeBtn", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0xFFFFFF, 100);
drawTriangle(10, 10, 90, 180, -5, 15);
endFill( );
}
[Link] = function ( ) {
var viewer = this._parent._parent;
[Link] = true;
[Link]( );
};
[Link] = function ( ) {
var viewer = this._parent._parent;
[Link] = false;
[Link]( );
};
}

The resize button should always appear in the lower-right corner of the image view pane:

[Link] 2004-02-06
Strona 74 z 180

[Link]._x = w;
[Link]._y = h;

Like the makeViewPane( ) method, the makeTitleBar( ) method is long but not overly complex. L
take a closer look at some of the code.

First, we create the bar portion of the title bar if it doesn't exist. We also assign onPress( ) and
onRelease( ) methods to it. When the bar is pressed, the onPress( ) method determines if the cli
a single-click or a double-click. A single-click initiates a startDrag( ) action. A double-click toggle
view pane's visibility, creating the effect of collapsing and expanding the view pane. We check fo
double-clicks by recording the time (using getTimer( )) of each press. If two presses occur within
milliseconds, it constitutes a double-click.

if ([Link] == undefined) {
[Link]("bar", [Link]( ));

[Link] = function ( ) {
var viewer = this._parent._parent;
[Link][[Link]](viewer);
var currentTime = getTimer( );
if (currentTime - [Link] < 500) {
[Link]._visible = ![Link]._visible;
} else {
[Link]( );
}
[Link] = currentTime;
};

[Link] = function ( ) {
viewer = this._parent._parent;
[Link]( );
[Link][[Link]](viewer);
};
}

Once we know the bar exists, we want to draw a rectangle with the specified dimensions. The cle
method makes sure that the previous content is cleared out first:

with ([Link]) {
clear( );
lineStyle(0, 0x000000, 100);
beginFill(0xDFDFDF, 100);
drawRectangle(w, h);
endFill( );
_x = w/2;
_y = h/2;
}

If the title text field is undefined, we create it. Also, the text field should be nonselectable. This i
important because otherwise it could interfere with the events of the bar portion of the title bar.

if ([Link] == undefined) {
[Link]("title", [Link]( ),
0, 0, 0, 0);
[Link] = false;
}

If the close button is not yet defined, we create it and draw a square within it. When the close bu
is released, the visibility of the image view pane is set to false. This creates the effect of closing
image view pane. However, since the component is still on the Stage, it can later be reopened w
having the reload the image.

[Link] 2004-02-06
Strona 75 z 180

if ([Link] == undefined) {
[Link]("closeBtn", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0xE7E7E7, 100);
drawRectangle(10, 10);
endFill( );
}
[Link] = function ( ) {
this._parent._parent._visible = false;
};
}

When the image is loaded, the onImageLoad( ) method is invoked automatically. This is importa
because once the image is loaded, we want to correctly size the image view pane to accommoda
image.

[Link] = function ( ) {
var img = [Link];
[Link]([Link], img._width, 20);
[Link](img._width, img._height);
img._y += 21;
[Link][[Link]](this);
};

The isBeingResized property is set to true only when the resize button is being pressed. When
occurs, we invoke the resize( ) method and give it the coordinates of the resize button (which is
always in the lower-right corner of the image, at the maximum width and height).

[Link] = function ( ) {
if ([Link]) {
[Link]([Link]._x, [Link]._y);
}
};

25.3.3 Designing the Preview Pane


The Preview Pane component serves as a scrolling container for the image view panes for the low
resolution images.

Complete the following steps to create the Preview Pane component:

1. Create a new movie clip symbol named PreviewPane.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to PreviewPaneSymbol.

5. Click OK.

6. Edit the new symbol.

7. On the default layer, add the following code to the first frame:

#initclip

function PreviewPane ( ) {

[Link] 2004-02-06
Strona 76 z 180

// Add a scroll pane to the component, within which


// the preview images are contained.
[Link]("FScrollPaneSymbol", "sp", [Link]( ));

// Create a movie clip for the scroll content.


[Link]("content", _root.getNewDepth( ));

// Create a movie clip within the scroll content and draw a rectangle in i
// The background movie clip is used to properly align the scroll contents
// the scroll pane.
[Link]("background", [Link](
with ([Link]) {
lineStyle(0, 0x000000, 0);
drawRectangle(320, 240, 0, 0, 160, 120);
}

// Call updateViewer( ) to set the scroll pane to target the scroll conten
[Link]( );

// Create arrays to keep track of the images that are currently opened and
// images that have been loaded (whether open or not).
[Link] = new Array( );
[Link] = new Array( );
}

[Link] = new MovieClip( );

// Return a reference to an Image View Pane component if one already exists


// the specified URL. Image view panes may be within the preview pane but ma
// be visible if they were closed.
[Link] = function (url) {
for (var i = 0; i < [Link]; i++) {
if ([Link][i].url == url) {
return [Link][i].vp;
}
}
return false;
};

[Link] = function (w, h) {


[Link](w, h);
};

// The updateViewer( ) method adjusts the scroll content. This method is inv
// every time there is a change made to the scroll content.
[Link] = function ( ) {
[Link]._width = [Link]._width;
[Link]._height = [Link]._height;
[Link]([Link]);
if ([Link]._width > [Link]._width - 10) {
[Link](true);
}
};

// The bringToFront( ) method brings the


// specified image view pane to the foreground
[Link] = function (viewer) {

// Remove and return the last value in the currentViewAr array (the image v
// pane with the greatest depth).
var topViewer = [Link]( );

// If the specified image view pane is not already on top . . .

[Link] 2004-02-06
Strona 77 z 180

if (viewer != topViewer) {

// Swap the depths of the selected view pane with the top view pane, brin
// the selected view pane to the foreground.
[Link](topViewer);

// Search through the currentViewAr array for the index of the selected
// view pane and assign the value of the (previously) top view pane to th
// index in the array.
for (var i = 0; i < [Link]; i++) {
if ([Link][i] == viewer) {
break;
}
}
[Link][i] = topViewer;
}

// Append the selected viewer to the end of the currentViewAr array.


[Link](viewer);
};

// The open( ) method opens an image view pane given a URL and a title.
[Link] = function (url, title) {
var uniqueVal = [Link]( );

// If an image view pane with the same URL is already loaded, isURLLoaded(
// returns a reference to it. Otherwise, it returns false.
var loaded = [Link](url);

// If loaded is . . .
if (loaded == false) {
// . . . false, then create a new image view pane and load the image in
var vp = [Link]("ImageViewPaneSymbol", "vp" + uniqueVa
uniqueVal);
[Link](url, title);

// Set the onSelect callback function to the bringToFront( ) method


// so that when the image view pane is selected, it is always
// brought to the foreground.
[Link]("bringToFront", this);

// Set the onUpdate callback function to the updateViewer( )


// method so that when the image view pane is updated in any way,
// the preview pane is also updated.
[Link]("updateViewer", this);

// Add the image view pane to the currentViewAr and loadedAr arrays.
[Link](vp);
[Link]({url: url, vp: vp});
} else {
// If loaded is true, call the open( ) method of the image pane.
[Link]( );
}
};

[Link]("PreviewPaneSymbol", PreviewPane);

#endinitclip

The Preview Pane component is not very long, nor does it introduce too many new concepts. How
there are a few areas that could use a little further study.

The constructor creates a scroll pane and a movie clip for the scroll content. This part is standard

[Link] 2004-02-06
Strona 78 z 180

However, we use a little trick to keep the scroll content correctly positioned within the scroll pan
create the background movie clip within the scroll content clip. We then draw a rectangle within
background with an invisible outline! This may seem a little confusing at first, but there is a good
reason why we do it this way. A scroll pane always aligns the scroll content so that the upper-lef
corner of the actual content is in the upper-left corner of the scroll pane. The scroll pane doesn't
the scroll content relative to the registration point of the scroll content movie clip. The problem i
if the user opens one image view pane in the preview pane, that one image view pane is aligned
that the upper-left corner is in the upper-left corner of the preview pane. If the user then drags t
image view pane, the scroll pane will realign the scroll content such that the image view pane ap
in the same position as it did previously. By adding a background movie clip to the scroll content
is aligned with the upper-left corner at the scroll content's registration point, we force the scroll
to align the scroll content to the registration point (in most cases).

function PreviewPane ( ) {
[Link]("FScrollPaneSymbol", "sp", [Link]( ));
[Link]("content", _root.getNewDepth( ));
[Link]("background", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 0);
drawRectangle(320, 240, 0, 0, 160, 120);
}
[Link]( );
[Link] = new Array( );
[Link] = new Array( );
}

To close an image view pane we just make it invisible. This saves the user from having to reload
image if he decides to open it again. The isURLLoaded( ) method searches through the loadedAr
to find any image view pane (even an invisible one) that has already been loaded for the specifie
URL. If none is found, the method returns false.

[Link] = function (url) {


for (var i = 0; i < [Link]; i++) {
if ([Link][i].url == url) {
return [Link][i].vp;
}
}
return false;
};

We need to create the updateViewer( ) method to update the scroll pane when the scroll content
changes. First, we resize the background movie clip so that its dimensions match the rest of the
content. This helps to ensure that the content remains properly aligned. Then, the setScrollConte
method resets the scroll content for the scroll pane with the updated info. Finally, if the scroll pa
a vertical scrollbar, it is possible that part of the scroll contents can be hidden without the possib
scrolling horizontally. Therefore, if the scroll content's width is greater than the width of the scro
minus the width of a scroll bar, we tell the scroll pane to display a horizontal scroll bar.

[Link] = function ( ) {
[Link]._width = [Link]._width;
[Link]._height = [Link]._height;
[Link]([Link]);
if ([Link]._width > [Link]._width - 10) {
[Link](true);
}
};

When an image view pane is selected, it should be brought in front of the rest of the content. Th
currentViewAr array contains references to all the opened image view panes; the elements are
order of depth, with the greatest depth at the end of the array. Therefore, we can get a referenc

[Link] 2004-02-06
Strona 79 z 180

the image view pane that is currently on top by calling the pop( ) method of the currentViewAr
which also removes the element from the end of the array and returns its value. If the top view
and the selected view pane are not the same, we swap their depths and swap the positions of th
elements in the array.

[Link] = function (viewer) {


var topViewer = [Link]( );
if (viewer != topViewer) {
[Link](topViewer);
for (var i = 0; i < [Link]; i++) {
if ([Link][i] == viewer) {
break;
}
}
[Link][i] = topViewer;
}
[Link](viewer);
};

The open( ) method opens an image, given a URL and a title, and is designed such that it can
determine whether to make an existing image view pane visible or create a new image view pan

[Link] = function (url, title) {


var uniqueVal = [Link]( );
var loaded = [Link](url);
if (loaded == false) {
var vp = [Link]("ImageViewPaneSymbol", "vp" + uniqueVal,
uniqueVal);
[Link](url, title);
[Link]("bringToFront", this);
[Link]("updateViewer", this);
[Link](vp);
[Link]({url: url, vp: vp});
} else {
[Link]( );
}
};

25.3.4 Designing the Sequence Viewer


The sequence viewer component is the portion of the application that plays back the images as a
show. The component should fill the Flash Player with a black background and play the full image
sequence using setInterval( ) to determine when to change images.

Follow these steps to create the Sequence Viewer component:

1. Create a new movie clip symbol named SequenceViewer.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to SequenceViewerSymbol.

5. Click OK.

6. Edit the new symbol.

7. On the default layer, add the following code to the first frame:

[Link] 2004-02-06
Strona 80 z 180

#initclip

function SequenceViewer ( ) {

// The array of image items.


[Link] = new Array( );

// Create the black rectangle to fill the background of the Player.


[Link]("background", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0, 100);
drawRectangle([Link], [Link]);
endFill( );
_x = [Link]/2;
_y = [Link]/2;
}

// Make the viewer invisible to start.


this._visible = false;
}

// SequenceViewer subclasses MovieClip.


[Link] = new MovieClip( );

// Add an image to the sequence viewer by URL.


[Link] = function (url) {
var uniqueVal = [Link]( );

// Add an image component and load the image into it.


var img = [Link]("ImageSymbol", "image" + uniqueVal, uniqueVal);
[Link](url);

// Set the onLoad callback for the image component


// to the onImageLoad( ) method.
[Link]("onImageLoad", this);

// Add the image to the items array.


[Link](img);

// Make the image invisible to start.


img._visible = false;
};

// When the image loads, scale it to fill in the Player.


[Link] = function (img) {
[Link]([Link], [Link], true);
};

// Change the order of an image in the sequence.


[Link] = function (prevIndex, newIndex) {
var img = [Link][prevIndex];
[Link](prevIndex, 1);
[Link](newIndex, 0, img);
};

// Start the playback of the images.


[Link] = function (intervalm randomize) {

// Make the viewer visible.


this._visible = true;

// Set the itemIndex property to -1 so that the first image shown is index
[Link] = -1;

[Link] 2004-02-06
Strona 81 z 180

// Call the nextImage( ) method at the specified interval. Also, pass it


// value of the randomize parameter.
[Link] = setInterval(this, "nextImage", interval, randomize);
};

// The stop( ) method stops the playback of the images.


[Link] = function ( ) {

// Clear the interval.


clearInterval([Link]);

// Make the viewer invisible.


this._visible = false;

// Set the current image to be invisible. Otherwise, when the sequence is


// played again, this image will still be visible.
[Link][[Link]]._visible = false;
[Link] = 0;
};

// The nextImage( ) method is called at the specified


// interval when the sequence is played.
[Link] = function (randomize) {

// The previous image is made invisible and the index is incremented.


[Link][[Link]++]._visible = false;

// If we're at the last image, start over at the first image.


if ([Link] > [Link] - 1) {
[Link] = 0;
}

// If randomize is true, create a random index.


if (randomize) {
[Link] = [Link]([Link]( ) * ([Link] - 1));
}

// Make the item with the specified index visible.


[Link][[Link]]._visible = true;
};

// The removeItem( ) method removes the item with the specified index from
// movie and from the array.
[Link] = function (index) {
[Link][index].removeMovieClip( );
[Link](index, 1);
};

[Link]("SequenceViewerSymbol", SequenceViewer);

#endinitclip

The Sequence Viewer component is not very complicated. Let's shed some light on the parts tha
might appear to be more complicated than they really are.

The constructor does three things. First of all, it creates the items array property, which is used
hold references to all the Image components. Next, it creates the background movie clip, which
black rectangle that fills the Player while the sequence viewer is playing. And finally, the constru
initializes the viewer as invisible because we don't want to see the sequence viewer until the use
selects the option to play the sequence.

function SequenceViewer ( ) {

[Link] 2004-02-06
Strona 82 z 180

[Link] = new Array( );


[Link]("background", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0, 100);
drawRectangle([Link], [Link]);
endFill( );
_x = [Link]/2;
_y = [Link]/2;
}
this._visible = false;
}

The addItem( ) method is a straightforward method that adds a new Image component to the vi
When the image is added, we also append it to the items array. The items array is what determ
the order in which the sequence plays back, so each new image is added to the end of the seque
Additionally, we want to make each new image invisible, because the playback works by turning
and off the visibility of the images in sequence.

[Link] = function (url) {


var uniqueVal = [Link]( );
var img = [Link]("ImageSymbol", "image" + uniqueVal, uniqueVal);
[Link](url);
[Link]("onImageLoad", this);
[Link](img);
img._visible = false;
};

The changeOrder( ) method changes the order of an element in the sequence, given the original
and the new index. We accomplish this by first deleting the element at the old index and then in
it into the array at the new index.

[Link] = function (prevIndex, newIndex) {


var img = [Link][prevIndex];
[Link](prevIndex, 1);
[Link](newIndex, 0, img);
};

We want to play back the images, one at a time, at a set interval. Therefore, we use the setInter
function to repeatedly call a method that updates the image display. In this case, we save the in
ID to a property (playInterval) so that we can clear the interval when the user stops the playb

[Link] = function (interval, randomize) {


this._visible = true;
[Link] = -1;
[Link] = setInterval(this, "nextImage", interval, randomize);
};

The stop( ) method clears the play interval, first and foremost. This stops the images from being
played. We also want to make the viewer invisible again. Additionally, it is important that we res
last image that was visible to be invisible again. If we didn't do this, there could be problems wit
overlapping images when the sequence is played again.

[Link] = function ( ) {
clearInterval([Link]);
this._visible = false;
[Link][[Link]]._visible = false;
[Link] = 0;
};

The nextImage( ) method is called at the interval when the sequence is played. Each time the m

[Link] 2004-02-06
Strona 83 z 180

is called, we make the previous image invisible and make the current image visible. In this case,
increment the itemIndex value within the first line. Because the increment operator (++) appea
the end of the variable, the value is incremented after the previous value is used in the first line.
saves a line of code, although you could insert another line after the first and increment the valu
there instead.

[Link] = function (randomize) {


[Link][[Link]++]._visible = false;
if ([Link] > [Link] - 1) {
[Link] = 0;
}
if (randomize) {
[Link] = [Link]([Link]( ) * ([Link] - 1));
}
[Link][[Link]]._visible = true;
};

25.3.5 Designing the Sequencer Item Component


The sequencer is composed of sequence items. The items are rectangles into which thumbnails a
loaded. Figure 25-2 shows an example of the sequencer with two sequence items in it.

Figure 25-2. The sequencer with two sequence items

The Sequence Item component should have the following functionality:

z Loads a thumbnail from a URL

z Is selectable (outline highlights blue to indicate selection)

z Can be dragged and dropped within the constraints of the sequencer

Complete the following steps to create the Sequence Item component:

1. Create a new movie clip symbol named SequenceItem.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to SequenceItemSymbol.

5. Click OK.

6. Edit the new symbol.

7. On the default layer, add the following code to the first frame:

#initclip 1

function SequencerItem ( ) {

[Link] 2004-02-06
Strona 84 z 180

// Create the background movie clip (the rectangular frame). Add a fill an
// outline to the background and draw a filled rectangle and an outline in
// them.
[Link]("background", [Link]( ));
[Link]("fill", [Link](
[Link]("outline",
[Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0xFFFFFF, 100);
drawRectangle(100, 50);
endFill( );
_x = 50;
_y = 25;
}
with ([Link]) {
lineStyle(0, 0x000000, 100);
drawRectangle(100, 50);
_x = 50;
_y = 25;
}

// Create a color object to target the outline.


[Link] = new Color([Link]);
};

[Link] = new MovieClip( );

// The loadImage( ) method adds an image component and loads an image from
[Link] = function (url) {
[Link] = url;
[Link]("ImageSymbol", "img", [Link]( ));
[Link](url, 100, 50);
[Link]("onImageLoad", this);
};

// The onImageLoad( ) method is the callback function that is invoked


// automatically when the image has completed loading. At that point, it sca
// the image to fit within the sequence item frame and moves it to the cente
[Link] = function (imageHolder) {
[Link](100, 50);
[Link]._x = [Link]._width/2 - [Link]._width/2;
[Link]._y = [Link]._height/2 - [Link]._height/2;
};

// The onEnterFrame( ) method continually checks to see if the component is


// dragged (dragging is set to true when the component is pressed). If it is
// method performs a series of actions.
[Link] = function ( ) {
if ([Link]) {

// Loop through all the other sequence items in the sequencer (this._par
// and if the item that is being dragged has a lower depth than another
// that it is being dragged over, swap depths.
for (var mc in this._parent) {
if ([Link](this._parent[mc]) && [Link]( ) <
this._parent[mc].getDepth( )) {
[Link](this._parent[mc]);
}
}

// Get a reference to the sequencer's scroll pane and its scroll content
var sp = this._parent._parent.sp;
var sc = [Link]( );

[Link] 2004-02-06
Strona 85 z 180

// If the mouse is outside the scroll pane, increment or decrement the s


// position accordingly. Also, move the sequencer item accordingly.
if (sp._xmouse > sp._width) {
[Link]([Link]( ).x + 5, 0);
} else if (sp._xmouse < 0) {
[Link]([Link]( ).x - 5, 0);
}
this._x = sc._xmouse - [Link];
}
};

[Link] = function ( ) {

// Get the x coordinate of the mouse within the item's coordinate system a
// time the mouse was pressed.
[Link] = this._xmouse;

// Get the x coordinate of the item before it is moved. This is used to sn


// items to the correct positions.
[Link] = this._x;

// Set dragging to true so that the actions in


// the onEnterFrame( ) method are activated.
[Link] = true;

// Make the item draggable along the X axis within the sequencer.
[Link](false, 0, this._y, this._parent._width, this._y);

// Toggle the selected state.


[Link] = ![Link];
if ([Link]) {
[Link](0xFF);
[Link][[Link]](this);
}
};

[Link] = function ( ) {

// Set dragging to false so the onEnterFrame( ) actions


// stop executing and stop the draggability.
[Link] = false;
[Link]( );

// Get the drop target and split it into an array using a slash as the
// delimiter. The value of the drop target is given in Flash 4 syntax, so
// slashes are used where dots are used in Flash 5+ syntax.
var itemBAr = this._droptarget.split("/");

// Remove the end items from the array until


// the last element contains the value "item".
while(itemBAr[[Link] - 1].indexOf("item") == -1) {
[Link]( );
}

// Call the onDrop( ) callback function, and pass it the reference to thi
// and the drop target item.
[Link][[Link]](this, eval([Link]("/")));

if (![Link]) {
[Link]( );
}
};

[Link] 2004-02-06
Strona 86 z 180

// Set the onReleaseOutside( ) method to do the same thing as onRelease( )


[Link] = [Link]

[Link] = function ( ) {
[Link] = false;
[Link](0);
};

[Link] = function (functionName, path) {


if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link] = function (functionName, path) {


if (path == undefined) {
path = this._parent;
}
[Link] = functionName;
[Link] = path;
};

[Link]("SequencerItemSymbol", SequencerItem);

#endinitclip

The Sequencer Item component contains many code elements that are similar to the other
components that we have already created throughout this chapter. However, there are a few par
the code that involve techniques that are unique to the Sequence Item component, and they bea
further discussion.

In the onEnterFrame( ) method, the component continually checks to see if the property named
dragging is true. The dragging property is true when, and only when, the user is pressing the
component. This technique is nothing new, as we have used it throughout several of the other
components. What is new is the rest of the code.

First of all, when the sequencer item is being dragged, we want to make sure that it appears abo
the other sequence items that might be in the sequencer. We achieve this by performing a hit te
every other sequencer item and swapping depths with any item that the selected component is
obscured by (overlapping and beneath). Sequencer items are contained within a scroll content m
clip within the sequencer, so we can loop through all the elements of the parent movie clip using
for...in statement. Then, we check to see if the selected item is touching another sequencer item
a hitTest( ) method. If the selected item is beneath an item for which the hit test is true, we use
swapDepths( ) to bring the selected item forward.

for (var mc in this._parent) {


if ([Link](this._parent[mc]) && [Link]( ) <
this._parent[mc].getDepth( )) {
[Link](this._parent[mc]);
}
}

The next part of the onEnterFrame( ) method code may look like the most challenging thus far, b
is not so bad once you understand the problem we are trying to solve. When the user clicks on t
sequencer item and then drags the mouse pointer beyond the sequencer scroll pane, the scroll p
does not scroll. This is not the desired behavior. We want the scroll pane to automatically scroll i
same direction as the mouse pointer. To accomplish this, we continually compare the position of
mouse pointer to the boundaries of the scroll pane (given by 0 on the left and _width on the righ
the mouse position is greater than the width of the scroll pane, we increment the scroll pane's sc

[Link] 2004-02-06
Strona 87 z 180

position by five. On the other hand, if the mouse position is less than zero (meaning it is to the l
the scroll pane), we decrement the scroll position by five. In addition to this, we move the seque
item; otherwise, the sequencer item and the scroll pane content would be out of synch. In the on
( ) method, we recorded the value of the x coordinate where the mouse clicked on the item to be
with. We then set the x coordinate of the item to the x coordinate of the mouse pointer minus th
offset at which the user clicked on the item.

var sp = this._parent._parent.sp;
var sc = [Link]( );
if (sp._xmouse > sp._width) {
[Link]([Link]( ).x + 5, 0);
} else if (sp._xmouse < 0) {
[Link]([Link]( ).x - 5, 0);
}
this._x = sc._xmouse - [Link];

The onPress( ) method contains only a few things that need to be mentioned here. The x coordin
the sequencer item at the time it is clicked is saved in the startPosition property. This value is
later, when the item is released (and the dropItem( ) method is called), to determine where to p
the item. Also, the startDrag( ) method constrains the area in which the item can be moved to a
horizontal line spanning the width of the sequencer scroll pane's contents.

[Link] = function ( ) {
[Link] = this._xmouse;
[Link] = this._x;
[Link] = true;
[Link](false, 0, this._y, this._parent._width, this._y);
[Link] = ![Link];
if ([Link]) {
[Link](0xFF);
[Link][[Link]](this);
}
};

The onRelease( ) method involves some code that might appear confusing until we look at it in a
more detail. This method attempts to get a reference to a sequencer item onto which the selecte
sequencer item is dropped. We then call the dropItem( ) method of the sequencer with both a
reference to the selected item and the drop target item. This is relatively simple, except for the f
that Flash reports the innermost nested movie clip as the drop target. This means that if, for exa
the selected sequencer item is dragged over and dropped onto the sequencer item instance nam
item3, the value returned by the _droptarget property might be "/seqncr/sc/item3/img/imageH
or "/seqncr/sc/item3/background/fill" (in which seqncr is the instance name of the sequencer on
main timeline). The reason for this is that the _droptarget property reports the nested movie cl
[Link] and [Link] instead of the parent movie clip, item3.

This is the expected behavior. However, we want to extract the portion of the path that resolves
sequencer item instance, such as "/seqncr/sc/item3". Since the parts of the path returned by
_droptarget are separated by slashes (since it is given in Flash 4 syntax), it is convenient to us
split( ) method to split the string into an array using a slash as the delimiter. At that point, we u
while loop to remove elements from the end of the array until the last element contains the subs
"item". Then, we call the onDrop( ) callback function with a reference to the selected sequencer
(this), and a reference to the drop target item. To get an actual reference to the drop target ite
have to use join( ) to reassemble the path as a Flash 4-syntax string and use eval( ) to convert t
string to a movie clip reference.

[Link] = function ( ) {
[Link] = false;
[Link]( );
var itemBAr = this._droptarget.split("/");
while(itemBAr[[Link] - 1].indexOf("item") == -1) {

[Link] 2004-02-06
Strona 88 z 180

[Link]( );
}
[Link][[Link]](this, eval([Link]("/")));
if (![Link]) {
[Link]( );
}
};

25.3.6 Designing the Sequencer Component


The sequencer is the part of the application in which the thumbnails are loaded and can be order
the user. The sequencer itself is composed of a scroll pane and sequencer item components. The
sequencer must:

z Add new sequencer items when the user chooses to add an image to the sequencer

z Remove a selected sequencer item

z Reorder sequencer items when they are dragged and dropped by the user

z Target a sequence viewer

Complete these steps to create the Sequencer component:

1. Create a new movie clip symbol named Sequencer.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to SequencerSymbol.

5. Click OK.

6. Edit the new symbol.

7. On the default layer, add the following code to the first frame:

#initclip

function Sequencer ( ) {

// Define the sequencer to have a width of 500 and a height of 75.


var w = 500;
var h = 75;

// Define the width and height for each sequencer item.


[Link] = 100;
[Link] = 50;

// Add a scroll pane and set the size of it to the width and height.
[Link]("FScrollPaneSymbol", "sp", [Link]( ));
[Link](w, h);

// Create a scroll content movie clip and create a background in it. The
// background serves a similar purpose to the background for the scroll con
// in the Preview Pane component.
[Link]("sc", [Link]( ));

[Link] 2004-02-06
Strona 89 z 180

[Link]("background", 0);
with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0xFFFFFF, 100);
drawRectangle(w, h);
endFill( );
_x += w/2;
_y += h/2;
}

// Create the array that is used to store the order of the sequencer items
[Link] = new Array( );
}

[Link] = new MovieClip( );

[Link] = function ( ) {
return [Link];
};

// Add an item to the sequencer with a URL to an image.


[Link] = function (url) {
var uniqueVal = [Link]( );

// Add a Sequencer Item component and load the image into it.
var item = [Link]("SequencerItemSymbol", "item" + uniqueVal,
uniqueVal);
[Link](url);

// Position the sequencer item such that it is to the right of any other i
// in the sequencer.
item._x += ([Link] * 105) + 5;
item._y += 5;

[Link]("dropItem", this);
[Link]("setSelected", this);

// Set the background of the scroll content to match the width of the scro
// content plus five (so that there is a five-pixel margin on the right si
// the scroll content).
[Link]._width = [Link]._width + 5;
[Link]._x = [Link]._width/2;

// Update the scroll pane view.


[Link]([Link]);

// Add the sequencer item to the items array.


[Link](item);
};

// The dropItem( ) method is invoked any time one of the sequencer items is
// released by the user. It is passed a reference to the item that was dropp
// and a reference to the drop target.
[Link] = function (itemA, itemB) {

// If the drop target (itemB) is either undefined or is the scroll content


// background, reset itemA back to the starting position.
if (itemB == undefined || itemB == [Link]) {
itemA._x = [Link];
} else {
// . . . Otherwise, set itemA to the same position as the drop target.
itemA._x = itemB._x;
var aIndex = 0;
var bIndex = 0;

[Link] 2004-02-06
Strona 90 z 180

var aSet = false;


var bSet = false;

// Loop through the sequencer items and find the indexes of itemA and it
for (var i = 0; i < [Link]; i++) {
if ([Link][i] == itemA) {
aIndex = i;
aSet = true;
} else if ([Link][i] == itemB) {
bIndex = i;
bSet = true;
}
if (aSet && bSet) {
break;
}
}

// Shift the rest of the items appropriately.


if (aIndex < bIndex) {
for (var i = aIndex + 1; i <= bIndex; i++) {
[Link][i]._x -= ([Link] + 5);
}
} else {
for (var i = bIndex; i < aIndex; i++) {
[Link][i]._x += ([Link] + 5);
}
}

// Change the order of the items.


[Link](aIndex, 1);
[Link](bIndex, 0, itemA);

// Call the changeOrder( ) method of the targeted sequence viewer so th


// order of the images during playback is correct.
[Link](aIndex, bIndex);
}
};

// Set an item to be selected, and deselect the rest of the items.


[Link] = function (item) {
for (var i = 0; i < [Link]; i++) {
if ([Link][i] == item) {
[Link] = i;
} else {
[Link][i].deselect( );
}
}
};

// The removeSelected( ) method removes the selected item from the sequence
[Link] = function ( ) {

// Shift all the items after the removed item.


for (var i = [Link] + 1; i < [Link]; i++) {
[Link][i]._x -= 105;
}

// Remove the selected Sequencer Item component.


[Link][[Link]].removeMovieClip( );

// Remove the item from the items array.


[Link]([Link], 1);

// Remove the item from the sequence viewer as well.

[Link] 2004-02-06
Strona 91 z 180

[Link]([Link]);
};

// Set the targeted sequence viewer.


[Link] = function (sequenceVwr) {
[Link] = sequenceVwr;
};

[Link]("SequencerSymbol", Sequencer);

#endinitclip

The sequencer component employs many of the same techniques as several of the other compon
throughout this chapter, so much of the code should be quite familiar to you. However, there are
code snippets that do require a little further illumination.

The dropItem( ) method is, perhaps, the most intimidating of the methods in this component cla
However, upon closer examination you will see that it is not difficult to understand. The method
to be the onDrop callback function for each of the sequencer items. This means that each time a
sequencer item is dropped, the dropItem( ) method is called and passed a reference to the item
was dropped, as well as the drop target item. We first determine whether the drop target is anot
sequencer item. If the drop target item (itemB) is undefined or the scroll content background, w
know that the item was not dropped on another sequencer item. Therefore, we want to reset the
position of the dropped item to its starting position, which is recorded in the item's startPositi
property:

if (itemB == undefined || itemB == [Link]) {


itemA._x = [Link];
}

Otherwise, if the drop target is a sequencer item, we rearrange all the sequencer items appropria
The first thing to do, therefore, is to set the position of itemA to the position of itemB. This snap
itemA to the correct slot in the sequencer.

itemA._x = itemB._x;

Once itemA is in place, we still need to accomplish several remaining tasks, for which we need to
the indexes of itemA and itemB within the items array. Therefore, we use a for loop to search fo
itemA and itemB in the items array. When we find a match, we set the appropriate variable (eit
aIndex or bIndex) to the value of the looping index (i). To make the loop more efficient, we use
Boolean variables to keep track of whether itemA and itemB have been located. Once both index
have been located, we break out of the for loop.

var aIndex = 0;
var bIndex = 0;
var aSet = false;
var bSet = false;
for (var i = 0; i < [Link]; i++) {
if ([Link][i] == itemA) {
aIndex = i;
aSet = true;
} else if ([Link][i] == itemB) {
bIndex = i;
bSet = true;
}
if (aSet && bSet) {
break;
}
}

[Link] 2004-02-06
Strona 92 z 180

Once the indexes are known, we can reposition the appropriate items. If itemA was originally
positioned to the left of itemB, we shift all the elements from (but not including) itemA through
including) itemB to the left by the width of a single item plus the 5-pixel buffer. Otherwise, if ite
was originally to the right of itemB, we shift everything from (and including) itemB up to (but no
including) itemA to the right by the width of a single item plus the 5-pixel buffer.

if (aIndex < bIndex) {


for (var i = aIndex + 1; i <= bIndex; i++) {
[Link][i]._x -= ([Link] + 5);
}
} else {
for (var i = bIndex; i < aIndex; i++) {
[Link][i]._x += ([Link] + 5);
}
}

Finally, we adjust the order of the items in the items array as well as the targeted sequence view
We use the splice( ) method to first remove itemA from the items array. Then, we use the splice
method to insert itemA back into the array at the index that was previously assigned to itemB.

[Link](aIndex, 1);
[Link](bIndex, 0, itemA);
[Link](aIndex, bIndex);

25.3.7 Designing the Menu Component


The Menu component is the part of the application that does the following:

z Reads an XML document and populates a list box with available images

z Allows users to preview low-resolution images

z Allows users to add images to the sequencer

z Allows users to start the playback of the sequence of images

The first step in designing the Menu component is to create the XML document that the menu us
populate itself. We won't actually load the XML directly from the Menu component (we do that in
main application routine), but you need to be familiar with the structure of the document to
understand parts of the Menu component code. Follow these steps:

1. Open a new text document in an external text editor.

2. Add code with the following structure to your document. You can use the URLs that are in
sample document (if you will play the Flash movie in the Standalone Player), or you can re
them with your own valid URLs to images on your own computer or the server on which yo
be serving the Flash movie. This example document includes two <image> elements. If you
to add more images to your application, you can add more <image> elements to your XML
document.

<images>
<image>
<title>Waterfall</title>
<thumbnail>[Link]
</thumbnail>
<lowRes>[Link]
<full>[Link]

[Link] 2004-02-06
Strona 93 z 180

</image>
<image>
<title>Lake</title>
<thumbnail>[Link]
</thumbnail>
<lowRes>[Link]
<full>[Link]
</image>
</images>

3. Save the document as [Link] to the same directory as your Flash document.

The root element of the XML document is <images>, and the root element contains <image> child
nodes for each image that should appear in the menu. Each <image> element, in turn, has four c
nodes: <title>, <thumbnail>, <lowRes>, and <full>. The title is the name of the image that ap
both in the list box and in the title bar of the preview image. If you do not have a low-resolution
and/or thumbnail version of an image, you can use the same URL for two or all three of the imag
variations. The application automatically resizes the thumbnails, and the preview images can be
size. The only purpose in having variations for each of the images is because the low-resolution a
thumbnail versions can have smaller file sizes (and therefore take less time to load).

To create the Menu component, complete the following steps:

1. Create a new movie clip symbol named Menu.

2. Edit the linkage properties of the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Set the linkage identifier to MenuSymbol.

5. Click OK.

6. Edit the new symbol.

7. On the default layer, add the following code to the first frame:

#initclip

function Menu ( ) {

// Create the list box to list the available images and add a value to it
// says "loading . . . " to initialize it.
[Link]("FListBoxSymbol", "imagesMenu", [Link]( ));
[Link]("loading...");

// Add the preview, sequencer add, and play buttons.


[Link]("FPushButtonSymbol", "previewBtn", [Link]( ));
[Link]("FPushButtonSymbol", "addBtn", [Link]( ));
[Link]("FPushButtonSymbol", "playBtn", [Link]( ));

// Create a table to organize the menu elements.


var tr0 = new TableRow(0, new TableColumn(0, [Link]));
var tr1 = new TableRow(0, new TableColumn(0, [Link]));
var tr2 = new TableRow(0, new TableColumn(0, [Link]));
var tr3 = new TableRow(0, new TableColumn(0, [Link]));
var t = new Table(3, 0, 0, tr0, tr1, tr2, tr3);
}

[Link] = new MovieClip( );

[Link] 2004-02-06
Strona 94 z 180

// The setValues( ) method takes an XML object parameter and populates the m
[Link] = function (xmlData) {

// Remove the "loading . . . " message from the list box.


[Link](0);

var imageNodes = [Link];


var imageNode, title, thumbnail, full;

// Loop through all the <image> nodes.


for (var i = 0; i < [Link]; i++) {
imageNode = imageNodes[i];

// Get the title, thumbnail URL, low-resolution URL, and full image URL.
title = [Link];
thumbnail = [Link];
lowRes = [Link]
full = [Link];

// Add an item to the list box. The item should display the image title
// the data for the item should be an object with thumbnail, full, lowRe
// and title properties.
[Link](title, {thumbnail: thumbnail, full: full,
lowRes: lowRes, title: title});
}

// Adjust the width of the list box to accommodate the titles.


[Link]( );

[Link]("preview image");
[Link]("add to sequence");
[Link]("start sequence");
[Link]("startSequence", this);
[Link]("previewImage", this);
[Link]("addImageToSequence", this);
};

// Set the reference to the preview pane.


[Link] = function (previewPn) {
[Link] = previewPn;
};

// This is the callback function for the preview button. It calls the open(
// method of the preview pane with the selected low-resolution URL and title
[Link] = function ( ) {
var selected = [Link]( );
var lrURL = [Link];
var title = [Link];
[Link](lrURL, title);
};

// Set the reference to the sequencer and sequence viewer.


[Link] = function (sqncr, seqViewer) {
[Link] = sqncr;
[Link] = seqViewer;
};

// This is the callback function for the sequencer add button. It calls the
// addItem( ) methods of both the sequencer and the sequence viewer with th
// appropriate URLs.
[Link] = function ( ) {
var selected = [Link]( );
var tnURL = [Link];

[Link] 2004-02-06
Strona 95 z 180

var fullURL = [Link];


[Link](tnURL);
[Link](fullURL);
};

// This is the callback function for the start sequence playback button. It
// the play( ) method of the sequence viewer with an interval of 3000
// milliseconds (3 seconds) per image.
[Link] = function ( ) {
[Link](3000);
};

[Link]("MenuSymbol", Menu);

#endinitclip

The Menu component class is fairly straightforward. The setValues( ) method is the only portion
that may potentially be a little confusing at first. The method is passed an XML object parameter
XML object should be in the same format as the [Link] document, and it should not have an
extra whitespace nodes (we take care of all of this in the main routine of the application). Then,
the setValues( ) method, we extract the values for each image's title and the three URLs. We the
assign those values to items within the list box. The label for each list box item should be the ti
but the data should be an object that contains all the values for that image. This is important be
it then gives us access to all that information for an image when it is selected from the menu.

for (var i = 0; i < [Link]; i++) {


imageNode = imageNodes[i];
title = [Link];
thumbnail = [Link];
lowRes = [Link];
full = [Link];
[Link](title, {thumbnail: thumbnail, full: full,
lowRes: lowRes, title: title});
}
[ Team LiB ]
[ Team LiB ]

25.4 Putting Together the Application


Once you have created all the components, you can breathe a sigh of relief knowing that you hav
completed all the difficult work. All that remains is to put the components together so they work
conjunction with one another to produce a complete, working application.

The main routine of the application is responsible for the following:

z Adding all the necessary components to the movie at runtime

z Loading the XML data

z Listening for keypresses

To complete the main routine of the image viewer application, add the following code to the first
frame of the default layer of the main timeline:

// Include the necessary libraries in the document. [Link] is from Chapter


// and [Link] is from Chapter 4
#include "[Link]"
#include "[Link]"
// [Link] and [Link] are from Chapter 11.

[Link] 2004-02-06
Strona 96 z 180

#include "[Link]"
#include "[Link]"
// [Link] is from Chapter 8.
#include "[Link]"

function init ( ) {
draw( );

// Create the XML object and load the [Link] document. Be sure to set
// ignoreWhite to true so that the extra whitespace nodes are eliminated.
imagesXML = new XML( );
[Link] = true;
[Link]("[Link]");
[Link] = XMLOnLoad;
}

function draw ( ) {

// Create the Menu component and the Preview Pane component.


_root.attachMovie("MenuSymbol", "imagesMenu", _root.getNewDepth( ));
_root.attachMovie("PreviewPaneSymbol", "previewPn", _root.getNewDepth( ));
[Link](previewPn);

// Add the menu and preview pane to a table row.


tr0 = new TableRow(3, new TableColumn(3, imagesMenu),
new TableColumn(3, previewPn));

// Create the Sequencer component and add it to a table row.


_root.attachMovie("SequencerSymbol", "seqncr", _root.getNewDepth( ));
tr1 = new TableRow(3, new TableColumn(3, seqncr));

// Create the table to position the elements on the Stage.


appTable = new Table(3, 0, 0, tr0, tr1);

// Create the sequence viewer and set the proper references in the Sequencer an
// Menu components.
_root.attachMovie("SequenceViewerSymbol", "seqViewer", _root.getNewDepth( ));
[Link](seqViewer);
[Link](seqncr, seqViewer);
}

// This is the onLoad method for the XML object.


function XMLOnLoad ( ) {

// Call the setValues( ) method of the Menu component and


// pass it a reference to the XML object.
_root.[Link](this);

// Calculate the dimensions of the preview pane according to the new width of th
// menu, then set the size of the preview pane and render the table.
var menuW = _root.imagesMenu._width;
var pvW = [Link] - menuW - 12;
var pvH = [Link] - _root.seqncr._height - 12;
_root.[Link](pvW, pvH);
_root.[Link](true);
}

// Create a listener object to detect keypresses. If the delete key is pressed, c


// the removeSelected( ) method of the sequencer to remove the selected sequence
// item. Otherwise, call the stop( ) method of the sequence viewer to stop the
// playback (if it is playing).
keyListener = new Object( );
[Link] = function ( ) {
if ([Link]( ) == [Link]) {

[Link] 2004-02-06
Strona 97 z 180

_root.[Link]( );
} else {
_root.[Link]( );
}
};
[Link](keyListener);

init( );
[ Team LiB ]
[ Team LiB ]

25.5 Wrapping It Up
Congratulations. You have completed the image viewer/slideshow application. We've applied
many of the techniques from recipes throughout the book. We've also seen how to create an
application from a series of custom components. And we've explored real-world techniques to
address operational requirements such as synchronization, performance, and dynamic
interaction. The remaining chapters explore additional applications that apply and extend what
you've learned.

[ Team LiB ]
[ Team LiB ]

Chapter 26. Creating an MP3 Jukebox


One of the exciting features introduced in Flash MX was the ability to load MP3s at runtime
using the Sound class. In this chapter, we create an MP3 jukebox application that enables users
to load MP3 files from various locations. The user can create a playlist of songs and play them
back. To make things extra saucy, we also add a five-second cross-fade between songs. Refer
to Chapter 13 for basic recipes regarding sound playback. Refer to Chapter 14 for information
on recording and publishing audio with FlashCom.

[ Team LiB ]
[ Team LiB ]

26.1 Creating an Application Overview


The MP3 player that you create in this chapter is composed of several parts, so the first step is
to determine what these pieces are and how they work together to create an entire application.
The final files are available for download from [Link] Here is a brief
synopsis of each part of this application:

Local MP3 selector

Combines HTML, JavaScript, and a Flash movie using a local connection to select MP3 files
from the hard drive of the client computer so that they can be played in the jukebox.

Server MP3 selector

Allows users to select an MP3 from the server from which the jukebox is being served so
that it can be played. This feature uses Flash Remoting.

Jukebox controller component

[Link] 2004-02-06
Strona 98 z 180

A custom component used to control the playback of the songs in the jukebox playlist.
The jukebox controller is composed of two instances of the Sound Controller component
from Recipe 13.16.

Main jukebox Flash movie

Ties everything else together.


[ Team LiB ]
[ Team LiB ]

26.2 Developing the MP3 Selectors


In the following sections, we create both the local and server MP3 selectors. These are the parts
jukebox application that enable a user to select MP3s from both her own hard drive and from the
server by using a graphical user interface instead of having to type in a URL or path to the file.

26.2.1 Creating a Local MP3 Selector


For a user to be able to add an MP3 to her jukebox playlist from her local hard drive, she needs t
know the path to that file. This process can be simplified for the user if we can provide her with a
of browsing her hard drive graphically and selecting a file with her mouse. This is the idea behind
local MP3 selector feature.

HTML allows you to create forms with elements of type "file". File form fields include a Browse bu
that opens a dialog box so that users can select files from their local hard drives. Flash does not
natively support a local browse feature; however, we can devise a workaround using HTML, Java
and a Flash movie with a local connection. There are three files necessary for the local MP3 selec
functionality—two HTML pages and one Flash movie. Let's look at each of these files individually.

[Link] Making the Form page

We will use a standard HTML file form field in a small pop-up browser window to allow the user t
select a file from her hard drive. Create a new HTML document named [Link] and ad
following code to it:

<!-- Create a form (must be multipart/form-data in order for the file field
type to work properly). The form should submit to [Link]
(an HTML page we'll create next) using the GET method. -->
<form enctype="multipart/form-data" method="get" action="[Link]">

<!-- Create a file input field named path. -->


<input type="file" name="path">
<br>

<!-- Create a Submit button. -->


<input type="submit">
</form>

As you can see, [Link] is not very complicated. If you test the page in a web browse
can see that a Browse button is created automatically as part of the file input field. Clicking the B
button opens a dialog box that allows you to select a local file.

[Link] Making the Submit page

Once a user has selected a file using the [Link] page and clicked the Submit button,
form data is sent to another HTML page named [Link]. We want this HTML page to
the form data (specifically, the value from the file field) and pass it to a Flash movie, which we w

[Link] 2004-02-06
Strona 99 z 180

create next. You can use the FLASHVARS attribute of the <OBJECT> and <EMBED> tags to pass a va
the movie loaded into the HTML page. Normally, if the values you want to pass to a Flash movie
static, you can hardcode them into the HTML code, as shown in this example (FLASHVARS attribu
shown in bold):

<OBJECT classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
codebase="[Link]
[Link]#version=6,0,0,0"
WIDTH="550" HEIGHT="400" id="myMovie" ALIGN="">
<PARAM NAME=movie VALUE="[Link]">
<PARAM NAME=quality VALUE=high>
<PARAM NAME=bgcolor VALUE=#FFFFFF>
<PARAM NAME=FLASHVARS VALUE="param1=value1&param2=value2">
<EMBED src="[Link]" quality=high bgcolor=#FFFFFF
WIDTH="550" HEIGHT="400" NAME="myMovie" ALIGN=""
TYPE="application/x-shockwave-flash"
PLUGINSPAGE="[Link]
FLASHVARS ="param1=value1&param2=value2">
</EMBED>
</OBJECT>

However, we want to obtain the file value dynamically from the HTML form. One option is to use
JavaScript to extract the value from the URL—the form values are appended to the URL since we
the GET method to submit the form—and then use the JavaScript write( ) method to generate th
<OBJECT> and <EMBED> tags within the HTML page. This solution offers several advantages. First
because JavaScript is run on the client and understood by almost all web browsers, you don't ha
worry about any server-side language. Furthermore, JavaScript shares the same syntax and man
core classes with ActionScript, so it is approachable for most ActionScript developers.

In the following code block, you can see the HTML and JavaScript code that extracts the form va
from the URL and passes it a Flash movie using the FLASHVARS attribute. Add this code to an HTM
document named [Link] and save it to the same directory as [Link].

<HTML>
<HEAD>
<TITLE>Get Path</TITLE>
</HEAD>
<BODY bgColor=#FFFFFF leftmargin="0" marginheight="0" marginwidth="0" topmargin="
<SCRIPT language=JavaScript>
<!--

var path = [Link]("=")[1];


var swfCode = "<OBJECT classid=\'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\' ";
swfCode += " codebase=\'[Link]
[Link]#version=6,0,0,0\'";
swfCode += " WIDTH=300 HEIGHT=150>";
swfCode += " <PARAM NAME=movie VALUE='[Link]'>";
swfCode += " <PARAM NAME=quality VALUE=best> ";
swfCode += " <PARAM NAME=FlashVars VALUE='path=" + path + "'>";
swfCode += " <EMBED src='[Link]'";
swfCode += " FLASHVARS ='path=" + path + "'";
swfCode += " quality=best WIDTH=300 HEIGHT=150 ";
swfCode += " TYPE=\'application/x-shockwave-flash\' ";
swfCode += "PLUGINSPAGE=\'[Link]
[Link]?P1_Prod_Version=ShockwaveFlash\'> ";
swfCode += " </EMBED> </OBJECT> ";

[Link](swfCode);
//-->
</SCRIPT>

</BODY>

[Link] 2004-02-06
Strona 100 z 180

</HTML>

Let's take a closer look at some of the JavaScript code. First of all, we want to get the value that
submitted by the form. In JavaScript the query string portion of the URL (everything following th
can be referenced by [Link]. In this case, the value of [Link] is of the for
path=userSelectedPath. You can use the split( ) method to split the value into an array using t
equals sign as the delimiter; the chosen file path is stored in the array's second element. Hence,
use:

var path = [Link]("=")[1];

Next, we want to construct the <OBJECT> and <EMBED> tags. Most of the string defining the tags
hardcoded to contain the necessary attributes. The name of our Flash movie, [Link],
specified as the movie parameter for the <OBJECT> tag and the src attribute for the <EMBED> tag
Notice that the FLASHVARS attribute is set to include the file path obtained from the form.

var swfCode = "<OBJECT classid=\'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\' ";


swfCode += " codebase=\'[Link]
[Link]#version=6,0,0,0\'";
swfCode += " WIDTH=300 HEIGHT=150>";
swfCode += " <PARAM NAME=movie VALUE='[Link]'>";
swfCode += " <PARAM NAME=quality VALUE=best> ";
swfCode += " <PARAM NAME=FLASHVARS VALUE='path=" + path + "'>";
swfCode += " <EMBED src='[Link]'";
swfCode += " FLASHVARS ='path=" + path + "'";
swfCode += " quality=best WIDTH=300 HEIGHT=150 ";
swfCode += " TYPE=\'application/x-shockwave-flash\' ";
swfCode += "PLUGINSPAGE=\'[Link]
[Link]?P1_Prod_Version=ShockwaveFlash\'> ";
swfCode += " </EMBED> </OBJECT> ";

Once the string is constructed, we use the [Link]( ) method to output the value in the H
page.

[Link](swfCode);

[Link] Making the local connection Flash movie

To finish the local MP3 selector, we must create a Flash movie that takes the value passed to it v
FLASHVARS and sends it to the main jukebox movie. You can enable movie-to-movie communicat
with two Flash movies running on the same computer using a LocalConnection object.

We'll name our Flash movie [Link], as per the movie and src attributes in the preced
[Link] page. This movie should do the following:

z Send the local MP3 file path to the main jukebox movie using a local connection

z Display the selected MP3 file path to the user

z Include a button that allows the user to close the pop-up window

Here are the steps involved in creating this Flash movie:

1. Create a new Flash document named [Link] in the same directory as the HTML f
you have already created.

2. Adjust the document settings (under Modify Document) so that the movie has dimens

[Link] 2004-02-06
Strona 101 z 180

300 x 150 pixels, which are the dimensions of the browser window that we will open for
displaying this movie.

3. Add the PushButton component to the document's Library by dragging an instance from th
Components panel onto the Stage and then deleting the instance. (The symbol is copied to
Library.)

4. Add the following code to the first frame of the default layer of the main timeline:

// Include [Link] from Chapter 7 and [Link] from Chapter 8.


#include "[Link]"
#include "[Link]"

// Create a text field to display the path.


_root.createTextFieldAuto("pathUploadConfirm", _root.getNewDepth( ));
[Link] = true;
[Link] = "the file: \n" + path +
"\n has been added to the playlist";

// Format the text so it is centered.


tf = new TextFormat( );
[Link] = "center";
[Link](tf);
pathUploadConfirm._y = [Link]/2 - pathUploadConfirm._height/2;
pathUploadConfirm._x = [Link]/2 - pathUploadConfirm._width/2;

// Create a Close button so the user can close the pop-up window.
_root.attachMovie("FPushButtonSymbol", "closeBtn", _root.getNewDepth( ));
closeBtn._y = pathUploadConfirm._y + pathUploadConfirm._height + 5;
closeBtn._x = [Link]/2 - closeBtn._width/2;
[Link]("close window");

// When the Close button is released, call the [Link]( ) method for th
// HTML page using the getURL("javascript: void( );") technique.
[Link] = function ( ) {
[Link]("javascript: void([Link]( ));");
};

// Create a local connection and send the MP3 path over a connection named
// "pathSendConnection". The listening movie (the main jukebox movie) needs
// have a receivePathInfo( ) method to receive this value.
sender = new LocalConnection( );
[Link]("pathSendConnection", "receivePathInfo", path);

5. Save the document and export the movie as [Link].

26.2.2 Creating the Server MP3 Selector


In the preceding section, we developed a mechanism by which the user can select an MP3 from h
local hard drive. In this section, we create a similar mechanism that allows the user to select an
from a server. This portion of the application requires the use of Flash Remoting. Moreover, the
example shown here uses ColdFusion to browse the contents of the server directory. However, th
feature is not essential to the overall functioning of the application, and if you do not use Flash
Remoting or ColdFusion, you can skip over this portion.

The server MP3 selector involves three basic parts:

z A ColdFusion Component (CFC) that retrieves directory and file information and returns it t
Flash movie that calls it

[Link] 2004-02-06
Strona 102 z 180

z A Flash movie that calls the CFC function via Flash Remoting and displays the results in a T
component

z An HTML page for the Flash movie

The server MP3 selector opens up in a new browser window, as does the local MP3 selector. The
MP3 selector should allow a user to browse the directory in which the CFC is saved, as well as an
the subdirectories. The user can select a file and add it to the playlist in the main jukebox movie
way of a local connection.

[Link] Making the CFC

The first step in creating the server MP3 selector is to make the CFC that retrieves the directory
file information. The CFC needs only one method, which takes a parameter specifying the name
directory about which to return information. The method returns an object with the information a
that directory, including the directory name and the files and subdirectories it contains.

To make the CFC, do the following:

1. Create a new file named [Link] in the ColdFusion web root (for example,
C:\CfusionMX\wwwroot).

2. Add the following code to the CFC:

<cfcomponent>
<cffunction name="getDirectoryInfo" access="remote" returntype="struct">

<!--- Tell the method to expect a string parameter named dir --->
<cfargument name="dir" type="string" required="false">

<cfscript>
// Use the path of the CFC on the server as the default path in case th
// dir parameter is undefined.
getdir = #GetDirectoryFromPath(GetCurrentTemplatePath( ))#;
if (isDefined("[Link]")){
getdir = [Link];
}
</cfscript>

<!--- Use the <cfdirectory> tag to get a directory listing for the speci
path. Sort the directory contents first by type, then by name. --->
<cfdirectory directory="#getdir#" name="dirListing" sort="type ASC, name

<cfscript>
// Create a Struct to return the directory information. Add to this a
// property that contains the value of the current directory as well a
// dirListing property that contains the results of <cfdirectory>.
res = StructNew( );
[Link] = getdir;
[Link] = dirListing;

// Return the Struct.


return res;
</cfscript>
</cffunction>
</cfcomponent>

3. Save the CFC.

In the first part of the CFC method, we determine the path to the directory for which we should g

[Link] 2004-02-06
Strona 103 z 180

information. The first time the method is called, we don't know the path to the directory, but on
subsequent calls (i.e., calls to retrieve subdirectory information) we do. For this reason, the dir
parameter should not be required. Also, the directory path value that we use (getdir) should de
to the path to the directory containing the CFC, but if a parameter is passed to the CFC method,
value should be used instead.

<cfargument name="dir" type="string" required="false">


<cfscript>
getdir = #GetDirectoryFromPath(GetCurrentTemplatePath( ))#;
if (isDefined("[Link]")){
getdir = [Link];
}
</cfscript>

The <cfdirectory> tag returns an array of Structs that provide information about all the files an
subdirectories within a given directory. Each of the Structs has both a type and a name property,
which type can be either "Dir" or "File" and name is the name of the file or subdirectory. We sort
results first by type and then by name, yielding a listing in which the subdirectories are grouped
together and alphabetized, as are the files.

<cfdirectory directory="#getdir#" name="dirListing" sort="type ASC, name ASC">

[Link] Making the server MP3 selector Flash movie

The next part of the server MP3 selector is the Flash movie that interfaces with the CFC via Flash
Remoting. The Flash movie is opened in its own pop-up browser window, and it displays the dire
listing using a Tree component. Once the user selects a file, the path is sent to the main jukebox
movie using a local connection, just as with the local MP3 selector.

Here are the steps to complete to make the MP3 selector Flash movie:

1. Create a new Flash document named [Link].

2. Create an instance of the Tree component in the document's Library. You can do this by dr
an instance of the component from the Components panel onto the Stage and then deletin
The Tree component is part of the Flash UI Components Set 2 available for free from the F
Exchange ([Link]

// Include [Link] for Flash Remoting support.


#include "[Link]"

// Include [Link] from Chapter 7 and [Link] from Chapter 8.


#include "[Link]"
#include "[Link]"
// Include [Link] and [Link] from Chapter 11.
#include "[Link]"
#include "[Link]"

function init ( ) {

// Create a Flash Remoting connection. If necessary, adjust the example ga


// URL to match your server configuration.
var gwURL = "[Link]
[Link](gwURL);
var conn = [Link]( );

// Create a service object that maps to the DirectoryBrowser CFC.


dirBrowserSrvc = [Link]("DirectoryBrowser");

// Create a response object. When a result is returned to this response ob

[Link] 2004-02-06
Strona 104 z 180

// call the addNodes( ) function (defined next) with the result returned
// the service function.
dirInfoRes = new Object( );
[Link] = function (result) {
_root.addNodes(result);
};

// Call the getDirectoryInfo( ) service function to initialize the movie w


// the contents of the ColdFusion web root (where the CFC is stored). Tell
// Flash to send any responses to the dirInfoRes response object.
[Link](dirInfoRes);

// Create a local connection for sending the path to the main jukebox movi
sender = new LocalConnection( );

// The currentNode variable is used to keep track of the tree node that is
// being viewed. Initially no node is being viewed, so set it to null.
currentNode = null;
}

// Create the form in which the user can browse the directory and subdirecto
// using a Tree component.
function createTreeForm ( ) {

// Create the Tree component instance as well as a text field to display th


// name of the selected file and a Submit button.
_root.attachMovie("FTreeSymbol", "tree", _root.getNewDepth( ));
_root.createInputTextField("path", _root.getNewDepth( ), 0, 0, 150, 20);
_root.attachMovie("FPushButtonSymbol", "submitBtn", _root.getNewDepth( ))

// When a node is selected in the Tree component,


// call the doSelect( ) function.
[Link]("doSelect");

[Link]("submitSelected");
[Link]("submit");

// Create a table for positioning the tree, text field, and button.
treeTr0 = new TableRow(5, new TableColumn(5, tree));
treeTr1 = new TableRow(5, new TableColumn(5, path));
treeTr2 = new TableRow(5, new TableColumn(5, submitBtn));
treeTable = new Table(5, 0, 0, treeTr0, treeTr1, treeTr2);

// Create a form and add the elements to the form.


treeForm = new Form( );
[Link](tree);
[Link](path);
[Link](submitBtn);
}

// Create a form that displays the path of the selected file and allows the u
// to close the window with a button after the file has been submitted. This
// is almost identical to the code contained within [Link].
function createConfirmForm ( ) {
_root.createAutoTextField("pathUploadConfirm", _root.getNewDepth( ));
[Link] = true;
[Link] = "the file: \n\n\n has been added to the playlist"
tf = new TextFormat( );
[Link] = "center";
[Link](tf);
pathUploadConfirm._y = [Link]/2 - pathUploadConfirm._height/2;
pathUploadConfirm._x = [Link]/2 - pathUploadConfirm._width/2;
_root.attachMovie("FPushButtonSymbol", "closeBtn", _root.getNewDepth( ));
closeBtn._y = pathUploadConfirm._y + pathUploadConfirm._height + 5;

[Link] 2004-02-06
Strona 105 z 180

closeBtn._x = [Link]/2 - closeBtn._width/2;


[Link]("close window");
[Link] = function ( ) {
[Link]("javascript: void([Link]( ));");
};

// Add the elements to a form.


confirmForm = new Form( );
[Link](pathUploadConfirm);
[Link](closeBtn);
}

// Create a multipage form so that treeForm and confirmForm can occupy the s
// space and only one is visible at a time.
function createMultiForm ( ) {
myMPForm = new MultiPageForm( );
[Link](treeForm);
[Link](confirmForm);
[Link](1);
}

// This function is invoked automatically every time a value is returned from


// service function (and it is passed that value in dlInfo).
function addNodes(dlInfo) {

// If currentNode is null, it was the first call to the service function.


// Therefore, create a root node for the Tree component and populate it wi
// the data for the web root directory listing.
if (currentNode == null) {

// The dir property is the full path to the directory that is being brow
// To get the name of that directory, split the path into an array using
// as a delimiter for Windows systems. Don't forget to escape the backsl
// If you are using a Unix-based server, use "/" as the delimiter instea
var dirNameAr = [Link]("\\");

// The directory name is mostly like the last element of the array.
var dirName = dirNameAr[[Link] - 1];

// If the directory path has a trailing slash, the last element of the a
// is an empty string, so the directory name is the second-to-last elemen
if (dirName == "") {
dirName = dirNameAr[[Link] - 2];
}

// Create the root node.


var node = new FTreeNode( );
[Link](dirName);
[Link]([Link]);
[Link](node);
currentNode = node;
}

// The currentNode is the one being browsed. Set a custom property for the
// current node that contains the full path on the server of the directory
// node represents.
[Link] = [Link];

// Add a trailing slash to baseDir, if necessary, so that you can safely ap


// a subdirectory name to it.
if ([Link]("\\") != [Link] -
[Link] += "\\";
}

[Link] 2004-02-06
Strona 106 z 180

// The custom hasContents property indicates whether the contents of the


// directory the node represents have already been loaded.
[Link] = true;

var dl = [Link];

// Loop through all the directory listing's elements, and add nodes to the
// current node for each of the subdirectories and files.
for (var i = 0; i < [Link]( ); i++) {
var tmpNode = new FTreeNode( );
[Link]([Link](i).name);
[Link]([Link] + [Link](i).name);
[Link] = [Link](i).type;
if ([Link](i).[Link]( ) == "dir") {
[Link](true);
}
[Link](currentNode, tmpNode);
}
}

// The doSelect( ) function is the callback function for when a node is sel
// It is automatically passed a reference to the Tree component.
function doSelect (tr) {

// Get the selected node.


var sn = [Link]( );

// If the selected node is not a branch node (meaning it does not have any
// child nodes), it represents a file; otherwise, it represents a director
// Display the file name in the path text field and store the full path
// information in a custom property of the text field so that if the user
// clicks the Submit button, the full path can be located easily.
if (![Link]( )) {
[Link] = [Link]( );
[Link] = [Link]().substr([Link]( ).[Link]);
[Link] = "the file: \n" + [Link] +
"\n has been added to the playlist";
}

// Since a new node has been selected, set the


// currentNode to the selected node.
currentNode = sn;

// If the directory listing hasn't already been downloaded, retrieve it fr


// the server by passing the service function the full path to the directo
// represented by the selected node
if (![Link]) {
[Link](dirInfoRes, [Link]( ));
}
}

// The submitSelected( ) function is invoked when the Submit button is clic


function submitSelected ( ) {

// If the data property of the text field is defined (meaning a file has b
// selected) send the filename to the main jukebox movie using a local
// connection (sender). Set the multipage form to the second page.
if ([Link] != undefined) {
[Link]("pathSendConnection", "receivePathInfo", [Link]);
[Link](2);
}
}

init( );

[Link] 2004-02-06
Strona 107 z 180

createTreeForm( );
createConfirmForm( );
createMultiForm( );

3. Save the Flash document. We export the movie in the next section.

Now let's look at some of the ActionScript code in this document a little more closely. Much of th
is quite straightforward, but some sections involve techniques with which you might not be famil

The init( ) function does not do much that is unusual. It creates a connection object and a servic
object that maps to the [Link] file. In addition, it creates a response object for ha
the results from the service function. This uses standard Flash Remoting techniques, as discusse
Chapter 20. The call to the service function is standard as well, but notice that we don't pass any
parameters to the function. The first time this service function is called, we don't yet know the p
the directory to list. Remember that you designed the CFC function such that if it receives no
parameter, it uses the directory in which the CFC is stored.

function init ( ) {
var gwURL = "[Link]
[Link](gwURL);
var conn = [Link]( );
dirBrowserSrvc = [Link]("DirectoryBrowser");
dirInfoRes = new Object( );
[Link] = function (result) {
_root.addNodes(result);
};
[Link](dirInfoRes);
sender = new LocalConnection( );
currentNode = null;
}

The createTreeForm( ) and createCofirmForm( ) functions are very straightforward. They each cr
form elements, position them, and add them to forms. Then, the createMultiForm( ) function add
forms to a multipage form and sets it to display the first page. This allows the two forms to occu
same space while only one is visible.

function createMultiForm ( ) {
myMPForm = new MultiPageForm( );
[Link](treeForm);
[Link](confirmForm);
[Link](1);
}

The addNodes( ) function is called whenever a result is returned from the service function, which
use to populate the selected node in the Tree component. The first time this function is called, th
currentNode variable is null, and the function must create the root node of the Tree component.
this case, it needs to get the directory name from the full path. One convenient way to do this is
split the path into an array using the slash as a delimiter. For example, if the path value is
"C:\CFusionMX\wwwroot\", you can create an array with values "C:", "CFusionMX", "wwwroot", a
empty string (because of the trailing slash). The directory name is always either the last element
the array (in the case of no trailing slash) or the second-to-last element (in the case of a trailing
slash). This code finds the directory name in either case:

if (currentNode == null) {
var dirNameAr = [Link]("\\");
var dirName = dirNameAr[[Link] - 1];
if (dirName == "") {
dirName = dirNameAr[[Link] - 2];
}
var node = new FTreeNode( );

[Link] 2004-02-06
Strona 108 z 180

[Link](dirName);
[Link]([Link]);
[Link](node);
currentNode = node;
}

When the user selects a file element from a tree node, you need to know the path to that file on
server. For this purpose, you should store the directory path ([Link]) as a property of the n
that is currently being populated. Additionally, to append the filename to the path, the path mus
in a trailing slash, so we add a trailing slash, if necessary:

[Link] = [Link];
if ([Link]("\\") != [Link] - 1) {
[Link] += "\\";
}

The custom hasContents property tells Flash if a node already has contents loaded into it:

[Link] = true;

Once the contents have been retrieved from the server, it would be wasteful to request that
information again, so we'll make future Flash Remoting requests only if hasContents is not true

The last part of the addNodes( ) function populates the selected node with the contents. The
dirListing property of the object that is returned from the service function is an array of object
which each object has a name and type property (this is the value that the <cfdirectory> return
populate the selected node, use a for statement to loop through all the elements of the object ar
For each element, create a tree node in which the label is the name of the file or subdirectory, an
data is the full path to the file or subdirectory. Furthermore, if the element is a subdirectory, use
setIsBranch( ) method to configure the node so that it can be opened (expanded into a subdirect
listing).

var dl = [Link];
for (var i = 0; i < [Link]( ); i++) {
var tmpNode = new FTreeNode( );
[Link]([Link](i).name);
[Link]([Link] + [Link](i).name);
[Link] = [Link](i).type;
if ([Link](i).[Link]( ) == "dir") {
[Link](true);
}
[Link](currentNode, tmpNode);
}

The doSelect( ) callback function is invoked whenever a tree node is selected. To process the nod
you must determine its type. Nodes that represent files are not branch nodes, as detected with t
isBranch( ) method.

When a user selects a file from the Tree component, you should:

1. Display the filename in the path text field

2. Store the file's path so that you can access it easily, if and when the user chooses to subm
file to the jukebox

3. Display the path in the pathUploadConfirm text field so that the user gets the confirmatio
she has added the correct file before clicking Submit

Notice that the [Link] value is a substring of the selected node's data value. The jukebox mo

[Link] 2004-02-06
Strona 109 z 180

needs to know only the relative path to the file and not the full path. Since the CFC and the
jukebox .swf file are stored in the same directory, the relative path to the selected file is the diffe
between the full path to the file and the full path to the application's root directory (which is stor
the baseDir property of the Tree component's root node object). For example, if the full path to
selected file is C:\CFusionMX\wwwroot\myMp3.mp3 and the full path to the application's root dir
is C:\CFusionMX\wwwroot\, then the relative path to the file is simply myMp3.mp3. You can emp
little trick to determine the relative path: start with the file's full path, and then extract the subs
starting from the length of the root's path and spanning to the end of the string. Additionally, se
currentNode variable to reference the selected node. If the contents of the node have not been
downloaded, call the service function to retrieve the data, passing it the path to the directory tha
node represents.

function doSelect (tr) {


var sn = [Link]( );
if (![Link]( )) {
[Link] = [Link]( );
[Link] = [Link]().substr([Link]( ).[Link]);
[Link] = "the file: \n" + [Link] +
"\n has been added to the playlist";
}
currentNode = sn;
if (![Link]) {
[Link](dirInfoRes, [Link]( ));
}
}

When the user clicks the Submit button, send the relative path of the selected file to the main ju
using a local connection. Additionally, display the next page of the multipage form (the confirma
screen).

function submitSelected ( ) {
if ([Link] != undefined) {
[Link]("pathSendConnection", "receivePathInfo", [Link]);
[Link](2);
}
}

[Link] Making the HTML page for the server MP3 selector

The last step in making the server MP3 selector is to export the .swf file and create the HTML pa
which to embed it. Flash's Publish feature creates both the .swf and .html files. Afterwards, we m
the .html file to set the margins to 0 so that the Flash movie is flush with the top-left corner of t
browser window.

Here are the steps to create the .swf file and HTML page:

1. With the [Link] open in Flash, choose File Publish Settings, which opens
Publish Settings dialog box.

2. Under the Formats tab, select the Flash and HTML checkboxes. Also, select the Use Default
Names checkbox so that Flash generates files named [Link] and
[Link].

3. Click the Publish button to generate the .swf and .html files.

4. Click OK to close the Publish Settings dialog box.

5. Close the Flash document.

[Link] 2004-02-06
Strona 110 z 180

6. Open [Link] in a text editor or web page editor. If you use a WYSIWYG edi
such as Dreamweaver, switch to Code view mode to modify the code directly.

7. Modify the <body> tag so that it reads:

<BODY bgcolor="#FFFFFF" leftmargin="0" topmargin="0">

8. Save [Link] and close it.

[Link] Notes on the server MP3 selector

Note the following when implementing the server MP3 selector:

z The server MP3 selector allows the user to browse the directory (and its subdirectories) in
the CFC is stored on the server. Therefore, to let the user select MP3 files from the server,
them within that directory or one of its subdirectories.

z The Tree component distinguishes between selecting a node and opening/expanding a nod
our select handler function is not called only when a node is selected. When the user clicks
node icon to highlight it, our doSelect( ) handler function is triggered. However, a branch n
can be opened/expanded by clicking on the plus sign next to the node icon. This does not
automatically select the node, and there is no simple way to detect this event. The problem
that nodes aren't populated until they are selected. Therefore, a node that has been opene
not selected does not display its contents. There are at least two possible solutions:

{ Leave things as they are, and require the user to both select and expand a node to v
the contents.

{ Revise the code so that it recursively populates all the subdirectories from the beginn

z As designed, the main jukebox movie must be served from a web server (as opposed to ru
from a local file) to work correctly with the server MP3 selector. Furthermore, the CFC, the
movies, and the HTML files must be in the same directory. This ensures that the paths retu
from the CFC match up relative to the location of the Flash movie.

[ Team LiB ]
[ Team LiB ]

26.3 Developing the Jukebox Application


In the next sections, you will create both the SoundController component and the main jukebox
code that is responsible for making this application work. All the subsequent code is contained w
single Flash document. Therefore, the first step in this process is to create a new Flash documen
[Link] and save it.

26.3.1 Adding the Sound Controller


The JukeboxController component is an essential part of the jukebox application. It is required to
songs. The component uses two SoundController components so that one song can be queued w
another is playing. Here are the steps to follow to complete the component:

1. The jukebox controller requires the SoundController component from Recipe 13.16. If you
that exercise, you should have a Sound Controller menu option in the Components panel, w
contains a SoundController component. Otherwise, you can download and install the comp

[Link] 2004-02-06
Strona 111 z 180

SoundController component from [Link]

2. Once you have the SoundController component installed, create a copy of it in your jukebo
Library by dragging an instance onto the Stage and deleting the instance. The symbol rem
Library, even after the instance is deleted.

3. Create a new movie clip symbol named JukeboxController.

4. Open the linkage properties for the JukeboxController symbol using the Library panel's pop
Options menu.

5. Select the Export for ActionScript and Export on First Frame checkboxes.

6. Give the symbol a linkage identifier of JukeboxControllerSymbol.

7. Click OK to close the Linkage Properties dialog box.

8. Edit the JukeboxController symbol.

9. Add the following code to the first frame of the default layer:

// Make sure to enclose the code in #initclip/#endinitclip so that this code


// executes before any code on the main timeline.
#initclip

// In the JukeboxController constructor, create two SoundController instance


// store references to them in the currentPlayer and queuePlayer properties.
// the second player instance by setting its _alpha to 0.
function JukeboxController ( ) {
[Link]("SoundControllerSymbol", "player2", _root.getNewDepth( )
[Link]("SoundControllerSymbol", "player1", _root.getNewDepth( )
this.player2._alpha = 0;
[Link] = this.player1;
[Link] = this.player2;
}

// Make sure that JukeboxController subclasses MovieClip.


[Link] = new MovieClip( );

// The loadSong( ) method uses the SoundController's setTarget( ) method t


// currentPlayer's target sound and tell it to play once it is loaded.
[Link] = function (snd) {
[Link](snd, true);
};

// The loadQueue( ) method sets the target sound for queuePlayer without pl
// the sound immediately.
[Link] = function (snd) {
[Link](snd);
};

// The startNext( ) method starts the next song.


[Link] = function ( ) {
// Use the custom fade( ) method (from [Link] in Chapter 7) to fade
// the currentPlayer sound down and the queuePlayer sound up.
[Link](5);
[Link](5, true);

// Swap the references for currentPlayer and queuePlayer. If currentPlayer


// player1, it becomes player2 and queuePlayer becomes player1.
var tmp = [Link];

[Link] 2004-02-06
Strona 112 z 180

[Link] = [Link];
[Link] = tmp;

// Swap the sound controllers' depths so that


// currentPlayer has the greater depth.
[Link]([Link]);

// If the new current song is loaded, begin playback. Otherwise, the


// onEnterFrame( ) method monitors it until it is loaded, then begins pla
if ([Link]) {
[Link]( );
} else {
[Link] = function ( ) {
if ([Link]) {
[Link]( );
delete [Link];
}
}
}
};

// Register the class to the corresponding linkage identifier name.


[Link]("JukeboxControllerSymbol", JukeboxController);

#endinitclip

The JukeboxController component code is not very difficult once you examine it more closely.

To begin with, it creates two instances of the SoundController component. One controller is visib
the other is hidden. We set the _alpha property to 0 instead of setting the _visible property to
This hides the second controller but also allows us to fade visually between the two controllers. W
two properties that references the two controller instances, allowing us to switch between them

function JukeboxController ( ) {
[Link]("SoundControllerSymbol", "player2", _root.getNewDepth( ));
[Link]("SoundControllerSymbol", "player1", _root.getNewDepth( ));
this.player2._alpha = 0;
[Link] = this.player1;
[Link] = this.player2;
}

The startNext( ) method is responsible for switching between the two sound controllers. First, it
custom [Link]( ) method to fade visually between the two controller instances. If we sim
the _visible properties of the two controllers to true and false, there is no need to change the
However, because we adjust the _alpha property instead, we must ensure that the controller for
current song has the greater depth using the swapDeths( ) method. If the target sound for the n
current player is loaded, we play it immediately. Otherwise, it begins playing as soon as it has lo
later sections, you'll see that the code also fades in sounds as they start and fades out sounds as
stop.

[Link] = function ( ) {
[Link](5);
[Link](5, true);
var tmp = [Link];
[Link] = [Link];
[Link] = tmp;
[Link]([Link]);
if ([Link]) {
[Link]( );
} else {
[Link] = function ( ) {
if ([Link]) {

[Link] 2004-02-06
Strona 113 z 180

[Link]( );
delete [Link];
}
};
}
};

26.3.2 Creating the Main Jukebox Movie


The final step in the jukebox application is to create the main jukebox Flash movie that incorpora
the other elements. The jukebox movie has the following functionality:

z A text field in which a URL/path to an MP3 file can be typed directly to be added to the play

z Buttons that open the local and server MP3 selectors

z A menu in which the playlist is displayed (items can be reordered and deleted)

z A jukebox controller to control the playback of the MP3s

Here are the steps you should complete to create the main jukebox movie:

1. Open [Link] if it is not already open.

2. Add the following code to the default layer of the main timeline:

// Include [Link] from Chapter 13 and [Link] from Chapter 11.


#include "[Link]"
#include "[Link]"
// Include [Link] and [Link],
// which come with the Flash Remoting components.
#include "[Link]"
#include "[Link]"

// The init( ) function creates a local connection that listens for songs th
// have been added via the local or server MP3 selectors. Additionally, the
// init( ) function creates a recordset to hold the playlist information.
function init ( ) {
receiver = new LocalConnection( );
[Link] = function (path) {
if (path != "" && path != undefined && path != null) {
_root.addSongToList(path);
}
};
[Link]("pathSendConnection");
songsList = new RecordSet("url, snd");
}

// The createElements( ) method creates all the component instances and tex
// fields.
function createElements ( ) {
_root.attachMovie("FListBoxSymbol", "songsListBox", _root.getNewDepth( ))
_root.createAutoTextField("newSongURLLabel", _root.getNewDepth( ),
0, 0, 0, 0, "URL to MP3:");
_root.createInputTextField("newSongURL", _root.getNewDepth( ));
_root.attachMovie("FPushButtonSymbol", "moveUpBtn", _root.getNewDepth( ))
_root.attachMovie("FPushButtonSymbol", "moveDownBtn", _root.getNewDepth(
_root.attachMovie("FPushButtonSymbol", "removeBtn", _root.getNewDepth( ))
_root.attachMovie("FPushButtonSymbol", "addSoundBtn", _root.getNewDepth(
_root.attachMovie("FPushButtonSymbol", "startPlayBtn", _root.getNewDepth(

[Link] 2004-02-06
Strona 114 z 180

_root.attachMovie("FPushButtonSymbol", "getLocalBtn", _root.getNewDepth(


_root.attachMovie("FPushButtonSymbol", "getServerBtn", _root.getNewDepth(
_root.attachMovie("JukeboxControllerSymbol", "jukeboxCtrl", _root.getNewDep

[Link]("Add Song");
[Link]("Move Up");
[Link]("Move Down");
[Link]("Remove Song");
[Link]("Begin Playback");
[Link]("Get Local MP3");
[Link]("Get Server MP3");
[Link]("addSound");
[Link]("startNextSong");
[Link]("moveSongUp");
[Link]("moveSongDown");
[Link]("removeSong");
[Link]("getMP3");
[Link]("getMP3");
}

// The layoutElements( ) function positions the components and text fields


// table.
function layoutElements ( ) {
tc0 = new TableColumn(5, songsListBox);
tc1 = new TableColumn(5, startPlayBtn, moveUpBtn, moveDownBtn, removeBtn);
tc2 = new TableColumn(5, newSongURLLabel, newSongURL, addSoundBtn,
getLocalBtn, getServerBtn);
tc3 = new TableColumn(5, jukeboxCtrl);
tr0 = new TableRow(5, tc0);
tr1 = new TableRow(5, tc1, tc2);
tr2 = new TableRow(5, tc3);
t = new Table(5, 0, 0, tr0, tr1, tr2);
}

// The formatSongsList( ) function is used by the [Link]


// call in updateListView( ) to format each element in the song list.
function formatSongsList(element) {
// Get the name of the MP3 from the URL/path. If you split
// the URL using "/" as the delimiter, the name of the MP3 is
// the last element of the resulting array.
var urlAr = [Link]("/");
if ([Link] == 1) {
urlAr = [Link]("\\");
}
var name = urlAr[[Link] - 1];

// Create the object to return to bindFormatFunction( ). The data should b


// element itself, and the label should be the name of the MP3 file.
var obj = new Object( );
[Link] = element;
[Link] = name;

// If the song is already loaded, display its duration next to the name.
// Otherwise, display the percentage that has loaded.
if ([Link]) {
[Link] += " (" + [Link]([Link]) +
} else {
[Link] += " [" + [Link] + "%]";
}
return obj;
}

// The updateListView( ) function is called continually to update the playl


// menu to reflect load progress while any songs are loading.

[Link] 2004-02-06
Strona 115 z 180

function updateListView ( ) {
var selected = _root.[Link]( );
[Link](_root.songsListBox, _root.songsList,
_root.formatSongsList);
_root.[Link]( );
_root.[Link](selected);
}

// The addSongToList( ) function adds a song to the playlist from a URL.


function addSongToList (url) {

// Load a sound into a new sound object created using the custom
// createNewSound( ) method from Recipe 13.1.
var mySound = [Link]( );
[Link](url);

// Add the song to the songsList recordset.


[Link]({url: url, snd: mySound});

// Create an onEnterFrame( ) method that calls updateListView( ) continu


// until the song is loaded.
[Link] = function ( ) {
_root.updateListView( );
if ([Link]) {
delete [Link];
}
};
}

// The startNextSong( ) function plays the next song in the playlist.


function startNextSong ( ) {

// The counter variable keeps track of which song is currently being playe
// it is undefined or greater than the number of songs, reset it to 0.
if (counter >= [Link]( ) || counter == undefined) {
counter = 0;
}

// Highlight the current song in the playlist.


[Link](counter);

// Get the sound object for the current song.


var snd = [Link](counter).[Link];

// Set the sound to fade in for the first five seconds


// and to fade out for the last five seconds.
[Link](5000);
[Link](5000);

// When the song fades out, call this function (startNextSong( )) again.
[Link]("startNextSong");

// When the song ends (as opposed to when it starts fading out), call
// stopPlayer( ) to stop the song.
[Link]("stopPlayer");

// Load the new song into the queue and tell the
// jukebox controller to play the next song.
[Link](snd);
[Link]( );
counter++;
}

// When the end of the song is reached, this function stops the song.

[Link] 2004-02-06
Strona 116 z 180

function stopPlayer ( ) {
_root.[Link]( );
}

// The addSound( ) function is the click handler for the Add Song button; i
// a song to the playlist from the URL typed into the text field.
function addSound ( ) {
var url = [Link];
[Link] = "";
addSongToList(url);
}

// The moveSongUp( ) function moves a song up in the playlist.


function moveSongUp ( ) {

// Get the index of the of playlist item the user has selected.
var selected = [Link]( );

// Move the song only if it isn't already first in the list.


if (selected > 0) {

// Insert the item into the list at one index prior to its current posit
[Link](selected - 1, [Link](selected).data)

// Remove the item from its original position in the list.


[Link](selected + 1);

// Update the view of the playlist and set the selected index such that
// song that was moved is highlighted.
updateListView( );
[Link](selected - 1);
}
}

// The moveSongDown( ) function does the opposite of the moveSongUp( ) fun


function moveSongDown ( ) {
var selected = [Link]( );
// Move the song only if it isn't already last in the list.
if (selected < [Link]( ) - 1) {
[Link](selected + 2, [Link](selected).data)
[Link](selected);
updateListView( );
[Link](selected + 1);
}
}

// The removeSong( ) function removes a song from the list.


function removeSong ( ) {

// Get the index of the selected song in the playlist.


var selected = [Link]( );

// Make sure a song is selected before trying to remove one.


if (selected != undefined) {

// Get the sound associated with the song.


var snd = [Link](selected).snd;

// Remove the song from the playlist.


[Link](selected);
updateListView( );

if (selected < counter) {


// If the index of the deleted song is less than counter (the index of

[Link] 2004-02-06
Strona 117 z 180

// song that is currently playing), decrement counter.


counter--;
} else if (selected == counter) {
// If the deleted song was the current song, fade out the song from it
// current play position.
[Link](5000, [Link]);
}
}
}

// The getMP3( ) function is the click handler for the


// getLocalBtn and getServerBtn buttons. It opens the HTML
// pages for the selectors in new browser windows.
function getMP3 (btn) {
if (btn == getLocalBtn) {
[Link]("javascript:void([Link]('[Link]',
'_blank', 'width=300,height=150'))");
}
else if (btn == getServerBtn) {
[Link]("javascript:void([Link]('[Link]',
'_blank', 'width=210,height=360'))");
}
}

init( );
createElements( );
layoutElements( );

3. Save the Flash document and publish the .swf and .html files.

At this point, you have completed the entire jukebox application. You can test it by opening juke
in a web browser. If you are using the server MP3 selector, you need to open [Link] so th
served by the web server. For example, if you have a web server running on your local machine,
use [Link]

Most of the code in the main jukebox movie becomes clearer with a little further examination, so
a look at some of the code elements in more detail.

The init( ) function creates the local connection object that listens for communications from both
selectors. Both MP3 selectors send messages over the connection named "pathSendConnection"
receivePathInfo( ) method, and they pass that method the URL/path to the selected MP3. The
receivePathInfo( ) method passes the URL/path along to the addSongToList( ) function to add th
the playlist. The init( ) function also creates a recordset that is used to keep track of the songs in
playlist. A recordset is convenient for populating a list box with complex data, which in this case
URL/path to the MP3 file as well as a Sound object for the song.

function init ( ) {
receiver = new LocalConnection( );
[Link] = function (path) {
if (path != "" && path != undefined && path != null) {
_root.addSongToList(path);
}
};
[Link]("pathSendConnection");

songsList = new RecordSet("url, snd");


}

The formatSongsList( ) function is a formatter function that is used by the [Link]


( ) method in the updateListView( ) function. The playlist displays the name of each song as well
duration of the song or the percentage that has loaded. These values are obtained from the song

[Link] 2004-02-06
Strona 118 z 180

recordset. Each record has a url column that contains the URL/path to the MP3 file. You can ext
name of the MP3 from this value using the same technique discussed earlier (in the MP3 selector
which you split the URL/path into an array using "/" as the delimiter. If the url value is a URL, it
be split using a forward slash (/) as the delimiter. However, if the MP3 has been added to the pla
the local MP3 selector, the url can potentially use backslashes. In that case, split the value into
using the backslash (\) as the delimiter. Don't forget to escape the backslash delimiter. Additiona
item in the playlist list box should contain a data value that includes both the URL/path to the M
as the Sound object into which the song has been (or is being) loaded. You can accomplish this b
the list box element's data property to the record from the recordset.

var urlAr = [Link]("/");


if ([Link] == 1) {
urlAr = [Link]("\\");
}
var name = urlAr[[Link] - 1];
var obj = new Object( );
[Link] = element;
[Link] = name;

If the song has already loaded, you can use the duration property to determine its length in sec
SoundController class (the class for the SoundController component) includes a static timeDispla
method to format the time in standard minutes and seconds. Otherwise, if the song has not yet
you cannot access the duration, so you should display the percentage that has loaded instead.

if ([Link]) {
[Link] += " (" + [Link]([Link]) + ")";
} else {
[Link] += " [" + [Link] + "%]";
}

The updateListView( ) function is called to update the playlist display, such as when a new song
or an old one is moved or removed. Also, the display is updated to show a song's load progress.
updateListView( ) function uses the [Link]( ) method to update the conte
list box. However, there are a few other things that need to be done for formatting and display t
correct. First of all, because the MP3 names can vary in length, use the adjustWidth( ) method to
the list box to fit its contents. Also, call setSelectedIndex( ) to reselect the item that was selecte
updateListView( ) was invoked.

function updateListView ( ) {
var selected = _root.[Link]( );
[Link](_root.songsListBox, _root.songsList,
_root.formatSongsList);
_root.[Link]( );
_root.[Link](selected);
}

The addSongToList( ) method is called when the user clicks the Add Song button or adds a song
local or server MP3 selector. The function adds the new song to the songsList recordset. The UR
passed automatically to the addSongToList( ) function, so that part is already known. For the snd
the function creates a new Sound object and loads the song into it. Finally, addSongToList( ) atta
onEnterFrame( ) method to the mc property of the Sound object—the mc property is a reference
sound-holder movie clip created automatically by [Link]( ). The onEnterFrame(
calls the updateListView( ) function continually until the song has completed loading.

function addSongToList (url) {


var mySound = [Link]( );
[Link](url);
[Link]({url: url, snd: mySound});
[Link] = function ( ) {
_root.updateListView( );

[Link] 2004-02-06
Strona 119 z 180

if ([Link]) {
delete [Link];
}
};
}

The startNextSong( ) function is interesting in that it is both the click handler function for the Be
Playback button and the onFadeOut callback function for each song. When the function is invoke
the next song in the playlist. The code in this function is central to the core functioning of the juk
let's look at each piece of it. First, it uses the counter variable to keep track of which song is be
played. The startNextSong( ) function increments counter each time it completes. Therefore, if c
greater than the number of songs in the playlist, start back at the beginning of the playlist by se
counter to 0.

if (counter >= [Link]( ) || counter == undefined) {


counter = 0;
}

Each time the next song starts, you want to highlight that song in the playlist. You can do this w
setSelectedIndex( ) method:

[Link](counter);

Each item in the playlist list box has data that includes both url and snd properties. The snd pro
Sound object into which the song has been loaded, and we can use it to instruct the song to beg
To perform a cross-fade between songs, each song should fade in for five seconds at its beginnin
fade out for five seconds at its end. The next song should start when the onFadeOut event occur
the current song. This way, the next song begins playing at the point that the previous song beg
fade out.

var snd = [Link](counter).[Link];


[Link](5000);
[Link](5000);
[Link]("startNextSong");
[Link]("stopPlayer");
[Link](snd);
[Link]( );
counter++;

The moveSongUp( ) and moveSongDown( ) functions change the order of the selected song in th
They first insert a copy of the selected item into the playlist at the new position using the addIte
method, which automatically shifts the subsequent items in the list box. Then, they simply delete
selected song from the original position using the removeItemAt( ) method. Here is the code for
moveSongUp( ); moveSongDown( ) is similar:

function moveSongUp ( ) {
var selected = [Link]( );
if (selected > 0) {
[Link](selected - 1, [Link](selected).data);
[Link](selected + 1);
updateListView( );
[Link](selected - 1);
}
}

The removeSong( ) function is the click handler function for the Remove Song button. The functi
sure that a song is selected before trying to remove anything. If the getSelectedIndex( ) method
songsListBox returns undefined, the function doesn't remove anything. If a song is selected, th
function does several things. First, it retrieves the Sound object associated with the song (becau
cannot get the reference after deleting it). It then removes the song from the playlist by deleting

[Link] 2004-02-06
Strona 120 z 180

the recordset and calling updateListView( ) to refresh the playlist's display. If the removed song
currently playing, it fades out the song using fadeOut( ). Because of the way that you have confi
each song in the startNextSong( ) function, when the current song is instructed to fade out, the
automatically starts.

function removeSong ( ) {
var selected = [Link]( );
if (selected != undefined) {
var snd = [Link](selected).snd;
[Link](selected);
updateListView( );
if (selected < counter) {
counter--;
} else if (selected == counter) {
[Link](5000, [Link]);
}
}
}

[ Team LiB ]
[ Team LiB ]

26.4 Wrapping It Up
As with any application, there is not one "right" way to plan and develop the jukebox
application. However, I hope you have gained some insights and ideas related to good practices
working with Sound objects, custom components, FLASHVARS, recordsets, local connections,
and Flash Remoting.

[ Team LiB ]
[ Team LiB ]

Chapter 27. A Personalized My Page


Application
Personalization is an important part of many web applications, as seen in so-called My Page
applications, such as those available at My Yahoo!, My Excite, and My MSN. Typical My Page
applications offer modules that the user can turn on or off and position within the page. A
module can be a .swf file that gives access to a web service or performs some service function,
so we often use the terms "service" or "service module" interchangeably with "module," even if
the module is not a formal web service. Examples of modules offered by My Page applications
include:

z Scheduler

z Email

z News

z Weather

z Stocks

[Link] 2004-02-06
Strona 121 z 180

z Web search

z Shopping

These modules usually can be customized according to the user's preferences. For example, if
the user activates a weather module, he is given the option of specifying the city for which to
display the weather forecast.

In this chapter, you will create a My Page application in Flash. This application draws on many
skills detailed throughout recipes in previous chapters, including:

z Local shared objects

z Loading and processing XML

z Using predefined components

z Creating custom components

z Using the Drawing API's methods

z Loading external SWFs

z Using Flash Remoting to consume web services

z Controlling movie clips with ActionScript

[ Team LiB ]
[ Team LiB ]

27.1 Formulating the Application Overview


Before starting any application, formulate a plan. Clarify the project goals and map out a
strategy to meet them. A My Page application requires a framework that allows various modules
to be added, as illust rated in Figure 27-1.

Figure 27-1. A conceptual overview of the My Page framework

[Link] 2004-02-06
Strona 122 z 180

The application should have a scalable architecture that allows the developer to offer more
modules and allows the user to add more of these modules to his customized page. The My
Page framework should not care about the particulars of each module and should treat all
modules uniformly.

The sample application code that is provided in this chapter includes the following files:

[Link]

The My Page framework document

[Link]

A note module

[Link]

An address book module

[Link]

A web search modular service (uses Google's web service)

[Link]

An XML document that tells the framework which modules are available

[Link]

An ActionScript file with functions that are shared by multiple movies

The final files can be downloaded from [Link]

[ Team LiB ]
[ Team LiB ]

27.2 Creating the Framework


The My Page framework is a single Flash movie, [Link], which performs the following tasks:

z Loads available module types from an XML file, which it uses to populate a menu

z Creates a new visual container (a custom component instance) when a user chooses a mod
and loads the module into it

z Remembers each module's position and state between sessions using a local shared object

27.2.1 Sharing Common Functions


The first step in creating the framework is to create the [Link] file, which is shared
all the Flash movies, including [Link]. [Link] should contain the following cod

[Link] = function ( ) {
// If no currentDepth is defined, initialize it to 1.
if ([Link] == undefined) {

[Link] 2004-02-06
Strona 123 z 180

[Link] = 1;
}
// Return the new depth and increment it by 1 for next time.
return [Link]++;
};

This code adds a getNewDepth( ) method to the MovieClip class such that all MovieClip objects in
it. The getNewDepth( ) method generates a unique depth each time it is called. This is important
because this chapter creates all application elements at runtime via ActionScript. Each dynamica
created element must reside on its own depth to prevent elements from overwriting each other.

27.2.2 Configuring the Available Modules


Later in this chapter we create three modules that are made available to the framework. Howeve
framework should be able to accommodate other modules as well. We achieve this flexibility usin
XML document that contains information about the available modules the framework should load
now, create [Link] as shown in the following example, which lists the three modules that w
create shortly.

<choices>
<choice title="Address Book" source="[Link]" />
<choice title="Notes" source="[Link]" />
<choice title="Search the Web" source="[Link]" />
</choices>

27.2.3 Adding a Component


The next step in the framework development is to create a custom component—the instances of
act as the containers into which each module is loaded. The main module container component a
uses another custom component, so there are two steps in this process. First, you should define
child component, UnitComponentBar, which is used as the title bar for the main module containe
component. Then, you should define UnitComponent.

[Link] Defining UnitComponentBar

Follow these steps to define UnitComponentBar:

1. Within [Link], insert a new movie clip library symbol named UnitComponentBar.

2. Set the symbol to Export for ActionScript and Export in First Frame using the Linkage optio
under the Library panel's Options pop-up menu.

3. Give it the linkage identifier UnitComponentBarSymbol.

In the following example, you can see the code that you should place on the first frame of the de
layer of the UnitComponentBar symbol. We'll examine portions in more detail following the code

The getNewDepth( ) method is used within the component class even though you cannot yet see
has been made available. We will #include it in the framework document later. Because getNew
( ) is defined as a method for all MovieClip objects, it is accessible from any timeline once it has
loaded in a movie.

// #initclip with an option of 0 means that this code loads


// before anything else, which is important, since it must be
// defined before it can be used in UnitComponent.
#initclip 0

// The constructor is called when a new UnitComponentBar instance is created.

[Link] 2004-02-06
Strona 124 z 180

function UnitComponentBar ( ) {

// Create the movie clip into which the bar is drawn.


[Link]("barHolder", [Link]( ));
// Create the title text field.
[Link]("title", [Link]( ));
[Link] = false;

// Create a movie clip for the Close button that lets users close the module.
[Link] = this._parent.createEmptyMovieClip("closer",
this._parent.getNewDepth( ));
}

// UnitComponentBar extends the MovieClip class.


[Link] = new MovieClip( );

// The draw( ) method draws the title bar with the given width, height, and fill
// color. It also aligns the title bar to the parent UnitComponent instance
// according to the margin parameter, m, and sets the color of the text.
[Link] = function (w, h, m, fillColor, titleColor) {

// Draw the title bar.


with ([Link]) {
lineStyle(1, 0, 100);
beginFill(fillColor, 100);
drawRectangle(w + m, h, 0, 0, (w + m)/2, h/2);
endFill( );
}

// Set the text color


[Link] = titleColor;

// Draw the Close button.


with ([Link]) {
lineStyle(1, titleColor, 100);
beginFill(0, 0);
drawRectangle(10, 10, 0, 0, 5, 5);
endFill( );
_x = w - m - 5;
_y = 5;
}

// Invoke the close( ) method to remove the module if the Close button is clic
[Link] = [Link];
};

// This function sets the text of the title bar.


[Link] = function (titleText) {
[Link] = titleText;
};

// When a title bar is clicked, make its parent UnitComponent instance draggable.
[Link] = function ( ) {
this._parent.startDrag( );

// Check for overlapping siblings.


this._parent.onEnterFrame = this._parent.checkOverlaps;
};

// Stop the dragging when released.


[Link] = function ( ) {

// Stop dragging the item.


this._parent.stopDrag( );

[Link] 2004-02-06
Strona 125 z 180

// Notify all listeners of the unit's new position


var l = this._parent.listeners;
for (var i = 0; i < [Link]; i++) {
l[i].onMoved(this._parent);
}

// Disable hit testing now that the drag event is over.


delete this._parent.onEnterFrame;
};

// Close the parent UnitComponent instance.


[Link] = function ( ) {
this._parent.onClose( );
this._parent.removeMovieClip( );
};

// Register the class.


[Link]("UnitComponentBarSymbol", UnitComponentBar);

#endinitclip

This constitutes a rather long listing with some important code that we should look at more close

To begin with, it is important that the #initclip directive include the load order parameter. In t
case, use the value 0 because the code enclosed between #initclip and #endinitclip should
before any other code in the movie. This is because UnitComponentBar instances are created wit
UnitComponent objects (as you will see in the next section), and, therefore, the definition must b
loaded first to be accessible.

#initclip 0

The UnitComponentBar constructor creates the close box movie clip with the createEmptyMovieC
method. Notice, however, that the method is invoked from this._parent instead of just this. T
reason for this is that the close box instance must be on top of the UnitComponentBar instance t
function properly. For convenience, a reference to the close box movie clip created in the parent
to a property named closeSquare:

[Link] = this._parent.createEmptyMovieClip("closer",
this._parent.getNewDepth( ));

The onPress( ) and onRelease( ) event handler methods are defined for all UnitCompontBar insta
defining them on the prototype property of the class. Both of these definitions, as well as the cl
method, invoke methods defined for the parent UnitCompont instance by using this._parent to
reference it:

[Link] = function ( ) {
this._parent.startDrag( );
this._parent.onEnterFrame = this._parent.checkOverlaps;
};

[Link] = function ( ) {
this._parent.stopDrag( );
var l = this._parent.listeners;
for (var i = 0; i < [Link]; i++) {
l[i].onMoved(this._parent);
}
this._parent.onEnterFrame = null;
};

[Link] = function ( ) {

[Link] 2004-02-06
Strona 126 z 180

this._parent.onClose( );
this._parent.removeMovieClip( );
};

Finally, the class must be registered to be associated automatically with new instances of the mo
library symbol in which it is defined. The linkage identifier UnitComponentBar is passed as the fir
parameter, and a reference to the class is passed as the second parameter to the [Link]
( ) method to accomplish this:

[Link]("UnitComponentBarSymbol", UnitComponentBar);

[Link] Defining UnitComponent

Now that we've defined the UnitComponentBar, let's define the UnitComponent that contains it:

1. Within [Link], drag an instance of the ProgressBar component from the Components p
the Stage in order to add it to the Library. After doing so, you can delete the instance from
Stage.

2. Insert a new movie clip library symbol named UnitComponent.

3. Set the library symbol to Export for ActionScript and Export in First Frame using the Linkag
option under the Library panel's pop-up Options menu.

4. Give it the linkage identifier UnitComponentSymbol.

Here is the code that goes on the first frame of the default layer of the new symbol. Look over th
briefly. We'll examine aspects of it in more detail shortly.

// Load this code second by using the load order parameter value of 1.
#initclip 1

// The UnitComponentGroup class keeps track of each UnitComponent instance that i


// created within the same timeline.
function UnitComponentGroup ( ) {}

// groupMembers is an associative array that holds references to each sibling


// instance (i.e., other members of the group).
[Link] = new Object( );

// The onNew( ) method is called when a new UnitComponent instance is created.


[Link] = function (newUnit) {

// Add the new UnitComponent instance to the groupMembers array.


[Link][newUnit._name] = newUnit;
var sibs;

// Loop through each sibling (each UnitComponent).


for (var unit in [Link]) {
sibs = new Array( );

// Loop through all the groupMember elements to create an array of siblings (n


// including itself) for each UnitComponent instance.
for (var sib in [Link]) {
if ([Link][unit] != [Link][sib]) {
[Link]([Link][sib]);
}
}

// Call the UnitComponent instance's setSiblings( ) method with a reference

[Link] 2004-02-06
Strona 127 z 180

// newly created sibs array.


[Link][unit].setSiblings(sibs);
}
};

// The constructor creates the movie clip and text field objects for a module.
function UnitComponent ( ) {

// Create the outline box and title bar movie clips.


[Link]("outline", [Link]( ));
[Link]("UnitComponentBarSymbol", "bar", [Link]( ));

// Add the placeholder for the loaded module.


[Link]("loadPlaceHolder", [Link]( ));

// Obtain a unique depth for the new module.


[Link] = [Link]( );

// Create a unit grouping to contain this and any sibling modules (see the
// discussion following this example).
if (this._parent.unitGroup == undefined) {
this._parent.unitGroup = new UnitComponentGroup( );
}
[Link] = this._parent.unitGroup;
[Link](this);
}

// UnitComponent extends the MovieClip class.


[Link] = new MovieClip( );

// Set the default attributes of all units.


[Link] = 0x000000;
[Link] = 0xFFFFFF;
[Link] = new Array( );

// Define the following properties and set them to null. They will be assigned a
// value by other methods.
[Link] = null;
[Link] = null;
[Link] = null;

// Load an external .swf file into the component.


[Link] = function (url) {

// Load the module into loadPlaceHolder.


[Link](url);

// Create a text field to label the progress bar.


[Link]("loading", [Link]( ));
[Link] = "loading...";

// Create a progress bar using the ProgressBar component.


[Link]("FProgressBarSymbol", "progress", [Link]( ));
[Link]._y = [Link]._height + 5;

// Start an onEnterFrame( ) loop to update the progress bar.


[Link] = [Link];
};

// The onLoading( ) method updates the progress while loading and then performs
// actions when the module has loaded.
[Link] = function ( ) {

// Update the loading module's progress bar with the loaded and total bytes.

[Link] 2004-02-06
Strona 128 z 180

var lBytes = [Link]( );


var tBytes = [Link]( );
[Link](lBytes, tBytes);

// If the module has loaded completely, then do the following:


if ([Link]( ) == 100) {

// Remove the progress bar and its label text field.


[Link]( );
[Link]( );

// Call the drawOutline( ) and the [Link]( ) methods with th


// loaded module's dimensions. This creates a placeholder rectangle for the n
// loaded module.
var margin = 5;
var w = [Link]._width + (margin * 2);
var h = [Link]._height + (margin * 2);
[Link](w, h, margin);
[Link](w, 20, margin, [Link], [Link]);

// Create a table to position the title bar and the outline. Use a spacing of
// so that there are no spaces between the two.
var t = new Table(0, 0, 0, new TableRow(0,
new TableColumn(-1, [Link], [Link])));
[Link]._x = [Link]._x + margin;
[Link]._y = [Link]._y + margin;

// Stop the onEnterFrame( ) loop by deleting it.


delete [Link];
}
};

// Set the siblings property to an array containing references to all other


// UnitComponent instances.
[Link] = function (s) {
[Link] = s;
};

// Draw the outline and background for the component.


[Link] = function (w, h, margin) {
with ([Link]) {
lineStyle(1, 0, 100);
beginFill(0xFFFFFF, 100);
drawRectangle(w + margin, h + margin, 0, 0, (w + margin)/2, (h + margin)/2);
endFill( );
}
};

// Set the title of the component, as displayed in the title bar.


[Link] = function (title) {
// Call the UnitComponentBar instance's setTitle( ) method.
[Link](title);
};

// Check whether the instance is overlapped by another component instance.


[Link] = function ( ) {

// Loop through all the siblings.


for (var i = 0; i < [Link]; i++) {

// If the instance is overlapped by a sibling--hitTest( ) is true and the sib


// depth is greater than this instance's depth--swap the depths.
if ([Link]([Link][i]) &&
[Link][i].getDepth() > [Link]( )) {

[Link] 2004-02-06
Strona 129 z 180

[Link]([Link][i]);
}
}
};

// Add a listener object (an object with an onMoved( ) method defined), which is
// called automatically when the component instance is moved (see the onMoved( )
// made from [Link]( ) within drawBar( )).
[Link] = function (listener) {
[Link](listener);
};

// Register the class to a symbol linkage name.


[Link]("UnitComponentSymbol", UnitComponent);

#endinitclip

Some of the code in preceding example requires illumination. The example code first defines a h
class, UnitComponentGroup. The UnitComponentGroup class is used to keep track of all the
UnitComponent instances that are within the same timeline. Each time a new instance is created
added to the UnitComponentGroup instance. I refer to the UnitComponent instances within the s
timeline as siblings. These siblings are stored within a groupMembers associative array property o
UnitComponentGroup object.

function UnitComponentGroup ( ) {}
[Link] = new Object( );

The UnitComponentGroup class defines only one method, onNew( ), which is invoked whenever a
UnitComponent is created. It is passed a reference to the new UnitComponent, and it adds that
reference to it's groupMembers property. Then, it loops through all the siblings and updates their
properties so that each maintains a current list of all its siblings. This array is used by the
UnitComponent instances whenever they are being dragged by the user to perform hit tests with
siblings (because siblings love to hit one another).

[Link] = function (newUnit) {


[Link][newUnit._name] = newUnit;
var sibs;
for (var unit in [Link]) {
sibs = new Array( );
for (var sib in [Link]) {
if ([Link][unit] != [Link][sib]) {
[Link]([Link][sib]);
}
}
[Link][unit].setSiblings(sibs);
}
};

Next, the UnitComponent class is defined to extend the MovieClip class. This is important since
UnitComponent instances have a visual representation.

[Link] = new MovieClip( );

The constructor method for UnitComponent instances does a few interesting things. For one thin
creates an instance of the UnitComponentBar component by calling attachMovie( ) with a symbo
linkage identifier of "UnitComponentBarSymbol". Because the symbol linkage identifier
"UnitComponentBarSymbol" is registered to the UnitComponentBar class, the UnitComponentBar
constructor is called.

function UnitComponent ( ) {
. . .

[Link] 2004-02-06
Strona 130 z 180

[Link]("UnitComponentBarSymbol", "bar", [Link]( ));


. . .
}

Also within the constructor, the instance is assigned a UnitComponentGroup object. If none exist
is created on the parent timeline. Then, the group's onNew( ) method is invoked and passed a re
to the new UnitComponent.

function UnitComponent ( ) {
. . .
if (this._parent.unitGroup == undefined) {
this._parent.unitGroup = new UnitComponentGroup( );
}
[Link] = this._parent.unitGroup;
[Link](this);
}

The purpose of the load( ) method—repeated in the following excerpt—is straightforward once yo
a step back from the minutiae. It loads the external .swf module (located at the specified url) in
component. It also creates a progress bar and a text label that says "loading . . . ". The trickiest
technique is the onEnterFrame( ) event handler used to detect whether the .swf file is loaded, w
assigned a reference to the onLoading( ) method. This means that the onLoading( ) method is ca
continuously as the playhead waits in the frame.

[Link] = function (url) {


[Link](url);
[Link]("loading", [Link]( ), 0, 0, 0, 0);
[Link] = "loading . . . ";
[Link] = true;
[Link]("FProgressBarSymbol", "progress", [Link]( ));
[Link]._y = [Link]._height + 5;
[Link] = [Link];
};

The onLoading( ) method is called repeatedly until the module loads completely. The onLoading(
method checks the status of the loading module using getBytesLoaded( ) and getBytesTotal( ) a
updates the progress bar using setProgress( ). When the module is loaded, the progress bar and
label are removed, since they are no longer needed. At that time, the drawOutline( ) method and
title bar's draw( ) method are called once to draw the container to the proper dimensions for the
module. (Both methods are passed the values of the loaded module's dimensions.) Finally, the
onEnterFrame( ) event handler method is deleted since it is no longer needed once the module f
loading.

[Link] = function ( ) {
var lBytes = [Link]( );
var tBytes = [Link]( );
[Link](lBytes, tBytes);
if ([Link]( ) == 100) {
[Link]( );
[Link]( );
var margin = 5;
var w = [Link]._width + (margin * 2);
var h = [Link]._height + (margin * 2);
[Link](w, h, margin);
[Link](w, 20, margin, [Link], [Link]);
var t = new Table(0, 0, 0, new TableRow(0,
new TableColumn(-1, [Link], [Link])));
[Link]._x = [Link]._x + margin;
[Link]._y = [Link]._y + margin;
delete [Link];
}

[Link] 2004-02-06
Strona 131 z 180

};

The checkOverlaps( ) method also benefits from explanation. This method is called continuously
user drags the instance. It checks whether the dragged instance is overlapped by any of its siblin
the siblings array. It uses hitTest( ) to see if the current and sibling instances overlap in the
coordinate space, and it compares the depths of the current and sibling instances to see if the si
appears above the current instance. If these two conditions are met, it calls the swapDepths( ) m
to put the current instance above the sibling.

[Link] = function ( ) {
for (var i = 0; i < [Link]; i++) {
if ([Link]([Link][i]) &&
[Link][i].getDepth() > [Link]( )) {
[Link]([Link][i]);
}
}
};

27.2.4 Setting Up the Framework Form


Now that the component container has been defined, the only thing left to do for the framework
the code for the movie's main routine on the first frame of the default layer of the main timeline

Before adding the code to the main timeline, you need to make sure that the Flash document ha
required component symbols in the library. In addition to the custom components you created in
previous sections in this chapter, the framework also uses the ComboBox and PushButton compo
So the first thing you should do is add the corresponding component symbols to the document's
You can do this by dragging instances of each of the components on the Stage, and then deleting
instances. The symbols are retained in the Library.

The following code creates the form that allows the user to add modules to his page, and it reme
the user's page settings using a local shared object (and loads the form if it was created previou
This code constitutes the main routine in [Link].

// Include the necessary libraries.


// Include [Link] from Chapter 7.
#include "[Link]"
//Include [Link] and [Link] from Chapter 11
#include "[Link]"
#include "[Link]"
// Include [Link] from Chapter 8 and [Link] from Chapter 4.
#include "[Link]"
#include "[Link]"

// Create a new local shared object (LSO) if it doesn't yet exist.


// Otherwise, reload the saved page state from the existing shared object.
function initLSO ( ) {
lso = [Link]("myPage");
// Create a new local shared object, if necessary.
if (![Link]) {
[Link] = true;
[Link] = new Object( );
} else {
// Otherwise, load the saved state from the existing local shared object.
var o = [Link];
var newUnit;
for (var item in o) {
newUnit = openUnit(o[item].title, o[item].source, item);
newUnit._x = o[item].x;
newUnit._y = o[item].y;
}

[Link] 2004-02-06
Strona 132 z 180

}
// The LSO is notified whenever a unit moves, so it can resave the page state.
[Link] = saveUnitPosition;
}

// Save the current position of a unit to the shared object.


function saveUnitPosition (unit) {
[Link][[Link]].x = unit._x;
[Link][[Link]].y = unit._y;
[Link]( );
}

// Read the module information from the XML document into an array
// and pass the array to initForm( ).
function loadXML ( ) {
servicesXML = new XML( );
[Link] = initForm;
[Link] = true;
[Link]("[Link]");
[Link] = this;
// Define an onLoad( ) handler to parse the data into an array.
[Link] = function ( ) {
var services = new Array( );
var items = [Link];
var attribs;
for (var i = 0; i < [Link]; i++) {
attribs = items[i].attributes;
[Link]({label: attribs["title"], data: attribs["source"]});
}
[Link](services);
};
}

// Create a combo box populated with the array of modules passed to the function.
// Include a button to add the currently selected module to the page.
function initForm (services) {
[Link]("FComboBoxSymbol", "choicesMenu", [Link]( ));
[Link](services);
[Link]( );
[Link]("FPushButtonSymbol", "addBtn", [Link]( ));
[Link]("add to my page");
[Link]("addUnit");
var t = new Table(10, 10, 10, new TableRow(5, new TableColumn(5, choicesMenu),
new TableColumn(5, addBtn)));
}

// The addUnit( ) function is the callback function for addBtn.


function addUnit ( ) {
var si = [Link]( );
openUnit([Link], [Link]);
}

// Open a module unit based on the title and source parameters passed
// to it. If index is null, the unit is new, so assign a new index.
// Otherwise, restore the index, as loaded from the shared object.
function openUnit (title, source, index) {
var u = [Link]("UnitComponentSymbol",
"unit" + [Link]( ),
[Link]( ));
[Link](title);
[Link](source);
// Delete the unit if the user clicks the close box.
[Link] = function ( ) {
delete this._parent.[Link][[Link]];

[Link] 2004-02-06
Strona 133 z 180

};
if (index != null) {
[Link] = index
} else {
var openUnit = new Object( );
[Link] = title;
[Link] = source;
[Link][[Link]] = openUnit;
[Link]( );
}
[Link](lso);
return u;
}

// Call initLSO( ) and loadXML( ) to start the movie.


initLSO( );
loadXML( );

The main routine of the framework application, as shown in the preceding example, consists of j
few major functions. To better understand some of the inner workings of these functions, let's ex
them more closely.

The initLSO( ) function is responsible for opening the local shared object (LSO) and handling any
data. First of all, it uses the [Link]( ) method to try to locate the shared object n
"myPage". If such an object exists, it is opened; otherwise, it is created. If the object did not exi
previously, the exists property would not be true. When working with shared objects, you can
benefit from setting such a Boolean property. In this case, the object should be initialized by def
the exists property so that, in future sessions, the framework knows that the object is no longe

Additionally, you should define the opened property as a new associative array to store the modu
states. If the object already exists from a previous session, the function uses a for . . . in loop to
through the opened associative array and calls openUnit( ) to open any modules that were open
previous session. Finally, for each module, the openUnit( ) function sets the local shared object a
listener (see next listing). Because the listener objects for UnitComponent instances look for an
onMoved( ) method for callbacks, you should create a method of this name for the shared object

function initLSO ( ) {
lso = [Link]("myPage");
if (![Link]) {
[Link] = true;
[Link] = new Object( );
} else {
var o = [Link];
var newUnit;
for (var item in o) {
newUnit = openUnit(o[item].title, o[item].source, item);
newUnit._x = o[item].x;
newUnit._y = o[item].y;
}
}
[Link] = saveUnitPosition;
}

The openUnit( ) function opens a new module for a given source using the attachMovie( ) metho
Notice that the attachMovie( ) method returns a reference to the movie clip it creates. Setting a
variable (u) to this reference is handy because it can save having to type out the full path to the
clip within the function. Because the name "UnitComponent" is registered to the UnitComponent
the newly created unit is actually a UnitComponent instance. Therefore, the methods of the
UnitComponent class can be invoked from this object.

The setTitle( ) and load( ) methods are called to perform the necessary tasks, and the onClose( )

[Link] 2004-02-06
Strona 134 z 180

callback method is defined such that when a unit is closed, the reference to it in the local shared
is removed. The next part of the function checks to see if the function has been passed a value f
index. An index value is passed only when the unit is being reopened from a previous session.
ActionScript functions do not validate the number of parameters passed to the function, so if ind
not passed to the function, it is simply undefined when used within the function.

function openUnit (title, source, index) {


var u = [Link]("UnitComponentSymbol",
"unit" + [Link]( ),
[Link]( ));
[Link](title);
[Link](source);
[Link] = function ( ) {
delete this._parent.[Link][[Link]];
};
if (index != undefined) {
[Link] = index
} else {
var openUnit = new Object( );
[Link] = title;
[Link] = source;
[Link][[Link]] = openUnit;
[Link]( );
}
[Link](lso);
return u;
}

The framework also uses the loadXML( ) function to load the values for the available modules. Th
loadXML( ) function uses an XML object to load the values from [Link]. Generally, you sho
consider loading content into a Flash movie from XML documents or other sources. When you do
you effectively separate content from functionality to make for cleaner code.

The loaded results are handled within the onLoad( ) method, as defined for this object. The onLo
method loops through each of the modules of the loaded document and creates an array of objec
label and data properties. This array is in the format that can be used to populate a ComboBox
component easily. After the array is assembled, the onLoad( ) method invokes the initForm( ) fu
on the main timeline and passes it the array.

The initForm( ) function call is a little bit tricky because it is invoked from the root property of th
servicesXML object. The root property is a custom property (see bolded line in the following cod
is defined as a reference to the main timeline. When you set a reference to another object by wa
property, such as root in this example, you can maintain relative relationships. For example, be
initForm( ) is called from within the XML object's onLoad( ) method, it is impossible to call it with
making an absolute reference (_root.initForm( )) unless a reference property is set, as is done h

function loadXML ( ) {
servicesXML = new XML( );
[Link] = true;
[Link]("[Link]");
[Link] = this;
[Link] = function ( ) {
var services = new Array( );
var items = [Link];
var attribs;
for (var i = 0; i < [Link]; i++) {
attribs = items[i].attributes;
[Link]({label: attribs["title"], data: attribs["source"]});
}
[Link](services);
};

[Link] 2004-02-06
Strona 135 z 180

}
[ Team LiB ]
[ Team LiB ]

27.3 Creating Service Modules


The My Page framework does not know or care which modules are being loaded, so you can add
kind of service module you want, using the three sample modules as exemplars. Remember to u
relative addresses; avoid absolute references using _root. Because the service module movies a
loaded into movie clips, _root refers to the framework's main timeline and not to the main timel
of the service .swf.

27.3.1 Making Notes


A common feature of My Page applications is the ability to make notes that are stored between
sessions. This feature might be used for making daily goals lists, reminders, directions, or phone
numbers. The notes module is instructional in that it uses a local shared object to store
information—the user's notes—between sessions.

The notes module uses the ScrollBar component, so add the component symbol to the [Link]
document's Library by dragging an instance from the Components panel to the Stage (and then
deleting the instance from the Stage).

The following example shows the code for [Link] as it should appear on the first frame of the
main timeline:

// Include [Link] from Chapter 7 and [Link] from Chapter 8.


#include "[Link]"
#include "[Link]"

// Create the form elements used.


function initNotes ( ) {

// Create the text field used for accepting user input and displaying notes.
[Link]("notes", [Link]( ), 0, 0, 100, 200);
[Link] = true;
[Link] = true;

// Create the scrollbar for the notes text field.


[Link]("FScrollBarSymbol", "sb", [Link]( ));

// Set the notes field width to 200 pixels, counting the scrollbar.
notes._width = 200 - sb._width;

// Tie the scrollbar to the notes field and position it at the field's right.
sb._x = notes._width;
[Link](notes._height);
[Link](notes);
}

// This function opens the LSO and reads and writes the user's notes.
function initLSO ( ) {

// Open the local shared object named "notes".


notesLSO = [Link]("notes");

// Create a temporary property that references the notes field.


[Link] = notes;

// If the shared object has any stored values, display them in the notes field.

[Link] 2004-02-06
Strona 136 z 180

[Link] = [Link];

// Set up notesLSO to listen for changes made to the notes field.


[Link](notesLSO);
[Link] = function ( ) {
[Link] = [Link];
[Link]( );
};
}

// Call initNotes( ) and initLSO( ) to start the movie.


initNotes( );
initLSO( );

The notes module is a relatively short application, and it uses much of the same logic as the
framework if you look closely. There are only two functions in the entire application. The first one
initNotes( ), performs the straightforward task of creating the TextField and ScrollBar objects tha
are used to accept user input and display the notes.

The initLSO( ) function is a little more complex, so let's examine it more closely. The function firs
opens an LSO named "notes", if one exists; otherwise, a new LSO is created. Next, a reference t
the notes text field is assigned to a custom property of the shared object named notesRef. This
so that within the onChanged( ) method of the shared object, it can address the notes text field
relative fashion. The relative addressing is extremely important in the case of a movie such as th
one that will be loaded into a movie clip within another movie. Because SharedObject and other
non-MovieClip objects do not have timelines, it is not possible to make relative addresses using
_parent, so creating a reference property is a great way to achieve the desired results. The shar
object is added as a listener to the notes field, and an onChanged( ) method—which is invoked
automatically when the contents of the notes field change—is defined.

27.3.2 Keeping Track of Addresses


Another useful service module is one that keeps track of email addresses and names.

The address book module uses the PushButton and ListBox components. Therefore, you should a
the corresponding component symbols to the [Link] document's Library by dragging
instances from the Components panel to the Stage (and deleting the instances).

The following example shows the code that you should place on the first frame of [Link]
implement an address book. This module uses a local shared object to store the addressees'
information.

// Include [Link] from Chapter 7 and [Link] from Chapter 8.


#include "[Link]"
#include "[Link]"
// Include [Link] from Chapter 11
#include "[Link]"

// Set up the address book form, including input fields, buttons, and a list box
// display the results of a search.
function initForm ( ) {

// Create the text field for the column headings (name and email address).
[Link]("nameLabel", [Link]( ), 0, 0, 75, 20);
[Link] = "Name";

// Position the email label dynamically to the right of the name label.
[Link]("emailLabel", [Link]( ), 0, 0, 75, 20);
[Link] = "Email";

[Link] 2004-02-06
Strona 137 z 180

// Create the text field for the addressee's name and email address.
[Link]("name", [Link]( ), 0, 0, 75, 20);
[Link]("email", [Link]( ), 0, 0, 75, 20);

// Create an Add button used to add a new addressee to the address book.
[Link]("FPushButtonSymbol", "addBtn", [Link]( ));
[Link]("add entry");
[Link]("addEntry");

// Add a search field.


[Link]("searchLabel", [Link]( ), 0, 0, 75, 20);
[Link] = "Search";
[Link]("search", [Link]( ), 0, 0, 75, 20);

// Add a Search button for submitting the entry search.


[Link]("FPushButtonSymbol", "searchBtn", [Link]( ));
[Link]("search for entry");

// Create the callback for the Search button.


[Link]("searchEntries");

// Create the list box that displays search results.


[Link]("FListBoxSymbol", "output", [Link]( ));
[Link](180, 40);

// Create a button that removes entries from the address book.


[Link]("FPushButtonSymbol", "removeBtn", [Link]( ));
[Link]("remove entry");
[Link]("removeEntry");

// Use a table to position all the elements.


tr0 = new TableRow(5, new TableColumn(5, nameLabel, name),
new TableColumn(5, emailLabel, email));
tr1 = new TableRow(5, new TableColumn(5, addBtn));
tr2 = new TableRow(5, new TableColumn(5, searchLabel, search, searchBtn));
tr3 = new TableRow(5, new TableColumn(5, output, removeBtn));
t = new Table(5, 0, 0, tr0, tr1, tr2, tr3);
}

// Open the shared object and, if it is new, initialize a blank address array.
function openSharedObject ( ) {
addressLSO = [Link]("addressBook");
if ([Link] == undefined) {
[Link] = new Array( );
}
}

// Add an entry to the shared object (called from the Add button).
function addEntry ( ) {
var entries = [Link];
[Link]([Link] + " [" + [Link] + "]");
[Link]( );
[Link]( );
[Link] = "";
[Link] = "";
}

// Search the entries array for any entries containing the specified substring.
function searchEntries ( ) {
var entries = [Link];
var matches = new Array( );
for (var i = 0; i < [Link]; i++) {
// Keep a record of all matching entries.
if (entries[i].indexOf([Link]) != -1)

[Link] 2004-02-06
Strona 138 z 180

[Link](entries[i]);
}
// If there are matches, display them.
if ([Link] > 0) {
[Link](matches);
} else {
// Otherwise, tell the user that none were found.
[Link]( );
[Link]("no matches found");
}
}

// Remove the selected entry from the entries array (triggered by the Remove butt
function removeEntry ( ) {
var entries = [Link];
var val = [Link]( );
// Remove the item from the ListBox.
[Link]([Link]( ));
for (var i = 0; i < [Link]; i++) {
if (entries[i] == val) {
// Remove the addressee and update the stored data.
[Link](i, 1);
[Link]( );
break;
}
}
}

// Call initForm( ) and openSharedObject( ) to start the movie.


initForm( );
openSharedObject( );

The address book module consists of five functions. Two of the functions, initForm( ) and
openSharedObject( ), handle initialization actions for the application. The other three functions a
callback functions for the form buttons.

Although initForm( ) is a relatively long function, no single aspect is too complicated. The functio
responsible for creating the form using functions such as createTextField( ) and attachMovie( );
then sets properties for the created objects.

The openSharedObject( ) function simply opens the shared object and, if it is a new object,
initializes the entries array as a new property.

The addEntry( ) function adds a new entry to the array stored in the shared object. It adds the t
from the name and email text fields to the array, and then sorts the array alphabetically before
saving it using the flush( ) method. For more information, see Recipe 16.4.

You are not strictly required to use the flush( ) method on the LSO to save the data. However, th
are several reasons why you should do so here. First of all, as in any situation, there is always th
possibility that the data may not save if the user's settings are not correct and you leave it to the
automatic save feature. Another reason is that calling flush( ) saves the data immediately;
otherwise, the attempt to save data occurs when the movie is closed.

function addEntry ( ) {
var entries = [Link];
[Link]([Link] + " [" + [Link] + "]");
[Link]( );
[Link]( );
[Link] = "";
[Link] = "";
}

[Link] 2004-02-06
Strona 139 z 180

When the user clicks on searchBtn, the searchEntries( ) function is called. This function loops
through each of the values stored in the entries array of the shared object. It uses the indexOf
method to determine if each entry contains the value entered into the search text field. If so, th
entry is added to an array which is then used to populate the output list box. The need to manu
implement a search function is typical, and searches are usually accomplished using indexOf( ).
Because indexOf( ) searches strings and not arrays, we search each element of the array separa
Beware, however, that for large text fields, or when searching very large numbers of array
elements, indexOf( ) may prove to be slow.

function searchEntries ( ) {
var entries = [Link];
var matches = new Array( );
for (var i = 0; i < [Link]; i++) {
if (entries[i].indexOf([Link]) != -1)
[Link](entries[i]);
}
if ([Link] > 0) {
[Link](matches);
} else {
[Link]( );
[Link]("no matches found");
}
}

The removeEntry( ) function removes the selected value from the list box and from the shared
object. You should remove the entry from both so that not only is the display updated to reflect
deletion but so is the actual data store. The function removes the item from the list box using th
removeItemAt( ) method. The function loops through all the entries stored in the shared object u
it finds the one that matches the selected entry, and then removes it with the splice( ) function a
saves the updated shared object. In this example, a break statement is used after a matching en
is found and removed. This is a good practice because it ensures that the program does not
needlessly loop through the remaining values. However, if you want the function to remove all
entries (even duplicates) that match the value selected, remove the break statement.

function removeEntry ( ) {
var entries = [Link];
var val = [Link]( );
// Remove the item from the ListBox
[Link]([Link]( ));
for (var i = 0; i < [Link]; i++) {
if (entries[i] == val) {
// Remove the addressee and update the stored data
[Link](i, 1);
[Link]( );
break;
}
}
}

27.3.3 Searching the Web Using Google


Another common and useful service is one that enables the user to search the Web from his My
Page application. This service module uses Flash Remoting for ColdFusion MX. Alternatively, if yo
are using Flash Remoting for .NET, you can use the same code and adjust the gateway URL to po
to an .aspx page. The rest of the code remains the same. No matter which server platform you a
using, however, you must obtain a developer's key, which is free, from Google by registering at
[Link]

Note that the gateway URL depends on your Flash Remoting set up, such as ColdFusion, .NET, J2
or PHP. (Refer to Chapter 20 for more details on Flash Remoting.) On the other hand, the URL of

[Link] 2004-02-06
Strona 140 z 180

Google web service used in the call to getService( ) is always the location of the .wsdl file provid
by Google (this is the same URL used by everything that calls the Google web service). You, as a
Google developer accessing its web service, are identified by the unique Google key—specified in
params object created within doSearch( )—and not via a unique URL.

The search service requires both the ScrollBar and PushButton components. So the first thing yo
should do is add the corresponding component symbols to the [Link] document's Library by
dragging instances from the Components panel to the Stage.

The following example shows the code that you should place on the first frame of the main timel
for [Link]:

// Include [Link] for the Flash Remoting API.


#include "[Link]"
// Include [Link] from Chapter 7 and [Link] from Chapter 8.
#include "[Link]"
#include "[Link]"
//Include [Link] from Chapter 11.
#include "[Link]"

// The initForm( ) function creates the form elements used by the search service
function initForm ( ) {

// Create the input text field that allows the user to enter a search string.
[Link]("searchString", [Link]( ), 0, 0, 150, 20);
[Link] = "<type search string here>";

// When search field gains focus, remove any existing text (such as the initial
// <type search string here> value).
[Link] = function ( ) {
[Link] = "";
};

// Create a Search button to initiate the search.


[Link]("FPushButtonSymbol", "searchBtn", [Link]( ));
[Link]("Search");
[Link]("doSearch");

// Create a text field to display the search results.


[Link]("searchResults", [Link]( ), 0, 0, 300, 100);
[Link] = true;
[Link] = true;
[Link] = true;
[Link] = true;

// Add a scrollbar for the search results.


[Link]("FScrollBarSymbol", "sb", [Link]( ));
[Link](searchResults);
[Link](searchResults._height);

// Add a Previous button to get the previous 10 search results.


[Link]("FPushButtonSymbol", "prevBtn", [Link]( ));
[Link]("Previous 10");
[Link] = false;
[Link]("doSearch");

// Add a Next button to get the next 10 search results.


[Link]("FPushButtonSymbol", "nextBtn", [Link]( ));
[Link]("Next 10");
[Link] = false;
[Link]("doSearch");

// Create a table to properly align the searchResults text field

[Link] 2004-02-06
Strona 141 z 180

// and the scrollbar to one another.


scrollerTr0 = new TableRow(0, new TableColumn(0, searchResults),
new TableColumn(0, sb));
scrollerTable = new Table(5, 0, 0, scrollerTr0);

// Create a table to position the form elements, including the scrollerTable


// subtable.
tr0 = new TableRow(5, new TableColumn(5, searchString),
new TableColumn(5, searchBtn));
tr1 = new TableRow(5, new TableColumn(5, scrollerTable));
tr2 = new TableRow(5, new TableColumn(5, prevBtn), new TableColumn(5, nextBtn))
t = new Table(5, 0, 0, tr0, tr1, tr2);
}

// The initNetServices( ) function initializes the Flash Remoting objects.


function initNetServices ( ) {

// This uses the default localhost standalone CFMX installation URL. If you use
// different one, change this code appropriately. See Chapter 20 for details on
// to make a connection and create a service object.
gwUrl = "[Link]
[Link](gwUrl);
conn = [Link]( );

// This is the service object for the Google Web service WSDL.
srv = [Link]("[Link]
}

// Create the response object.


res = new Object( );

// When the result is returned, format the contents and display them in the
// searchResults text field as HTML.
[Link] = function (result) {
var r = searchResults;
[Link] = true;
[Link] = "";
var re = [Link];

// Loop through the search results and add them as HTML to the searchResults fi
for (var i = 0; i < [Link]; i++) {
[Link] += "<font color=\"#FF\"><a href=\"" + re[i].url +
"\">" + re[i].title +
"</a></font>" + "<br>";
[Link] += re[i].snippet + "<br>";
if (re[i].summary != "") {
[Link] += re[i].summary + "<br>";
}
[Link] += "<br><br>";
}

// Reset the vertical and horizontal scroll positions.


[Link](0);
[Link](0);

// Get the estimated total number of results.


var count = [Link];

// Enable the Previous 10 results button if we're


// not already viewing the first page.
if ([Link] > 0) {
[Link] = true;
} else {
[Link] = false;

[Link] 2004-02-06
Strona 142 z 180

// If the ending index is less than the estimated total number of results,
// enable the Next 10 results button, but otherwise disable it
// so that users cannot try to load results past the last page.
if ([Link] < count) {
[Link] = true;
} else {
[Link] = false;
}

// Set the next and previous starting indexes.


[Link] = [Link];
if ([Link] - 10 < 0) {
[Link] = 0;
} else {
[Link] = [Link] - 11;
}
};

[Link] = function (status) {


trace([Link]);
};

// Perform the search.


function doSearch (cmpnt) {
var start = 0;

// If the calling component is the next or previous button,


// set the start value appropriately.
if (cmpnt._name != "searchBtn") {
start = [Link];
}

// Create the parameter object that the


// doGoogleSearch( ) web service method expects.
var params = new Object( );
[Link] = "xxxxxxxxxxxxxxxx"; // Your Google registration key goes here.
params.q = [Link];
[Link] = start;
[Link] = 10;
[Link] = true;
[Link] = "";
[Link] = false;
[Link] = "";
[Link] = "";
[Link] = "";
[Link](res, params);
}

// Call initNetServices( ) and initForm( ) to start the movie.


initNetServices( );
initForm( );

The search service is the longest of the three modules we've implemented, and quite a bit of its
code warrants further explanation.

The initNetServices( ) function uses the NetServices object's methods in the standard ways. The
gateway URL used in the example code points to the Flash Remoting gateway for the localhost, a
would be installed for a standard installation of ColdFusion MX as a standalone server on port 85
The service object is opened using the URL to the Google web service .wsdl file as the service na
This is the standard way in which web services are consumed directly from a Flash movie using
Flash Remoting (for ColdFusion MX), as discussed in Recipe 20.24.

[Link] 2004-02-06
Strona 143 z 180

function initNetServices ( ) {
gwUrl = "[Link]
[Link](gwUrl);
conn = [Link]( );
srv = [Link]("[Link]
}

Web services can also be consumed directly from Flash movies if you are using Flash Remoting
for .NET, in which case only the gateway URL differs. In that case, point the gateway to an .aspx
page that exists within the root directory of the IIS web application in which Flash Remoting is
installed.

Thus, when using [Link], change gwURL, as follows:

function initNetServices ( ) {
gwUrl = "[Link]
...
}

To perform a Google search, attach the required search parameters as properties of a generic ob
and pass the object to the Google web service, as shown in the doSearch( ) method. For exampl
the q property specifies the search string and the start property specifies the starting record
requested from the search.

The response object's onResult( ) method receives the search results returned from the Google w
service. The return value is an object containing several properties, including an array containing
matches for the search.

The names and datatypes of the properties of both the submission and return objects are
determined by the web service's .wsdl file (open the .wsdl file in a text editor to explore them
further). The return properties that we are most interested in with regards to this example are th
following:

estimatedTotalResultsCount

The total number of results from the search (estimated).

startIndex

The index of the first returned result relative to the total results (not just the one page of
results).

endIndex

The index of the last returned result relative to the total results.

resultElements

An array of results. Each element in the array is an object whose properties are:

url

The URL to the web page associated with a search result

title

The title of the web page

[Link] 2004-02-06
Strona 144 z 180

snippet

A text snippet from the web page

summary

A textual summary of the web page

The onResult( ) method loops through each of the elements in the resultElements array and
displays the values in the searchResults text field. The code uses the searchResults field's
htmlText property so that HTML tags such as <a href> can be used to create links, and the <fo
and <u> HTML tags can be used to create formatting easily. I have chosen to display each search
result's title, snippet, and summary. I made this decision in keeping with the conventions for how
search results are displayed on the Google web page; however, you are free to experiment with
the results are displayed. In this example, the result's title is made into a hyperlink to the URL
associated with the page. This is accomplished using an <a href> HTML tag. Also, because links
not automatically have formatting applied to them in Flash (as they do in most HTML browsers),
have added a <font> tag to color the title blue and a <u> tag to underline the title.

[Link] = function (result) {


var r = searchResults;
[Link] = true;
[Link] = "";
var re = [Link];
for (var i = 0; i < [Link]; i++) {
[Link] += "<font color=\"#0000FF\"><u><a href=\"" + re[i].url +
"\">" + re[i].title + "</a></u></font><br>";
[Link] += re[i].snippet + "<br>";
if (re[i].summary != "") {
[Link] += re[i].summary + "<br>";
}
[Link] += "<br><br>";
}
. . .
};

The onResult( ) method also ensures that the user cannot browse to results that do not exist (pr
to the first page or after the last page). The example enables and disables the prevBtn and next
instances, as appropriate, when the user is on the first or last page of results. If the result objec
startIndex is not greater than 0, it means that the first page of results is being displayed, so th
prevBtn is disabled. Likewise, if the endIndex is not less than the estimatedTotalResultsCount
property, the nextBtn is disabled. Additionally, both the nextBtn and prevBtn objects store a va
in a custom property named start. I define the start property here so that the program can ke
track of the starting index of the next and previous set of search results. Each time a new page o
results is returned to the onResult( ) method, the start property for nextBtn and prevBtn is
updated to reflect the change. The next page of results always has a starting index that is one m
than the endIndex value of the current set. Likewise, the previous page of results will always be
startIndex value of the current page minus 11 (assuming that there are 10 results per page)
because Google adds 1 to the start parameter passed as part of the search request. Just in cas
the indexing is skewed at some point in browsing next and previous pages, you should make sur
that the start property for the prevBtn is never less than 0. In the event that it is, simply reset
to 0.

var count = [Link];


if ([Link] > 0) {
[Link] = true;
} else {
[Link] = false;
}

[Link] 2004-02-06
Strona 145 z 180

if ([Link] < count) {


[Link] = true;
} else {
[Link] = false;
}
[Link] = [Link];
if ([Link] - 10 < 0) {
[Link] = 0;
} else {
[Link] = [Link] - 11;
}

Finally, there is the doSearch( ) function, which calls the Google web service. This function, for a
importance, is surprisingly short. The function is set as the callback function for the Search butto
(searchBtn) instance and also for the Previous and Next buttons (prevBtn and nextBtn). This is
because each time the user wants to view another page (next or previous) of search results, the
Flash movie needs to call the web service method again. While the actions taken are the same n
matter which button invokes the function, the start value sent to the web service differs. If
searchBtn invokes the function, the starting index is 0, and the web service returns the first pag
results. However, if prevBtn or nextBtn invokes the function, the starting index is passed in as a
property of the calling component ([Link]). In this way, the pages of results returned hav
different starting indexes instead of always beginning with 0.

Other than the if statement that handles the starting index, the rest of the function is the same
whether initiated by the Search, Previous, or Next button. It creates the params object containin
the search parameters and sends it to the web service method. Each of the properties of the par
object are required and defined by the .wsdl file for the web service. The only property that chan
dynamically within this example is the start property. In the example, the maxResults property
set to a fixed value of 10 so that the number of results per page is always 10. The value 10 is th
maximum value allowed by Google. However, you could also set the value to any number betwe
and 9. You could also allow the user to select the number of results to return per page. One poss
approach to this would be to add a ComboBox instance with values from 1 to 10, and to dynamic
set the maxResults property according to the selected value.

You must specify your Google registration key as the [Link] property.

The srv object on which the Google service is called is the one returned by the earlier call to
getService( ). The res parameter passed to doGoogleSearch( ) is the results text field defined
earlier (and used here as the result object).

function doSearch (cmpnt) {


var start = 0;
if (cmpnt._name != "searchBtn") {
start = [Link];
}
var params = new Object( );
[Link] = "XXXXXXXXXXXXXXXXX"; // Your Google registration key goes here.
params.q = [Link];
[Link] = start;
[Link] = 10;
[Link] = true;
[Link] = "";
[Link] = false;
[Link] = "";
[Link] = "";
[Link] = "";
[Link](res, params);

[Link] 2004-02-06
Strona 146 z 180

}
[ Team LiB ]
[ Team LiB ]

27.4 Putting It All Together


You should make sure that you have exported all the .swf files for the service modules and the
framework. Place all the .swf files and the [Link] document in the same folder and run
the [Link] movie. You should be able to open the services, move them around, close
them, etc. Between sessions, the framework should remember your user preferences.
[ Team LiB ]
[ Team LiB ]

27.5 Extending the Framework


As it is presented, the framework stores the user's settings in a local shared object on the user's
computer. Ideally, however, the application should save the data in a way that the user's
preferences can be retrieved from any computer. This can be accomplished in a variety of ways.
The data could be stored to a database, a text file, an XML file, or using a FlashCom Server remo
shared object. The following example stores the data to an XML document on the server. Withou
relying on a FlashCom server remote shared object or other proprietary system, the solution use
an ActionScript class, a simple server-side script for file I/O operations, and an XML document.
Refer to Chapter 19 for more information on saving and retrieving XML.

27.5.1 Making a Server-Side Shared Object


Without much modification to the framework movie, you can have it use a server-side faux share
object instead of a local shared object (or instead of a true FlashCom server remote shared objec
First, let's understand how the process works. Using an ActionScript class, FauxSharedObject, yo
can mimic the operations of a local shared object. You can perform the saving and retrieval
operations by serializing and deserializing the data with XML, and then use a server-side script
(ColdFusion in this case) to perform the file operations.

Save the FauxSharedObject class definition, shown in the following example, in an ActionScript f
named [Link] and place it in Flash's Include directory:

// Include [Link] for Flash Remoting API.


#include "[Link]"

// FSOResponse is a response object used for the Flash Remoting


// service function calls made by FauxSharedObject.
function FSOResponse (parent) {
[Link] = parent;
}

[Link] = function (result) {


if (result != null) {
[Link]([Link](result));
[Link][[Link]]( );
}
};

// The constructor accepts a filename and a gateway URL


// used by the Flash Remoting connection.
function FauxSharedObject (name, gwURL) {
[Link] = name;
[Link] = new FSOResponse(this);
[Link] = new FSOResponse( );

[Link] 2004-02-06
Strona 147 z 180

[Link](gwURL);
}

// Define the properties of the class.


[Link] = new Object( );

// Initialize the Flash Remoting connection and service. The service object
// connects to [Link], as shown in Section 27.5.2.
[Link] = function (gwURL) {
[Link](gwURL);
var conn = [Link]( );
[Link] = [Link]("FauxSharedObject");
};

// A static method that serializes an object to XML.


[Link] = function (obj) {
var sb = "<obj>";
if (obj instanceof Array) {
sb = "<obj type=\"array\">";
}
for (var i in obj) {
sb += "<index name=\"" + i + "\"";
if (obj[i] instanceof Object) {
sb += ">";
sb += [Link](obj[i]);
sb += "</index>";
} else {
sb += " val=\"" + obj[i] + "\" />";
}
}
sb += "</obj>";
return sb;
};

// A static method that deserializes XML to an object.


[Link] = function (source) {
[Link] = true;
var sXML = new XML(source);
var cn = [Link];
var items = new Object( );
if ([Link] == "array") {
items = new Array( );
}
var attribs, childCn;
for (var i = 0; i < [Link]; i++) {
attribs = cn[i].attributes;
childCn = cn[i].childNodes;
if ([Link] > 0) {
items[[Link]] = [Link](childCn[0]);
} else {
items[[Link]] = [Link];
}
items[[Link]] = new Object( );
for (var j in attribs) {
items[[Link]][j] = attribs[j];
}
}
return items;
};

// Load an object from the server by calling Flash Remoting's open( ) method.
[Link] = function ( ) {
[Link]([Link], [Link]);
};

[Link] 2004-02-06
Strona 148 z 180

// Save the object in serialized form to the server, calling Flash Remoting's
// save( ) method. Mimics the shared object flush( ) method.
[Link] = function ( ) {
[Link]([Link], [Link],
[Link]([Link]));
};

// Called automatically when the object has loaded from the server
[Link] = function (val) {
[Link] = val;
};

// Allow the user to define a callback function for when the object has loaded.
[Link] = function(functionName, path) {
if (path == undefined) {
path = this._parent;
}
[Link] = path;
[Link] = functionName;
};

Now that you have had an opportunity to look over the code in the preceding example, let's look
more closely at the parts that make up the FauxSharedObject class and its helper class,
FSOResponse.

The code first defines the FSOResponse class, which is a Flash Remoting response object class u
by FauxSharedObject. I chose to separate the response object portion into its own class in keepi
with object-oriented design. The constructor method takes a reference to the FauxSharedObject
that instantiates it, and it sets its custom parent property to that reference. This reference is us
within the onResult( ) method to invoke the loadHandler( ) method of the parent FauxSharedObj
when results are returned. The parent property is another example in which using a reference c
be useful in establishing relationships between objects. Because FauxSharedObject and
FSOResponse are not MovieClip classes, you cannot rely on the _parent property or similar mea
to relate the objects. A reference such as parent provides this bridge between the objects.

function FSOResponse (parent) {


[Link] = parent;
}

[Link] = function (result) {


if (result != null) {
[Link]([Link](result));
[Link]( );
}
};

The constructor method for the FauxSharedObject class takes parameters indicating the name of
the object that is stored on the server (which is the same as the username in this example) as w
as the Flash Remoting gateway URL to use for the Flash Remoting calls.

The decision to save the file on the server by the username is perfectly valid
as long as you can ensure that the username is always unique. In this
example application, for the sake of simplicity, there is no user registration
that would allow users to create accounts with unique usernames.

The constructor also creates the response objects as FSOReponse instances and invokes the
initNetServices( ) method to initialize the NetServices objects:

function FauxSharedObject (name, gwURL) {

[Link] 2004-02-06
Strona 149 z 180

[Link] = name;
[Link] = new FSOResponse(this);
[Link] = new FSOResponse( );
[Link](gwURL);
}

The FauxSharedObject class defines a data property as an associative array to closely mimic the
functionality of local shared objects. Creating a property of the same type, with the same name,
and with the same intended functionality as with an LSO, you can ensure that minimum
modifications are needed to integrate FauxSharedObject instances in place of SharedObject
instances in existing applications.

[Link] = new Object( );

The FauxSharedObject class serializes and deserializes data and sends it between the Flash mov
and the server. Serialization converts a reference datatype (an object), which cannot be stored i
file or database on the server, into a format that can be saved. The code uses a custom XML-bas
structure to perform the serialization. Objects are converted to <obj> elements with nested
<index> elements. The <index> elements always have a name attribute, which is the name of th
variable/property for the object. If the <index> represents a primitive datatype, it also has a val
attribute holding the value of the variable. On the other hand, if the <index> represents an objec
it has a nested <obj> element. Here is an example of an object and the corresponding serialized
form:

obj = new Object( );


obj.prop1 = "a value";
obj.prop2 = "another value";
obj.prop3 = {a: "eh", b: "bee"};
/*
<obj>
<index name="prop1" val="a value" />
<index name="prop2" val="another value" />
<index name="prop3">
<obj>
<index name="a" value="eh" />
<index name="b" value="bee" />
</obj>
</index>
</obj>
*/

To convert objects to their serialized forms, the FauxSharedObject class defines a static method,
serializeObject( ). A static method is one that is invoked directly from the class rather than from
instances of the class. Because serializeObject( ) and deserializeObject( ) do not need to be used
from specific instances of the class, it makes sense to define them as static methods. To define a
static method attach it to the FauxSharedObject top-level object and not to its prototype.

The serializeObject( ) method accepts an object as a parameter and converts the object properti
to <index> elements that are appended to a string. The serializeObject( ) method uses the
instanceof operator to check whether an object contains a property that is itself an object. If so,
that object is passed to serializeObject( ) in a recursive manner (i.e., the function calls itself).
Recursion allows a function to handle an arbitrarily nested data structure, such as an object that
contains other objects or a directory structure with nested subfolders. See Recipe 7.10 and Recip
19.7 for more examples of recursive functions.

[Link] = function (obj) {


var sb = "<obj>";
if (obj instanceof Array) {
sb = "<obj type=\"array\">";
}

[Link] 2004-02-06
Strona 150 z 180

for (var i in obj) {


sb += "<index name=\"" + i + "\"";
if (obj[i] instanceof Object) {
sb += ">";
sb += [Link](obj[i]);
sb += "</index>";
} else {
sb += " val=\"" + obj[i] + "\" />";
}
}
sb += "</obj>";
return sb;
};

The counterpart to serialization is deserialization—the conversion of XML data back to the origina
object. The static deserializeObject( ) method takes an XML string and parses it into an XML obje
It then loops through all the child nodes of the root <obj> element and converts them back to
properties of an object. If a child node contains nested elements, the node represents an object.
with the serializeObject( ) method, deserializeObject( ) calls itself recursively, passing in the nex
<obj> element, to handle child nodes that contain nested objects.

[Link] = function (source) {


[Link] = true;
var sXML = new XML(source);
var cn = [Link];
var items = new Object( );
if ([Link] == "array") {
items = new Array( );
}
var attribs, childCn;
for (var i = 0; i < [Link]; i++) {
attribs = cn[i].attributes;
childCn = cn[i].childNodes;
if ([Link] > 0) {
items[[Link]] = [Link](childCn[0]);
} else {
items[[Link]] = [Link];
}
items[[Link]] = new Object( );
for (var j in attribs) {
items[[Link]][j] = attribs[j];
}
}
return items;
};

The load( ) method calls the Flash Remoting service object's open( ) method to open the shared
object data from the server:

[Link] = function ( ) {
[Link]([Link], [Link]);
};

The [Link]( ) method mimics a local shared object's flush( ) method by calling
service object's save( ) function to save the data to the server. The flush( ) method, as with the
data property, uses the same name and performs the same function as its analog in the
SharedObject class. This is a good practice when writing custom classes to perform similar tasks
existing classes because it makes the classes largely interchangeable.

[Link] = function ( ) {
[Link]([Link], [Link],
[Link]([Link]));

[Link] 2004-02-06
Strona 151 z 180

};

Finally, the setLoadHandler( ) method provides a means for a function to be set as a callback
function for when the shared object data is loaded. The FSOResponse object's onResult( ) metho
automatically tries to call this function if it is defined. This is important because, unlike local shar
objects, the FauxSharedObject is asynchronous. This means that, unlike a local shared object, in
which the data is retrieved from the user's computer immediately, the FauxSharedObject might
take milliseconds or even seconds to retrieve the data from the server. The rest of the ActionScr
code continues to execute while the FauxSharedObject data is being retrieved, so any functional
that relies on the retrieved data must be handled only after that data has been returned. The
RSOResponse instance is automatically called (because of Flash Remoting) when a response is
returned from the server. By setting a loadHandler( ) method for automatic callback, you can
define custom functions for each FauxSharedObject instance to know how to handle the particula
results returned from the server.

[Link] = function (functionRef) {


[Link] = functionRef;
};

27.5.2 Performing File Operations


Our example My Page application requires that the data sent between the Flash movie and the
server be saved to a file on the server side. The following example shows how to save the data
using a ColdFusion Component (CFC). You should save the CFC to the server's web root as
[Link], since this is the service name that Flash looks for. If you are comfortable
with another language/platform that works with Flash Remoting (e.g., [Link] or J2EE), you ca
easily adapt this code. See Chapter 20 for examples in various server-side languages.

<cfcomponent>
<cffunction name="save" access="remote">
<cfargument name="name" type="string" required="true">
<cfargument name="data" type="string" required="true">
<cffile action="write"
file="#GetDirectoryFromPath(GetBaseTemplatePath( ))##name#.so"
output="#data#" nameconflict="overwrite">
<cfreturn #GetDirectoryFromPath(GetBaseTemplatePath( ))#>
</cffunction>
<cffunction name="open" access="remote">
<cfargument name="name" type="string" required="true">
<cffile action="read"
file="#GetDirectoryFromPath(GetBaseTemplatePath( ))##name#.so"
variable="data">
<cfreturn #data#>
</cffunction>
</cfcomponent>

The ColdFusion Component in the preceding example consists of only two functions: save and
open. The <cffunction> and <cfargument> tags define the functions and the parameters they
accept. You should recognize these functions from the FauxSharedObject ActionScript class, sinc
makes calls to them by way of Flash Remoting. Each of these two functions performs basic file
operations made available by the ColdFusion <cffile> tag. The save function writes the data to
file in the same directory with the name specified by the name parameter followed by the .so
extension. The open function does the reverse by reading the contents of the specified file. The
<cfreturn> tags merely return the results to the calling function.

If you want to adapt this code to another language/platform, refer to the language's file I/O API.
You should create a script or program that is accessible to Flash Remoting with the service name
"FauxSharedObject." Also, within the script or program should be two methods: one named save
that saves the specified data to a given filename, and one named open that reads the contents o
given file and returns the result.

[Link] 2004-02-06
Strona 152 z 180

27.5.3 Modifying the My Page Framework


We're almost done adapting the framework to store the data remotely. One more round of
modification is needed to make the framework work with the server-side shared objects. The
following example shows the changes to [Link] in abridged format. Notice that much of the
code remains the same as the version from Section 27.2.4 earlier in the chapter, but some of it
placed into new functions to support the asynchronous callback functionality that is now required

// Include [Link] from Chapter 7 and [Link] from Chapter 8.


#include "[Link]"
#include "[Link]"
// Include [Link] and [Link] from Chapter 11.
#include "[Link]"
#include "[Link]"
// Include [Link] from Chapter 4.
#include "[Link]"
// Include the FauxSharedObject class from earlier in this chapter.
#include "[Link]"

// Open the movie with a login form to determine


// for which user to retrieve the settings.
function initLogin ( ) {

// Create a text field to label the input field.


[Link]("usernameLabel", [Link]( ));
[Link] = "username:";

// Create the input text field.


[Link]("username", [Link]( ));

// Create the Login button for logging in.


[Link]("FPushButtonSymbol", "loginBtn", [Link]( ));
[Link]("login");
[Link]("initLSO");

// Place the form in the center of the movie.


var x = [Link]/2 - (usernameLabel._width + username._width)/2;

loginTr0 = new TableRow(5, new TableColumn(5, usernameLabel),


new TableColumn(5, username));
loginTr1 = new TableRow(5, new TableColumn(5, loginBtn));
loginTable = new Table(5, x, [Link]/2, loginTr0, loginTr1);
}

// Opens the FauxSharedObject determined by the username entered.


function initLSO ( ) {
// Get username.
var name = [Link];

// Remove login form now that it is no longer needed.


[Link]( );
[Link]( );
[Link]( );

// Open the FauxSharedObject.


gw = "[Link]
lso = new FauxSharedObject(name, gw);
[Link]("soCallback", this);
[Link]( );

// Load the XML for the services menu.


loadXML( );
}

[Link] 2004-02-06
Strona 153 z 180

// Some of this function is the same as what used to be in initLSO( ) but is mov
// here because it has to be opened on callback now that Flash Remoting is used.
function soCallback ( ) {
trace("called");
if ([Link] == undefined) {
[Link] = new Object( );
}
var o = [Link];
var newUnit;
for (var item in o) {
newUnit = openUnit(o[item].title, o[item].source, item);
newUnit._x = o[item].x;
newUnit._y = o[item].y;
}
[Link] = function (unit) {
[Link][[Link]].x = unit._x;
[Link][[Link]].y = unit._y;
[Link]( );
};
}

function loadXML ( ) {
// Remains the same as original
}

function initForm (services) {


// Remains the same as original
}

function addUnit ( ) {
// Remains the same as original
}

function openUnit (title, source, index) {


// Remains the same as original
}

// This call to initLogin( ) replaces the calls to


// initLSO( ) and loadXML( ) in original.
initLogin( );

The [Link] code does not change a tremendous amount. The changes, as shown in the
preceding example, involve the following:

z A new function, initLogin( ), creates a login form.

z initLSO( ) is modified to create a FauxSharedObject.

z soCallback( ) is a new function that does much of what initLSO( ) did previously, but it is n
a separate function that is called automatically when the shared object data is loaded from
the server.

z Instead of invoking initLSO( ) and loadXML( ) at the beginning of the movie, initLogin( ) is
invoked.

The login form needs to be added so a user can identify himself to the My Page application so th
his stored settings are loaded. This example does not provide any password security, but you
might want to experiment by adding a password field and integrating the login form with a Flash
Remoting back end to perform validation. You may want to refer to Chapter 28 for how such a
system can be implemented.

[Link] 2004-02-06
Strona 154 z 180

The primary technical modification to [Link] is that it now uses a FauxSharedObject. Becaus
the FauxSharedObject class uses Flash Remoting, the operations are asynchronous. This means
that it might take a moment for the data from the server to load.

Due to the asynchronous nature, you must place any operations that depend
on the loaded data within a callback function that will be called when the
data is loaded, and not before.

These actions are now all placed within soCallback( ), which is set as the callback function for the
FauxSharedObject in the initLSO( ) function:

function initLSO ( ) {
. . .
[Link]("soCallback", this);
. . .
}

The code within the soCallback( ) function is the same code that was contained within the initLSO
( ) function. If you have any questions regarding that functionality, refer to the discussion of
initLSO( ) in Section 27.2.4.

27.5.4 Reexamining the Services


The Google search service does not rely on local shared objects at all and thus will work perfectly
regardless of which computer is used to access it. The notes and address book modules, on the
other hand, rely on local shared objects for saving their information. Try to adapt each of these
services to use either FauxSharedObject or your own Flash Remoting code.
[ Team LiB ]
[ Team LiB ]

27.6 Wrapping It Up
This chapter should have given you a solid foundation upon which to build your own My Page
application. I hope it has sparked a lot of ideas for your own further development. Experiment
to develop this application further. See XMethods ([Link] and SalCentral
([Link] for lots of interesting web services that you may want to
incorporate into your application. Perhaps you might want to incorporate stock ticker services,
email-sending services, or news services—all of which you can find at XMethods and SalCentral.

[ Team LiB ]
[ Team LiB ]

Chapter 28. A Scheduler Program


In the preceding six chapters, we created a variety of applications that drew on skills learned in
recipes from earlier chapters. In this chapter, we develop a scheduler application, which, not
surprisingly, relies on the Date class covered in Chapter 10 and various techniques you've
learned in other chapters. The completed version of the application is available at
[Link]

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 155 z 180

28.1 Designing the Application Structure


The first thing to do when developing this scheduler program is to determine which pieces
should make up the entire application, and how they fit together. We'll design the first version
of the application to write data to a local shared object. Later, we'll revise the application so
that it can be written to disk and retrieved from any computer. Initially, the entire application
can be built using a single Flash document (though later we will add a server-side script as
well). This application depends on the Calendar component available as part of the Flash UI
Components Set 2. You must drag an instance from the Components panel to the Stage to add
it to the Library.

In the course of the chapter, we will create the following custom components:

Time selector component (TimeSelector)

This component is composed of a menu of 24 button movie clips (TimeSelectorItem


components). Each item in the menu represents an hour of the day.

Time selector item component (TimeSelectorItem)

These components make up the time selector menu. When a user has added schedule
information to the hour that corresponds to the time selector item, the item is
highlighted.

Schedule component (Schedule)

The Schedule component is the framework for each of the schedule dates (ScheduleItem
components). The Schedule component is analogous to the binder that holds the pages of
a day planner.

Schedule item component (ScheduleItem)

The Schedule component is composed of schedule items. Each schedule item corresponds
to a calendar date. A schedule item can have values for the following:

Hourly schedule

Contains the user's schedule for each hour of the day

Notes

A user's overall notes for the day

To do

A user's to-do list for the day

Notifiers

An array of events the user has specified he would like to be notified of the day
before the event

Notifier displayer component (Notifier)

This component displays a list of notifications for upcoming scheduled events.

[Link] 2004-02-06
Strona 156 z 180

[ Team LiB ]
[ Team LiB ]

28.2 Making the Components


After formulating the general overview of the application's pieces, the next step is to create each
components. All the components in the subsequent sections should be placed within a single Flas
document. To start, create a new Flash document named [Link].

28.2.1 Designing the TimeSelector Component


The TimeSelector component consists of a scroll pane in which 24 TimeSelectorItem instances ap
Additionally, the TimeSelector component should have the following functionality:

z You should be able to make the component selectable or nonselectable. When the user is a
notes or to-do items to a daily schedule, the entire time selector should become nonselecta
signify this.

z You should be able to highlight or remove a highlight from any of the time selector items b

z You should be able to set a callback function that is automatically invoked whenever a time
item is selected.

To create the TimeSelector component, complete the following steps:

1. The TimeSelector component requires you to include the ScrollPane component in your mo
Create a copy of the ScrollPane component in your movie's Library by dragging an instance
the Components panel to the Stage. You can then delete the instance on stage; a copy of t
symbol remains in the Library.

2. Create a new movie clip symbol named TimeSelector.

3. Open the linkage properties for the symbol.

4. Select the Export for ActionScript and Export in First Frame checkboxes.

5. Give the symbol a linkage identifier of TimeSelectorSymbol.

6. Click OK to close the Linkage Properties dialog box.

7. Edit the TimeSelector symbol.

8. Add the following code to the first frame of the default layer:

#initclip 0

// The constructor creates a scroll pane instance and populates it with 24


// instances of the TimeSelectorItem component.
function TimeSelector( ) {

// Create the scroll pane instance.


[Link]("FScrollPaneSymbol", "sp", [Link]( ));
[Link](150, 380);

// Create a scroll content movie clip.


[Link]("sc", [Link]( ));

[Link] 2004-02-06
Strona 157 z 180

// The times property is an array that the component uses to keep track of
// the TimeSelectorItem instances.
[Link] = new Array( );
var time;

// Perform the same actions 24 times, once for each hour of the day.
for (var i = 0; i < 24; i++) {

// Create an instance of the TimeSelectorItem component and set its y


// coordinate so that it appears directly below the previous
// TimeSelectorItem.
time = [Link]("TimeSelectorItemSymbol", "time" + i,
[Link]( ), {_y: i * 20});

// The [Link]( ) method sets the time displayed by th


// component based on an integer from 0 (12 A.M.) to 23 (11 P.M.).
[Link](i);

// The [Link]( ) method sets the callback function


// should be invoked when the item is selected by the user.
[Link]("onSelectTime", this);

// Add the TimeSelectorItem component to the times array.


[Link](time);
}

// Set the scroll pane's scroll content.


[Link]([Link]);
}

// TimeSelector extends MovieClip.


[Link] = new MovieClip( );

// The setSelectable( ) method sets whether


// items in the time selector are selectable.
[Link] = function (isSelectable) {

// If isSelectable is true, enable all TimeSelectorItems and call


// [Link]( ) with a value of false so that the items app
// normally. Otherwise, dim and disable the TimeSelectorItems.
for (var i = 0; i < [Link]; i++) {
[Link][i].enabled = isSelectable;
[Link][i].grayOut(!isSelectable);
}
};

// The [Link]( ) method calls the


// [Link]( ) method of the item that corresponds to th
// specified index. See [Link]( ) for more information
[Link] = function (index, hasValue) {
[Link][index].setHasValue(hasValue);
};

// The onSelectTime( ) method is the callback function for each of the


// TimeSelectorItem instances (as assigned in the class constructor). The cmp
// parameter is a reference to the TimeSelectorItem that has been selected.
[Link] = function (cmpt) {

// Call [Link]( ) for every item except the selected


for (var i = 0; i < [Link]; i++) {
if ([Link][i] != cmpt) {
[Link][i].deselect( );
}

[Link] 2004-02-06
Strona 158 z 180

// Invoke the onSelect callback function.


[Link][[Link]](cmpt);
};

// The setOnSelect( ) method allows you to define a callback function for th


// TimeSelector component. The functionName parameter is the name of the
// function, and the path parameter is a reference to a timeline in which th
// callback function exists.
[Link] = function (functionName, path) {
if (path == undefined) {
path = this._parent;
}
[Link] = path;
[Link] = functionName;
};

[Link]("TimeSelectorSymbol", TimeSelector);

#endinitclip

The TimeSelector component is not very complex. The component loads 24 TimeSelectorItems in
scroll pane and acts as an interface by which some commands can be sent to the items. The con
simply creates these instances and places them in a scroll pane.

function TimeSelector( ) {
[Link]("FScrollPaneSymbol", "sp", [Link]( ));
[Link](150, 380);
[Link]("sc", [Link]( ));
[Link] = new Array( );
var time;
for (var i = 0; i < 24; i++) {
time = [Link]("TimeSelectorItemSymbol", "time" + i,
[Link]( ), {_y: i * 20});
[Link](i);
[Link]("onSelectTime", this);
[Link](time);
}
[Link]([Link]);
}

The setSelectable( ) method enables or disables all the associated TimeSelectorItems, graying th
if appropriate. This method is used when the user selects another entry type (such as notes or th
list), and it serves to visually indicate whether the user is making hourly entries.

[Link] = function (isSelectable) {


for (var i = 0; i < [Link]; i++) {
[Link][i].enabled = isSelectable;
[Link][i].grayOut(!isSelectable);
}
};

When the user selects a TimeSelectorItem, the [Link]( ) method is invoked


automatically (see the constructor in which you set the onSelect callback function for each item)
an item is selected, call the deselect( ) method for all the other TimeSelectorItems, and call the
callback function for the TimeSelector.

[Link] = function (cmpt) {


for (var i = 0; i < [Link]; i++) {
if ([Link][i] != cmpt) {
[Link][i].deselect( );

[Link] 2004-02-06
Strona 159 z 180

}
}
[Link][[Link]](cmpt);
};

28.2.2 Designing the TimeSelectorItem Component


The TimeSelectorItem component constitutes the content of the TimeSelector component. Each
TimeSelectorItem represents one hour of the day, and it should include the following functionalit

z The component should draw a filled rectangle.

z You should be able to set a time label based on an integer from 0 to 23.

z When the item is selected, the rectangle's outline should be bolded, and when deselected,
outline should not be bolded.

z You should be able to change the rectangle's fill color to highlight the component. (This is
indicate that an hour has a scheduled event.)

z You should be able to gray out the component to indicate it is disabled.

z You should be able to specify a callback function that is invoked when the item is selected.

To create the TimeSelectorItem component, complete the following steps.

1. Create a new movie clip symbol named TimeSelectorItem.

2. Open the linkage properties for the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Give the symbol a linkage identifier of TimeSelectorItemSymbol.

5. Click OK to close the Linkage Properties dialog box.

6. Edit the TimeSelectorItem symbol.

7. Add the following code to the first frame of the default layer:

#initclip 0

// In the constructor, create the rectangle with a fill and outline and crea
// text field to use as the label.
function TimeSelectorItem( ) {

// Create a movie clip and two nested movie clips within it, one for the f
// and one for the outline.
[Link]("indicator", [Link]( ));
[Link]("fill", [Link]( )
[Link]("outline", [Link](

// Draw the fill as a white rectangle using the drawRectangle( ) method.


with ([Link]) {
lineStyle(0, 0x000000, 0);
beginFill(0xFFFFFF, 100);
drawRectangle(150, 20);

[Link] 2004-02-06
Strona 160 z 180

endFill( );
_x += 75;
_y += 10;
}

// Call the drawOutline( ) method to draw the outline.


[Link]( );

// Create color objects to control the color for both the outline and the
[Link] = new Color([Link]);
[Link] = new Color([Link]);

// Create a text field in which the time label should appear.


[Link]("time", [Link]( ), 0, 0, 150, 20);
}

// TimeSelectorItem extends MovieClip.


[Link] = new MovieClip( );

// The drawOutline( ) method draws/redraws the rectangle's outline. If sele


// is true, draw the line with a thickness of 2. Otherwise, use no outline.
[Link] = function (selected) {
with ([Link]) {
clear( );
if (selected) {
lineStyle(2, 0x000000, 100);
} else {
lineStyle(0, 0x000000, 100);
}
drawRectangle(150, 20);
_x = 75;
_y = 10;
}
};

// The static getDisplayTime( ) method takes an integer from 0 to 23 and re


// a string in the format HH AM/PM (i.e., 12 AM, 3 PM, etc.).
[Link] = function (timeVal) {
var time;
if (timeVal == 0) {
time = "12 AM";
} else if (timeVal < 11) {
time = timeVal + " AM";
} else if (timeVal == 12) {
time = "12 PM";
} else {
time = (timeVal - 12) + " PM";
}
return time;
};

// The setTime( ) method takes an integer from 0 to 23 and it displays the


// corresponding time label in the text field.
[Link] = function (timeVal) {
[Link] = timeVal;
[Link] = [Link](timeVal);
};

// The grayOut( ) method changes the colors of the outline and fill movie c
[Link] = function (grayOut) {
if (grayOut) {
[Link](0xBCBCBC);
if ([Link]) {
[Link](0xE4E4E4);

[Link] 2004-02-06
Strona 161 z 180

}
} else {
[Link](0);
if ([Link]) {
[Link](0xCCEFFD);
}
}
};

// The setHasValue( ) method highlights or removes a highlight from the


// TimeSelectorItem by setting the color of the fill.
[Link] = function (hasValue) {
if (hasValue) {
[Link](0xCCEFFD);
[Link] = true;
} else {
[Link](0xFFFFFF);
[Link] = false;
}
};

// When an item is selected, call drawOutline(true) to bold the outline.


[Link] = function ( ) {
[Link](true);
};

// When an item is deselected, call drawOutline( ) to unbold the outline.


[Link] = function ( ) {
[Link]( );
};

// Get the time (an integer from 0 to 23) that


// corresponds to the TimeSelectorItem.
[Link] = function ( ) {
return [Link];
};

// When the item is rolled over, set the color of the fill to a light gray.
[Link] = function ( ) {
[Link](0xDFDFDF);
};

// When the item is rolled out, set the color of the fill to either white (i
// user has not scheduled anything for the hour) or light blue (if the user h
// scheduled something for the hour).
[Link] = function ( ) {
if ([Link]) {
[Link](0xCCEFFD)
} else {
[Link](0xFFFFFF);
}
};

// When the item is clicked on, call the select( ) method and
// the onSelect callback function.
[Link] = function ( ) {
[Link]( );
[Link][[Link]](this);
};

// Set the onSelect callback function.


[Link] = function (functionName, path) {
if (path == undefined) {
path = this._parent;

[Link] 2004-02-06
Strona 162 z 180

}
[Link] = path;
[Link] = functionName;
};

[Link]("TimeSelectorItemSymbol", TimeSelectorItem);

#endinitclip

Although the TimeSelectorItem class has a lot of code, there is nothing within the code that is un
complicated. The majority of the code involves either drawing shapes or changing colors.

28.2.3 Designing the Schedule Component


The Schedule component acts as a container for each of the ScheduleItem components. While
ScheduleItem components correspond to specific calendar dates, the Schedule component is the
framework within which each of these items is stored. The Schedule component should have the
following functionality:

z It should allow you to add new schedule items for calendar dates.

z It should keep track of all notifications that a user has set, and it should alert the user at t
appropriate interval automatically.

z It should save the data to a local shared object and retrieve that data whenever the applic
reopened.

To create the Schedule component, complete the following steps:

1. Create a new movie clip symbol named Schedule.

2. Open the linkage properties for the symbol.

3. Select the Export for ActionScript and Export in First Frame checkboxes.

4. Give the symbol a linkage identifier of ScheduleSymbol.

5. Click OK to close the Linkage Properties dialog box.

6. Edit the Schedule symbol.

7. Add the following code to the first frame of the default layer:

#initclip

function Schedule( ) {

// Create associative arrays to keep track of schedule items and notifiers


[Link] = new Object( );
[Link] = new Object( );

// Create a local shared object or open an existing LSO.


[Link] = [Link]("mySchedule");

// If LSO is not new, populate the schedule


// application with the retrieved data.
if ([Link] != undefined) {
for (var i in [Link]) {

[Link] 2004-02-06
Strona 163 z 180

var dt = [Link][i].siDate;
[Link](dt);
[Link][dt].setValues([Link][i]);
}
[Link] = [Link];
}

// Run the notifier routine now and call it once per hour.
[Link]( );
[Link] = setInterval(this, "runNotify", 3600000);
}

[Link] = new MovieClip( );

// The save( ) method is called when the module is closed;


// it writes the schedule data to the local shared object.
[Link] = function ( ) {

// Create a schedules associative array and fill it


// with the values from every schedule item.
[Link] = new Object( );
for (var i in [Link]) {
[Link][i] = [Link][i].getValues( );
}

// Create a notifiers associative array that has the


// value of the notifiers property of the schedule component.
[Link] = [Link];

// Invoke the flush( ) method to write the data to disk.


[Link]( );
};

// The onCloseNotification( ) method is the callback function


// for when the user closes a notifier window. It removes
// the Notifier component instance from the movie.
[Link] = function (cmpt) {
[Link]( );
};

// Get a schedule item by index (which is a date string).


[Link] = function (index) {
return [Link][index];
};

// Add a new schedule item by index (which is a date string).


[Link] = function (index) {
var uniqueVal = [Link]( );

// Create a new instance of the ScheduleItem component and assign a referen


// to the new component to an element of the items associative array.
[Link][index] = [Link]("ScheduleItemSymbol", "item" + uniqueV
uniqueVal);
[Link][index].setDate(index);
};

// The runNotify( ) method is called at hourly


// intervals to display any new updates.
[Link] = function ( ) {

// Create a date for tomorrow and get that value as a formatted date strin
var d = new Date( );
[Link]([Link]( ) + 1);
index = [Link]("MM-dd-yyyy");

[Link] 2004-02-06
Strona 164 z 180

// Check to see if a notifier exists for that date.


if ([Link][index] != undefined) {
var notification = "";

// Loop through all the elements for that date's notifiers and add them
// the notification string.
for (var i = 0; i < [Link][index].length; i++) {
if ([Link][index][i]) {
notification += index + " " +
[Link](i) + newline +
[Link][index].getSchedule(i) + "\n\n";
}
}

// Delete the notifier information so that


// the user will not be notified again.
delete [Link][index];

// Call the displayNotifications( ) method


// to display information to the user.
[Link](notification);
}
};

// Create a new NotifierDisplayer component instance and display the notific


// information to the user.
[Link] = function (displayValue) {
[Link]("NotifierDisplayerSymbol", "noteDisp", [Link](
[Link]._x = [Link]/2 - [Link]._width/2;
[Link]._y = [Link]/2 - [Link]._height/2;
[Link](displayValue);
};

// The setNotify( ) method adds a new notifier to the schedule, or, if doNo
// is not true, it removes any existing notifiers for the date and time.
[Link] = function (dt, hour, doNotify) {
if (doNotify) {
if ([Link][dt] == undefined) {
[Link][dt] = new Array( );
}
[Link][dt][hour] = true;
} else {
delete [Link][dt][hour];
}
};

// Call the open( ) method for a schedule item, which makes it visible, by
[Link] = function (index) {
[Link][index].open( );
};

// The onClose( ) method is invoked automatically when a schedule item is c


// This method invokes the schedule's onClose callback function.
[Link] = function ( ) {
[Link][[Link]]( );
};

// Set the onClose callback function.


[Link] = function (functionName, path) {
if (path == undefined) {
path = this._parent;
}
[Link] = path;

[Link] 2004-02-06
Strona 165 z 180

[Link] = functionName;
};

[Link]("ScheduleSymbol", Schedule);

#endinitclip

Let's examine the Schedule component code more closely to shed light on parts of the code that
be unclear.

When you initialize the Schedule component, it must create properties to keep track of the sched
items and the notifiers. It uses an associative array for each of these properties so that the keys
the form of formatted date strings (instead of simply integer indexes). Additionally, it creates (or
the shared object in which the schedule information is stored. If the shared object already exists
retrieved data is used to populate the schedule. Finally, it sets up an interval for notifications, wh
once an hour. Although this example auto-saves the data when the user clicks the close box, you
implement a manual save feature using a button instead.

function Schedule( ) {
[Link] = new Object( );
[Link] = new Object( );
[Link] = [Link]("mySchedule");
if ([Link] != undefined) {
for (var i in [Link]) {
var dt = [Link][i].siDate;
[Link](dt);
[Link][dt].setValues([Link][i]);
}
[Link] = [Link];
}
[Link]( );
[Link] = setInterval(this, "runNotify", 3600000);
}

The addItem( ) method adds a new schedule item. The index is a formatted date string, and the
schedule item is added to the items associative array using that date string as the key. This mak
easy to retrieve a reference to the schedule item later on.

[Link] = function (index) {


var uniqueVal = [Link]( );
[Link][index] = [Link]("ScheduleItemSymbol", "item" + uniqueVal,
uniqueVal);
[Link][index].setDate(index);
};

The runNotify( ) method is invoked every 60 minutes; it checks for any notifications for the next
calculate the date that represents the next day, add one to the current date. From this date, you
create a formatted date string (the format used by the keys of the notifiers associative array)
the [Link]( ) method. Each element of the notifiers associative array is an array of notifi
for that day. Therefore, if there is an entry in the notifiers associative array for the next day, l
through all the elements of that array and create a string of those values. Then, run the
displayNotifications( ) method to actually open the notifier displayer.

[Link] = function ( ) {
var d = new Date( );
[Link]([Link]( ) + 1);
index = [Link]("MM-dd-yyyy");
if ([Link][index] != undefined) {
var notification = "";
for (var i = 0; i < [Link][index].length; i++) {
if ([Link][index][i]) {

[Link] 2004-02-06
Strona 166 z 180

notification += index + " " +


[Link](i) + newline +
[Link][index].getSchedule(i) + "\n\n";
}
}
delete [Link][index];
[Link](notification);
}
};

28.2.4 Designing the ScheduleItem Component


The ScheduleItem component represents a single calendar date. Each schedule item consists of
selector, an input text field, and a list box from which the user can select the type of entry he wa
make (hourly schedule, notes, or to-do list). The schedule item should have the following functio

z A user should be able to select from different types of schedule entries, and he should be a
make an entry for that type by typing into a text field.

z A user should be able to select from available hours in the time selector and add an entry f
hour. Time selector items corresponding to hours with entries should be colorized to indica

z A user should be able to specify whether the scheduler should notify him of hourly entries
day before they are scheduled to occur.

z A user should be able to close a schedule item window.

To create the ScheduleItem component, complete the following steps:

1. The ScheduleItem component requires you to include the ScrollBar and CheckBox compone
your movie. Create a copy of these components in your movie's Library by dragging an ins
each component from the Components panel onto the Stage and then deleting them. A cop
symbols remains in the Library.

2. Create a new movie clip symbol named ScheduleItem.

3. Open the linkage properties for the symbol.

4. Select the Export for ActionScript and Export in First Frame checkboxes.

5. Give the symbol a linkage identifier of ScheduleItemSymbol.

6. Click OK to close the Linkage Properties dialog box.

7. Edit the ScheduleItem symbol.

8. Add the following code to the first frame of the default layer:

#initclip 2

function ScheduleItem ( ) {

// Draw the component.


[Link]( );

// Create arrays to hold information about the hourly schedule entries and
// entries for which the user has chosen to be notified.

[Link] 2004-02-06
Strona 167 z 180

[Link] = new Array( );


[Link] = new Array( );

// Initialize the component, such that 12 A.M. is the selected hour.


[Link] = 0;

// Initialize the component so that it is not visible.


this._visible = false;
}

// ScheduleItem extends MovieClip.


[Link] = new MovieClip( );

// Retrieve the schedule item's values.


[Link] = function ( ) {
var valuesObj = new Object( );

// siDate is the formatted date string that indicates the date to which th
// schedule item corresponds.
[Link] = [Link];

// These properties are the entries (and notifiers) for the schedule item.
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
return valuesObj;
};

// The setValues( ) method is used to populate a schedule item when the valu
// have been saved to disk and have been retrieved again when the schedule
// application is reopened. The valuesObj is in the same form as the valuesOb
// that getValues( ) returns. Therefore, setValues( ) does essentially the
// of what getValues( ) does.
[Link] = function (valuesObj) {
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];

// In addition to setting the properties of the schedule item, call the


// setHasValue( ) method for the time selector for any times that have en
for (var i = 0; i < [Link]; i++) {
if ([Link][i] != undefined) {
[Link](i, true);
}
}
};

// The draw( ) method creates the component on the Stage.


[Link] = function ( ) {

// Create a text field for the schedule item title.


[Link]("title", [Link]( ), 0, 0, 550, 20);
[Link] = false;
[Link] = true;
[Link] = true;
[Link] = 0xDFDFDF;

// Create a TimeSelector instance and set its onSelect callback function.


[Link]("TimeSelectorSymbol", "times", [Link]( ));
[Link]("onSelectTime", this);

// Create an input text field so that the user can make entries.

[Link] 2004-02-06
Strona 168 z 180

[Link]("scheduleInfo", [Link]( ), 0, 0, 234,


[Link] = true;

// When the user removes focus from the text field (usually by clicking
// elsewhere in the movie), call the saveInfo( ) method to save the entry
[Link] = function ( ) {
this._parent.saveInfo( );
};

// Create a scrollbar for the input text field.


[Link]("FScrollBarSymbol", "sb", [Link]( ));
[Link]([Link]);
[Link]([Link]._height);

// Create a movie clip for the notification checkbox.


[Link]("notifyBox", [Link]( ));

// Draw a filled rectangle as a background.


[Link]("background",
[Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0xFFFFFF, 100);
drawRectangle([Link]._width + [Link]._width, 30);
endFill( );
_x = _width / 2;
_y = _height / 2;
}

// Attach a CheckBox component instance and set the callback function for
[Link]("FCheckBoxSymbol", "notifyCheckBox",
[Link]( ), {_x: 10, _y: 10}
[Link]("notify me");
[Link]("notifyChange", this);

// Use a table to position all the elements that you have created thus far
midTr0 = new TableRow(0, new TableColumn(0, [Link]),
new TableColumn(0, [Link]));
midTr1 = new TableRow(0, new TableColumn(0, [Link]));
midTable = new Table(0, 0, 0, midTr0, midTr1);

// Add a list box so the user can select an entry type.


[Link]("FListBoxSymbol", "entryTypeMenu", [Link]( ));
[Link](150, 380);
[Link]._height = 380;

// Set the values for the list box.


[Link](["hourly", "to do list", "notes"]);
[Link]("onSelectEntryType", this);
[Link](0);

// Create a table to position all the elements, including ones from midTab
tr0 = new TableRow(0, new TableColumn(0, [Link]));
tr1 = new TableRow(0,
new TableColumn(0, [Link]),
new TableColumn(0, midTable),
new TableColumn(0, [Link]));
t = new Table(0, 0, 0, tr0, tr1);

// Create a button movie clip to let the user close the schedule item wind
[Link]("closeBtn", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0xFFFFFF, 100);

[Link] 2004-02-06
Strona 169 z 180

drawRectangle(10, 10);
endFill( );
_x = 540;
_y = 10;
}
[Link] = function ( ) {
this._parent.close( );
// Invoke the save( ) method of the schedule to
// save the data when the item window is closed.
this._parent._parent.save( )
};
};

// Return the entry for an hour.


[Link] = function (hour) {
return [Link][hour];
};

// The notifyChange( ) method is the callback function for the notification


// checkbox. This method calls the setNotify( ) method of the parent schedu
// adds to the notifiers array property.
[Link] = function (cmpt) {
this._parent.setNotify([Link], [Link], [Link]( ));
[Link][[Link]] = [Link]( );
};

// The onSelectEntryType( ) method is the callback function that is invoked


// a user selects an item from the list box.
[Link] = function (cmpt) {
var index = [Link]( );
switch (index) {
case 0:
// If the first item (hourly) is selected, make sure the time selector
// set to selectable and also enable the checkbox.
[Link](true);
[Link] = "hourly";
[Link]( );
[Link](true);
break;
case 1:
// If the second item (to-do list) is selected, make sure the time sel
// is not selectable and disable the checkbox.
[Link](false);
[Link] = "";
[Link] = "toDo";
[Link] = [Link];
[Link](false);
[Link](false);
break;
case 2:
// If the third item (notes) is selected, make sure the time selector
// not selectable and disable the checkbox.
[Link](false);
[Link] = "";
[Link] = "notes";
[Link] = [Link];
[Link](false);
[Link](false);
}
};

// The setDate( ) method takes a formatted date string as a parameter and s


// the title text and the siDate property for the schedule item.
[Link] = function (val) {

[Link] 2004-02-06
Strona 170 z 180

[Link] = val;
var tf = new TextFormat( );
[Link] = true;
[Link] = "center";
[Link] = 15;
[Link](tf);
[Link] = val;
};

[Link] = function ( ) {
this._visible = true;
};

[Link] = function ( ) {
this._visible = false;
this._parent.onClose( );
};

// The saveInfo( ) method is invoked whenever the text field loses focus.
[Link] = function ( ) {
switch ([Link]) {
case "hourly":
// If the user has made an hourly schedule entry, save that entry in th
// hourlySchedule array and colorize the corresponding time selector i
// otherwise, remove any colorization on a time selector item.
if ([Link] != "") {
[Link][[Link]] = [Link];
[Link]([Link], true);
} else {
[Link]([Link], false);
}
break;

case "toDo":
// Save the to-do information.
[Link] = [Link];
break;

case "notes":
// Save the notes information.
[Link] = [Link];
}
};

// The onSelectTime( ) method is the callback function


// when a time selector item is chosen.
[Link] = function (cmpt) {
[Link] = [Link]( );
[Link]( );
};

// The selectTime( ) method displays any saved entries for a selected time
// input text field. Also, if the user has chosen to be notified for the even
// the checkbox is checked.
[Link] = function ( ) {
if ([Link][[Link]] != undefined) {
[Link] = [Link][[Link]];
} else {
[Link] = "";
}
if ([Link][[Link]] != undefined) {
[Link]([Link][[Link]]
} else {
[Link](false);

[Link] 2004-02-06
Strona 171 z 180

}
};

[Link]("ScheduleItemSymbol", ScheduleItem);

#endinitclip

Although the ScheduleItem component code is not particularly daunting, it is still worth taking a
look at some of what is going on here.

The getValues( ) and setValues( ) methods do the reverse of one another. The getValues( ) meth
used in conjunction with the Schedule component's save( ) method. It returns an object that con
user data, which is, in turn, saved to the shared object. Then, when the data is retrieved from th
object, the same data object is passed back to the setValues( ) method to repopulate the schedu

[Link] = function ( ) {
var valuesObj = new Object( );
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
return valuesObj;
};

[Link] = function (valuesObj) {


[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
for (var i = 0; i < [Link]; i++) {
if ([Link][i] != undefined) {
[Link](i, true);
}
}
};

The onSelectEntryType( ) method is the change handler function for the entry types list box. Alth
there are many lines of code in the method, it is really nothing complex. The entire method is a
switch statement that sets the time selector's selectable status, the entryType property, and oth
modifications depending on the type of entry the user has selected (hourly, to-do list, or notes):

[Link] = function (cmpt) {


var index = [Link]( );
switch (index) {
case 0:
[Link](true);
[Link] = "hourly";
[Link]( );
[Link](true);
break;
case 1:
[Link](false);
[Link] = "";
[Link] = "toDo";
[Link] = [Link];
[Link](false);
[Link](false);
break;
case 2:
[Link](false);
[Link] = "";

[Link] 2004-02-06
Strona 172 z 180

[Link] = "notes";
[Link] = [Link];
[Link](false);
[Link](false);
}
};

The saveInfo( ) method is invoked automatically when the user removes the focus from the inpu
field. This technique ensures that the schedule item's data is updated to reflect the new entry (o
changes). This method doesn't do anything particularly tricky. It just sets the appropriate schedu
property values depending on the type of entry.

[Link] = function ( ) {
switch([Link]) {
case "hourly":
if ([Link] != "") {
[Link][[Link]] = [Link];
[Link]([Link], true);
} else {
[Link]([Link], false);
}
break;
case "toDo":
[Link] = [Link];
break;
case "notes":
[Link] = [Link];
}
};

The selectTime( ) method is invoked whenever a user selects a time selector item. In this metho
should make sure that if the schedule item already has an entry for the selected hour, the text fi
should be populated with that data. Otherwise, remove any text from the text field. Also, if the u
chosen to be notified for an event stored for that hour, you should mark the checkbox as selecte
Otherwise, deselect the checkbox.

[Link] = function ( ) {
if ([Link][[Link]] != undefined) {
[Link] = [Link][[Link]];
} else {
[Link] = "";
}
if ([Link][[Link]] != undefined) {
[Link]([Link][[Link]]);
} else {
[Link](false);
}
};

28.2.5 Designing the NotifierDisplayer Component


The NotifierDisplayer component is nothing more than a scrollable text field with a rectangle bac
and a Close button. This simple component should do the following:

z Draw a rectangle for the background

z Add a text field and a scrollbar that scrolls the text field

z Add a button that enables the user to close the displayer

[Link] 2004-02-06
Strona 173 z 180

To create the NotifierDisplayer component, complete the following steps.

1. The NotifierDisplayer component requires you to include the ScrollBar and PushButton com
in your movie. You should already have included the ScrollBar component earlier, so you o
to include the PushButton component by dragging an instance from the Components panel
Stage and then deleting the instance. A copy of the symbol remains in the Library.

2. Create a new movie clip symbol named NotifierDisplayer.

3. Open the linkage properties for the symbol.

4. Select the Export for ActionScript and Export in First Frame checkboxes.

5. Give the symbol a linkage identifier of NotifierDisplayerSymbol.

6. Click OK to close the Linkage Properties dialog box.

7. Edit the NotifierDisplayer symbol.

8. Add the following code to the first frame of the default layer:

#initclip 0

function NotifierDisplayer ( ) {

// Create a movie clip and draw a filled square in it. This serves as the
// background for the component.
[Link]("background", [Link]( ));
with ([Link]) {
lineStyle(0, 0x000000, 100);
beginFill(0xFFFFFF, 100);
drawRectangle(300, 300);
endFill( );
_x = 150;
_y = 150;
}

// Create a text field in which the notifications can be displayed.


[Link]("notificationText", [Link]( ), 0, 0, 240,
[Link] = true;
[Link] = true;

// Create a scrollbar and set its size.


[Link]("FScrollBarSymbol", "sb", [Link]( ));
[Link]([Link]._height);

// Create a push button that closes the notifier displayer.


[Link]("FPushButtonSymbol", "closeBtn", [Link]( ));
[Link]("close");
[Link]("onCloseNotification", this._parent);

// Use a table to position the elements.


tr0 = new TableRow(0, new TableColumn(0, [Link]),
new TableColumn(0, [Link]));
tr1 = new TableRow(0, new TableColumn(0, [Link]));
t = new Table(5, 30, 30, tr0, tr1);
}

// NotifierDisplayer extends MovieClip.


[Link] = new MovieClip( );

[Link] 2004-02-06
Strona 174 z 180

// The display( ) method sets the notification text.


[Link] = function (value) {
[Link] = value;
[Link]([Link]);
};

[Link]("NotifierDisplayerSymbol", NotifierDisplayer);

#endinitclip
[ Team LiB ]
[ Team LiB ]

28.3 Putting the Application Together


The final step in creating a working scheduler application is to create the main code routine on th
main timeline of the Flash document. Essentially, the main routine is responsible for three things

z Creating an instance of Macromedia's Calendar component (the FCalendarSymbol must be


available in the Library)

z Creating an instance of the Schedule component

z Creating functionality that opens a schedule item that corresponds to a calendar date when
the calendar date is double-clicked

All you need to do to complete the scheduler application is add the following code to the first fram
of the default layer of the Flash document's main timeline:

// Include [Link] from Chapter 4.


#include "[Link]"
// Include [Link] from Chapter 7 and [Link] from Chapter 8.
#include "[Link]"
#include "[Link]"
// Include [Link] from Chapter 10 and [Link] from Chapter 11.
#include "[Link]"
#include "[Link]"

function init( ) {

// Create an instance of the Calendar component.


_root.attachMovie("FCalendarSymbol", "cal", _root.getNewDepth( ));
[Link]("onSelectDate");
[Link]([Link], [Link]);

// Create a Schedule component instance.


_root.attachMovie("ScheduleSymbol", "scheduler", _root.getNewDepth( ));
[Link]("onScheduleClose", _root);
}

// The onSelectDate( ) function is the callback function for the Calendar compon
function onSelectDate(cmpt) {

// Get the time, in milliseconds, since the movie started playing.


var currentTime = getTimer( );
var selectedDate = [Link]( );

// Select the date that has been clicked in the Calendar component.
if (selectedDate == undefined || selectedDate == previousDate) {
[Link](null);
[Link](previousDate);

[Link] 2004-02-06
Strona 175 z 180

[Link]("onSelectDate");
}

// If the click is a double-click, open the schedule item that corresponds to th


// date. If no schedule item for that date has been created, create it first.
if (currentTime - previousTime < 500) {
if ([Link]([Link]("MM-dd-yyyy")) == undefined) {
[Link]([Link]("MM-dd-yyyy"));
}
[Link]([Link]("MM-dd-yyyy"));
cal._alpha = 33;
}

// Keep track of the time differences between clicks.


previousTime = currentTime;

// Set the previousDate to the date that the user has clicked.
previousDate = (selectedDate == undefined) ? previousDate : selectedDate;
}

// When the schedule item is closed, reset the calendar's transparency to opaque.
function onScheduleClose( ) {
cal._alpha = 100;
}

init( );

Although the main code routine of the scheduler application is short, it probably requires the mo
explanation of any of the code in this application thus far. Let's take a look at what this code is
doing.

The init( ) and onScheduleClose( ) functions are both straightforward, but the onSelectDate( )
function requires a closer look. The onSelectDate( ) function is the callback function that is invok
whenever a calendar date is selected. By default, clicking a date toggles its selection in the Calen
component, but we want a double-click to open the corresponding schedule item. To do this, we
need to do several things. First, to determine whether a date has been double-clicked, we need t
calculate the time difference between clicks. We start with the current timer value:

var currentTime = getTimer( );

The rest of the code for determining double-clicking comes later in the function, so we'll examine
that in a moment.

If the user selects one date and then another, there is no problem. However, if the user clicks a
and then clicks it again, you need to make sure the same date remains selected (instead of togg
to unselected, which is the standard behavior of Macromedia's Calendar component). If a user cl
the same date two or more times in a row, we trick the Calander component by setting the selec
calendar item to the previously selected date (which happens to be the same date). The trouble
this approach is that it again triggers the calendar's change handler, causing an infinite, recursiv
loop. To avoid this problem, we temporarily set the calendar's change handler to null just before
setting the selected item. Then, immediately following that assignment, we reset the change
handler.

var selectedDate = [Link]( );


if (selectedDate == undefined || selectedDate == previousDate) {
[Link](null);
[Link](previousDate);
[Link]("onSelectDate");
}

Returning to detecting double-clicks, you can check for a double-click by comparing the differenc

[Link] 2004-02-06
Strona 176 z 180

between the current timer value and the timer value from when the user last selected a calendar
date. If the difference is less than 500 milliseconds (half a second), you should consider it a doub
click and open up the schedule item that corresponds to the selected date. If no schedule item fo
that date has been created, you should create it first.

if (currentTime - previousTime < 500) {


if ([Link]([Link]("MM-dd-yyyy")) == undefined) {
[Link]([Link]("MM-dd-yyyy"));
}
[Link]([Link]("MM-dd-yyyy"));
cal._alpha = 33;
}

Finally, for future calculations, you should store the current timer value as the previous timer va
You should also store the selected date as the previous date for future calculations. The selected
date may be undefined because of the built-in toggling functionality of the Calendar component.
this is the case, set the previous date to the value already stored as the previous date:

previousTime = currentTime;
previousDate = (selectedDate == undefined) ? previousDate : selectedDate;

[ Team LiB ]
[ Team LiB ]

28.4 Making the Scheduler Application Available Online


The scheduler application that you have created thus far retains a user's schedule only when it is
consistently from the same computer. This is because the schedule data is stored in a local share
object. However, with just a few small changes, you can adapt this application so it can be acces
online from any computer. In the next two sections, you can choose from three different alternat
for how to do this.

28.4.1 Using Remote Shared Objects


The easiest way to convert the scheduler application to make it available online is to store the da
a remote shared object instead of a local shared object. However, this option is available only if
have access to a FlashCom server.

To modify the application to use a remote shared object, complete the following steps:

1. Create a new FlashCom application on the server. Name the application schedulerApp.

2. Edit the Schedule component code, as shown in the following code block. Most of the code
remains the same, so only the code that changes is shown. Modifications are shown in bold

#initclip

function Schedule( ) {
[Link] = new Object( );
[Link] = new Object( );

// Create a new net connection and connect to the FlashCom application.


var myConnection = new NetConnection( );
[Link]("rtmp:/schedulerApp");
// Create a remote shared object and connect to it. When the shared object
// is retrieved, call the setValues( ) method.
[Link] = [Link]("mySchedule", [Link], true);

[Link] 2004-02-06
Strona 177 z 180

[Link](myConnection);
[Link] = this;
[Link] = function( ) {
[Link]( );
}
[Link]( );
[Link] = setInterval(this, "runNotify", 600000);
}

[Link] = new MovieClip( );

// setValues( ) is called when the remote shared object data is retrieved.


// code within the method body is the same code that was previously in the
// constructor method.
[Link] = function ( ) {
if ([Link] != undefined) {
for (var i in [Link]) {
var dt = [Link][i].siDate;
[Link](dt);
[Link][dt].setValues([Link][i]);
}
[Link] = [Link];
}
}

// The remainder of the code does not change.

28.4.2 Using Flash Remoting


The second option for saving the scheduler data on a server is to use Flash Remoting. Obviously
option works only if you have access to a Flash Remoting server. However, you can use this met
without Flash Remoting by using the FauxSharedObject class from Chapter 27. You need to have
FauxSharedObject class ready to include in your Flash document and to also have the ColdFusion
Component on the server, as per the instructions from Chapter 27. Once you have that code in p
you can quickly modify your schedule application by changing a small amount of code in the sche
component. The following shows the relevant code with the changes in bold:

#initclip
// Include the FauxSharedObject class from Chapter 27.
#include "[Link]"

function Schedule ( ) {
[Link] = new Object( );
[Link] = new Object( );
// Use a FauxSharedObject in place of the local shared object. Set the load han
// to the new setValues( ) method and load the data.
[Link] = new FauxSharedObject("mySchedule",
"[Link]
[Link]("setValues", this);
[Link]( );
[Link]( );
[Link] = setInterval(this, "runNotify", 600000);
[Link] = setInterval(this, "save", 5000);
}

[Link] = new MovieClip( );

// The code in setValues( ) was previously included in the constructor.


[Link] = function ( ) {
if ([Link] != undefined) {
for (var i in [Link]) {
var dt = [Link][i].siDate;
[Link](dt);

[Link] 2004-02-06
Strona 178 z 180

[Link][dt].setValues([Link][i]);
}
[Link] = [Link];
}
}

// The remainder of the code does not change.


[ Team LiB ]
[ Team LiB ]

28.5 Using LoadVars and XML


The third way you can modify the scheduler application is to use server-side scripts that read an
to a file. The ColdFusion versions of these scripts are provided here in this example, but if you ne
use another technology, it is not too difficult. You need two scripts: one to write data and one to
the data. The write script should take two parameters submitted via HTTP POST: the name of th
which to write and the data to be written. Here is the ColdFusion example that you should name
[Link]:

<cffile action="write"
file="#GetDirectoryFromPath(GetBaseTemplatePath( ))##[Link]#.so"
output="#[Link]#" nameconflict="overwrite">

The other script should read the data from the file and return it to Flash. This script should accep
single parameter submitted via HTTP GET: the name of the file from which to read. Here is the
ColdFusion example. You should name this file [Link].

<cfsetting enablecfoutputonly="yes">
<cffile action="read"
file="#GetDirectoryFromPath(GetBaseTemplatePath( ))##[Link]#.so"
variable="data">
<cfoutput>#data#</cfoutput>

Finally, you should make a few changes to the client-side ActionScript code in the Schedule com
The relevant snippet of code from that component is shown here with changes in bold:

#initclip

// The serializeObject( ) and deserializeObject( ) functions are the exact same


// as the serializeObject( ) and deserializeObject( ) methods of the FauxShared
// class from Chapter 27. See Chapter 27 for more information on these functions.
_global.serializeObject = function (obj) {
var sb = "<obj>";
if (obj instanceof Array) {
sb = "<obj type=\"array\">";
}
for (var i in obj) {
sb += "<index name=\"" + i + "\"";
if (obj[i] instanceof Object) {
sb += ">";
sb += serializeObject(obj[i]);
sb += "</index>";
} else {
sb += " val=\"" + obj[i] + "\" />";
}
}
sb += "</obj>";
return sb;
};

_global.deserializeObject = function (source) {

[Link] 2004-02-06
Strona 179 z 180

[Link] = true;
var sXML = new XML(source);
var cn = [Link];
var items = new Object( );
if ([Link] == "array") {
items = new Array( );
}
var attribs, childCn;
for (var i = 0; i < [Link]; i++) {
attribs = cn[i].attributes;
childCn = cn[i].childNodes;
if ([Link] > 0) {
items[[Link]] = deserializeObject(childCn[0]);
} else {
items[[Link]] = [Link];
}
items[[Link]] = new Object( );
for (var j in attribs) {
items[[Link]][j] = attribs[j];
}
}
return items;
};

function Schedule ( ) {
[Link] = new Object( );
[Link] = new Object( );

// Instead of a shared object, simply create an object with a data property.


[Link] = new Object( );
[Link] = new Object( );

// Create a flush( ) method for the object that uses a LoadVars object to send
// serialized data to the [Link] script.
[Link] = function ( ) {
serializedStr = serializeObject([Link]);
var sendLV = new LoadVars( );
[Link] = "myScheduleLV";
[Link] = serializedStr;
[Link]("[Link]
null, "POST");
};

[Link] = this;

// Create a load method for the object that uses an XML object to load the data
// from the [Link] script.
[Link] = function ( ) {
var loadXML = new XML( );
[Link] = true;
[Link](
"[Link]
[Link] = this;

// When the data loads, deserialize it and then call the setValues( ) method
[Link] = function ( ) {
[Link] = deserializeObject(this);
[Link]( );
};
};
[Link]( );
[Link]( );
[Link] = setInterval(this, "runNotify", 600000);
[Link] = setInterval(this, "save", 5000);

[Link] 2004-02-06
Strona 180 z 180

[Link] = new MovieClip( );

// The setValues( ) method contains code that was previously in the constructor.
[Link] = function ( ) {
if ([Link] != undefined) {
for (var i in [Link]) {
var dt = [Link][i].siDate;
[Link](dt);
[Link][dt].setValues([Link][i]);
}
[Link] = [Link];
}
};

// The remainder of the code stays the same.


[ Team LiB ]
[ Team LiB ]

28.6 Wrapping It Up
Well, we've completed our scheduler application and even enhanced it to work online. We've
come a long way since the initial recipes of Chapter 1. Even though this book is task-oriented, I
hope you've gained the experience, perspective, and knowledge to apply the lessons you've
learned to a wide variety of problems, including those not specifically addressed by a pre-built
recipe. Good luck with all your future ActionScript programming.

[ Team LiB ]

[Link] 2004-02-06
Strona 1 z 5

[ Team LiB ]

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

mk:@MSITStore:D:\SHARED\OReilly%20-%20ActionScript%20Cookbook%20-%... 2004-02-06
Strona 2 z 5

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

mk:@MSITStore:D:\SHARED\OReilly%20-%20ActionScript%20Cookbook%20-%... 2004-02-06
Strona 3 z 5

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 -

mk:@MSITStore:D:\SHARED\OReilly%20-%20ActionScript%20Cookbook%20-%... 2004-02-06
Strona 4 z 5

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


1
188 \u00bc /4 223 \u00df ß
1
189 \u00bd /2 224 \u00e0 à
3
190 \u00be /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

mk:@MSITStore:D:\SHARED\OReilly%20-%20ActionScript%20Cookbook%20-%... 2004-02-06
Strona 5 z 5

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

[ Team LiB ]

mk:@MSITStore:D:\SHARED\OReilly%20-%20ActionScript%20Cookbook%20-%... 2004-02-06
Strona 1 z 1

[ Team LiB ]

Colophon

Our look is the result of reader comments, our own experimentation, and feedback from
distribution channels. Distinctive covers complement our distinctive approach to technical
topics, breathing personality and life into potentially dry subjects.

The animal on the cover of ActionScript Cookbook is a crab-eating opossum (Philander


opossum). It can be found throughout Mexico, Central America, and South America. The length
of the animal can vary, though it is usually between 250 and 350 millimeters. Its prehensile tail
often grows to about the same length.

P. opossum is an omnivore. It eats insects, lizards, eggs, worms, frogs, small mammals, and
birds, along with seeds, bananas, and leaves. It reproduces all year round, though the number
of offspring varies. During the dry summer months when food is scarce, the litters can be as
low as one or two young, while as many as seven young can be born during the more plentiful
rainy season.

Most opossum nests are built in the low branches of trees, though they can also be found on
the ground or in burrows. P. opossum spends most of its time on the forest floor, where it
forages for food, though it will occasionally take to the trees. It was once thought that P.
opossum was nocturnal, but it has often been observed scampering and foraging during the
day. When it senses danger, P. opossum will hiss or yelp, and it can be a capable fighter.

Matt Hutchinson was the production editor and copyeditor for the ActionScript Cookbook. Reg
Aubry, Sarah Sherman, and Claire Cloutier provided quality control. Johnna Dinse wrote the
index.

Emma Colby designed the cover of this book, based on a series design by Edie Freedman. The
cover image is a 19th-century engraving from Cuvier's Animals. Emma Colby produced the
cover layout with QuarkXPress 4.1 using Adobe's ITC Garamond font.

David Futato designed the interior layout. This book was converted by Andrew Savikas to
FrameMaker 5.5.6 with a format conversion tool created by Erik Ray, Jason McIntosh, Neil
Walls, and Mike Sierra that uses Perl and XML technologies. The text font is Linotype Birka; the
heading font is Adobe Myriad Condensed; and the code font is LucasFont's TheSans Mono
Condensed. The illustrations that appear in the book were produced by Robert Romano and
Jessamyn Read using Macromedia FreeHand 9 and Adobe Photoshop 6. The tip and warning
icons were drawn by Christopher Bing. This colophon was written by Matt Hutchinson.

The online edition of this book was created by the Safari production group (John Chodacki,
Becki Maisch, and Madeleine Newell) using a set of Frame-to-XML conversion and cleanup tools
written and maintained by Erik Ray, Benn Salter, John Chodacki, and Jeff Liggett.
[ Team LiB ]

mk:@MSITStore:D:\SHARED\OReilly%20-%20ActionScript%20Cookbook%20-%... 2004-02-06
Strona 1 z 47

[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

! (NOT) operator
#include directives, reusing code and
&& (AND) operator
> operator
>= operator
< operator
<= operator
<image> element, Menu component XML document
.as files, reusing code and
.NET
Flash Remoting configuration[NET
Flash Remoting configuration
{ } (curly braces), associative arrays and
|| (OR) operator

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

aa property, transparency and


ab property, transparency and
abstract classes, Flash Paint
acceptClient( ) method
action message format (AMF)
ActionScript Interpreter [See Interpreter]
addColumn( ) method, TableRow class
addItem( ) method 2nd
combo box population
addItemAt( ) method
combo box population
addListener( ) method 2nd 3rd 4th
addNodes( ) function 2nd
addProperty( ) method
address book module, My Page application
[Link] file
addSongToList( ) function 2nd
adjustWidth( ) method
menu size
admin property
administrator client (video chat/message center application) 2nd
alignment
form elements
radio buttons
allowDomain( ) method 2nd
_alpha property
onEnterFrame( ) event handler
transparency and
AMF (Action Message Format)
AMF (action message format)

[Link] 2004-02-06
Strona 2 z 47

AMF format
AMFPHP, PHP support and
AND (&&) operator
angles, unit value conversion
animation
buttons
circle instances, adding to movie
circles
initCircles( ) function
component creation
initialization data loading, XML and
onEnterFrame( ) method
stages, application
annuities
anonymous functions
apostrophes in strings
appendChild( ) method 2nd
appending array elements
[Link] property
[Link]( ) method
[Link]( ) method
[Link]( ) method
applications
animation in stages
Flash Paint
assembling after creating
component building
planning
FlashCom, creating 2nd
image viewer/slideshow application
MP3 player
My Page
planning
scheduler
video/chat message center
appreciation of assets, calculating
Array class
methods
Array( ) function
array-access notation, movie clip dynamic names
array-access operator
[Link]( ) method
arrays
associative
creating
loops
reading elements
converting
from strings 2nd
to strings 2nd
copying
copying data by value
data providers, dependent menus and
elements
adding
inserting in middle
removing
retrieving
reversing
setting
image viewer/slideshow application

[Link] 2004-02-06
Strona 3 z 47

sequence viewer
integer-indexed
literal notation
looping through
multidimensional
of arrays
of objects
parallel
searches, matches
sorting
custom sorts
values stored
XML objects from
ASC files, video chat/message center application
ASCII, string conversion
ASObjects, typed objects and
[Link] [See also .NET]
service functions, calling using Flash Remoting
typed objects
receiving
returning
ASR files, SSAS
asset loading
checking
percent loaded
performing actions during
progress bar 2nd
assignment operators
compound
equality operator comparison
assignment statements, mathematical operators
associative arrays
{ } (curly braces)
creating
elements, reading
keys
loops
Object constructor and
object literal notation and
attaching sounds at runtime
attachMovie( ) method
UI components on forms
attachSound( ) method 2nd
attachVideo( ) method, FlashCom
attributes
movie clips
XML 2nd
adding
nested nodes and
reading
attributes property, XMLNode object
audio [See also sounds]2nd [See also MP3 player application]
capability detection
FlashCom
microphone capture/playback
playback, net stream
publishing to FlashCom server
authentication, private-key encryption
auto-complete, text fields in forms
autoSize property

[Link] 2004-02-06
Strona 4 z 47

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

Back button, Flash Paint application toolbar


backgroundColor property
backgrounds, text fields
bases, numbers
converting between
beginFill( ) method
beginGradientFill( ) method
binary numbers, specifying
bindFormatFunction( ) method
bindFormatStrings( ) method
bitshift left operator, RGB color values
bitwise or operator, RGB color values
body
for statements
switch statements
borderColor property, text fields
borders, text fields
bottomScroll property
boundaries, movie clips
break statements
array searches
buttons
animation
event handler methods, movie clips
movie clips as
hit area
sounds and

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

calculations [See also math]


annuities
appreciation of assets
distance between two points
future value
loan amortization
loan size
points along a circle
retirement savings
units of measure conversion
calendars, forms
call( ) method
callback functions, listener events
callback methods, Flash Paint components
caller client (video chat/message center application)
calling client (video chat/message center application)
calling functions
[Link] from Flash
ColdFusion from Flash
Java from Flash
JSP from Flash
named functions

[Link] 2004-02-06
Strona 5 z 47

remote functions on a service


callingClient property
[Link]
[Link]( ) method, FlashCom
case conversion, strings
case expressions, switch statements
case keyword, switch statements
case sensitivity, strings
case-sensitive sorting
Centigrade/Fahrenheit conversion
[Link]( ) method
[Link]( ) method
CFC (ColdFusion Component)
methods
passing complex parameters to
passing named parameters to
MP3 player application
My Page application file operations
service function calling
.cfm files
[Link]
[Link]
[Link]
[Link]
[Link]
CFM pages
typed object parameters
CFMX
Flash Remoting and
CGI scripts
Perl and
changeOrder( ) method
channels, stereo sound
characters
strings
one at a time processing
removing/replacing
whitespace
checkboxes
forms
adding
values
values
circles
animation
initCircles( ) function
instances, adding to movie
Drawing API
drawCircle( ) method
pen position coordinates
points along, calculating
classes
abstract classes
Array class
custom
creating
listener events
DataGlue
Date
Flash Paint, defining
Form

[Link] 2004-02-06
Strona 6 z 47

inheritance
instances
constructor functions
creating
instantiation
LoadVars
MovieClip, Drawing API and
MultiPageForm
NetConnection
properties
getter/setter properties
read-only
prototype property
RecordSet
Selection
SharedObject, persistent information and
subclasses, creating
subclassing
superclasses
Flash Paint
Table 2nd
TableColumn
TableRow
TextField
TextFormat
ToolbarButtonClass
XML
XMLSocket class
XPath
clear( ) method 2nd
client-side functions, invoking from server movies
client-side shared objects
adding data
reading values
clients
FlashCom
invoking server-side functions
tracking
shared object, broadcasting data to remote
clocks, creating
code
Expert Mode and
reusable
creating
named functions and
reusing
coin flip simulation
ColdFusion
Query objects
service functions, calling using Flash Remoting
typed objects
receiving
returning
web services, consuming using Flash Remoting
ColdFusion Component [See CFC]
ColdFusion MX
Flash Remoting configuration
loading external images
color
components
controlling dynamically

[Link] 2004-02-06
Strona 7 z 47

fills
Drawing API
gradients
hexadecimals
introduction
restoring
RGB values
current value and
extracting
sliders and
tint
transformation
values
color selector component
Flash Paint
color selector, Flash Paint
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method 2nd 3rd
columns, recordsets
names
sorting on single
combo boxes, forms
adding at runtime
makeDependent( ) method
menus and
populating
<select> tag and
comments
communicating between movies
accepting from other domains
broadcasting to remote shared object clients
different computers
FlashCom and
local connections
receipt validation
sending data and
same computer
XMLSocket class
CommunicationCenterApp
compare functions, references
complex parameters, passing to CFC methods
components
animations
color selector
Flash Paint
building
superclass
image viewer/slideshow application
creating
image
Image Viewer
menu
sequence item
sequence viewer
sequencer
MP3 player application
My Page application
UnitComponent
UnitComponentBar

[Link] 2004-02-06
Strona 8 z 47

reusable
scheduler application
NotifierDisplayer
Schedule
ScheduleItem
TimeSelector
TimeSelectorItem
composite datatypes
compound assignment operators
concat( ) method
copying arrays
concatenating strings
condenseWhite property
conditions
AND (&&) operator
if statements
NOT (!) operator
OR (||) operator
switch statements
configuration
Flash Remoting for .NET
Flash Remoting for ColdFusion MX
Flash Remoting for J2EE
Flash Remoting for Perl
Flash Remoting for PHP
My Page Application modules
conflicting variables
connect( ) method, NetConnection object
connection objects, Flash Remoting
connections, Flash Remoting
constructor functions
class instances and
custom classes and
Sound( )
constructors
Date( )
current date
time, current
recordsets
continuous compounding, future value and
converting
angle measurements
arrays to strings 2nd
Fahrenheit/Centigrade
number bases
strings
to arrays 2nd
to/from ASCII
to/from Unicode
units of measure
coordinates
circles, calculating
movie clips
boundaries
converting to/from Stage
coordinates of pen position
circles
ellipse
polygons
rectangles
triangles

[Link] 2004-02-06
Strona 9 z 47

copying
arrays
copying data by value
movie clips 2nd
createConfirmForm( ) function
createElement( ) method
createEmptyMovieClip( ) method 2nd
sounds and
createMultiForm( ) function
createNewSound( ) method
static
createTextNode( ) method
createTreeForm( ) function
currency, number formatting
currentNode variable
currentViewAr array, image viewer/slideshow application
curves, Drawing API 2nd
curveTo( ) method
line style
curveTo( ) method 2nd 3rd
custom classes, creating

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

data transmission
HTTP and
security
data validation in forms
databases
queries, SSAS
recordsets
DataGlue class
UI components, populating
[Link]( ) method
DataGrid component
DRK (DevNet Resource Kit)
datagrids, recordsets and
DataTable objects, .NET
datatypes
composite
Flash Remoting, transmitting to back end
primitive
typeof( ) operator
Date class
Date( ) constructor
current date
time, current
Date( ) method, number generation
[Link]( ) method
[Link]( ) method
[Link]( ) method
formatting date/time
[Link]( ) method
[Link]( ) method, formatting date/time
dates [See also times]
current, finding
Date( ) constructor and

[Link] 2004-02-06
Strona 10 z 47

day name, retrieving


DMYHMSM format, converting to Epoch milliseconds
elapsed intervals, calculating
Epoch milliseconds
formatting
month name, retrieving
parsing from strings
PHP and
variables, naming
day name, retrieving
deal( ) method, playing card simulation
decimals, delimiters
declaring variables
var keyword
default keyword, switch statements
degToRad( ) method
dependent menus, forms
depths, movie clips
swapping
unique depths
deserialization
converting strings to arrays
design, image viewer/slideshow application
detection
audio capabilities
display settings
Flash Player version
operating system
system language
dials, forms
dice roll simulation
dimensions, sizing images
directives
directories, FlashCom registered application directories
directory paths, tree node properties
[Link]
display
resolution in pixels
settings, detecting
displayNotifications( ) method
distance, calculating
DLL methods, [Link] pages and
DMYHMSM format, converting to Epoch milliseconds
domain restrictions, proxy scripts
doSelect( ) function
dragging movie clips
drawCircle( ) method 2nd
drawEllipse( ) method and
drawEllipse( ) method
drawCircle( ) method and
Drawing API
circles
curves 2nd
curveTo( ) method
line style
ellipse
fills
color
complex gradients
gradients
line drawing 2nd

[Link] 2004-02-06
Strona 11 z 47

line style
lineStyle( ) method
lineTo( ) method
masks
MovieClip class and
paths
pen metaphor
pen position coordinates
pen style
polygons
rectangles
line style
rounded rectangles
repositioning
shapes
triangles
[Link] file, [Link]( ) method
drawRectangle( ) method
drawRegularPolygon( ) method
drawSimpleRectangle( ) method
drawTriangle( ) method
drop-down lists
dropItem( ) method
duplicateMovieClip( ) method
duration property, sound playback
dynamic text, displaying at runtime

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

elapsedDays( ) method
elapsedYears( ) method
elements
arrays
adding
associative, reading
inserting in middle
removing
retrieving
reversing
setting
sorting
XML
adding to objects
attributes, adding
attributes, reading
extracting
names
Ellipse button, Flash Paint application toolbar
ellipse, Drawing API
drawEllipse( ) method
else clauses, if statements
else if clauses, if statements
email, sending from Flash
embedded fonts
rotating text
text fields
embedFonts property, UI components

[Link] 2004-02-06
Strona 12 z 47

empty movie clips


encryption
one-way hash encryption
public key
symmetric
endFill( ) method
entering text
filter text
password input fields
user input fields
Epoch milliseconds
converting to/from DMYHMSM format
equality expressions
equality operator, assignment operator comparison
escape sequences, special characters
event handlers
buttons, movie clips
[Link]( )
_alpha property
onEnterFrame( ), sounds
onRollover( )
onScroller( )
events
listener events 2nd 3rd
custom classes
scrolling text
sound playback end
text selection
user input responses
exactFit mode, scaling
Expert Mode, code entry and
expressions
equality
regular, pattern matching
external images, loading
external JPEGs, loading
external MP3 sound, loading to Flash movies
external SWF files, loading
from trusting domains

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

fadeIn( ) method, sounds


fadeOut( ) method
fading in sounds
fading movie clips
fading out sounds
Fahrenheit/Centigrade conversion
fast-forwarding net streams
Fill button, Flash Paint application toolbar
fills, Drawing API
color
complex gradients
gradients
color
filtering text input
filters, recordsets

[Link] 2004-02-06
Strona 13 z 47

firstChild property (XML)


Flash
asset loading
checking
percentage
performing actions during
progress bar 2nd
remote content, loading by proxy
Flash Color Mixer panel
Flash Communication Server MX [See FlashCom]
Flash Exchange web site
Flash Paint
applications
assembling after creating
component building
planning
classes
abstract classes
defining
color selector
color selector component
components, superclass
ShapeClass component, creating
shapes
text
Text component
toolbar buttons
ToolbarButton component, creating
Flash Player
menu items, hiding
scaling
version detection
Flash Remoting
AMF messages
CFMX and
ColdFusion MX configuration
Components download and install
connection objects
connection, establishing
datatypes, transmitting to back end
gateways
J2EE configuration
Java functions, calling
JRun 4 and
JSP functions, calling
named result functions
.NET configuration
NetConnection class
objects
receiving [Link] typed objects
receiving ColdFusion typed objects
receiving Java typed objects
returning typed from [Link]
returning typed from ColdFusion
returning typed from Java
parameters
passing complex to CFC methods
passing named to CFC methods
Perl configuration
PHP configuration
RecordSet classes

[Link] 2004-02-06
Strona 14 z 47

response objects
results handling
scheduler application
service functions
[Link], calling
calling ColdFusion
remote, invoking
results from multiple
results handling
service objects, creating
services
SSAS and
support
web services 2nd
FlashCom
acceptClient( ) method
applications
creating 2nd
directory location
audio
microphone, capture/playback
call( ) method
clients
server-side functions, invoking from movie
tracking
communication between movies and
introduction
live content, publishing
net streams
fast-forwarding
rewinding
onAppStart( ) method
onConnect( ) method
onDisconnect( ) method
playlists
recordMessageInfo( ) method
registered application directories
seek( ) method
server-side functionality
Sound class
streams, seeking based on total length
term usage
video objects, adding at runtime
video, web cam as source
FlashCom server
applications, subdirectory
audio, publishing
audio/video content subscription
client-side functions, invoking from movie
connecting to
video, publishing
[Link], .NET bin directory
[Link] object
[Link] file
flipping coin, simulation
focus, text fields
fonts, text fields
embedded
for keyword
for loops
arrays

[Link] 2004-02-06
Strona 15 z 47

searches
leading zeros
for statements
body
initialization expression
nested
[Link]( ) method and
syntax
test expressions
text nodes, in XML objects
update expressions
for...in statement, nested movie clips
Form class
advanced features forms
formatSongsList( ) function
formatting
date
numbers
currency
text field text
text fields, portions of fields
text, user input fields
time
milliseconds as minutes and seconds
forms
advanced features
calendars
checkboxes
adding
values
combo boxes
adding at runtime
populating
data validation
dials
elements
position
tables and
input text fields
input validation
list boxes, adding at runtime
menus
adding
dependent
selected items
setDataProvider( ) method
sizing
message boxes
multipage
submitting to URLs
validation
MultiPageForm class
My Page application framework
populating
push buttons
adding at runtime
radio buttons
adding
alignment
forms
values, selected

[Link] 2004-02-06
Strona 16 z 47

server-side scripts
sliders
submitting to URLs
tab order
tabIndex properties
text fields, auto-complete
UI components
appearance
color
runtime
validation errors
Forward button, Flash Paint application toolbar
frame rate, movie clips
actions
repeating
framework, My Page application
extending
form
modifications
fscommand( ) function
function literals 2nd
reasons to use
functions
address book module, My Page application
anonymous
Array( )
callback functions, listener events
calling, named functions
client-side, invoking from FlashCom server movie
constructor functions
custom classes and
Sound( )
definition
exiting
fscommand( )
getTimer( )
getVersion( )
handlers
initCircles( )
initLSO( )
isNaN( )
loadMovieNum( )
[Link]( )
[Link]( )
[Link]( )
movie clip playback
named
named functions, reusable code and
openUnit( )
parameters
parseInt( )
results
return statements
searchEntries( )
server-side, invoking from FlashCom client movie
service functions, Flash Remoting
setInterval( ) 2nd
sharing, My Page application
sorter functions
String( ) 2nd
terminating

[Link] 2004-02-06
Strona 17 z 47

future value
calculation
continuous compounding

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

[Link]
gateways, Flash Remoting
getBounds( ) method
getBytesLoaded( ) method 2nd
getBytesTotal( ) method 2nd
getDay( ) method
getFullYear( ) method
getMonth( ) method 2nd
[Link] file
[Link] file
getPan( ) method
getPercentComplete( ) method
getRGB( ) method
getSelectedItem( ) method
getService( ) method
getter/setter properties
classes
listener events and
watching
getTime( ) method
number generation
getTimer( ) function
getTransform( ) method
getValue( ) method
checkboxes
getValues( ) method
getVersion( ) function
getVolume( ) method
globalStyleFormat object, component appearance
globalToLocal( ) method
GMT (Greenwich Mean Time), UTC and
Google search module, My Page application
gotoAndPlay( ) method, movie clip playback
gotoAndStop( ) command
gradient fills, Drawing API
complex
linear
radial
graphics on progress bar, hiding
Greenwich Mean Time (GMT), UTC and

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

handlers
functions
onEnterFrame( )
hasContents property
hash tables

[Link] 2004-02-06
Strona 18 z 47

hexadecimal numbers
literals
specifying
hit area, movie clip buttons
hit tests, movie clips
hitTest( ) method 2nd
movie clip mouseovers and
hitTestOutline( ) method
Hobbelt's MD5 library
hscroll property
HTML (Hypertext Markup Language)
text fields and
whitespace restriction
HTML pages, MP3 player application
htmlText property
HTTP (Hypertext Transfer Protocol)
data transmission and
requests, SSAS
hyperlinks in text fields
Hypertext Markup Language [See HTML]
Hypertext Transfer Protocol [See HTTP]

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

if statements
else clauses
else if clauses
ignoreWhite property (XML)
image component, image viewer/slideshow application
Image View Pane (image viewer/slideshow application)
Image Viewer component (image viewer/slideshow application)
image viewer/slideshow application
components
creating
design
features
image component
Image View Pane
main routine
Menu
Preview Pane
Sequence Item
Sequence Viewer
Sequencer
images, loading external
importing sounds to Library
in/out points, sound playback
indexOf( ) method
inheritance
classes
prototype-based
init( ) function, video chat/message center application
initCircles( ) function
initialization data, animation
initialization expression, for statement
initializing movies, XML data
initLSO( ) function

[Link] 2004-02-06
Strona 19 z 47

initMessagesList( ) function 2nd


input text fields, forms
input to forms, validation
insertBefore( ) method
insertion point, text fields
instances
classes
constructor functions
new operator and
creating
listener events
objects, adding properties
text field instance names, referencing and
instantiation
integer-indexed arrays
interest rates, appreciation calculation and
Interpreter
interval action repeats
invoking functions
isBeingResized property 2nd
isNaN( ) function
isURLLoaded( ) method
iterations

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

J2EE Flash Remoting configuration


Java
functions, calling from Flash Remoting
ResultSet objects
typed objects
receiving
returning
join( ) method, converting arrays to strings
joining strings
multiple in single expression
JPEG files, loading external
JRun 4, Flash Remoting and
JSP functions, calling from Flash Remoting
[Link]
JukeboxController component (MP3 player application)

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

keys, associative arrays


keywords
for
switch
var 2nd
XML searches

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 20 z 47

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

labels on radio buttons


languages
codes
loading content to
multiple
system language, checking
lastIndexOf( ) method
leading zeros, numbers as strings
letters, reversing strings by
libraries, movie clips
library file requirements, image viewer/slideshow application
Library sounds, importing
Line button, Flash Paint application toolbar
line drawing
Drawing API
line style 2nd
lineStyle( ) method
lineTo( ) method
pen position
linear gradient fills
lineStyle( ) method 2nd
curves
lineTo( ) method 2nd 3rd
linkage identifiers, UI components
list boxes, forms
adding at runtime
makeDependent( ) method
menus and
population and
listener event handlers
listener events 2nd
custom classes
getter/setter properties
legacy Flash 4 properties
listener objects
text fields
lists, drop-down
literal notation, arrays
literals
function literals
hexadecimal
octal numbers
live content publishing, FlashCom
live streams, publishing
load( ) method
loading sounds
loading variables
formatting
from server-side script
progress checking
from text files
loading XML
loadMovie( ) method 2nd 3rd
loadMovieNum( ) function
loadSound( ) method 2nd
loadVariables( ) method
LoadVars class
LoadVars object, submitting forms
[Link]( ) method

[Link] 2004-02-06
Strona 21 z 47

[Link]( ) method
[Link]( ) method
loading variables from text file
[Link]( ) method
[Link]( ) method
[Link] file
[Link] file
loan amortization
loan calculation
local connections
communication and
communication between movies
receipt validation
sending data and
local selector (MP3 player application)
Flash movie connection
form page
submit page
local shared object [See LSO]
LocalConnection object
[Link]( ) method
[Link] (MP3 player application)
locally persistent information, storing/retrieving
localToGlobal( ) method
loops
arrays
associative arrays
sound playback
low resolution preview pane (image viewer/slideshow application)
LSO (local shared object)
movies in same domain
My Page application and
persistent information and
saving to client computer

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

main routine
image viewer/slideshow application
scheduler application
[Link], video chat/message center application
makeDependent( ) method
makeTitleBar( ) method
makeViewPane( ) method 2nd
masks, Drawing API and
matching patterns [See pattern matching]
math [See calculations]
Math object
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link] file
[Link]( ) method
[Link]( ) method
[Link]( ) function
[Link]( ) method
[Link]( ) function

[Link] 2004-02-06
Strona 22 z 47

[Link]( ) function
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
mathematical operators
>
>=
<
<=
assignment operators
compound
equality operators and
assignment statements
equality
postfix operators
prefix operators
trigonometric
maxchars property
maxhscroll property
maximum length of text input field
maxscroll property
Menu (image viewer/slideshow application)
menus
Flash Player items, hiding
forms
adding
dependent
selected items
setDataProvider( ) method
sizing
image viewer/slideshow application
pop-up
populating, recordsets and
message boxes, forms
messageNum property
methods
addItem( )
addItemAt( )
addListener( )
addProperty( )
adjustWidth( )
allowDomain( ) 2nd
appendChild( ) 2nd
[Link]( )
[Link]( )
[Link]( )
Array class
[Link]( )
attachSound( ) 2nd
attachVideo( )
beginFill( )
beginGradientFill( )
bindFormatFunction( )
bindFormatStrings( )
call( )
callback methods, Flash Paint components
[Link]( )
[Link]( )
[Link]( )
[Link]( )

[Link] 2004-02-06
Strona 23 z 47

[Link]( )
[Link]( ) 2nd 3rd
concat( ) 2nd
connect( )
createElement( )
createEmptyMovieClip( ) 2nd
createNewSound( ) 2nd
createTextNode( )
curveTo( ) 2nd 3rd
Date( ), number generation
[Link]( )
[Link]( )
[Link]( )
deal( ), playing card simulation
degToRad( )
drawCircle( ) 2nd
drawEllipse( )
drawRegularPolygon( )
drawSimpleRectangle( )
drawTriangle( )
duplicateMovieClip( )
elapsedDays( )
elapsedYears( )
endFill( )
fadeIn( )
fadeOut( )
getBounds( )
getBytesLoaded( ) 2nd
getBytesTotal( ) 2nd
getDay( )
getFullYear( )
getMonth( ) 2nd
getPan( )
getSelectedItem( )
getService( )
getTime( )
number generation
getTransform( )
getValue( ) 2nd
getVolume( )
globalToLocal( )
hitTest( ) 2nd
hitTestOutline( )
image component (image viewer/slideshow application)
Image View Component (image viewer/slideshow application)
indexOf( )
insertBefore( )
join( )
lastIndexOf( )
lineStyle( ) 2nd
curves
lineTo( ) 2nd 3rd
loadMovie( ) 2nd
loadSound( ) 2nd
loadVariables( )
[Link]( )
[Link]( )
[Link]( ) 2nd
[Link]( )
[Link]( )
[Link]( )

[Link] 2004-02-06
Strona 24 z 47

localToGlobal( )
makeDependent( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
moveTo( ) 2nd
movie clip playback
movie timelines
MovieClip class
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
MP3 player application
[Link]( )
[Link]( ) 2nd
[Link]( )
nextFrame( )
[Link]( )
objects and
custom
onChanged( ) 2nd
onEnterFrame( )
onKillFocus( )
onLoad( )
onSetFocus( ) 2nd
onSoundComplete( )
onSync( )
parseDate( )
play( )
playToPoint( )
prevFrame( )
query( )
radToDeg( )
randRange( )
playing card simulation
random-number generation
rolling dice
[Link]( )
[Link]( )
[Link]( )
[Link]( )

[Link] 2004-02-06
Strona 25 z 47

[Link]( )
[Link]( )
[Link]( )
reverse( )
seek( )
[Link]( )
[Link]( ) 2nd
send( )
setButtons( )
setDataProvider( )
setFullYear( )
setGroupName( )
setMask( )
setMonth( )
setNewTextFormat( )
setPan( ) 2nd
setPositions( )
setSelectedIndices( )
setStyleProperty( )
setTime( )
setTransform( ) 2nd 3rd
setValidator( )
setVolume( )
[Link]( )
[Link]( )
[Link]( )
slice( ) 2nd
sort( ) 2nd
sortOn( )
splice( )
split( ) 2nd
start( )
startDrag( )
static
stop( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( )
[Link]( ) 2nd
subclasses
submitToURL( ) 2nd
substr( )
substring( )
superclasses, subclass versions
swapDepths
[Link]( )
[Link]( )
[Link]( )
toLowerCase( )
toUpperCase( )
trim( )
validate( ) 2nd
video chat/message center application
XML class
[Link]( )
[Link]( )
[Link]( ) 2nd
XMLNode class
[Link]( ) method

[Link] 2004-02-06
Strona 26 z 47

minutes, milliseconds formatting


modules, My Page application
configuration
service modules
month name, retrieving
Moock Flash Player Inspector
mortgage calculation
mouse pointer
coordinating with scroll pane
custom
[Link]( ) method
mouseovers, movie clips
moveSongDown( ) function
moveSongUp( ) function
moveTo( ) method 2nd
movie clips
adding from library
animation, circle instances
as buttons
attachMovie( ) method
attributes
boundaries
buttons, hit area
ColorSelector component
controlling
coordinate spaces, converting to/from Stage
copying
depths
swapping
unique depths
dragging
duplicating
empty
fading
frame rate, repeating actions and
hit tests
mouse pointers
mouseovers
nested
accessing
for...in statement
parents
overlapping
playback
reversing
timeline
playback methods
properties, timeline variables
referencing
repeating actions
stacking order, changing
targeting
Sound objects
targets, dynamic names
text display
timelines
methods affecting
sound and
transparency
MovieClip class
Drawing API methods

[Link] 2004-02-06
Strona 27 z 47

subclassing
[Link]( ) method 2nd
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
depths
[Link]( ) method 2nd
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( )
_alpha property
event handler 2nd
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
stop( ) function and
movies
communication
communication between
accepting from other domains
broadcasting to remote shared object clients
different computers
local connections and
receipt validation
same computer
external MP3 sound
initializing, XML data and
sharing information
subscribing to audio/video content (FlashCom server)
.swf files, loading 2nd
MP3 player application
features
JukeboxController
local selector
Flash movie connection
form page
submit page
main jukebox movie
server selector
ColdFusion Component (CFC)
Flash movie creation
HTML page creation
implementation notes
testing
MP3 sounds, loading external to Flash movies
multidimensional arrays
multipage forms
submitting to URLs
validation
MultiPageForm class
My Page application

[Link] 2004-02-06
Strona 28 z 47

address book module


components, adding
file operations
framework
extending
form
modifications
function sharing
LSO, limiting
modules
configuration
service modules
notes module
openUnit( ) function
planning
scalability and
search module
server-side shared objects
UnitComponent
UnitComponentBar
[Link] file

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

named functions
calling
reusable code and
named parameters, passing to CFC methods
named result functions, Flash Remoting
names
dynamic, movie clips
XML elements
nested nodes, XML
attributes and
nesting
for statements
movie clips
accessing
for...in statements
parents
.NET
DataTable objects
web services, consuming using Flash Remoting
net streams
fast-forwarding
playback control
rewinding
NetConnection class 2nd
FlashCom
NetConnection object, connect( ) method
[Link]( ) method
[Link]( ) method 2nd
[Link]( ) method
new operator, class instances
nextFrame( ) method
nextImage( ) method
noBorder mode, scaling

[Link] 2004-02-06
Strona 29 z 47

nodeName property (XML)


nodes, XML
nested
searching for
NOT (!) operator
notation
array, literal notation
array-access, movie clip dynamic names
notes module, My Page application
[Link] file
NotifierDisplayer component, scheduler application
notifyIncomingCall( ) function
[Link]( ) method, radix parameter
numbers
angle values, converting units
bases
converting between
coin flip simulation
dice roll simulation
formatting
currency
playing card simulation
random, generating
rounding
strings, zeros, leading/trailing
unique, generating

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

Object constructor, associative arrays


object literal notation, associative arrays
[Link]( ) method
objects 2nd [See also individual objects]
arrays of
client-side shared objects
[Link]
hash tables
instances, adding properties
instantiation, Array( ) function
introduction
listener events
listener objects
text fields
LoadVars, submitting forms
LocalConnection
Math object
methods
custom
properties
shared
client-side, adding data
local
remote
server-side
server-side, My Page application
Sound
TextFormat

[Link] 2004-02-06
Strona 30 z 47

typed
receiving with [Link]
receiving with ColdFusion
receiving with Java
returning from [Link]
returning from ColdFusion
returning from Java
XML
creating
creating from arrays
elements, adding
populated
text nodes, adding
whitespace, removing
octal numbers
literals
specifying
onAppStart( ) method
onAppStop( ) callback handler
onChanged( ) method
onConnect( ) method
onDisconnect( ) method
onDrop( ) function
one-way hash encryption
algorithms
data transmission security
onEnterFrame( )
animation and
event handler 2nd
movie clip mouseovers and
onEnterFrame( ) event handler
onImageLoad( ) method
onKillFocus( ) method 2nd
online capability (scheduler application)
Flash Remoting
LoadVars and XML
remote shared objects
onLoad( ) method
onLoadProgress( ) method
onPress( ) event handler, UnitComponentBar
onPress( ) method 2nd
onRelease( ) event handler, UnitComponentBar
onRelease( ) method 2nd
onRollover( ) event handler, movie clip mouseovers and
onScheduleClose( ) function
onScroller( ) event handler
onSelectDate( ) function
onSelectEntryType( ) method
onSetFocus( ) method 2nd
onSoundComplete( ) method
onStatus( ) method
onSync( ) method
remote shared objects
openUnit( ) function
operating system detection
operators
array-access operator
bitshift left
bitwise or
mathematical
assignment

[Link] 2004-02-06
Strona 31 z 47

assignment statements
new operator, class instances and
OR (||) operator
Output window, trace( ) command and
overlapping movie clips, testing for

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

panning sound 2nd


parallel arrays
parameters
complex, passing to CFC methods
looping, sound playback
named, passing Flash Remoting to CFC methods
typed object, CFM pages
_parent keyword
parseDate( ) method
parseInt( ) function
parsing
dates from strings
strings, word by word
password property
passwords, user input fields
paths
Drawing API
target, movie clip control
[Link]
pattern matching
regular expressions and
strings
pause( ) method
pausing sound playback
pen metaphor, Drawing API
pen position, Drawing API
coordinates
circles
ellipse
rectangles
polygons
repositioning
triangles
pen style, Drawing API
Perl, Flash Remoting configuration
persistent information
local, storing/retrieving
storing on server
PHP
date and time and
Flash Remoting configuration
pixels, display resolution
play( ) method
in/out points
movie clip playback
playback, movie clips
functions
methods
reversing

[Link] 2004-02-06
Strona 32 z 47

timeline
Player
alignment of movie
scaling
playing card simulation
playing net stream audio/video
playing sounds 2nd
duration property
events, playback end
fading in
fading out
FlashCom audio
in/out points
looping
pausing
playback time
position property
queuing multiple
resuming
sound controller component example program
stopping
playlists, FlashCom
playToPoint( ) method
pointers, custom
polygons
pop( ) method
removing array elements
pop-up menus
populating
forms, prepopulating
Menu component (image viewer/slideshow application)
menus, recordsets and
recordsets
menu components
position property, sound playback
positioning radio buttons in forms
postfix operators, mathematical operators
pragmas
prefix operators, mathematical operators
prepopulating forms
prevFrame( ) method
Preview Pane (image viewer/slideshow application)
primitive datatypes
private-key encryption, authentication and
progress bar
asset loading and
graphics, hiding
load progress
text, hiding
progress checking, variable loading
projectors
dimensions
location on screen
SWF Studio and
properties
[Link]
autoSize
bottomScroll
condenseWhite
getter/setter properties
watching

[Link] 2004-02-06
Strona 33 z 47

hscroll
htmlText
inheritance, listener events and
maxchars
maxhscroll
maxscroll
movie clips, timeline variables
object instances, adding
objects and
password property
prototype
prototype chains
setting
read-only
restrict
scroll
[Link]
[Link]
[Link]
subclasses
[Link]
hasAudio
hasMP3
language
os
screenResolutionX
screenResolutionY
text property
TextField class
transform object, color
XML
firstChild
ignoreWhite
nodeName
XML class
XMLNode class
prototype chains
prototype properties, setting
prototype property
proxies
domain restrictions and
loading remote content to Flash movies
public key encryption, data transmission security
push buttons, forms
adding at runtime
push( ) method, array elements, appending
Pythagorean theorem, calculating distance

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

Query objects, ColdFusion


query( ) method
quotes in strings

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 34 z 47

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

radial gradient fills


radio buttons, forms
adding groups
alignment
labels
value of selected button
radix parameter
[Link]( ) method
parseInt( ) function
radToDeg( ) method
random-number generation
randRange( ) method
coin flip
playing card simulation
random number generation
rolling dice
ratios in sizing images
read-only properties
reading recordsets
reading text nodes, XML
reading values, client-side shared objects
reading XML
receivePathInfo( ) method
[Link]( ) method
recordMessageInfo( ) method
RecordSet class
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
recordsets
columns
names
sorting by single
constructors
creating
datagrids and
filters
populating
menu component instances
reading
rows
sorting by single column
Rectangle button, Flash Paint application toolbar
rectangles
Drawing API
line style
rounded rectangles
pen position coordinates
references
compare functions, sort( ) method
movie clips
text fields
[Link]( ) method
registered application directories, FlashCom
regular expressions, pattern matching and
regular polygons, Drawing API
relative paths, determining

[Link] 2004-02-06
Strona 35 z 47

relative time, calculating


remote content, loading by proxy
remote functions, invoking (Flash Remoting)
remote shared objects
clients, broadcasting data
saving data
scheduler application
updates
removeMessage( ) method 2nd
removeSong( ) function
removing characters from strings
render( ) method, tables
repeating actions in movie clips
repeating operations
replacing characters in strings
resizing menus in forms
resolution (display)
response objects, Flash Remoting
restoring color
restrict property
results
Flash Remoting
functions
variables, server-side processing
results functions, named (Flash Remoting)
results handling, multiple calls (Flash Remote)
ResultSet objects, Java
resume( ) method
resuming sound playback
retirement savings calculation
return statements
function results
reusable code
creating
named functions and
reusable components
reusing code
reverse( ) method
reversing array elements
reversing playback, movie clips
reversing strings
rewinding net streams
RGB color values
bitshift left operator
bitwise or operator
extracting
setting current value and
rolling dice simulation
root node, XML
rotating text
rounded rectangles
Drawing API
[Link]( ) method
rounding numbers
rows in recordsets
runNotify( ) method

[ Team LiB ]
[ Team LiB ]

[Link] 2004-02-06
Strona 36 z 47

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

saveInfo( ) method
saving remote shared object data
scalability, My Page applications
scale( ) method
scaling
exactFit mode
noBorder mode
showAll mode
scheduer application
online capability
LoadVars and XML
Schedule component, scheduler application
ScheduleItem component, scheduler application
scheduler application
components
features
main routine
NotifierDisplayer component
online capability
Flash Remoting
LoadVars and XML
remote shared objects
Schedule component
ScheduleItem component
TimeSelector component
TimeSelectorItem component
scope, variables
screens, projector location
scripts, server-side [See also CGI scripts]
loading variables from
sending data to
sending XML data
responses
variable results
scroll pane
coordinating with mouse pointer
positioning content in
updating
scroll property
ScrollBar UI component
scrollbars (image viewer/slideshow application), creating
scrolling text fields
events
programmatical
search module, My Page application
[Link] file
searchEntries( ) function
searches
arrays, matches
strings
pattern matching
substrings
XML
security sandbox
security, data transmission
seek( ) method, FlashCom
Select button, Flash Paint application toolbar
<select> tag, combo boxes and
Selection class

[Link] 2004-02-06
Strona 37 z 47

[Link]( ) method
[Link]( ) method 2nd
selectors (MP3 player application)
local
Flash movie connection
form page
submit page
server
ColdFusion Component (CFC)
Flash movie creation
HTML page creation
implementation notes
selectTime( ) method
send( ) method
sending data to server-side script
Sequence Item (image viewer/slideshow application)
Sequence Viewer (image viewer/slideshow application) 2nd
Sequencer (image viewer/slideshow application) 2nd
serialization
converting arrays to strings
server selector (MP3 player application)
ColdFusion Component (CFC)
Flash movie creation
HTML page creation
implementation notes
Server-Side ActionScript [See SSAS]
server-side component (video chat/message center application)
creating
server-side functionality, FlashCom applications
server-side functions
invoking from client movies
writing in ActionScript
server-side scripts
forms
sending data to
sending XML data, responses
variable results
variables, loading from
XML data, sending
server-side shared objects
adding data
My Page application
reading values
servers
FlashCom
connecting to
invoking client-side functions
persistent data storage
service functions
[Link], calling using Flash Remoting
CFC (ColdFusion Components) and
ColdFusion, calling using Flash Remoting
Flash Remoting
remote, invoking
results from multiple
results handling
service modules, My Page application
address book
Google Web searches
notes
service objects, Flash Remoting

[Link] 2004-02-06
Strona 38 z 47

services, Flash Remoting


[Link] file
setButtons( ) method, message boxes
setChangeHandler( ) method
setData( ) method, radio buttons
setDataProvider( ) method
datagrids and
menu items and
setFullYear( ) method
setGroupName( ) method
setInterval( ) function 2nd 3rd
setLabel( ) method, radio buttons
setLoadTarget( ) method
setMask( ) method
setMonth( ) method
setNewTextFormat( ) method
setPan( ) method 2nd
setPositions( ) method
setRGB( ) method
RGB color values
setScrollContent( ) method
setSelectable( ) method
setSelectedIndices( ) method, list boxes
setStyleProperty( ) method
setTime( ) method
settings
display, detecting
prompting user to change
Settings dialog box, Flash Player
setTransform( ) method 2nd
restoring color
sounds
setValidator( ) method
setValues( ) method 2nd
setVolume( ) method, sounds
ShapeClass component, Flash Paint
shapes
Drawing API 2nd
Flash Paint
shared objects
client-side
adding data
reading values
clients, broadcasting data to remote
local
movies in same domain
persistent information
saving to client computer
remote
saving data
updates
server-side
adding data
My Page application
reading values
SharedObject classes, persistent information and
[Link]( ) method
[Link]( ) method
[Link]( ) method
shift( ) method, removing array elements
showAll mode, scaling

[Link] 2004-02-06
Strona 39 z 47

size
menus, forms
text fields
sizing images (image viewer/slideshow application)
slice( ) method
copying arrays
parameters
sliders
color control and
forms
sound controller program
sort( ) method
compare function references
sorter functions
sorting
arrays
custom sorts
case-sensitive
recordsets
sortOn( ) method
Sound class, FlashCom
Sound [Link] file
Sound object
Sound objects
movie clips
Sound( ) constructor function, Sound object creation
sounds
attaching at runtime
buttons and
channels
createEmptyMovieClip( ) method
FlashCom audio
importing to Library
loading
onEnterFrame( ) event handler
panning 2nd
playing 2nd
duration property
events at stop
fading in
fading out
in/out points
looping
pausing
playback time
position property
queuing multiple
resuming
runtime control
sound controller component example program
stereo effects
stopping
timelines and
UI components
volume 2nd
special characters, whitespace
splice( ) method
inserting elements in arrays
removing array elements
split( ) method 2nd
spring buttons, toolbars

[Link] 2004-02-06
Strona 40 z 47

SSAS (Server-Side ActionScript)


database queries
HTTP requests
stacking order, movie clips
Stage
align property
coordinates, converting to/from movie clips
Stage object
[Link] property
[Link] property
Standalone Player, version detection and
Standalone Projectors, enhanced
start( ) method
sounds 2nd
first parameter
looping parameter
startDrag( ) method 2nd
startNext( ) method
startNextSong( ) function
startPosition property
statements
break statements
for statements
if statements
return statements
function results
switch statements
stereo effects, sound
stick buttons, toolbars
stop( ) function, [Link]( ) method and
stop( ) method
movie clip playback
sounds 2nd
[Link]( ) method
streams
definition
live, publishing
net streams
seeking based on total length
String( ) function 2nd
[Link]( ) method, for statement and
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method
[Link]( ) method 2nd
converting strings to arrays
strings
apostrophes in
case conversion
character-by-character processing
concatenation
converting
from arrays 2nd
to arrays 2nd
dates, parsing from
joining
multiple in single expression
numbers as, leading/trailing zeros
parsing word by word

[Link] 2004-02-06
Strona 41 z 47

quotes in
removing characters
replacing characters
reversing
searches
pattern matching
substrings
substrings
extracting
searches
Unicode ASCII conversion
whitespace
trimming
subclasses
creating
methods
MovieClip and
properties
superclass methods
subclassing
[Link] (MP3 player application)
submitToURL( ) method
multipage forms
submitting forms
substr( ) method
parameters
substring( ) method
parameters
substrings
extracting from strings
searches
superclasses
components, Flash Paint
methods, subclass variations
[Link] file 2nd
swapDepths( ) method 2nd
SWF files, loading
external
to movies
trusting domains
SWF Studio
projectors
dimensions
location on screen
Standalone Projectors
SWFKit Standalone Projectors
switch keyword
switch statements
body
case expressions
case keyword
default keyword
text expressions
symmetric encryption
data transmission security
keys
system language, checking
System object
[Link] object
[Link] properties
hasAudio

[Link] 2004-02-06
Strona 42 z 47

hasMP3
language
os
screenResolutionX property
screenResolutionY property
[Link]( ) method
[Link]( ) method

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

tab order, form elements


tabIndex properties, form elements
Table class
creating
defining
render( ) method
TableColumn class, defining
TableRow class
addColumn( ) method
defining
render( ) method
tables, form elements
tags, XML
targeting movie clips
dynamic names
Sound objects
terminating functions
test expressions, for statement
text
dynamic, displaying at runtime
Flash Paint
formatting
movie clips
progress bar, hiding
selecting in text fields
user input fields
creating
filtering
passwords
Text button, Flash Paint application toolbar
Text component, Flash Paint
text expressions, switch statements
text fields
backgroundColor property
backgrounds
borderColor property
borders
Button object properties and
focus
fonts
embedded
formatting text
portions of field
forms, auto-complete
HTML text in
HTML text, whitespace
hyperlinks

[Link] 2004-02-06
Strona 43 z 47

insertion point, setting


instance names
maxchars property
MovieClip object properties and
password property
referencing
restrict property
rotating text
runtime creation
scrolling
events
programmatically
selecting text
selecting, events
sizing
Text Tool and
Unicode text
user input fields
maximum length
responding to input
user input, text formatting
text files, loading variables from
text nodes
adding to objects
for statements
reading
trees
writing
text property
Text tool, creating text fields
TextField class
[Link]( ) method
TextFormat class
TextFormat object
setNewTextFormat( ) method
thickness of lines drawn [See Drawing API]
time [See also dates]
current, finding
Date( ) constructor
elapsed, calculating
Epoch milliseconds
formatting
milliseconds as minutes and seconds
PHP and
relative, calculating
timeDisplay( ) method
timeline variables, movie clips
timelines, movie clips
methods affecting
playback
sound and
timers
TimeSelector component, scheduler application
[Link]( ) method
TimeSelectorItem component, scheduler application
tint, color
title bar, image viewer/slideshow application
toLowerCase( ) method
toolbar buttons, Flash Paint
spring buttons
stick buttons

[Link] 2004-02-06
Strona 44 z 47

ToolbarButton component, Flash Paint


ToolbarButtonClass class
toString( ) method, Object class
toUpperCase( ) method
trace( ) command
tracking clients, FlashCom
trailing zeros, numbers as strings
transformations, color
transmitting data, security
transparency
adjusting
movie clips
Tree component
creating
node population
trees, XML
triangles, Drawing API
trigonometric methods
trim( ) method
trimming whitespace from strings
typeof( ) operator

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

UI components
appearance, customizing all
checkboxes
forms
adding at runtime
appearance
color
linkage identifiers
populating, DataGlue class and
ScrollBar component
sounds and
Unicode text, text fields
Unicode, string conversion
Uniform Resource Locators [See URLs]
unique depths, movie clips
unique numbers, generating
UnitComponent, My Page application
UnitComponentBar, My Page application
units of measure, conversion
unshift( ) method, inserting array elements
update expressions
for statements
updateListView( ) function 2nd
updateViewer( ) method
URLs (Uniform Resource Locators)
CGI scripts and
forms
multipage
submitting
variables, loading from text files
user input fields
filtering
maximum length

[Link] 2004-02-06
Strona 45 z 47

passwords
responding to input
text formatting
users
form validation errors
settings, prompting to change

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

validate( ) method
form input
multipage forms
validation
communication receipt
forms errors
forms input
forms, multipage
values
arrays
checkboxes
forms
color values
copying data by
radio buttons, selected
var keyword 2nd
variables
conflicts
declaring
var keyword
definition
loading
formatting
progress checking
from server-side script
from text files
naming, date
scope
sending, server-side script
server-side processing results
version detection, Flash Player
video
FlashCom
adding at runtime
playback, net stream
publishing to FlashCom server
video chat/message center application
administrator client component
calling client component
overview
server-side component
volume 2nd

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

[Link] 2004-02-06
Strona 46 z 47

watch( ) method, listener events


WDDX, data transfer and
web cam as source
web cam video, displaying in FlashCom
Web search module, My Page application
web services
Flash Remoting
.wsdl files
whitespace
HTML text fields, restricting
strings
trimming from
XML objects, removing
Windows Projectors, dimensions
words
parsing strings by
reversing strings by
writing XML
WSDL (Web Services Description Language)
.wsdl files, web services and
[Link], permission to run

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

_x property, forms
[Link], XPath class
XML
advantages
animation initialization data, loading
attributes 2nd
nested nodes and
data structure
elements
adding to objects
attributes, adding
attributes, reading
extracting
names
firstChild property
loading
movie initialization
nodeName property
nodes
nested
objects
creating
creating from arrays
elements, adding
populated
text nodes, adding
whitespace, removing
properties
ignoreWhite
reading
root node
scheduler application
online capability

[Link] 2004-02-06
Strona 47 z 47

searches
sending data
responses
sending data to server-side scripts
tags
text node
text nodes
adding to objects
for statements
reading
tree
reading elements
writing
XML class
methods
properties
XML declarations
XML documents, Menu component
populating
XML nodes
XML packets
XML tree
[Link]( ) method
[Link]( ) method
[Link]( ) method 2nd
XMLNode class
methods
properties
XMLNode object
attributes property
XMLSocket class, communication between movies
XPath class

[ Team LiB ]
[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y]

_y property, forms

[ Team LiB ]

[Link] 2004-02-06

You might also like