Flex applications typically utilize lots of data retrieved from both RPCs (server-side method calls) and user input collected in forms. One of the ways in which you can work with data is to use extensive ActionScript. ActionScript provides you with low-level access to all the data in your Flex application. Yet the ActionScript code can be redundant, and it can be time-consuming to write. Although extensive ActionScript may be necessary in some cases, the Flex framework provides a feature called data binding that simplifies working with data in most cases.
Data binding lets you associate data from one object with data from another object. There are lots of ways to use data binding. The following examples list a few of the most common uses for data binding:
Link form input controls (text inputs, checkboxes, etc.) with data models.
Link two or more controls (e.g., display a slider value in a text component).
In the following sections, we’ll look at the rules of data binding as well as examples of different ways to use data binding.
There are three basic ways to apply data binding:
Curly brace ({}
)
syntax
<mx:Binding>
BindingUtils
Each of these techniques for applying data binding has advantages and disadvantages, which we’ll discuss in the next few sections.
Using curly braces to apply data binding is the simplest and fastest technique. Throughout the early part of this book, you’ve seen quite a few examples of curly brace syntax. Placing curly braces around any expression causes it to be evaluated. Consider the following example with a combo box and a text input control:
<mx:HBox> <mx:ComboBox id="level"> <mx:Array> <mx:Object label="A" data="1" /> <mx:Object label="B" data="2" /> <mx:Object label="C" data="3" /> <mx:Object label="D" data="4" /> </mx:Array> </mx:ComboBox> <mx:TextInput id="selectedLevel" text="level.value" /> </mx:HBox>
In this example, the text
attribute of the text input is set to level.value
. In that format, the value is
interpreted literally, so the string level.value
displays in the text input.
Changing the text input tag to the following makes a big
difference:
<mx:TextInput id="selectedLevel" text="{level.value}" />
With this change the text input now selects the data
corresponding to the selected combo box item. As the user selects a
different combo box item, the value in the text input also updates.
This is because the text level.value
is now placed within curly
braces, so it is treated as an expression rather than as literal
text.
More than just evaluating the expression, the curly braces
attempt to make a data binding association. If the association is
successful, as the value of the target (in the example, the target is
level.value
) changes, the listening
property (the text property of the text input in this example) also
updates. The preceding example illustrates this because as the combo
box value changes, so does the value displayed in the text input. For
a more dramatic example, consider the following:
<mx:Panel id="panel" width="{panelWidth.value}" height="{panelHeight.value}"> <mx:NumericStepper id="panelWidth" value="200" minimum="200" maximum="400" stepSize="10" height="22"/> <mx:NumericStepper id="panelHeight" value="200" minimum="200" maximum="400" stepSize="10" height="22"/> </mx:Panel>
In this example, the panel contains two nested numeric steppers.
The panel uses data binding to link the width
property to the value of the first
numeric stepper and the height
property to the value of the second stepper. So as the user changes
the values of the numeric steppers, the width
and height
of the panel change
accordingly.
There are many scenarios in which you can use curly brace syntax for data binding. As you’ve seen in the preceding example, you can use the syntax to directly associate a target property with a property of a form control such as a text input. You can also link a value from a control to a data model, as the following example illustrates:
<mx:Model id="dataModel"> <userData> <email>{email.text}</email> <phone>{phone.text}</phone> <city>{city.text}</city> <state>{state.value}</state> </userData> </mx:Model> <mx:VBox> <mx:Label text="Email" /> <mx:TextInput id="email" /> <mx:Label text="Phone" /> <mx:TextInput id="phone" /> <mx:Label text="City" /> <mx:TextInput id="city" /> <mx:Label text="State" /> <mx:ComboBox id="state"> <mx:Array> <mx:Object label="CA" /> <mx:Object label="MA" /> </mx:Array> </mx:ComboBox> </mx:VBox>
The preceding code uses data binding to link the values from form controls to a data model. You can use data binding both to assign values to a data model, as in the preceding example, and to retrieve data from a data model and display it. And you can even use data binding in both directions at the same time. The following code, used in conjunction with the preceding example, formats and displays the text from the data model in a text area, updating as the user changes the values in the controls bound to the model.
<mx:TextArea width="200" height="200" text="{'Contact Information Email: ' + dataModel.email + ' Phone: ' + dataModel.phone + ' Location: ' + dataModel.city + ', ' + dataModel.state}" />
Perhaps an even more useful example is one in which you use data
binding to link data either directly from controls or from a data
model to an RPC component such as a RemoteObject
component. Using data binding
in this way allows you to make RPCs without having to write much, if
any, ActionScript. The following example uses data binding to link the
data from the data model in the preceding example to a RemoteObject
instance as the
parameters for a method call:
<mx:RemoteObject id="example" destination="exampleService"> <mx:method name="saveContactInformation"> <mx:arguments> <email>{dataModel.email}</email> <phone>{dataModel.phone}</phone> <city>{dataModel.city}</city> <state>{dataModel.state}</state> </mx:arguments> </mx:method> </mx:RemoteObject>
As the values in the data model update via data binding, so,
too, will the values in the RemoteObject
method arguments update. This
allows you to call the method by simply calling a send()
method with no parameters, as in the
following example:
<mx:Button label="Save" click="example.saveContactInformation.send()" />
This is a very simple example of working with RemoteObject
. The same principles are true
when working with HTTPService
and
WebService
. We discuss all of these
RPC techniques in more detail in Chapter 17.
Because curly brace syntax allows you to evaluate any
ActionScript expression, you can also use data binding with E4X
expressions. That means you can use data binding not only to link
XML data with control values, but also to link controls and RPC
components to XML values using E4X expressions. For instance, instead
of using a Model
object, as in the
earlier example, you can use an XML object as follows:
<mx:XML id="xmlData"> <userData email="{email.text}" phone="{phone.text}" city="{city.text}" state="{state.value}" /> </mx:XML>
You can then use E4X expressions to link the text area value to values from the XML object:
<mx:TextArea width="200" height="200" text="{'Contact Information Email: ' + xmlData.@email + ' Phone: ' + xmlData.@phone + ' Location: ' + xmlData.@city + ', ' + xmlData.@state}" />
The <mx:Binding>
tag
allows you to do exactly the same things as curly brace
syntax, but with MXML tags rather than inline expressions. The
<mx:Binding>
tag requires the
following attributes:
For the following example, the same basic premise is used as in the first example of the curly brace discussion: we’ll link the value from a combo box to a text input so that when the user changes the value in the combo box, the value in the text input also changes. First we’ll add the controls:
<mx:HBox> <mx:ComboBox id="level"> <mx:Array> <mx:Object label="A" data="1" /> <mx:Object label="B" data="2" /> <mx:Object label="C" data="3" /> <mx:Object label="D" data="4" /> </mx:Array> </mx:ComboBox> <mx:TextInput id="selectedLevel" /> </mx:HBox>
Notice that we’re not setting the text property of the text
input. With curly brace syntax we’d set the text property inline. With
the <mx:Binding>
tag we’ll
use a separate tag to achieve the goal of data binding. To link the
source (level.value
) and the
destination (selectedLevel.text
),
you can add the following tag to your code (note that the tag must
appear outside any layout container tags):
<mx:Binding source="level.selectedItem.data" destination="selectedLevel.text" />
This code works identically to how the curly brace example worked, yet it uses a different mechanism to achieve that goal.
You can use <mx:Binding>
with data models and RPC
components as well. We can rewrite the earlier data model example to
illustrate this point. First, add the data model, the RemoteObject
, and the controls. Note that in
this example, the data model and the remote method arguments do not
define any values inline:
<mx:Model id="dataModel"> <userData> <email></email> <phone></phone> <city></city> <state></state> </userData> </mx:Model> <mx:RemoteObject id="example" destination="exampleService"> <mx:method name="saveContactInformation"> <mx:arguments> <email></email> <phone></phone> <city></city> <state></state> </mx:arguments> </mx:method> </mx:RemoteObject> <mx:VBox> <mx:Label text="Email" /> <mx:TextInput id="email" /> <mx:Label text="Phone" /> <mx:TextInput id="phone" /> <mx:Label text="City" /> <mx:TextInput id="city" /> <mx:Label text="State" /> <mx:ComboBox id="state"> <mx:Array> <mx:Object label="CA" /> <mx:Object label="MA" /> </mx:Array> </mx:ComboBox> <mx:TextArea id="summary" width="200" height="200" /> </mx:VBox>
Next, we need to define the data bindings using the <mx:Binding>
tag:
<mx:Binding source="email.text" destination="dataModel.email" /> <mx:Binding source="phone.text" destination="dataModel.phone" /> <mx:Binding source="city.text" destination="dataModel.city" /> <mx:Binding source="state.value" destination="dataModel.state" /> <mx:Binding source="'Contact Information Email: ' + dataModel.email + ' Phone: ' + dataModel.phone + ' Location: ' + dataModel.city + ', ' + dataModel.state" destination="summary.text" /> <mx:Binding source="dataModel.email" destination="example.saveContactInformation.arguments.email" /> <mx:Binding source="dataModel.phone" destination="example.saveContactInformation.arguments.phone" /> <mx:Binding source="dataModel.city" destination="example.saveContactInformation.arguments.city" /> <mx:Binding source="dataModel.state" destination="example.saveContactInformation.arguments.state" />
You can also use E4X expressions with the <mx:Binding>
tag. Assume you change
the data model from a Model
object
to an XML
object as follows:
<mx:XML id="xmlData"> <userData email="{email.text}" phone="{phone.text}" city="{city.text}" state="{state.value}" /> </mx:XML>
You can then change the <mx:Binding>
tags as follows:
<mx:Binding source="email.text" destination="xmlData.@email" /> <mx:Binding source="phone.text" destination="xmlData.@phone" /> <mx:Binding source="city.text" destination="xmlData.@city" /> <mx:Binding source="state.value" destination="xmlData.@state" /> <mx:Binding source="'Contact Information Email: ' + xmlData.@email + ' Phone: ' + xmlData.@phone + ' Location: ' + xmlData.@city + ', ' + xmlData.@state" destination="summary.text" /> <mx:Binding source="xmlData.@email" destination="example.saveContactInformation.arguments.email" /> <mx:Binding source="xmlData.@phone" destination="example.saveContactInformation.arguments.phone" /> <mx:Binding source="xmlData.@city" destination="example.saveContactInformation.arguments.city" /> <mx:Binding source="xmlData.@state" destination="example.saveContactInformation.arguments.state" />
The <mx:Binding>
tag
requires more code than the curly brace syntax. Curly brace syntax can
appear inline within existing tags, whereas <mx:Binding>
syntax requires that you
add additional tags to your code. This may seem like a disadvantage at
first; however, in the long run it is advantageous to use <mx:Binding>
in most cases because it
allows you to create a cleaner separation between the UI layout and
the data used. Using the <mx:Binding>
tag is a cleaner
implementation of data binding, yet it does not allow any greater
functionality than curly brace syntax. If you need more functionality
(such as dynamically changing data binding endpoints at runtime), you
can use BindingUtils
, discussed in the next
section.
Although using <mx:Binding>
may have its advantages
(as mentioned in the preceding paragraph), the disadvantage is that
it requires more code to accomplish the same thing as simply using
curly braces within component tags. This is a trade-off that you
must evaluate for yourself.
In most cases, you should use curly brace or <mx:Binding>
syntax for data binding.
However, neither of those techniques let you dynamically configure
data binding at runtime. The mx.binding.utils.BindingUtils
class has a
static method called bindProperty()
that lets you
configure data binding from ActionScript. This ActionScript solution
provides the most flexibility and the lowest-level access to data
binding of all the techniques. As such, the BindingUtils.bindProperty()
method can be a
useful resource in those special cases in which you require more
flexibility than the other techniques afford you.
The syntax for the bindProperty()
method is as
follows:
BindingUtils.bindProperty(destinationObject, destinationProperty, sourceObject, sourceProperty);
The destination and source object parameters are object references and the property parameters are strings. The following example links the value from a combo box so that it displays in a text input:
BindingUtils.bindProperty(textInput, "text", comboBox, "value");
Because BindingUtils
is
ActionScript, you can place the code anywhere that you can place
ActionScript code. The following example uses a button to enable data
binding between a combo box and a text input when the user clicks the
button:
<mx:Script> <![CDATA[ import mx.binding.utils.BindingUtils; ]]> </mx:Script> <mx:VBox> <mx:ComboBox id="comboBox"> <mx:Array> <mx:Object label="1" /> <mx:Object label="2" /> <mx:Object label="3" /> <mx:Object label="4" /> </mx:Array> </mx:ComboBox> <mx:TextInput id="textInput" /> <mx:Button label="enable data binding" click="BindingUtils.bindProperty(textInput, 'text', comboBox, 'value')" /> </mx:VBox>
In the preceding example, the combo box and the text input are
not initially linked. However, when the user clicks on the button, it
calls the bindProperty()
method,
which links the controls such that the combo box value is displayed in
the text input, and the display changes as the value changes. To use
BindingUtils
, you must add an
import
statement, as in the
example.
The bindProperty()
method
returns a reference to a new mx.binding.utils.ChangeWatcher
object. The
ChangeWatcher
class defines a class of objects that represents the actual
data binding link between a source and a destination. Using bindProperty()
by itself allows you to
enable data binding at runtime, but if you want to further modify that
data binding, you’ll have to work with a ChangeWatcher
object. Using a ChangeWatcher
object, you can disable data
binding or change the source point.
The ChangeWatcher
object
returned by a bindProperty()
method
call represents that data binding association, and if you want to
change that association or stop it, you must use the ChangeWatcher
object. You can stop a data
binding association between two points by calling the unwatch()
method of the ChangeWatcher
object:
changeWatcher.unwatch();
You can retrieve the current source value using the getValue()
method:
changeWatcher.getValue();
You can change the source object using the reset()
method. The reset()
method accepts one parameter
specifying the new source object:
changeWatcher.reset(newSourceObject);
The reset()
method does not
allow you to change the property of the source object. If you want to
change the property, you must call unwatch()
to stop the current data binding
association. Then you can start a new association using BindingUtils.bindProperty()
:
changeWatcher.unwatch(); changeWatcher = BindingUtils.bindProperty(newSource, newProperty, destination, destinationProperty);
Example 14-2 uses BindingUtils
and ChangeWatcher
to toggle the source object
between two combo boxes. The example uses two combo boxes, a text input, and a
button. When the application initializes, it calls the initializeHandler()
method as that is
assigned to the Application
initialize handler. The initializeHandler()
method sets data binding
between the level
combo box and the
selectedLevel
text input. When the
user clicks the button, the data binding system calls the toggleDataBinding()
method, which uses the
reset()
method of the ChangeWatcher
object to change the source
object.
Example 14-2. Working with BindingUtils
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="initializeHandler(event)"> <mx:Script> <![CDATA[ import mx.binding.utils.BindingUtils; import mx.binding.utils.ChangeWatcher; private var _changeWatcher:ChangeWatcher; private var _currentHost:ComboBox; private function initializeHandler(event:Event):void { // Set the initial data binding, and assign the ChangeWatcher // object to the _changeWatcher property. _changeWatcher = BindingUtils.bindProperty(selectedLevel, "text", level, "value"); // Save a reference to the current source object. _currentHost = level; } private function toggleDataBinding(event:Event):void { // Determine the new source object. If the current source // object is level, set the new source to subLevel. If the // current source object is subLevel then set the new // source to level. _currentHost = _currentHost == level ? subLevel : level; // Use the reset() method to change the source object. _changeWatcher.reset(_currentHost); // Calling reset() changes the source for the data binding, but it does // not immediately update the destination. For that, you need to // manually update the destination value by retrieving the source value // using the getValue() method of the ChangeWatcher object. selectedLevel.text = _changeWatcher.getValue().toString(); } ]]> </mx:Script> <mx:VBox> <mx:ComboBox id="level"> <mx:Array> <mx:Object label="A" data="1" /> <mx:Object label="B" data="2" /> <mx:Object label="C" data="3" /> <mx:Object label="D" data="4" /> </mx:Array> </mx:ComboBox> <mx:ComboBox id="subLevel"> <mx:Array> <mx:Object label="A" data="1.1" /> <mx:Object label="B" data="1.2" /> <mx:Object label="C" data="1.3" /> <mx:Object label="D" data="1.4" /> </mx:Array> </mx:ComboBox> <mx:TextInput id="selectedLevel" /> <mx:Button label="toggle data binding" click="toggleDataBinding(event)" /> </mx:VBox> </mx:Application>