by Adobe

adobe_logo_bio

Created

22 March 2010

Requirements
User level
All
You can create reusable components by using ActionScript, and reference these components in your applications as MXML tags. Components created in ActionScript can contain graphical elements, define custom business logic, or extend existing Flex components.
 
Flex components are implemented as a class hierarchy in ActionScript. Each component in your application is an instance of an ActionScript class. All Flex visual components are derived from the ActionScript UIComponent class. To create your own components, you can create a subclass from the UIComponent class, or from any of its subclasses.
 
The class that you choose to use as the superclass of your custom component depends on what you are trying to accomplish. For example, you might require a custom button control. You could create a subclass of the UIComponent class, and then re-create all of the functionality built into the Flex Button class. A better and faster way to create your custom Button component is to create a subclass of the Flex Button class, and then modify it in your custom class.
 
This article covers:
 
Note: You can also create Flex components in MXML. For more information, see the Building components in MXML Quick Start.
 

 
Creating a simple ActionScript component

When you define a simple component in ActionScript, you do not create a component, but you modify the behavior of an existing component. In the following example you create a subclass of the ComboBox class to create a custom ComboBox called CountryComboBoxSimpleAS that is prepopulated with a list of countries.
 
Note: When instantiating your component using MXML, you use the name of your class as the tag name.
 
You can place custom components in the root folder of your project (default package) or in a subfolder. Adobe recommends the latter location as a best practice. In this example, the custom component is placed in a folder called components, which corresponds to the components package in ActionScript. In the main application MXML file, you map this folder to a namespace called custom and use the fully-qualified tag name of <custom:CountryComboBoxSimpleAS> to refer to the component.
 
Tip: In real-world applications, you may see custom components placed in a package structure that uses a reverse domain name structure (for example, xmlns:custom="com.adobe.quickstarts.customcomponents.*"). This convention avoids namespace conflicts between identically named components by different vendors. For example, two component libraries may each have a Map component that you use in your application. If one is in the com.vendorA package and the other is in the com.vendorB package, they do not conflict.
 
 
Example
components/CountryComboBoxSimpleAS.as
 
// components/CountryComboBoxSimpleAS.as package components { import mx.collections.ArrayList; import spark.components.ComboBox; public class CountryComboBoxSimpleAS extends ComboBox { // Constructor public function CountryComboBoxSimpleAS():void { // Call the constructor of the superclass. super(); var myData:ArrayList = new ArrayList(["United States", "United Kingdom"]); dataProvider = myData; } } }
Main application MXML file
 
<?xml version="1.0" encoding="utf-8"?> <!-- ASComponentSimple.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:custom="components.*" width="220" height="115"> <custom:CountryComboBoxSimpleAS/> </s:Application>

 
Referencing properties and methods of a custom component

The CountryComboBoxSimpleAS class extends the ComboBox class, so you can reference all of the properties and methods of the ComboBox control within the MXML tag that initializes your custom component, or in the ActionScript specified within an <fx:Script> tag. The following example specifies an event listener for the close event for your custom control:
 
 
Example
components/CountryComboBoxSimpleAS.as
 
// components/CountryComboBoxSimpleAS.as package components { import mx.collections.ArrayList; import spark.components.ComboBox; public class CountryComboBoxSimpleAS extends ComboBox { // Constructor public function CountryComboBoxSimpleAS():void { // Call the constructor of the superclass. super(); var myData:ArrayList = new ArrayList(["United States", "United Kingdom"]); dataProvider = myData; } } }
Main application MXML file
 
<?xml version="1.0" encoding="utf-8"?> <!-- ASComponentInheritance.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:custom="components.*" width="270" height="170"> <fx:Script> <![CDATA[ import flash.events.Event; private function handleCloseEvent(eventObj:Event):void { status.text = "You selected: \r" + countries.selectedItem as String; } ]]> </fx:Script> <s:Panel title="Custom component inheritence"> <s:layout> <s:VerticalLayout paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10"/> </s:layout> <custom:CountryComboBoxSimpleAS id="countries" close="handleCloseEvent(event);"/> <s:Label id="status" text="Please select a country from the list above." width="136"/> </s:Panel> </s:Application>

 
Applying styles to custom components

Style properties define the look of a component, from the size of the fonts used to the color of the background. Your custom ActionScript components inherit all of the styles of the base class, so you can set them in the same way as for that base class.
 
To change style properties in custom components, use the setStyle() method in the component's constructor. This applies the same style to all instances of the component, but users of the component can override the settings of the setStyle() method in MXML tags. Any style properties that are not set in the component's class file are inherited from the component's superclass.
 
