Working with components

The primary use of ActionScript in your applications is probably going to be for working with the visual controls and containers in your application. Flex provides several techniques for doing this, including referencing a Flex control in ActionScript and manipulating properties during the instantiation of controls and containers.

Referring to components

To work with a component in ActionScript, you usually define an id property for that component in the MXML tag. For example, the following code sets the id property of the Button control to the String "myButton":

<s:Button id="myButton" label="Click Me"/>

This property is optional if you do not want to access the component with ActionScript.

This code causes the MXML compiler to autogenerate a public variable named myButton that contains a reference to that Button instance. This autogenerated variable lets you access the component instance in ActionScript. You can explicitly refer to the Button control’s instance with its id instance reference in any ActionScript class or script block. By referring to a component’s instance, you can modify its properties and call its methods.

For example, the following ActionScript block changes the value of the Button control’s label property when the user clicks the button:

<?xml version="1.0"?>
<!-- usingas/ButtonExample.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
        <![CDATA[
        private function setLabel():void {
            if (myButton.label=="Click Me") { 
                myButton.label = "Clicked";
            } else {                
                myButton.label = "Click Me";                
            }
        }
        ]]>
    </fx:Script>

    <s:Button id="myButton" label="Click Me" click="setLabel();"/>
    
</s:Application>

The executing SWF file for the previous example is shown below:

The IDs for all tags in an MXML component, no matter how deeply nested they are, generate public variables of the component being defined. As a result, all id properties must be unique within a document. This also means that if you specified an ID for a component instance, you can access that component from anywhere in the application: from functions, external class files, imported ActionScript files, or inline scripts.

You can refer to a component if it does not have an id property by using methods of the component’s Spark container, such as the getElementAt() method. For MX containers, you can use the getChildAt() method.

You can refer to the current enclosing document or current object using the this keyword.

You can also get a reference to a component when you have a String that matches the name. To access an object on the application, you use the this keyword, followed by square brackets, with the String inside the square brackets. The result is a reference to the objects whose name matches the String.

The following example changes style properties on each Button control using a compound String to get a reference to the object:

<?xml version="1.0"?>
<!-- usingas/FlexComponents.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script><![CDATA[
        private var newFontStyle:String;
        private var newFontSize:int;

        public function changeLabel(s:String):void {
            s = "myButton" + s;

            if (this[s].getStyle("fontStyle")=="normal") {
                newFontStyle = "italic";
                newFontSize = 18;
            } else {
                newFontStyle = "normal";
                newFontSize = 10;     
            }

            this[s].setStyle("fontStyle",newFontStyle);
            this[s].setStyle("fontSize",newFontSize);
        }
        ]]>
    </fx:Script>

    <s:Button id="myButton1" 
        click="changeLabel('2')" 
        label="Change Other Button's Styles"/>
    <s:Button id="myButton2" 
        click="changeLabel('1')" 
        label="Change Other Button's Styles"/>
</s:Application>

The executing SWF file for the previous example is shown below:

This technique is especially useful if you use a Repeater control or when you create objects in ActionScript and do not necessarily know the names of the objects you want to refer to prior to run time. However, when you instantiate an object in ActionScript, to add that object to the properties array, you must declare the variable as public and declare it in the class’s scope, not inside a function.

The following example uses ActionScript to declare two Label controls in the application scope. During initialization, the labels are instantiated and their text properties are set. The example then gets a reference to the Label controls by appending the passed-in variable to the String when the user clicks the Button controls.

<?xml version="1.0"?>
<!-- usingas/ASLabels.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark"
    creationComplete="initLabels()">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
        <![CDATA[
        import mx.controls.Label;

        public var label1:Label;
        public var label2:Label;

        // Objects must be declared in the application scope. Adds the names to
        // the application's properties array.

        public function initLabels():void {
            label1 = new Label();
            label1.text = "Change Me";

            label2 = new Label();
            label2.text = "Change Me";

            addElement(label1);
            addElement(label2);
        }

        public function changeLabel(s:String):void {
            // Create a String that matches the name of the Label control.
            s = "label" + s;
            // Get a reference to the label control using the 
            // application's properties array.
            this[s].text = "Changed";
        }
        ]]>
    </fx:Script>

    <s:Button id="b1" click="changeLabel('2')" label="Change Other Label"/>
    <s:Button id="b2" click="changeLabel('1')" label="Change Other Label"/>

