Binding to functions, Objects, and Arrays

Using functions as the source for a data binding

You can use a function as part of the source of a data binding expression. Two common techniques with functions are to use bindable properties as arguments to the function to trigger the function, or to trigger the function in response to a binding event.

Using functions that take bindable properties as arguments

You can use ActionScript functions as the source of data binding expressions when using a bindable property as an argument of the function. When the bindable property changes, the function executes, and the result is written to the destination property, as the following example shows:

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

    <fx:Declarations>
        <s:CurrencyFormatter id="usdFormatter" useCurrencySymbol="true"/>
    </fx:Declarations>

    <s:TextInput id="myTI" text="Enter number here"/>
    <s:TextArea text="{usdFormatter.format(myTI.text)}"/>      
</s:Application>

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

In this example, Flex calls the CurrencyFormatter.format() method to update the TextArea control every time the text property of the TextInput control is modified.

If the function is not passed an argument that can be used as the source of a data binding expression, the function only gets called once when the applications starts.

Binding to functions in response to a data-binding event

You can specify a function that takes no bindable arguments as the source of a data binding expression. However, you then need a way to invoke the function to update the destination of the data binding.

In the following example, you use the [Bindable] metadata tag to specify to Flex to invoke the isEnabled()function in response to the event myFlagChanged. When the myFlag setter gets called, it dispatches the myFlagChanged event to trigger any data bindings that use the isEnabled()function as the source:

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

    <fx:Script>
        <![CDATA[
            import flash.events.Event;
            
            // Define a function that gets invoked 
            // in response to the myFlagChanged event.
            [Bindable(event="myFlagChanged")]
            private function isEnabled():String {
                if (myFlag)
                    return 'true';
                else
                    return 'false';
            }

            private var _myFlag:Boolean = false;

            // Define a setter method that dispatches the 
            // myFlagChanged event to trigger the data binding.
            public function set myFlag(value:Boolean):void {
                _myFlag = value;
                dispatchEvent(new Event("myFlagChanged"));
            }

            public function get myFlag():Boolean {
                return _myFlag;
            }
        ]]>    
    </fx:Script>

    <!-- Use the function as the source of a data binding expression. -->
    <s:TextArea id="myTA" text="{isEnabled()}"/>

    <!-- Modify the property, causing the setter method to 
        dispatch the myFlagChanged event to trigger data binding. -->
    <s:Button label="Clear MyFlag" click="myFlag=false;"/>
    <s:Button label="Set MyFlag" click="myFlag=true;"/>
</s:Application>

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

For more information on the [Bindable] metadata tag, see Using the Bindable metadata tag.

Using data binding with Objects

When working with Objects, you have to consider when you define a binding to the Object, or when you define a binding to a property of the Object.

Binding to Objects

When you make an Object the source of a data binding expression, the data binding occurs when the Object is updated, or when a reference to the Object is updated, but not when an individual field of the Object is updated.

In the following example, you create subclass of Object that defines two properties, stringProp and intProp, but does not make the properties bindable:

package myComponents
{
    // binding/myComponents/NonBindableObject.as

    // Make no class properties bindable. 
    public class NonBindableObject extends Object {
    
        public function NonBindableObject() {
            super();            
        }
        
        public var stringProp:String = "String property";

        public var intProp:int = 52;
    }
}

Since the properties of the class are not bindable, Flex will not dispatch an event when they are updated to trigger data binding. You then use this class in a Flex application, as the following example shows:

<?xml version="1.0"?>
<!-- binding/WholeObjectBinding.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    creationComplete="initObj();">
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    
    <fx:Script>
        <![CDATA[
            import myComponents.NonBindableObject;

            [Bindable]
            public var myObj:NonBindableObject = new NonBindableObject();

            [Bindable]            
            public var anotherObj:NonBindableObject = 
                new NonBindableObject();

            public function initObj():void {
                anotherObj.stringProp = 'anotherObject';
                anotherObj.intProp = 8;
            }                
        ]]>    
    </fx:Script>

    <!-- Data binding updated at application startup. -->
    <s:Label id="text1" text="{myObj.stringProp}"/>

    <!-- Data binding updated at application startup. -->
    <s:Label id="text2" text="{myObj.intProp}"/>

    <!-- Data bindings to stringProp not updated. -->
    <s:Button label="Change myObj.stringProp" 
        click="myObj.stringProp = 'new string';"/>
        
    <!-- Data bindings to intProp not updated. -->
    <s:Button label="Change myObj.intProp" 
        click="myObj.intProp = 10;"/>
        
    <!-- Data bindings to myObj and to myObj properties updated. -->
    <s:Button label="Change myObj" 
        click="myObj = anotherObj;"/>
</s:Application>

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

Because you did not make the individual fields of the NonBindableObject class bindable, the data bindings for the two Text controls are updated at application start up, and when myObj is updated, but not when the individual properties of myObj are updated.

When you compile the application, the compiler outputs warning messages stating that the data binding mechanism will not be able to detect changes to stringProp and intProp.

Binding to properties of Objects

To make properties of an Object bindable, you create a new class definition for the Object, as the following example shows:

package myComponents
{
    // binding/myComponents/BindableObject.as

    // Make all class properties bindable. 
    [Bindable]
    public class BindableObject extends Object {
    
        public function BindableObject() {
            super();
        }
        
        public var stringProp:String = "String property";

        public var intProp:int = 52;
    }
}

