Introduction
Some time ago, working on a Flex client application project, I've discovered the Flex Data Binding (I think anyone, who starts working with Flex meets with Data Binding very soon). Now, when I have some knowledge in that area, I want to share a few tips to make it easier for the beginner to utilize data binding in Flex applications.
By using Data Binding, we can easily link objects (data sources) between each other, which gives us a possibility to keep them synchronized. One of the areas, where data binding may be used is the linking of user interface elements between each other with the aim to provide a more interactive UI.
Simple Data Binding with MXML
Let us assume that we have two text fields:
<mx:TextInput id="sourceField" text="" />
<mx:TextInput id="destinationField" text="" />
We want the changes performed in the first input to be displayed on a second input. To achieve this, the following mxml code could be used:
<mx:Binding destination="destinationField.text" source="sourceField.text"/>
As a result the text, which is typed in a first text input will be automatically placed in a second text input. And this is what simple data binding stands for. The code of a simple application, which uses data binding, is as follows:
="1.0"="utf-8"
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Binding destination="destinationField.text" source="sourceField.text"/>
<mx:VBox>
<mx:TextInput id="sourceField" text="" />
<mx:TextInput id="destinationField" text="" />
</mx:VBox>
</mx:Application>
Simple Data Binding with ActionScript
The above example is easy to transform to ActionScript
. The benefit we get here is that we can use the data binding for dynamically created elements. We have the same two text fields:
public var sourceField : TextInput = new TextInput();
public var destinationField : TextInput = new TextInput();
Data binding will look like:
BindingUtils.bindProperty(destinationField, "text", sourceField, "text");
The first bindProperty
function parameter is a destination object, the second parameter is a destination object property name, the third parameter is a departure object and the fourth is a chain-object, which in a simple case represents a departure object property name (we will look in more detail on this parameter later on). And here goes an application code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
<mx:Script>
<![CDATA[
import mx.controls.TextInput;
import mx.binding.utils.BindingUtils;
public var sourceField : TextInput = new TextInput();
public var destinationField : TextInput = new TextInput();
public function init():void
{
sourceField.text = "";
parentContainer.addChild(sourceField);
destinationField.text = "";
parentContainer.addChild(destinationField);
BindingUtils.bindProperty(destinationField, "text", sourceField, "text");
}
]]>
</mx:Script>
<mx:VBox id="parentContainer"/>
</mx:Application>
Chain Object in a bindProperty Method
This object is used as a fourth parameter of a BindingUtils.bindProperty
function. It describes, in which way and to which parameter the data binding should be applied in a departure object. This object may be represented in three different forms: String, array and compound object.
String representation, which we used already, contains the property name of the departure object.
Array representation holds access hierarchy to inner property of a departure object. To understand its usage, let us create an example where two objects are used:
package src
{
public class SimpleObject
{
public var myText:String = new String();
public function SimpleObject():void
{
myText = "empty";
}
}
}
package src
{
public class ComplexObject
{
public var simpleInstance: SimpleObject = new SimpleObject();
public function ComplexObject():void
{
}
}
}
...
public var destinationField : TextInput = new TextInput();
public var complexInstance : ComplexObject = new ComplexObject();
So we have a ComplexObject
class instance and we want to connect the myText
field (which is inside the inner SimpleObject
object class) with the text
property of a TextInput
object. That is we want to bind complexInstance.simpleInstance.myText
to destinationField.text
. We will use an array as a chain object to achieve that:
BindingUtils.bindProperty(
destinationField,
"text",
complexInstance,
["simpleInstance","myText"]);
Flex will then concatenate the array data with points and will bind to property complexInstance.simpleInstance.myText
. An example application with the array type chain usage will look as follows:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
<mx:Script>
<![CDATA[
import src.ComplexObject;
import mx.controls.TextInput;
import mx.binding.utils.BindingUtils;
public var destinationField : TextInput = new TextInput();
public var complexInstance : ComplexObject = new ComplexObject();
public function init():void
{
destinationField.text = "";
parentContainer.addChild(destinationField);
BindingUtils.bindProperty(destinationField,
"text",
complexInstance,
["simpleInstance","myText"]);
}
]]>
</mx:Script>
<mx:VBox id="parentContainer"/>
</mx:Application>
In a compound object case, the chain is represented as:
var object : Object = {name:<property name>, getter: <value receiver function>};
where property name is a string
with a departure object property name and value receiver function:
function (host:<object of a departure type >) : <type of a return value>
{
}
Assume we have an instance of ArrayCollection
class and a text field.
public var array : ArrayCollection = new ArrayCollection();
public var destinationField : TextInput = new TextInput();
We want to disable the text field when the number of objects inside the collection will exceed the quantity of 10 and enable it otherwise.
BindingUtils.bindProperty(
destinationField,
"enabled",
array,
{
name:"length",
getter : function (host : ArrayCollection):Boolean
{ return host.length<10; }
});
And here is a full application code with compound object as a chain usage:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.TextInput;
import mx.binding.utils.BindingUtils;
public var destinationField : TextInput = new TextInput();
[Bindable]
public var array : ArrayCollection = new ArrayCollection();
public function init():void
{
destinationField.text = "some text";
parentContainer.addChild(destinationField);
BindingUtils.bindProperty(
destinationField,
"enabled",
array,
{
name:"length",
getter : function (host :
ArrayCollection):Boolean
{ return host.length>=10; }
});
}
]]>
</mx:Script>
<mx:VBox id="parentContainer">
<mx:Button label="add element" click="array.addItem(0);"/>
<mx:Text text="array length: {array.length}"/>
</mx:VBox>
</mx:Application>
bindSetter Method from BindingUtils Class
Another approach of a data binding usage is to invoke a callback function, when property of a departure object is changed. Assume we have a collection and a text field.
public var destinationField : TextInput = new TextInput();
public var array : ArrayCollection = new ArrayCollection();
Collection contains digits, which are added to the collection from outside. We need to display the sum of all collection elements in a text field (and we need to keep this sum up to date). This means we need to recalculate the sum of the elements, when the length of a collection changes. To achieve that, we will use the bindSetter
method of a BindingUtils
class:
BindingUtils.bindSetter(calculateSum , array, "length");
where calculateSum
will be a function:
function ( input : <departure>):void
{
}
In our case, calculateSum
will contain the following:
public function calculateSum(input : Number):void
{
var sum:Number = 0;
for (var i:int = 0; i<input; i++)
{
sum += array[i];
}
destinationField.text = sum.toString();
}
And here is an application code for the callback function usage:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.TextInput;
import mx.binding.utils.BindingUtils;
public var destinationField : TextInput = new TextInput();
[Bindable]
public var array : ArrayCollection = new ArrayCollection();
public function init():void
{
destinationField.text = "some text";
parentContainer.addChild(destinationField);
BindingUtils.bindSetter(calculateSum , array, "length");
}
public function calculateSum(input : Number):void
{
var sum:Number = 0;
for (var i:int = 0; i<input; i++)
{
sum += array[i];
}
destinationField.text = sum.toString();
}
]]>
</mx:Script>
<mx:VBox id="parentContainer">
<mx:Button label="add element" click="array.addItem(Math.random());"/>
<mx:List width="100" height="200" dataProvider="{array}"/>
</mx:VBox>
</mx:Application>
Using bindSetter with a Compound Chain Object
This is the most complex type of a data binding, but it is easy to understand. Assume we need to write a class, which collects data about the width changes, which happen with the observed button component. First, we write the class itself:
package src
{
import mx.collections.ArrayCollection;
public class Collector
{
public var array : ArrayCollection;
public function Collector():void
{
array = new ArrayCollection();
}
public function collect(str:String):void
{
array.addItem(str);
}
}
}
and a return
function, which prepares data about the button:
public function returnInfo(host:UIComponent):String
{
return "width:" + host.width + "; height:" + host.height;
}
Now, using bindSetter
method we will define that when a width
property of a button component changes, the returnInfo
function should be invoked:
BindingUtils.bindSetter(collector.collect, button,
{name:"width", getter: returnInfo});
And here is an application which demonstrates the compound chain object usage:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="init()">
<mx:Script>
<![CDATA[
import mx.core.UIComponent;
import mx.binding.utils.BindingUtils;
import src.Collector;
public var collector : Collector = new Collector();
public function init():void
{
BindingUtils.bindSetter(collector.collect, button,
{name:"width", getter: returnInfo});
data.dataProvider = collector.array;
}
public function returnInfo(host:UIComponent):String
{
return "width:" + host.width +
"; height:" + host.height;
}
public function changeButton():void
{
button.width = Math.round(Math.random() * 200);
button.height = Math.round(Math.random() * 100);
}
]]>
</mx:Script>
<mx:VBox>
<mx:Button label="change" click="changeButton();"/>
<mx:List id="data" width="200" height="400" />
<mx:Button id="button" width="200" height="100" label="mybutton"/>
</mx:VBox>
</mx:Application>
The returnInfo
function prepares data about a button width
, then this data automatically goes to function collector.collect()
where it is then handled appropriately.
History
- 2nd February, 2010: Initial post