Turning Components From MXML To Actionscript – Part 3

We ended the last section at an intermediate level of difficulty, by creating our own custom component in MXML with custom events, binding, and metadata. Now we are going into the advanced section. We will turn all of that MXML into Actionscript.

But first, let’s chat. Some of the reasons for making this laborious change is because we want to use asdocs on our components, we want to have smaller components that are as light as possible, we want to remove some of the overhead of binding, and honestly we are perfectionists. There are other reasons I am sure, but those are my reasons. When this blog post is complete we will have our MXML component’s Actionscript twin.

Let us start by creating our Actionscript file and then breaking down the MXML component architecture.

First, create the component.
customcomponent6
And set the properties in the new Actionscript class.
customcomponent7

Our Actionscript component is simple right now, but is about to become big fast. The initial state should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.unitedmindset.components
{
    import mx.containers.Panel;

    public class ComponentAS extends Panel
    {
        public function ComponentAS()
        {
            super();
        }
       
    }
}

Now we need to start transferring code from the MXML component to the Actionscript component. To do this we have to have a working understanding of the component lifecycle in Flex. When a component is added to a view the component goes through the following set up (in order).

1
2
3
4
5
6
Constructor called
Sets Parent
Computer Styles Settings
Preintialization event
createChildren called
calls invalidateProperties, invalidateSize, invalidateDisplayList to trigger commitProperties, measure, updateDisplayList later

The component stops here, until the addChild function is called on the component and then the remainder of the component lifecycle is invoked, in the following order:

1
2
3
4
5
6
7
8
9
10
Initialize event
childAdd event on Parent
Initialize event on Parent
Render event
calls commitProperties, measure, layoutChrome, updateDisplayList
updateComplete event
calls commitProperties, measure, layoutChrome, updateDisplayList if the previous calls invoked the invalidateProperties, invalidateSize, invalidateDisplayList functions
visible set to true
CreationComplete event
updateComplete event is fired whenever a change is made to the component

With this knowledge we know the order to move information from the MXML component to the Actionscript Component.

Step 1:
The first section in an MXML component is equivalent to setting parameters in the constructor of the Actionscript (AS) component. The first block that I am referencing is the following section:

1
2
3
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="vertical"
    width="100%" height="100%">

Taking that information to the AS Component we get the following:

1
2
3
4
5
6
7
8
9
10
11
12
//--------------------------
//
// Constructor
//
//--------------------------
public function ComponentAS()
{
    super();
    this.layout = "vertical";
    this.percentHeight = 100;
    this.percentWidth = 100;
}

Step 2:
Next let’s focus on creating the children of our component. You will remember that the MXML looked like this:

1
2
3
4
5
6
7
8
    <mx:DataGrid width="100%" height="100%" columns="{this.columns}" id="dataGrid"/>
    <mx:ControlBar>
        <mx:Button label="{this.label1}" click="this._button1Click_Handler(event)" id="button1"/>
        <mx:Spacer width="100%"/>
        <mx:Button label="{this.label2}" click="this._button2Click_Handler(event)" id="button2"/>
        <mx:Spacer width="100%"/>
        <mx:Button label="{this.label3}" click="this._button3Click_Handler(event)" id="button3"/>
    </mx:ControlBar>

In Actionscript we need to take some steps to ensure that our code checks for errors, checks that MXML provides for us. You will see these checks in the following code. One of the most important checks to preform is that the object you are creating is null before you create it. We perform this check so if another coder extends your component and accidentally calls the super.createChildren() twice, you won’t will end up with double components being added to your component. Also, remember to always call the super class before doing anything.
One thing you will notice is that the controlBar already exists in the panel component, so we just need to initialize it, not add in another.

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//--------------------------
//
// Children Components
//
//--------------------------
public var dataGrid:DataGrid;
public var button1:Button;
public var button2:Button;
public var button3:Button;
private var _spacer1:Spacer;
private var _spacer2:Spacer;