By placing the [Bindable] metadata tag before the class definition, you make bindable all public properties defined as variables, and all public properties defined by using both a setter and a getter method. You can then use the stringProp and intProp properties as the source for a data binding, as the following example shows:

<?xml version="1.0"?>
<!-- binding/SimpleObjectBinding.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx"
    creationComplete="initObj();">
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    
    <fx:Script>
        <![CDATA[
            import myComponents.BindableObject;

            [Bindable]
            public var myObj:BindableObject = new BindableObject();

            [Bindable]            
            public var anotherObj:BindableObject = 
                new BindableObject();

            public function initObj():void {
                anotherObj.stringProp = 'anotherObject';
                anotherObj.intProp = 8;
            }                
        ]]>    
    </fx:Script>

    <!-- Data binding updated at application startup. -->
    <s:Label id="text1" text="{myObj.stringProp}"/>

    <!-- Data binding updated at application startup. -->
    <s:Label id="text2" text="{myObj.intProp}"/>

    <!-- Data bindings to stringProp updated. -->
    <s:Button label="Change myObj.stringProp" 
        click="myObj.stringProp = 'new string';"/>
        
    <!-- Data bindings to intProp updated. -->
    <s:Button label="Change myObj.intProp" 
        click="myObj.intProp = 10;"/>
        
    <!-- Data bindings to myObj and to myObj properties updated. -->
    <s:Button label="Change myObj" 
        click="myObj = anotherObj;"/>
</s:Application>

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

Binding with arrays

When working with arrays, such as Array or ArrayCollection objects, you can define the array as the source or destination of a data binding expression.

Note: When defining a data binding expression that uses an array as the source of a data binding expression, the array should be of type ArrayCollection because the ArrayCollection class dispatches an event when the array or the array elements change to trigger data binding. For example, a call to ArrayCollection.addItem(), ArrayCollection.addItemAt(), ArrayCollection.removeItem(), and ArrayCollection.removeItemAt() all trigger data binding.

Binding to arrays

You often bind arrays to the dataProvider property of Flex controls, as the following example shows for the List control:

<?xml version="1.0"?>
<!-- binding/ArrayBindingDP.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;

            [Bindable]
            public var myAC:ArrayCollection = new ArrayCollection([
                "One", "Two", "Three", "Four"]);
                
            [Bindable]
            public var myAC2:ArrayCollection = new ArrayCollection([
                "Uno", "Dos", "Tres", "Quatro"]);
        ]]>    
    </fx:Script>
    
    <!-- Data binding updated at application startup,
        when myAC is modified, and when an element of
        myAC is modifed. -->
    <s:List width="150"
        dataProvider="{myAC}"/>

    <!-- Data bindings to myAC updated. -->
    <s:Button 
        label="Change Element" 
        click="myAC[0]='mod One'"/>
        
    <!-- Data bindings to myAC updated. -->
    <s:Button 
        label="Add Element" 
        click="myAC.addItem('new element');"/>
        
    <!-- Data bindings to myAC updated. -->
    <s:Button 
        label="Remove Element 0" 
        click="myAC.removeItemAt(0);"/>
        
    <!-- Data bindings to myAC updated. -->
    <s:Button 
        label="Change ArrayCollection" 
        click="myAC=myAC2"/>
</s:Application>

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

This example defines an ArrayCollection object, and then uses data binding to set the data provider of the List control to the ArrayCollection. When you modify an element of the ArrayCollection object, or when you modify a reference to the ArrayCollection object, you trigger a data binding.

Binding to array elements

You can use individual ArrayCollection elements as the source or a binding expression, as the following example shows:

<?xml version="1.0"?>
<!-- binding/ArrayBinding.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/mx">
    <s:layout>
        <s:VerticalLayout/>
    </s:layout>
    
    <fx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;

            [Bindable]
            public var myAC:ArrayCollection = new ArrayCollection([
                "One", "Two", "Three", "Four"]);
                
            [Bindable]
            public var myAC2:ArrayCollection = new ArrayCollection([
                "Uno", "Dos", "Tres", "Quatro"]);      
        ]]>    
    </fx:Script>

    <!-- Data binding updated at application startup 
        and when myAC modified. -->
    <s:Label id="text1" text="{myAC[0]}"/>

    <!-- Data binding updated at application startup, 
        when myAC modified, and when myAC[0] modified. -->
    <s:Label id="text2" text="{myAC.getItemAt(0)}"/>
    
    <s:Button id="button1"
        label="Change Element" 
        click="myAC[0]='new One'"/>

    <s:Button id="button2"
        label="Change ArrayCollection" 
        click="myAC=myAC2"/>
</s:Application>

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

If you specify an array element as the source of a data binding expression by using the square bracket syntax, [], data binding is only triggered when the application starts and when the array or a reference to the array is updated; data binding is not triggered when the individual array element is updated. The compiler issues a warning in this situation.

However, the data binding expression myAC.getItemAt(0) is triggered when an array element changes. Therefore, the text2 Text control is updated when you click button1, while text1 is not. When using an array element as the source of a data binding expression, you should use the ArrayCollection.getItemAt() method in the binding expression.

Clicking button2 copies myAC2 to myAC, and triggers all data bindings to array elements regardless of how you implemented them.