Flex Tip 2: Template Components

January 18, 2008 – 11:26 am

Here’s one of the old problems with the Flex Framework. Templates are not natively supported in Flex but there is one way you can do templates on custom components.
Lets say we want multiple panels inside the application that look the same, each one has a header with a title and a content area where inner components are placed. One would go ahead and create the header, title and all that is common in a panel each time a new panel is required but this would do a big problem when it comes to code update / change as it will require changing same thing in more places. Here comes the template idea. Instead of creating the header and title each time, why not create one template panel and then use it for all panels that are required.


Here is the panel that we want to use as a template:

First thing we do is create the component and add all that is required to have the template ready to customize. In our case we add the header and title. Note that the panel component its actually a Canvas, and save it as CustomPod.mxml in you’re src folder. The code looks like this:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#ffffff">
   
    <mx:Canvas width="100%" height="20"
        backgroundColor="#999999" />
   
    <mx:Label text="My Custom Panel" color="#ffffff" x="5" y="2" />
   
    <mx:Canvas id="podHolder" width="100%" top="20" bottom="0" />
   
</mx:Canvas>

Straight forward, it has a canvas that acts like a header, a Label for the title and most important a Canvas “podHolder” that will be the holder for all components inside the template. It has top=”20″ so all components added should not overlay with the header.

Now the next thing we add an array that will hold all components inside the template. When the component is initialized, we add all the components inside the array into the podHolder canvas, displaying them. The code now looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
    backgroundColor="#ffffff" creationComplete="addComponents()">
   
    <mx:Script>
    <![CDATA[
   
    private var _content:Array;
   
    public function set content( value:Array ):void
    {
        _content = value;
    }
   
    private function addComponents():void
    {
        podHolder.removeAllChildren();
       
        if( _content == null )
        {
            return;
        }
       
        for( var i:int = 0; i < _content.length; i++ )
        {
            podHolder.addChild( _content[ i ] );
        }
    }
   
    ]]>
   
    </mx:Script>
   
    <mx:Canvas width="100%" height="20"
        backgroundColor="#999999" />
   
    <mx:Label text="My Custom Panel" color="#ffffff" x="5" y="2" />
   
    <mx:Canvas id="podHolder" width="100%" top="20" bottom="0" />
   
</mx:Canvas>

We have the private _content Array with its setter content where we store all components. The creationComplete="addComponents()" code assures that when the component is created addComponents method is called, adding inside the podHolder all components. The if( _content == null ) assures that no error is received when no component is added inside a template component.

Add the best part is still to come. Add this code at line 5:

5
6
7
<mx:Metadata>
    [DefaultProperty( "content" )]
</mx:Metadata>

The DefaultProperty metadata tag changes the default property of the Flex component to a custom one, in our case “content”.
You can read more about DefaultProperty here.

And you’re final code should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
    backgroundColor="#ffffff" creationComplete="addComponents()">
   
    <mx:Metadata>
        [DefaultProperty( "content" )]
    </mx:Metadata>
   
    <mx:Script>
    <![CDATA[
   
    private var _content:Array;
   
    public function set content( value:Array ):void
    {
        _content = value;
    }
   
    private function addComponents():void
    {
        podHolder.removeAllChildren();
       
        if( _content == null )
        {
            return;
        }
       
        for( var i:int = 0; i < _content.length; i++ )
        {
            podHolder.addChild( _content[ i ] );
        }
    }
   
    ]]>
   
    </mx:Script>
   
    <mx:Canvas width="100%" height="20"
        backgroundColor="#999999" />
   
    <mx:Label text="My Custom Panel" color="#ffffff" x="5" y="2" />
   
    <mx:Canvas id="podHolder" width="100%" top="20" bottom="0" />
   
</mx:Canvas>

Now from the main class try adding a CustomPod component and add some more components in it, just like this:

1
2
3
<local:CustomPod x="20" y="20" width="200" height="160">
    <mx:Button label="My Button"/>
</local:CustomPod>

Running the example should show one CustomPod with a button inside, and the button should be right under the header. Note that adding x or y to the button will position the button using as reference the top-left point under the header, that is the top-left point of podHolder.

Try adding some more pods in the application and add some custom components in each of them.
HERE is the full source code for the example.

  1. 6 Responses to “Flex Tip 2: Template Components”

  2. Hi,

    When I use your sample in my application, it works OK when running.

    But in the design view, I can’t see the child added to the CustomPod.

    Is there a way to see the children added to the CustomPod even in Design view ? Not only when running…

    Thanks

    By Jerome on Apr 1, 2008

  3. If you are using DefaultProperty metadata inside the template component then there is no way you can see the children (other than those already existent inside component) at design view, because those will be added only at runtime.

    If you take out the DefaultProperty functionality but still have children defined inside the custom component then again you wont see the children added externally.

    The only way you can see the external children in design view is by removing DefaultProperty and also by not having any children inside the component. Basic idea is that Design View can’t render both internal and external defined childs, only the internal ones if present, if not, the external ones.

    By Adrian Aioanei on Apr 1, 2008

  4. So there’s no way to see both children ? too bad :(

    I’ve tried to create a component based on Button, with a DefaultProperty(”label”); and when defining the component and trying to set the property (with only text), and I can’t see the result in design view… is there a way ?

    Do you know what kind of code is executed in design mode ? updateDisplayList(), commitProperties(), set properties ? are all the functions executed like in execution, or are there any limits ? Is it possible to “debug” the design view with breakpoints or traces ? THe problem is that sometimes, I can’t understand why the design view doesn’t work, whereas my program runs fine.

    Thanks for your answers !

    By Jerome on Apr 1, 2008

  5. I don’t think there is any ActionScript code executed when in Design View.
    To be frank i don’t use design view at all, mainly because most of the components are loaded at runtime after some more processing. Secondly because it has these problems with previewing visual components and also because if you work with design view for adding components / settings properties it kinda mess up the code generated and i have to go back to code view and rearrange the whole thing.

    Didn’t encounter any situation in witch i would badly need design view ever since i’ve started using Flex Builder, so I’m using only code view.

    By Adrian Aioanei on Apr 1, 2008

  6. Well, ActionScript code is executed because if you write a graphics.drawRect() inside the updateDisplayList(), it is displayed… and if you access a null pointer in commitProperties(), the design view is broken (nothing at all is displayed). But I don’t know if everything is executed, and if so, in which order (for the set of the properties).

    The AddChild() seems to do something, because if you create a TextFormat and add it to a custom Label, you will see the text… but I can’t manage to add a real UIComponent.

    By Jerome on Apr 2, 2008

  7. I don’t think that “template” is the right way to call this.

    What you are trying to achieve here can be done in many ways withing the flex environment.
    For example extending a Container + using programmatic skin (if you want to control the looks nicely) and you could easily end up with the desired results and better performance.
    Adding 2 canvas’s into one is not that optimum for a component that you will use often…For example if you add 5 of these to the stage with relative positioning and percentage sizes and make a resize on the application the performance suffers a lot. You should consider some other options, more lighter for this, I guess…

    Cheers.

    By John on Apr 25, 2008

Post a Comment