//--------------------------
//
// Overrides
//
//--------------------------
override protected function createChildren():void
{
    // call super class createChildren()
    super.createChildren();
    // if the dataGrid doesn't exist, create dataGrid
    if(!dataGrid){
        dataGrid = new DataGrid();
        dataGrid.percentHeight = 100;
        dataGrid.percentWidth = 100;
        this.addChild(dataGrid);
    }
    // if controlBar doesn't exist, create the controlBar
    if(!this.controlBar){
        this.controlBar = new ControlBar();
        //add control bar
        this.addChildAt(ControlBar(this.controlBar),this.numChildren);
        this.createComponentsFromDescriptors();
    }
    // if the button doesn't exist, create button
    if(!button1){
        button1 = new Button();
        ControlBar(this.controlBar).addChild(button1);
    }
    // if the spacer doesn't exist, create spacer
    if(!_spacer1){
        _spacer1 = new Spacer();
        _spacer1.percentWidth = 100;
        ControlBar(this.controlBar).addChild(_spacer1);
    }
    // if the button doesn't exist, create button
    if(!button2){
        button2 = new Button();
        ControlBar(this.controlBar).addChild(button2);
    }
    // if the spacer doesn't exist, create spacer
    if(!_spacer2){
        _spacer2 = new Spacer();
        _spacer2.percentWidth = 100;
        ControlBar(this.controlBar).addChild(_spacer2);
    }
    // if the button doesn't exist, create button
    if(!button3){
        button3 = new Button();
        ControlBar(this.controlBar).addChild(button3);
    }
}

So far so good, if you check the code that we have now everything appears to be working just the way we want. Now it is time to start adding in the properties, events, and meta data.

Step 3:
Now we create your properties and override the commitProperties protected function to update the component as a change is made. We will use one general theory when making this refactor, the theory is that this…

1
2
[Bindable]
public var label1:String = "Button";

…becomes this:

1
2
3
4
5
6
7
8
9
10
11
12
private var _label1:String;
private var _label1Changed:Boolean = false;
public function get label1():String
{
    return this._label1;
}
public function set label1(value:String):void
{
    this._label1 = value;
    this._label1Changed = true;
    this.invalidateProperties();
}

… with this added into the commitProperties function:

1
2
3
4
5
6
// label 1
if(_label1Changed)
{
    _label1Changed = false;
    this.button1.label = label1;
}

We need to go back to where our button is being created and add the label to the button on instantiation.

1
2
3
4
5
6
// if the button doesn't exist, create button
if(!button1){
    button1 = new Button();
    button1.label = this.label1;
    ControlBar(this.controlBar).addChild(button1);
}