In the following example, you create a custom PaddedPanel component that extends the Panel component and applies a blue border color, sets rounded corners, and hides the drop shadow. Using this custom component is simpler—that is, results in less code and has no redundancy—than setting style properties every time you use a Panel component.
 
 
Example
components/PaddedPanel.as
 
// components/PaddedPanel.as package components { import spark.components.Panel; public class PaddedPanel extends Panel { public function PaddedPanel() { // Call the constructor of the superclass. super(); // Set the border styles. setStyle("borderColor", "blue"); setStyle("dropShadowVisible", false); setStyle("cornerRadius", 20); } } }
Main application MXML file
 
<?xml version="1.0" encoding="utf-8"?> <!-- ASComponentStyling.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:custom="components.*" width="300" height="130"> <custom:PaddedPanel title="Custom component styles"> <s:Label text="Set the border styles of the Panel."/> </custom:PaddedPanel> </s:Application>

 
Creating advanced ActionScript components

One of the common goals when you create ActionScript components is to create configurable and reusable components. For example, you can create ActionScript components that take properties, dispatch events, define new style properties, have custom skins, or use other customizations.
 
One design consideration when you create custom ActionScript components is reusability. That is, do you create a component that is tightly coupled to your application, or create one that is reusable in multiple applications?
 
You write a tightly coupled component for a specific application, often by making the component dependent on the application's structure, variable names, or other details. If you change the application, you might have to modify a tightly coupled component to reflect that change. A tightly coupled component is often difficult to use in another application without rewriting it.
 
You design a loosely coupled component for reuse. A loosely coupled component has a well-defined interface that specifies how to pass information to the component, and how the component passes back results to the application.
 
You typically define the properties of a loosely coupled component to pass information to it. These properties, defined by using implicit accessors (setter and getter methods), specify the data type of the parameter's value. In the following example, the CountryComboBoxAS custom component defines a public useShortNames property that exposes the _useShortNames private property by using the get useShortNames() and set useShortNames() accessor methods.
 
The [Inspectable] metadata tag for the _useShortNames private property defines an attribute, which appears in the attribute hints and Tag inspector in Adobe® Flash® Builder™. You can also use the metadata tag to limit the allowable values of the property.
 
Note: All public properties show up in MXML code hints and in the Property Inspector. If you have extra information about the property that will help code hints or the Property Inspector (such as enumeration values or that a String is actually a file path) then also add [Inspectable] metadata with that extra info.
 
MXML code hints and the Property Inspector properties all come from the same data, so if it shows up in one it should always show up in the other. ActionScript code hints, on the other hand, do not require metadata to work correctly so you will always see the appropriate code hints in ActionScript depending on what scope you are in. Flex Builder uses the public/protected/private/static, etc. identifiers plus the current scope to figure out what ActionScript code hints to show.
 
The best practice for defining components that return information back to the main application is to design the component to dispatch an event that contains the return data. In that way, the main application can define an event listener to handle the event and take the appropriate action. You also use events in data binding. The following example uses the Bindable metadata tag to make useShortNames a bindable property. The implicit setter for the useShortNames property dispatches the change event that is used internally by the Flex framework to make data binding work.
 
Note: There is more to creating advanced components in ActionScript than this introductory Quick Start can describe. For a more comprehensive review of the subject, see Create Advanced Spark Visual Components in ActionScript.
 
 
Example
components/CountryComboBoxAS.as
 
// components/CountryComboBoxAS.as package components { import flash.events.Event; import mx.collections.ArrayList; import spark.components.ComboBox; public class CountryComboBoxAS extends ComboBox { private var countryArrayShort:ArrayList = new ArrayList(["US", "UK"]); private var countryArrayLong:ArrayList = new ArrayList(["United States", "United Kingdom"]); // Determines display state. The inspectable metadata tag // is used by Flash Builder. [Inspectable(defaultValue=true)] private var _useShortNames:Boolean = true; // Implicit setter public function set useShortNames (state:Boolean):void { // Call method to set the dataProvider based on the name length. _useShortNames = state; if (state) { this.dataProvider = countryArrayShort; } else { this.dataProvider = countryArrayLong; } // Dispatch an event when the value changes // (used in data binding.) dispatchEvent(new Event("changeUseShortNames")); } // Allow other components to bind to this property [Bindable(event="changeUseShortNames")] public function get useShortNames():Boolean { return _useShortNames; } } }
Main application MXML file
 
