Pages

July 31, 2009

Creating a Custom Visual Component

Posted by Jeremy Mitchell
The Flex SDK includes a compiler, debugger, profiler and a class library. The Flex Class Library is commonly referred to as the Flex Framework and includes hundreds of ActionScript classes and interfaces used to develop Flex applications.

The classes serve as the basis for Flex's visual and non-visual components:

Visual component classes:Non-visual component classes:Shipped with the Flex SDK, most of these classes produce "built-in" components available for use immediately. Utilization only requires instantiation and an understanding of each component's API (the accessible properties and methods and the applicable events, styles and effects). API details are found in the Flex Language Reference and exposed through Flex Builder's code completion (hit control-space bar while in an MXML tag or after a dot in ActionScript when using dot notation to see the accessible members of the class).

Note: Some of these classes are created for you at application start up (i.e. Manager classes) or do not require instantiation for use (i.e. Utility classes).
The following example demonstrates utilization of a built-in Button component by way of a public property (label) and style (color) while registering a handler on the buttonDown event (aka event listener).

<mx:Button id="myButton" label="Hello" color="0xFF0000" buttonDown="doSomething(event)" />
90% of your Flex development will require built-in components. The rest will require custom components.

Note: The remainder of this entry will focus on visual components. Visual components eligible for participation in the Flex Framework descend from the UIComponent class which descends from the DisplayObject class. The DisplayObject class is the base class for all objects that can be placed on the display list.
A custom visual component is required in the following scenarios:
  1. You want to customize the functionality of a built-in visual component to meet your specific requirements
  2. Nothing in the Flex Class Library comes close to the functionality you want so you need to build a custom visual component from scratch
  3. You want to package multiple components into one custom visual component for the sake of modularity and/or reusability
We'll cover each scenario below:

Scenario #1: You want to customize the functionality of a built-in visual component to meet your specific requirements

In many instances, built-in components satisfy most of your needs. For example, you may need a control to render data as a vertical list while capturing the value of an HSlider's value property. The HSlider's value property will help determine the font color of each item in the vertical list.

Note: This custom component was built in a previous blog entry entitled Flex Examples - Item Renderers in Practice to demonstrate a technique used to facilitate real-time communication between a list-based control's item renderer and an external component.
Extending the capabilities of a built-in component

Since Flex already provides a control capable of rendering data as a vertical list - List, we'll start with that and extend the functionality to include our new property. This can be done in MXML or ActionScript.

Note: Extending an existing class for the purpose of customization follows a fundamental object-oriented design principle - The Open-Closed Principle. This principle says that classes should be open for extension, but closed for modification.
First, navigate to the desired package (directory) in Flex Builder where you'd like your custom component to reside.



If using MXML...

From the context menu (right-click), select File > New MXML Component. Set the 'Based on' field to 'List' and give your MXML component a name starting with a capital letter (to distinguish classes from objects, classes start with a capital letter whereas objects (instances of a class) do not).

The content of your new MXML component is as follows:

<?xml version="1.0" encoding="utf-8"?>
<mx:List xmlns:mx="http://www.adobe.com/2006/mxml">
    
</mx:List>
We're using a List as our base class (so List is our root tag), and we're going to extend its capabilities by adding any properties and methods we see fit. Here we will add a new property to the List control exposed publicly through a getter and setter.

<?xml version="1.0" encoding="utf-8"?>
<mx:List xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:Script>
        <![CDATA[
            private var _newProperty:Number;
            
            // property accessible via a public getter & setter
            // make it bindable if you anticipate its use as the source of a data binding
            [Bindable]
            public function get newProperty():Number{
                return _newProperty;
            }
            
            public function set newProperty(value:Number):void {
                _newProperty = value;
            }
        ]]>
    </mx:Script>
    
</mx:List>
If using ActionScript...

Again, from the context menu select File > New ActionScript Class. Set the 'Superclass' field to mx.controls.List (or hit browse and type in List) and give your ActionScript class a name starting with a capital letter. Flex Builder will create your class with the following contents:

package comps
{
    import mx.controls.List;

    public class MyList extends List
    {
        public function MyList()
        {
            super();
        }
        
    }
}
List is the base class we'd like to extend. Our new class (MyList) inherits the capabilities of a basic List control by extending it while providing an opportunity to customize the properties and methods to meet our needs.