</s:Application>

The executing SWF file for the previous example is shown below:

Calling component methods

You can invoke the public methods of a component instance in your application by using the following dot-notation syntax:

componentInstance.method([parameters]);

The following example invokes the adjustThumb() method when the user clicks the button, which invokes the public setThumbValueAt() method of the HSlider control:

<?xml version="1.0"?>
<!-- usingas/ComponentMethods.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
        <![CDATA[
        public function adjustThumb(s:HSlider):void {
            var randomNumber:int = (Math.floor(Math.random() * 10));
            s.setThumbValueAt(0, randomNumber);
        }
        ]]>
    </fx:Script>

    <mx:HSlider id="slider1" tickInterval="1" 
        labels="[1,2,3,4,5,6,7,8,9,10]" width="282"/>

    <s:Button id="myButton" 
        label="Change Thumb Position" 
        click="adjustThumb(slider1);"/>    
</s:Application>

The executing SWF file for the previous example is shown below:

To invoke a method from a child document (such as a custom MXML component), you can use the parentApplication, parentDocument, or FlexGlobals.topLevelApplication properties. For more information, see Application containers.

Note: Because Flex invokes the initialize event before drawing the component, you cannot access size and position information of that component from within the initialize event handler unless you use the creationComplete event handler. For more information on the order of initialization events, see About startup order.

Creating visual components in ActionScript

You can use ActionScript to programmatically create visual components using the new operator, in the same way that you create instances of any ActionScript class. The created component has default values for its properties, but it does not yet have a parent or any children (including any kind of internal DisplayObjects), and it is not yet on the display list in Flash Player or Adobe® AIR™, so you can’t see it. After creating the component, you should use standard assignment statements to set any properties whose default values aren’t appropriate.

Finally, you must add the new component to a container, by using the Spark container’s addElement() or addElementAt() methods, so that it becomes part of the visual hierarchy of an application. (For MX containers, you can use the addChild() or addChildAt() methods.) The first time that it is added to a container, a component’s children are created. Children are created late in the component’s life cycle so that you can set properties that can affect children as they are created.

When creating visual controls, you must import the appropriate package. In most cases, this is the mx.controls package, although you should check the ActionScript 3.0 Reference for the Adobe Flash Platform.

The following example creates a Button control inside the HGroup container:

<?xml version="1.0"?>
<!-- usingas/ASVisualComponent.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
        <![CDATA[
        import spark.components.Button;
        public var button2:Button;

        public function createObject():void {
            button2 = new Button();
            button2.label = "Click Me";
            hb1.addElement(button2);
        }
        ]]>
    </fx:Script>

    <s:HGroup id="hb1">
        <s:Button label="Create Object" click="createObject()"/>
    </s:HGroup>
</s:Application>

The executing SWF file for the previous example is shown below:

Flex creates the new child as the last child in the container. If you do not want the new child to be the last in the Spark container, use the addElementAt() method to change the order. You can use the setItemIndex() method after the call to the addElement() method, but this is less efficient. For MX containers, you use the addChildAt(), setChildIndex(), and addChild() methods.

You should declare an instance variable for each dynamically created component and store a reference to the newly created component in it, just as the MXML compiler does when you set an id property for a component instance tag. You can then access your dynamically created components in the same way as those declaratively created in MXML.

To programmatically remove a control in Spark containers, you can use the removeElement(), removeElementAt(), and removeAllElements() methods. For MX containers, you use the removeChild() or removeChildAt() methods. You can also use the removeAllChildren() method to remove all child controls from a container.

Calling the “remove” methods does not actually delete the objects from memory. If you do not have any other references to the child, Flash Player includes the object in garbage collection at some future point. But if you have a reference to that child, the child is not garbage collected.

In some cases, you declaratively define a component with an MXML tag. You can set the creationPolicy property of the component’s container to none to defer the creation of the controls inside that container. You can then create the component programmatically rather than declaratively. For information on using the creationPolicy property, see Improving startup performance.

