Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Flex Data Binding Tricks

4.00/5 (2 votes)
2 Feb 2010CPOL4 min read 46.6K   309  
This article describes the usage of Data Binding in Flex applications

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:

XML
<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:

XML
<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:

XML
<?xml version="1.0" encoding="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:

Java
public var sourceField : TextInput = new TextInput(); 
public var destinationField : TextInput = new TextInput();

Data binding will look like:

Java
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:

Java
<?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:

Java
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
      {
         //some logic
      }
   }
}

...

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:

Java
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:

Java
<?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:

Java
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:

Java
function (host:<object of a departure type >) : <type of a return value>
{
	// function body, where we can query the host parameter
	// since we know this is an object, used for departure
}

Assume we have an instance of ArrayCollection class and a text field.

Java
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.

Java
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:

Java
<?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.

Java
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:

Java
BindingUtils.bindSetter(calculateSum , array, "length");

where calculateSum will be a function:

Java
function ( input : <departure>):void
{
	//function contents
}

In our case, calculateSum will contain the following:

Java
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:

Java
<?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:

Java
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:

Java
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:

Java
BindingUtils.bindSetter(collector.collect, button, 
		{name:"width", getter: returnInfo});

And here is an application which demonstrates the compound chain object usage:

Java
<?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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)