Step 4:
Now we are going to add in the custom events and functions that we had from the MXML component, this part should just be a copy and paste – we will also move over the defaultProperty MetaData tag with the event tags into the head of your Actionscript file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.unitedmindset.components
{
    import flash.events.Event;
    import flash.events.MouseEvent;
   
    import mx.containers.ControlBar;
    import mx.containers.Panel;
    import mx.controls.Button;
    import mx.controls.DataGrid;
    import mx.controls.Spacer;
   
    [DefaultProperty("columns")]
    [Event(type="flash.events.Event",name="button1Click")]
    [Event(type="flash.events.Event",name="button2Click")]
    [Event(type="flash.events.Event",name="button3Click")]

    public class ComponentAS extends Panel
    {...

Add in the listener methods….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//--------------------------
//
// listeners
//
//--------------------------
private function _button1Click_Handler(event:MouseEvent):void
{
    dispatchEvent(new Event("button1Click"));
}

private function _button2Click_Handler(event:MouseEvent):void
{
    dispatchEvent(new Event("button2Click"));
}
       
private function _button3Click_Handler(event:MouseEvent):void
{
    dispatchEvent(new Event("button3Click"));
}

And don’t forget to add the event listeners to the buttons…

1
2
3
4
5
6
7
// if the button doesn't exist, create button
if(!button1){
    button1 = new Button();
    button1.label = this.label1;
    button1.addEventListener(MouseEvent.CLICK,this._button1Click_Handler);
    ControlBar(this.controlBar).addChild(button1);
}

Step 5:
Though this example does not require any layout help, you would use the updateDisplayList at this point to be able layout our component.

Step X:
Any steps beyond this point are up to your and your component. Do you need to add more methods? Listeners? Styles? If you do now would be the time to add in this code, but for my example the Actionscript code does everything that my MXML code does.

The final Actionscript component is below with full asdocs.

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package com.unitedmindset.components
{
    import flash.events.Event;
    import flash.events.MouseEvent;
   
    import mx.containers.ControlBar;
    import mx.containers.Panel;
    import mx.controls.Button;
    import mx.controls.DataGrid;
    import mx.controls.Spacer;
   
    /**
    * Default Property sets the columns
    */
 
    [DefaultProperty("columns")]
    /**
    * Event Triggered when the 1st Button is clicked
    */
 
    [Event(type="flash.events.Event",name="button1Click")]
    /**
    * Event Triggered when the 2nd Button is clicked
    */
 
    [Event(type="flash.events.Event",name="button2Click")]
    /**
    * Event Triggered when the 3rd Button is clicked
    */
 
    [Event(type="flash.events.Event",name="button3Click")]
   
   
    /**
     * Custom Panel with 3 buttons and a DataGrid.
     *
     * <p>
     * Useage:
     * </p>
     * <pre>
     * <components:ComponentAS label1="Label 1" label2="Label 2" label3="Label 3" title="AS Component" status="working">
     *  <mx:DataGridColumn headerText="hello"/>
     *  <mx:DataGridColumn headerText="world"/>
     * </components:ComponentAS>
     * </pre>
     * @author Administrator
     *
     */

    public class ComponentAS extends Panel
    {
        //--------------------------
        //
        // Constructor
        //
        //--------------------------
        public function ComponentAS()
        {
            super();
            this.layout = "vertical";
            this.percentHeight = 100;
            this.percentWidth = 100;
        }
       
        //--------------------------
        //
        // Children Components
        //
        //--------------------------
        public var dataGrid:DataGrid;
        public var button1:Button;
        public var button2:Button;
        public var button3:Button;
        private var _spacer1:Spacer;
        private var _spacer2:Spacer;
       
        //--------------------------
        //
        // createChildren
        //
        //--------------------------
        override protected function createChildren():void
        {
            // always call super class createChildren()
            super.createChildren();
            // if the dataGrid doesn't exist, create dataGrid
            if(!dataGrid){
                dataGrid = new DataGrid();
                dataGrid.percentHeight = 100;
                dataGrid.percentWidth = 100;
                this.addChild(dataGrid);
            }
            // if controlBar doesn't exist, create the controlBar
            if(!this.controlBar){
                this.controlBar = new ControlBar();
                //add control bar
                this.addChildAt(ControlBar(this.controlBar),this.numChildren);
                this.createComponentsFromDescriptors();
            }
            // if the button doesn't exist, create button
            if(!button1){
                button1 = new Button();
                button1.label = this.label1;
                button1.addEventListener(MouseEvent.CLICK,this._button1Click_Handler);
                ControlBar(this.controlBar).addChild(button1);
            }
            // if the spacer doesn't exist, create spacer
            if(!_spacer1){
                _spacer1 = new Spacer();
                _spacer1.percentWidth = 100;
                ControlBar(this.controlBar).addChild(_spacer1);
            }
            // if the button doesn't exist, create button
            if(!button2){
                button2 = new Button();
                button2.label = this.label2;
                button2.addEventListener(MouseEvent.CLICK,this._button2Click_Handler);
                ControlBar(this.controlBar).addChild(button2);
            }
            // if the spacer doesn't exist, create spacer
            if(!_spacer2){
                _spacer2 = new Spacer();
                _spacer2.percentWidth = 100;
                ControlBar(this.controlBar).addChild(_spacer2);
            }
            // if the button doesn't exist, create button
            if(!button3){
                button3 = new Button();
                button3.label = this.label3;
                button3.addEventListener(MouseEvent.CLICK,this._button3Click_Handler);
                ControlBar(this.controlBar).addChild(button3);
            }
        }
       
        //--------------------------
        //
        // public properties
        //
        //--------------------------
       
        //label 1
        private var _label1:String;
        private var _label1Changed:Boolean = false;
        /**
         *  Button 1 label
         */
   
        public function get label1():String
        {
            return this._label1;
        }
        public function set label1(value:String):void
        {
            this._label1 = value;
            this._label1Changed = true;
            this.invalidateProperties();
        }
       
        //label 2
        private var _label2:String;
        private var _label2Changed:Boolean = false;
        /**
         *  Button 2 label
         */
   
        public function get label2():String
        {
            return this._label2;
        }
        public function set label2(value:String):void
        {
            this._label2 = value;
            this._label2Changed = true;
            this.invalidateProperties();
        }
       
        //label 3
        private var _label3:String;
        private var _label3Changed:Boolean = false;
        /**
         *  Button 3 label
         */
   
        public function get label3():String
        {
            return this._label3;
        }
        public function set label3(value:String):void
        {
            this._label3 = value;
            this._label3Changed = true;
            this.invalidateProperties();
        }
       
        //columns
        private var _columns:Array;
        private var _columnsChanged:Boolean = false;
       
        public function get columns():Array
        {
            return this._columns.concat();
        }
        public function set columns(value:Array):void
        {
            this._columns = value;
            this._columnsChanged = true;
            this.invalidateProperties();
        }
       
        //--------------------------
        //
        // commit properties
        //
        //--------------------------
        override protected function commitProperties():void
        {
            // always call super class commitProperties()
            super.commitProperties();
            // label 1
            if(_label1Changed)
            {
                _label1Changed = false;
                this.button1.label = label1;
            }
            // label 2
            if(_label2Changed)
            {
                _label2Changed = false;
                this.button2.label = label2;
            }
            // label 3
            if(_label3Changed)
            {
                _label3Changed = false;
                this.button3.label = label3;
            }
           
            // columns
            if(_columnsChanged)
            {
                _columnsChanged = false;
                this.dataGrid.columns = this.columns;
            }
        }
       
        //--------------------------
        //
        // listeners
        //
        //--------------------------
        /**
         * @private
         * Button 1 Click Handler
         * @param event
         *
         */
   
        private function _button1Click_Handler(event:MouseEvent):void
        {
            dispatchEvent(new Event("button1Click"));
        }
       
        /**
         * @private
         * Button 2 Click Handler
         * @param event
         *
         */
   
        private function _button2Click_Handler(event:MouseEvent):void
        {
            dispatchEvent(new Event("button2Click"));
        }
       
        /**
         * @private
         * Button 3 Click Handler
         * @param event
         *
         */
   
        private function _button3Click_Handler(event:MouseEvent):void
        {
            dispatchEvent(new Event("button3Click"));
        }
    }
}

Wow, lots of code. Well let us finish up by looking back at our application. The same exact options are set in MXML like they would be if the component were made in MXML.

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:components="com.unitedmindset.components.*">
    <components:ComponentMXML label1="1" label2="2" label3="3" title="MXML Component" status="working">
        <mx:DataGridColumn headerText="hello"/>
        <mx:DataGridColumn headerText="world"/>
    </components:ComponentMXML>
    <components:ComponentAS label1="1" label2="2" label3="3" title="AS Component" status="working">
        <mx:DataGridColumn headerText="hello"/>
        <mx:DataGridColumn headerText="world"/>
    </components:ComponentAS>
</mx:Application>

And to no surprise, the two components look at act exactly the same.
customcomponent8

That is it, the Actionscript Component matches the MXML component and the transfer was pretty straightforward.

Make sure to check out the Connect Meeting, March 12th 09 at 8pm for this presentation.

  • Share/Bookmark

Comments (6)

[...] Next Post: Turning Components From MXML To Actionscript – Part 3 [...]

SajithMay 16th, 2009 at 1:31 pm

this is Superb.. This is how a useful basic topic present in a neat way. Really fantastic.

Jonathan CamposMay 16th, 2009 at 4:24 pm

Thanks! Glad it helps.

mohamedkamilJune 2nd, 2009 at 5:18 am

Thanks for your valuable post

GuillermoJune 5th, 2009 at 5:51 pm

How you change cornerRadius of buttons from AS?

Jonathan CamposJune 5th, 2009 at 6:06 pm

As the cornerRadius is a style you can set the style with the following code:

yourButton.setStyle(“cornerRadius”,20); < 20 being the cornerRadius you are setting.

I do usually recommend to set these properties in CSS rather than the layout though, just my preference.

Leave a comment

Your comment