package comps
{
    import mx.controls.List;

    public class MyList extends List
    {
        private var _newProperty:Number; // new property
        
        public function MyList()
        {
            super();
        }

        // getter and setter for our new property marked as bindable
        [Bindable]       
        public function get newProperty():Number {
            return _newProperty;
        }
        
        public function set newProperty(value:Number):void {
            _newProperty= value;
        }
        
    }
}
Now we can create an instance of our new, customized list and utilize our new property.

<comps:MyList newProperty="2" />
Or, we can use it as we intended based on our previous requirements.

<!-- Bind to the HSlider's value property -->
<comps:MyList newProperty="{myHSlider.value}" />
Overriding the behavior of a built-in component

In addition to extending the capabilities of a built-in component, we can change the way it currently behaves.

For example, a Label component is a perfect candidate for an Item Renderer. However, we may want to change the Label's text color when our Label is used as an Item Renderer and certain conditions apply.

Looking closely at the Label class' source file (ctrl-click a Label tag in Flex Builder) and knowing a thing or two about Item Renderers, we settle on the set data method. This method passes the data for an item to the Item Renderer.

Note: The Label class is eligible for item rendering based on the implementation of the IDataRenderer interface which enforces implementation of a data property getter and setter method. Without these methods, there would be no way to pass an item's data to the Label instance.
We would like to add some additional logic to the set method to meet our requirements, therefore, we will extend the Label class and override this existing method.

<!--- MyLabel.mxml used as an Item Renderer --->