The only component you can pass to the addElement() method is a class that implements the IVisualElement interface. In other words, if you create a new object that is not a subclass of mx.core.IVisualElement, you must wrap it in a class that implments IVisualElement before you can attach it to a container. The following example creates a new Sprite object, which is not a subclass of IVisualElement, and adds it as a child of the UIComponent (which implements IVisualElement) before adding it to the Panel container:

<?xml version="1.0"?>
<!-- usingas/AddingChildrenAsUIComponents.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
        <![CDATA[
        import flash.display.Sprite;
        import mx.core.UIComponent;

        private var xLoc:int = 20;
        private var yLoc:int = 20;
        private var circleColor:Number = 0xFFCC00;

        private function addChildToPanel():void {

            var circle:Sprite = new Sprite();
            circle.graphics.beginFill(circleColor);
            circle.graphics.drawCircle(xLoc, yLoc, 15);

            var c:UIComponent = new UIComponent();
            c.addChild(circle);
            panel1.addElement(c);
            
            xLoc = xLoc + 5;
            yLoc = yLoc + 1;
            circleColor = circleColor + 20;
        }
        ]]>
    </fx:Script>

    <s:Panel id="panel1" height="250" width="300"/>

    <s:Button id="myButton" label="Click Me" click="addChildToPanel();"/>
    
</s:Application>

The executing SWF file for the previous example is shown below:

About scope

Scoping in ActionScript is largely a description of what the this keyword refers to at a particular point. In your application’s core MXML file, you can access the Application object by using the this keyword. In a file defining an MXML component, this is a reference to the current instance of that component.

In an ActionScript class file, the this keyword refers to the instance of that class. In the following example, the this keyword refers to an instance of myClass. Because this is implicit, you do not have to include it, but it is shown here to illustrate its meaning.

class myClass { 
    var _x:Number = 3; 
    function get x():Number { 
        return this._x; 
    } 
    function set x(y:Number):void {  
        if (y > 0) { 
            this._x = y; 
        } else { 
            this._x = 0;  
        } 
    } 
}

However, in custom ActionScript and MXML components or external ActionScript class files, Flex executes in the context of those objects and classes, and the this keyword refers to the current scope and not the Application object scope.

Flex includes a FlexGlobals.topLevelApplication property that you can use to access the root application. In some cases, you can also use the parentDocument property to access the next level up in the document chain of an application, or the parentApplication property to access the next level up in the application chain when one Application object loads another Application object.

You cannot use these properties to access the root application if the loaded application was loaded into a separate ApplicationDomain or SecurityDomain, as is the case with sandboxed and multi-versioned applications. For more information, see Accessing the main application from sub-applications.

If you write ActionScript in a component’s event listener, the scope is not the component but rather the application. For example, the following code changes the label of the Button control to "Clicked" once the Button control is pressed:

<?xml version="1.0"?>
<!-- usingas/ButtonScope.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <s:layout> 
        <s:BasicLayout/> 
    </s:layout>

    <s:Panel width="250" height="100" x="65" y="24">
        <s:Button id="myButton" 
            label="Click Me" 
            click="myButton.label='Clicked'" 
            x="79.5" y="20"/>
    </s:Panel>
    <s:Button label="Reset" 
        x="158" y="149" 
        click="myButton.label='Click Me'"/>    
</s:Application>

The executing SWF file for the previous example is shown below:

Contrast the previous example with the following code:

<?xml version="1.0"?>
<!-- usingas/AppScope.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <!-- The following throws a compiler error because the app level scope does
         not have a label to set. -->
    <!-- <s:Button id="myButton" label="Click Me" click="{this.label='Clicked'}"/> -->

    <!-- <s:Button label="Reset" click="myButton.label='Click Me'"/> -->

</s:Application>

This code does not work because when an event listener executes, the this keyword does not refer to the Button instance; the this keyword refers to the Application or other top-level component instance. The second example attempts to set the label property of the Application object, not the label property of the Button.

Variables declared within a function are locally scoped to that function. These variables can share the same name as variables in outer scopes, and they do not affect the outer-scoped variable. If a variable is just used temporarily by a single method, make it a local variable of that method rather than an instance variable. Use instance variables only for storing the state of an instance, because each instance variable will take up memory for the entire lifetime of the instance. You can refer to the outer-scoped variable with the this. prefix.