<?xml version="1.0" encoding="utf-8"?> <!-- ASComponentAdvanced.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:custom="components.*" width="260" height="200"> <s:Panel title="Advanced custom components"> <s:layout> <s:VerticalLayout paddingLeft="10" paddingRight="10" paddingTop="10" paddingBottom="10"/> </s:layout> <!-- Set a custom property on a custom component --> <custom:CountryComboBoxAS id="countries" useShortNames="false"/> <!-- Use data binding to display the latest state of the property. --> <s:Label text="useShortNames = {countries.useShortNames}"/> <s:controlBarContent> <s:Button label="Toggle Display" click="countries.useShortNames = !countries.useShortNames;"/> </s:controlBarContent> <s:controlBarLayout> <s:HorizontalLayout horizontalAlign="right" paddingTop="3" paddingBottom="3" paddingRight="3"/> </s:controlBarLayout> </s:Panel> </s:Application>

 
Creating composite ActionScript components

A composite ActionScript component is a component that contains multiple component definitions within it. They might be graphical assets or a combination of graphical assets and component classes. For example, you can create a component that includes a button and a text field, or a component that includes a button, a text field, and a validator.
 
To create a composite ActionScript component, you specify a container class as its superclass, and then instantiate other Flex components as properties of the class.
 
When you create composite components, you should instantiate the controls inside the component's class file. Assuming that some of these controls have graphical assets, you must plan the layout of the controls that you are including, and set properties such as default values in your class file. You must also ensure that you import all the necessary classes that the composite component uses.
 
Because the class extends one of the base classes, such as Container and has other controls, you must instantiate each of the controls as children of the custom component and arrange them on the screen. In the following example, you create a custom NumericDisplay component that creates an on-screen numeric keypad with a display, similar to the ones found in pocket calculators. The NumericDisplay component extends the VGoup class. In the NumericDisplay component, you create a TextInput control and a TileGroup container and add these components as children of your container. Because the VGoup class contains built-in logic to layout its children vertically, the TileGroup container appears under the TextInput control.
 
Properties and methods of the individual controls are not accessible from the MXML author's environment unless you design your class to allow this. In the following example, although the NumericDisplay component has a TextInput component, you cannot set the TextInput control's text property in the MXML tag because the NumericDisplay component does not directly extend the TextInput class. In other words, composite components use composition not inheritance to create specialized components.
 
 
Example
components/NumericDisplay.as
 
// components/NumericDisplay.as package components { import flash.events.Event; import flash.events.MouseEvent; import mx.events.FlexEvent; import spark.components.Button; import spark.components.TextInput; import spark.components.TileGroup; import spark.components.VGroup; public class NumericDisplay extends VGroup { private var display:TextInput; private var buttonsTile:TileGroup = new TileGroup(); // Expose the _numButtons property to the // visual design view in Flex Builder 2. [Inspectable(defaultValue=10)] private var _numButtons:uint = 10; public function NumericDisplay() { super(); addEventListener(FlexEvent.INITIALIZE, initializeHandler); } // numButtons is a public property that determines the // number of buttons that is displayed [Bindable(event="numButtonsChange")] public function get numButtons ():uint { return _numButtons; } public function set numButtons( value:uint ):void { _numButtons = value; dispatchEvent(new Event("numButtonsChange")); } // Gets called when the component has been initialized private function initializeHandler(event:FlexEvent):void { // Display the component paint(); } // Add the label of the clicked button to the display private function buttonClickHandler(event:MouseEvent):void { display.text += (event.target as Button).label; } // Display the component private function paint():void { // Create the number display display = new TextInput(); display.width=185; addElement(display); buttonsTile.requestedColumnCount = 3; addElement(buttonsTile); // Create the buttons and add them to // the container. for (var i:uint = 0; i < _numButtons; i++ ) { var currentButton:Button = new Button(); currentButton.label = i.toString(); currentButton.addEventListener(MouseEvent.CLICK, buttonClickHandler); buttonsTile.addElement(currentButton); } } } }
components/PaddedPanel.as
 
// components/PaddedPanel.as package components { import spark.components.Panel; public class PaddedPanel extends Panel { public function PaddedPanel() { // Call the constructor of the superclass. super(); // Set the border styles. setStyle("borderColor", "blue"); setStyle("dropShadowVisible", false); setStyle("cornerRadius", 20); } } }
Main application MXML file
 
<?xml version="1.0" encoding="utf-8"?> <!-- ASComponentComposite.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:custom="components.*" width="300" height="225"> <custom:PaddedPanel title="Composite Component"> <custom:NumericDisplay numButtons="10"/> </custom:PaddedPanel> </s:Application>

 
For more information