So far, we've ignored the possibility that an instance of our ballSymbol may be dragged to the Stage manually in the Flash authoring environment. If that happens, the Ball( ) constructor executes at runtime, even for instances dragged to the Stage during authoring (assuming that Ball has been defined as a subclass before the instance is placed on the timeline). But there's no opportunity to set parameters via an initObject�as with attachMovie( ) or duplicateMovieClip( )�when the instance is created manually during authoring. To overcome this problem, we must define our movie clip symbol as a component.
A component is simply a movie clip symbol that provides a GUI for setting properties in author-time instances. Typically, components are also MovieClip subclasses that use the author-time properties as constructor parameters.
|
We'll study component creation more closely in Chapter 16. For now, let's see how to turn our ballSymbol into a component. We'll continue working with ballSymbol from the last section, which defines the Ball class constructor within its #initclip block.
Select ballSymbol in the Library.
In the Library's pop-up Options menu, select Component Definition. The Component Definition dialog box appears.
Click the + button five times to insert placeholders for five new component parameters.
For the five parameters, under Name, enter radius, velocity, color, x, and y. These are the cosmetic author-time parameter names shown to the end user. You'll recognize them as our Ball constructor parameters.
For the five parameters, under Variable, enter cp_radius, cp_velocity, cp_ballColor, cp_x, and cp_y. These are the property names that will be set in each ballSymbol instance. The prefix cp_ is our own convention, meaning "component parameter." We'll use it to identify and delete component parameters after our constructor is finished using them.
For the five parameters, using the pop-up menu under Type, choose Number, Object, Color, Number, and Number. These indicate the datatype of each parameter. The Color type also provides the user with a color picker for color selection.
Finally, for the five parameters, under Value, enter 0, {x:0, y:0}, #000000, 0, and 0. Notice that for velocity and color, a specialized interface offers easy entry of object properties and color values. These are the default values, but the user can change them when he drags the instance to the Stage.
Click OK.
Figure 14-1 shows our ballSymbol component definition.
Henceforth, when a user drags an instance of ballSymbol to the Stage, the Property inspector will provide them with a GUI for setting the instance's "component parameters," as shown in Figure 14-2.
The parameter values specified by the user in the Property inspector are automatically assigned to properties in the ballSymbol instance (the property names used are those we specified earlier in Step 5�cp_radius, cp_velocity, cp_ballColor, cp_x, and cp_y). Although we could simply use these instance properties as-is, we'll modify our Ball constructor to improve code modularity.
Recall that our original Ball constructor function expects its parameters to be supplied in a single package�the params object. Therefore, the constructor accesses each parameter as this.params.paramName, as shown in Example 14-1. The params object also neatly contains all our constructor parameters so that they can be deleted conveniently after construction. But our component parameters (cp_radius, cp_velocity, etc.), in contrast, are defined directly as properties of new ballSymbol instances, not on a params object. Just as with the attachMovie( ) initObject, it's legal to use these direct properties as-is, without any constructor function intervention. However, doing so encroaches on the duties of our constructor. A normal constructor function is responsible for creating instance properties and performing instance initialization; it also uses constructor parameters only to initialize the new instance.
In addition, normal constructor parameters are local to the constructor function, and they expire automatically when the constructor finishes executing. Our Ball constructor forces the component parameters (cp_radius, cp_velocity, etc.) into the role of constructor parameters by using them only to initialize the new Ball instance and by deleting them explicitly at the end of the function. In our Ball constructor, the component parameters are temporary and do not equate to the instance's internal properties. For example, we might ask the user to specify the ball's diameter as a component parameter but derive and store the ball's radius as a property instead. Distinguishing between component parameters and our Ball class's properties keeps our code centralized; properties are created in the constructor as expected, rather than through the Component Definition GUI.
Here's the revised code for our "componentized" Ball constructor. All other code for the Ball class remains the same. Notice how important code comments become�the constructor defines no parameters, so we must rely on comments to determine the expected parameters. (In the following code, the comments for version information, methods, and event handlers are omitted for brevity.) The constructor recognizes component parameters for deletion by looking for the cp_ prefix, which we specified earlier in the Component Definition dialog box.
/* * Ball Class. Extends MovieClip. * * Component Params: * cp_radius -The ball's size * cp_ballColor -The ball's color * cp_x -The ball's initial x position * cp_y -The ball's initial y position * cp_velocity -The ball's movement vector: {x:0, y:0} * * Methods: ... * * Event Handlers: ... */ /* * Class Constructor. Parameters are passed by attachMovie( )'s initObj. */ org.moock.Ball = function ( ) { // Create instance properties. this.velocity = this.cp_velocity; // Initialize instance. this.setPosition(this.cp_x, this.cp_y); this.setColor(this.cp_ballColor); this.setSize(this.cp_radius); this.onEnterFrame = this.move; // Remove component parameters from the instance (any property that starts // with the prefix cp_ is a component parameters). for (var p in this) { if (p.indexOf("cp_") != -1) { delete this[p]; } } };
Of course, now that we've changed our Ball constructor, we must modify our initObject to use the component property names when creating ballSymbol instances with attachMovie( ). This lets us use one consistent constructor function, whether the component is created manually at authoring time or programmatically at runtime.
Here's the revised attachMovie( ) approach:
var initObj = new Object( ); initObj.cp_radius = 10; initObj.cp_ballColor = 0xFF0000; initObj.cp_x = 250; initObj.cp_y = 250; initObj.cp_velocity = {x:12, y:4}; // Create a ball. this.attachMovie("ballSymbol", "redBall", 1, initObj);
If all clip instances of a subclass will be generated programmatically using attachMovie( ) or duplicateMovieClip( ), you should design the constructor to accept parameters attached to a single params object within the initObject. However, if you are creating components that will be instantiated at authoring time, you should design the constructor to accept individual component parameters starting with the cp_ prefix (and you should attach parameters of the same name directly to the initObject when instantiating the component programmatically).
Many visual components dynamically create their on-screen display at runtime by attaching exported movie clips inside the component instance. When a component that relies on exported assets is transferred from one .fla file to another, each asset must be transferred to the target Library. In Flash MX, there is no formal means of packaging a multisymbol component into a single unit. However, the conventional approach is to place an instance of each asset onto a timeline guide layer inside the component (see the Flash online Help for a discussion of guide layers). The assets on a guide layer are transferred automatically when the component is dragged between Libraries, but they do not show up in the component at runtime (unless the component attaches them explicitly).
The following code presents a generic component template that implements a MovieClip subclass. The template does not use a namespace, such as org.moock, as we did in our Ball example. Nor does the template store all constructor parameters on a single params object. These optional techniques can be implemented at your discretion. However, the template expects constructor parameters to be prefixed with cp_, and it deletes all component parameters after construction. Though deleting constructor parameters is not mandatory, it is wise coding practice.
// CODE PLACED ON FRAME 1 OF someClassSymbol IN THE LIBRARY. #initclip /* * SomeClass Class. Extends MovieClip * Version: 1.0.0 * Desc: Your description goes here * * Component Params: * cp_param1 -Short description * cp_param2 -Short description * * Methods: * someMethod( ) -Short description */ /* * Class Constructor * (Parameters are passed by an initObj or set as component parameters) */ _global.SomeClass = function ( ) { // Create Instance properties this.prop1 = this.cp_param1; // Initialize instance this.someMethod(this.cp_param2); // Remove component params (those with the cp_ prefix) from the instance for (var p in this) { if (p.indexOf("cp_") != -1) { delete this[p]; } } }; /* * Set MovieClip as SomeClass's superclass */ SomeClass.prototype = new MovieClip( ); /* * Associate the someClassSymbol in the Library with SomeClass */ Object.registerClass("someClassSymbol", SomeClass); /* * Instance Methods */ /* * Method: SomeClass.someMethod( ) * Desc: Short description * * Params: * param -Short description * */ SomeClass.prototype.someMethod = function (param) { trace("someMethod invoked with parameter of: " + param); }; #endinitclip
The following code shows example usage of the SomeClass template. To see a manually created SomeClass instance, download the componentTemplate.fla file from the Code Depot.
// Create an instance var initObj = new Object( ); initObj.cp_param1 = 15; initObj.cp_param2 = "Hello world"; this.attachMovie("someClassSymbol", "someClassInstance", 1, initObj);