<?xml version="1.0" encoding="utf-8"?>
<mx:Label xmlns:mx="http://www.adobe.com/2006/mxml"
    text="{data.name + ' (' + data.age + ')'}">
    <mx:Script>
        <![CDATA[            
            override public function set data(value:Object):void {
                // this is critical since we only want to inject additional logic
                super.data = value; 
                // new behavior
                var minAge:Number = 40;
                if (data.age < minAge) {
                    setStyle("color",0xFF0000);
                else {
                    setStyle("color",0x000000);
                }
            }
        ]]>
    </mx:Script>
    
</mx:Label>
Note: In most cases, it is critical that we preserve the base class' implementation of the method we wish to override. Therefore, in this example, we need to call super.data.
9 times out of 10, you can enhance a built-in component by extending or modifying its capabilities to fit your needs. Extend an existing class, add a new property or method, override an existing method, or even add new styles, effects or events....

Scenario #2: Nothing in the Flex Class Library comes close to the functionality you want so you need to build a custom visual component from scratch

On occasion, you may have the need to create a component from scratch. This approach is warranted when nothing is gained by extending the functionality of an existing, built-in component (container or control).

For example, you may choose to extend a VBox when you wish to "contain" child components while inheriting the built-in top-to-bottom layout capabilities. However, if layout for your component is dynamic and determined by a style setting, the layout rules of a VBox may not always apply.

As mentioned previously, all visual components eligible for participation in the Flex Framework descend from the UIComponent class. The UIComponent class implements 5 methods required of a visual component. These methods are known as the component lifecycle methods and they include:
  1. createChildren
  2. commitProperties
  3. measure
  4. layoutChrome
  5. updateDisplayList
The Flex Framework depends on these methods and every visual Flex component implements them directly or indirectly (through class inheritance).

Each method plays a particular role and makes a component unique to itself. For components added to the display list, these methods are called by the Flex Framework on application startup and may be called during the lifetime of a component as needed.

createChildren()

Implementation of the createChildren method is required if the visual component is a composite component. Composite components contain child objects. The createChildren method is only called once when the component is added to the display list.

commitProperties()

By coordinating property changes and ensuring expensive calculations are not performed unnecessarily, the commitProperties method can help developers achieve optimal application performance.

measure()

The logic required to determine the custom component's default size and, optionally, default minimum size is contained within the measure method.

layoutChrome()

Container components utilize the layoutChrome method to define border area.

updateDisplayList()

The sizing and positioning of child components is calculated within the updateDisplayList method.

For more information, Adobe has provided a more detailed description of each component lifecycle method.

The following example demonstrates the use of a custom visual component. This component consists of 2 child objects - a TextArea and a Button. The TextArea can be enabled/disabled programatically or by clicking the button. The layout can also be changed at runtime using the radio buttons.

View source is also enabled to provide insight into the structure of the custom component.


Scenario #3: You want to package multiple components into one custom visual component for the sake of modularity and/or reusability

Rather than creating an application consisting of one monolithic file, it is common practice to break an application down into pieces. This technique promotes code reuse and better organization.

Creating custom components to encapsulate distinct application views

Most Flex applications contain multiple views. A view consists of the components required to satisfy a particular task or objective. Each view could be constructed quite easily in the main application file. For example, the following application consist of 2 views - a Login View and a Search View.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="vertical">
    
    <mx:Script>
        <![CDATA[
            private function login(event:MouseEvent):void {
                vs.selectedIndex = 1;
            }
        ]]>
    </mx:Script>

    <mx:ViewStack id="vs"> <!-- navigation containers typically manage views -->
    
        <!-- Login View -->
        
        <mx:Panel title="Login">
            <mx:Form>
                <mx:FormItem label="Username">
                    <mx:TextInput/>
                </mx:FormItem>
                <mx:FormItem label="Password">
                    <mx:TextInput/>
                </mx:FormItem>
            </mx:Form>
            <mx:ControlBar>
                <mx:Button label="Login" click="login(event)"/>
            </mx:ControlBar>
        </mx:Panel>
        
        <!-- Search View -->
        
        <mx:VBox>
            <mx:HBox>
                <mx:TextInput/>
                <mx:Button label="Search"/>
            </mx:HBox>
            <mx:DataGrid/>
        </mx:VBox>
        
    </mx:>
    
</mx:Application>
However, as the application inevitably grows, so does the amount of code in the main application file. Before you know it, it is hard to tell where one view ends and another one begins.

To prevent duplicate code and unwieldy file sizes, consider breaking your application into pieces designed to perform a singular task (for example, "login" is a task represented by a unique view and warrants its own component). This is done by creating custom components like we did before.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:views="views.*"
    layout="vertical">
    
    <mx:Script>
        <![CDATA[
            private function login(event:Event):void {
                vs.selectedIndex = 1// navigate to the search view
            }
        ]]>
    </mx:Script>

    <mx:ViewStack id="vs">
    
        <!-- Login View -->
        
        <views:LoginView loggedIn="login(event)"/> <!-- custom component -->
        
        <!-- Search View -->
        
        <views:SearchView/> <!-- custom component -->
        
    </mx:ViewStack>
    
</mx:Application>

<!-- LoginView.mxml extends Panel -->

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml">

    <!-- this component dispatches a loggedin event -->
    <!-- that you can now listen for in MXML or ActionScript -->
    <mx:Metadata>
        [Event(name="loggedIn", type="flash.events.Event")]
    </mx:Metadata>

    <mx:Form>
        <mx:FormItem label="Username">
            <mx:TextInput/>
        </mx:FormItem>
        <mx:FormItem label="Password">
            <mx:TextInput/>
        </mx:FormItem>
    </mx:Form>
    <mx:ControlBar>
        <mx:Button label="Login" click="dispatchEvent(new Event('loggedIn'))"/>
    </mx:ControlBar>
    
</mx:Panel>

<!-- SearchView.mxml extends VBox -->

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:HBox>
        <mx:TextInput/>
        <mx:Button label="Search"/>
    </mx:HBox>
    <mx:DataGrid/>
</mx:VBox>
This becomes essential as your views become more complex (i.e. more internal (child) components, view states, events, etc).

Note: Because our Login view was packaged into a LoginView component, access to the application's login method was no longer available. Communication between components should always be accomplished using events, therefore, the LoginView component now dispatches an event. A listener is created in the main application file to handle the event.
<views:LoginView loggedIn="login(event)"/>
In this example, we've created two custom components (LoginView and SearchView) for the purpose of packaging multiple components together. We didn't reduce the amout of code but we did reduce clutter, and if we want, we can reuse these components without copying/pasting code.

Whether you're looking to extend the capabilities of an existing component, create a component from scratch or group components into views, Flex provides the mechanism to do so. Have fun!

6 comments:

Anonymous said...

Its excellent.
it provides all the basic knowledge about custom components

raj said...

its the best way of explaining custom components. For beginners like me it helps alot. And all of your other posts are also very nice. keep up the good work

Anonymous said...

good one

Anonymous said...

Its really very helpfull...I had been doing this from long time ....now gotta know this is also called as Custom Component ...thanks :)

Amar Shukla said...

Scenario #2 is the main scenario which is rarely explained in a simplistic way on the net but you also didn't try to explain it. You should have created a custom component which extends something like Sprite or UIComponent. Anyways any contribution is appreciable.

Pros said...

I was very pleased to find this site.I wanted to thank you for this great read!! I definitely enjoying every little bit of it and I have you bookmarked to check out new stuff you post.