Create custom reusable Flex’s components with Actionscript

I’ve played for a while with Flex’s library and now I’m experimenting my own custom components and I would like to share them and the knowledge necessary to build personal, reusable cool components.

Fundamentally there are two ways to realize custom Flex’s components: one (and the easiest) is to create an MXML file, the second is to create an Actionscript class.


MXML component

The MXML file will contains a components like a Canvas (which is an excellent class to start to build on) as the root node, instead of the classic Application and it will be filled by others several small components already available in Flex (Label, TextField, Button…), realizing a new complex object which can will be used inside MXML Applications by simply writing a tag with the same name of the MXML file. Another step required is to create an XML name space for the custom component created, this can be accomplished simply by insert an xmlns attribute inside the Application node, similarly to the default mx name space from adobe (xmlns:mx=”http://www.adobe.com/2006/mxml“), it must report a name after the colons and a path to the package containing the component. This is an example of a name space declaration for my own custom tag:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:dz="com.daveoncode.controls.*" layout="vertical">

It creates a name space called “dz” for all the classes (the “*” wildcard means “all”) inside the package “com/daveoncode/controls”. This is instead how a simple MXML based component could be (this is only a basic example, a such component is pretty useless):

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" title="Login" width="400" height="300">
    <mx:Form>

        <mx:FormItem label="UserName:">
            <mx:TextInput width="200" />
        </mx:FormItem>

        <mx:FormItem label="Password:">
            <mx:TextInput width="200" displayAsPassword="true" />
        </mx:FormItem>

        <mx:FormItem>
            <mx:Button label="Login" />
        </mx:FormItem>

    </mx:Form>
</mx:Panel>

Now, supposing that we save the example above as LoginPanel.mxml, under “com/oursite/components”, we can use it in a mxml Application in this way:

<mx:Application xmlns:cc="com.oursite.components.*" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <cc:LoginPanel />
...

This is pretty cool, because lets us separate defined areas of a GUI (like a login panel) and reuse them everywhere with no efforts… although this is an useful thing, is not so much exciting as creating a real custom component (something that is not made of other existent Flex components), whit its own properties, methods and style support!
To create such object, we should write it entirely in Actionscript.

Actionscript component

We can start from extending an existent Flex class, in order to inherit all the necessary methods to interact properly with the framework. The most important thing to know, is how to handle our class configuration, in fact in Flex we can’t put configuration logic inside constructor, because it will be automatically called by Flex every time a tag with the components name is encountered and there is no way to pass arguments to the constructor from the MXML tag, so such approach:

public function MyCustomComponent(param1:String, param2:Number) {

    this.param1 = param1;
    this.param2 = param2;

}

cant’ be used! We have instead to write getter and setter which will be automatically called by Flex when required. This kind of getter/setter must use Actionscript’s get and set keywords, in order to get/set a property. So, we have to forget classic getter/setter methods like:

public function setMyParam(value:String):void {
    myParam = value;
}

public function getMyParam():String {
    return myParam;
}

and adopt these:

public function set myParam(value:String):void {
    myParam = value;
}

public function get myParam():String {
    return myParam;
}

The get/set key is a really cool feature of Actionscript language, because it lets to set an object property in a declarative manner (myObject.property = X), without renunce to encapsulation. In this way when in MXML file we will declare tag’s attributes:

<cc:MyCustomComponent myParam="Hello!" /> 

Those setters will be used… but when exactly? Good question, another thing to understand, in order to realize custom and good components, is in fact the creational procedure adopted by the framework, which is divisible in these steps (in execution order):

  1. Construction: in this step the component is instantiated and code inside the constructor is executed (typically the code will consist of a calling to super() and optionally event listeners configuration and few more)
  2. Configuration: getters methods are invoked by Flex to configure class’ instance
  3. Attachment: this step occours only when the component is added to the display list. This is an automatic task if the component is called from an MXML (with the relative tag) and occours through Actionscript when addChild() or addChildAt() are invoked
  4. Initialization: this is the most intense phase, because during initialization a lot of tasks are performed and several methods invoked. The first thing that happens is the dispatching of preinitialize event (FlexEvent.PREINITIALIZE), then the protected method createChildren() is called (we have to override this method in order to populate our custom components with desired content), after its execution another event is dispatched, this time is the initialize (FlexEvent.INITIALIZE). After these, a series of invalidations and validations operations occours and finally creationcomplete is dispatched (FlexEvent.CREATION_COMPLETE)

I realized a custom component called StarPicker, it is a control which can be used to rate objects (songs,  movies, books…) by selecting (with a click) among N stars (N is one of the several parameters that can be configurated by the user, such color, size, space between stars and so on),  it is bindable (we can bind another control to StarPicker’s value) and finally it support CSS styles… yes, with Flex we can create extreme custom components that will support custom user-defined properties (such “power”, “coolness”…). Although I’m quite embarrassed (because I’m not already so confident with Flex), I’m going to show you how I realized my first Flex component.

My first step was create an Actionscript class (under my package “com.daveoncode.controls”) which extends Canvas, then I defined several public constants for default settings, by using the Flex naming convenction “DEFAULT_MY_SETTING_NAME”, several private variable to handle settings values and as many private variables to track settings changes. So, before the constructor I’ve such stuff (every prameter has 3 types of variables/constants related to it):

public static const DEFAULT_STARS:uint = 5;
// ...more constants

private var _stars:uint;
// ...more vars

private var starsDefined:Boolean;
// ...more booleans

The constructor contains a call to super and some listeners creation (and I enable the doubleclick over the component):

public function StarPicker() {

    super();

    // This component allows double click in order to deselect stars
    this.doubleClickEnabled = true;

    // Listens for preinitialize event
    this.addEventListener(FlexEvent.PREINITIALIZE, this.preInitializeHandler);

    // Listens for the click event over the stars
    this.addEventListener(MouseEvent.CLICK, this.clickHandler);

    // Listens for the double click event over the stars
    this.addEventListener(MouseEvent.DOUBLE_CLICK, this.doubleClickHandler);

}

The only thing I want to analize in the code above is the listener for FlexEvent.PREINITIALIZE… why I used this listener? The answer is: I use it to set default values if they are not provided by the user (with user I mean who use the component in the MXML file, not the Application’s user), because I want to handle default settings only after all the setters are called by Flex in order to set properly the boolean flags “myparamDefined”, which I use to grant the right precedence between style and inline tag’s attribute. If a flag is setted (myparamDefined == true) then when CSS assign a value to the same parameter this is not used, because inline attribute have the precedence and win. So the handler has several ternary operators like:

this._stars = this._stars == 0 ? StarPicker.DEFAULT_STARS : this._stars;

In order to populate the picker with stars I override  createChildren() method, into which I use addChild() to insert N Star object (which is a Class in the same package, which basically draws a star shape and has methods to change colors and size), I also override  measure() method which is used by Flex to know the exact size of the components (typycally when it must calculates clipping and resizing):

override protected function measure():void {

    super.measure();

    // StarPicker's height will be the same as the stars
    this.measuredHeight = this.measuredMinHeight = this._starSize + this._starBorderSize;

    // StarPicker's width will be the same as the sum of the stars width + space
    this.measuredWidth = this.measuredMinWidth = (this._starSize + this._starSpace) * this._stars;

}

In practice I’m telling Flex that my component should be never clipped and is larger as the sum of the stars width it contains. Oh… I also override the styleChanged() method, which is called by Flex every time a component’s style parameter changes:

override public function styleChanged(styleProp:String):void {

    super.styleChanged(styleProp);

    this._stars = this.stars;
    // ... other (re)settings

}

And style metatags? Yes, I putted them before class declaration:

[Style(name="stars", type="uint", inherit="no")]
// ... more styles

With style metatag we can declare the name of styles properties that our components will suport, so with the previous declaration will be possible to create CSS like:

.myCustomComponent {
    stars: 4;
}

Ok, I wrote too much, this is an example of the custom component that I realized (you can download, use and view source code by selecting “Flex components” on blog’s menu):

The example above is not a screenshot… you can test it ;-)

  • Sanoran Triamesh

    Hi,
    Your article is very helpful.
    When I tried to create a custom component, I discovered that, with SDK 3.4, the configuration (set methods) is happening after the component initialization (e.g. createChildren)
    Has this changed? Is this order documented anywhere?
    Thanks
    ST

  • Sreenivasa Rao

    hello,

    It is very good explanation. Can I have the source code of this?

    Regards,
    Srini

  • http://www.daveoncode.com Davide Zanotti

    Sorry guys, I lost the source code of the example :(

  • http://Website Zee

    If you could correct the english mistakes in this article, this would have become so easy to understand. Anyways, good effort.

  • http://www.riaxe.com Susrut Mishra

    Great example of overriding inbuilt properties. Thanks for sharing.

  • suchand

    I have a action script project for working al alternativa3d demo. i am trying to make that as3 file to a cutom component to blend it with a mxml file.
    followign is my as3 file, pls help me out.
    package
    { import alternativa.engine3d.controllers.SimpleObjectController;
    import alternativa.engine3d.core.Camera3D;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.core.Resource;
    import alternativa.engine3d.core.View;
    import alternativa.engine3d.lights.AmbientLight;
    import alternativa.engine3d.lights.DirectionalLight;
    import alternativa.engine3d.loaders.ParserCollada;
    import alternativa.engine3d.loaders.ParserMaterial;
    import alternativa.engine3d.loaders.TexturesLoader;
    import alternativa.engine3d.materials.StandardMaterial;
    import alternativa.engine3d.objects.Mesh;
    import alternativa.engine3d.objects.Surface;
    import alternativa.engine3d.resources.ExternalTextureResource;
    import flash.display.Sprite;
    import flash.display.Stage3D;
    import flash.display3D.Context3D;
    import flash.events.Event;
    [SWF(width = 800, height = 800, backgroundColor = 0x303030, frameRate = 100)]
    public class GorgylDemo extends Sprite
    {
    [Embed(“resources/123.DAE”, mimeType=”application/octet-stream”)]
    private static const ModelClass:Class;
    private var stage3D:Stage3D;
    private var rootContainer:Object3D = new Object3D();
    private var controller:SimpleObjectController;
    private var camera:Camera3D;
    private var dLight:DirectionalLight;
    private var ambient:AmbientLight;
    private var modelContainer:Object3D;
    public function GorgylDemo()
    {
    camera = new Camera3D(10, 100000);
    camera.view = new View(stage.stageWidth, stage.stageHeight, false, stage.color, 1, 4);
    addChild(camera.view);
    camera.y = -100;
    controller = new SimpleObjectController(stage, camera, 100);
    controller.lookAtXYZ(0,0,-50);
    rootContainer.addChild(camera);
    addLights();
    addModel();
    stage3D = stage.stage3Ds[0];
    stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
    stage3D.requestContext3D();
    }
    private function addLights():void
    {
    ambient = new AmbientLight(0xc0d0f0);
    rootContainer.addChild(ambient);
    dLight = new DirectionalLight(0xFFFFFF);
    dLight.intensity = 1.5;
    dLight.y = -500;
    dLight.z = 500;
    dLight.lookAt(0, 0, 0);
    rootContainer.addChild(dLight);
    }
    private function addModel():void
    {
    var parser:ParserCollada = new ParserCollada();
    parser.parse(XML(new ModelClass()), “resources/image/”, true);
    var object:Object3D; // Prepare objects materials
    /*for each (object in parser.objects)
    {
    var mesh:Mesh = object as Mesh;
    if (mesh != null)
    {
    for (var i:int = 0; i < mesh.numSurfaces; i++)
    {
    var surface:Surface = mesh.getSurface(i);
    if (surface.material != null)
    {
    var material:ParserMaterial = ParserMaterial(surface.material);
    surface.material = new StandardMaterial(material.textures["diffuse"], material.textures["bump"], material.textures["specular"], null, material.textures["transparent"]);
    }
    }
    }
    } */
    modelContainer = new Object3D();
    rootContainer.addChild(modelContainer);
    for each (object in parser.hierarchy)
    {
    modelContainer.addChild(object);
    }
    }
    private function onContextCreate(event:Event):void
    {
    // Upload resources
    var context3D:Context3D = stage3D.context3D;
    var textures:Vector. = new Vector.();
    for each (var resource:Resource in rootContainer.getResources(true))
    {
    if (resource is ExternalTextureResource)
    {
    textures.push(resource);
    }
    else
    {
    resource.upload(context3D);
    }
    }
    var texturesLoader:TexturesLoader = new TexturesLoader(stage3D.context3D);
    texturesLoader.loadResources(textures);
    addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }
    private function onEnterFrame(e:Event = null):void
    {
    modelContainer.rotationZ += .01;
    modelContainer.z = 30 * Math.sin (modelContainer.rotationZ * 3);
    dLight.rotationZ -= 0.01;
    var kappa:Number = .5 * ( Math.sin(2 * dLight.rotationZ) + 1);
    dLight.color = 0xff0000 + ((kappa * 0xff) << 8) + (kappa * 0xff);
    dLight.intensity = .5 + kappa ;
    controller.update();
    camera.render(stage3D);
    }
    }
    }