|
|
|
This article is sponsored by Adobe.
|
|
Advertisements
|
||
|
Summary
Data binding is among the most common UI programming tasks. Although the open-source Flex framework has supported data binding since its inception, the latest Flex 4 SDK adds two-way data binding as well. This tutorial illustrates two-way data binding in Flex 4.
A key concept in object-oriented programming centers around an object encapsulating some state, and then notifying other interested objects when that internal state changes. User interfaces represent some of the best examples of stateful object-oriented design in part because it is natural to model a UI component as an object with state. A button component, for instance, might have a state indicating whether the button is pressed or released, the text the button displays, and so on.
As a result of modeling UI components with objects, one of the most common GUI programming tasks involves arranging for an object to register interest in changes in another object's state and, concomitantly, for one object to notify others of its internal state changes. A user clicking on a button component, for instance, would cause that button to enter a "pressed" state; other interested components and objects in the GUI application might want to be notified of that state change.
Linking object properties in that manner is such a frequent programming task that a UI toolkit should ideally support some form of syntax to make data binding less tedious: Given an object and a property of that object, it should be possible to easily arrange for objects to be notified upon changes to that property. Such notification could take the form of a method invocation or direct updates to a designated property of a target object with the source object's new property value.
Flex has provided one-way data binding since its inception, and Flex 4 adds two-way data binding as well: Flex 4's two-way data binding provides special syntax for specifying that a pair of object properties should always update each other.
A good use-case for two-way data binding is a Fahrenheit to Celsius converter application. Such an application presents two text input fields—one for entering a Fahrenheit value and the other for entering a Celsius degree—and typically presents a button component as well. When the user presses the button, the application converts the Fahrenheit to Celsius degrees and vice versa.
It would be more convenient to use this application without having to
press a button to initiate the conversion: Simply typing a new value
into either text box should update the value in the other text box. In
other words, the text property of the Celsius field should
be bound to the text property of the Fahrenheit field, and
vice versa.
Arranging for the two properties to mutually update each other required two separate data binding steps in Flex 3:
<TextInput id="fahrenheit" text="{celsius.text}"/>
<TextInput id="celsius" text="{fahrenheit.text}">
In Flex 4, this can be accomplished with a single expression:
<TextInput id="fahrenheit" text="@{celsius.text}"/>
<TextInput id="celsius">
The special syntax works by placing an @ in front of one
of the bound field values, and then leaving the other field unbound.
At this point, typing a value in one text field is mirrored in the other text field.
In order to implement the Fahrenheit to Celsius conversion, it is helpful to introduce a helper object that performs the numeric conversion. ActionScript's support for first-class properties makes implementing such a helper object easy.
Properties in ActionScript are supported with special syntax: a
get following the function keyword to read a
property value, and a set following the function keyword to
set a property value. An implementation of a Fahrenheit-to-Celsius
converter helper object is as follows:
package com.artima {
import flash.events.Event;
import flash.events.EventDispatcher;
[Bindable]
public class DegreeConverter extends EventDispatcher {
private var fahrenheitDegree: Number;
private var celsiusDegree: Number;
public function set fahrenheit(n: Number): void {
fahrenheitDegree = n;
celsiusDegree = (fahrenheitDegree - 32) * 5/9;
dispatchEvent(new Event("celsiusChanged", true, true));
}
[Bindable(event="fahrenheitChanged")]
public function get fahrenheit(): Number {
return fahrenheitDegree;
}
public function set celsius(n: Number): void {
celsiusDegree = n;
fahrenheitDegree = celsiusDegree * 9/5 + 32;
dispatchEvent(new Event("fahrenheitChanged", true, true));
}
[Bindable(event="celsiusChanged")]
public function get celsius(): Number {
return celsiusDegree;
}
}
}
This implementation maintains the Fahrenheit and Celsius values as
instance variables, and declares properties for each. When one of the
properties is updated via the property's set method, the
other instance variable is updated with the appropriate value.
This implementation also specifies data binding via the
Bindable annotation. This is necessary, because setting the
Fahrenheit value must cause the UI to update the Celsius value as well,
and vice versa. This class uses internal events to signal such updates:
Updating the Fahrenheit property's set method updates both the
fahrenheitDegree and celsiusDegree instance
variables, and then dispatches an event to signal that the Celsius
property was updated as well.
The Celsius property, in turn, is bound to the
celsiusChanged event: Any object listening to the
DegreeConverter's celsius property is notified when that
event dispatches.
The DegreeConverter class can be used in the following
Flex 4 application:
<?xml version="1.0" encoding="utf-8"?<
<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/halo" minWidth="1024"
minHeight="768" xmlns:artima="com.artima.*">
<fx:Declarations>
<artima:DegreeConverter id="converter"/>
</fx:Declarations>
<s:VGroup>
<mx:Label text="Calculator" fontWeight="bold" fontSize="32"/>
<mx:Form>
<mx:FormItem label="Fahrenheit:">
<mx:TextInput text="@{converter.fahrenheit}"/>
</mx:FormItem>
<mx:FormItem label="Celsius:">
<mx:TextInput text="@{converter.celsius}"/>
</mx:FormItem>
</mx:Form>
</s:VGroup>
</s:Application>
This example declares an instance of DegreeConverter and
associates it with the id converter. In Flex 4, such
declarations must be placed inside the Declarations MXML
tag. The two text input fields each declare two-way data binding on the
converter's respective properties: This allows those fields
not only to set the property values, but also to listen to changes in
those properties.
The result is that the Celsius field updates immediately as a user types a value in the Fahrenheit field and, vice versa, the Fahrenheit field updates as soon as the user types in a Celsius value.
You may wonder at this point why the program does not enter into an
infinite loop. After all, updating a Fahrenheit value causes a new
Celsius value to be entered in the Celsius field: Should that new value
not then cause an update to the converters
celsius property, resulting in another round of event
dispatch and property updates, and so on? Such circular updates don't
occur because the Flex data binding framework is smart enough to stop
such event propagations at the appropriate level. In addition, the text
input field in Flex only fires an update event when the input value was
changed via a UI (by the user), and not when a change in the displayed
value occurred as a result of updates to a data model, such as
converter.
Have an opinion about two-way data binding in Flex 4? Discuss this article in the Articles Forum topic, Two-Way Data Binding in Flex 4.
Adobe's Flash Builder 4
http://labs.adobe.com/technologies/flashbuilder4
Flex 4 SDK
http://opensource.adobe.com/wiki/display/flexsdk/Flex+SDK
Gumbo Project
http://opensource.adobe.com/wiki/display/flexsdk/Gumbo
Flex.org
http://www.flex.org
Frank Sommers is Editor-in-Chief of Artima.
|
This article is sponsored by Adobe.
|