Saturday, June 4, 2011

Flex: Two Way Data Binding in Flex 4

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.

No comments :

Post a Comment