DataBinding under the hood (part 2): Features
There are many different ways how to use DataBinding.
Beyond the obvious ones there are a few more which could be a nice feature for some weird hacks…
Flex gives us 4 different techniques to use DataBinding:
- Curly Brackets in MXML
- Binding tag
- BindingUtils
- ChangeWatcher
Lets have an overview of the possibilities of each technique:
Curly Brackets in MXML
The most obvious one: Binding a source value to a destination value:
This works also for readOnly properties and static constants. You can omit the Bindable metadata tag because the Binding is only triggered at application startup. Static variables are not supported (the Adobe docs are not correct at this). Also Binding to style properties are not supported.
To add 2 way Binding simply use the curly brackets syntax in both objects crosswise:
<mx:TextInput id="input3" text="{input4.text}"/> <mx:TextInput id="input4" text="{input3.text}"/>
Here some code examples for Actionscript and E4X expressions:
// Method calls and calculations <mx:Text text="{ Math.round(Number(myTI.text)) * 6 / 7 }" /> // Perform string concatenation <mx:Text text="Cool stuff for lazy {myTI.text}" /> // Perform a conditional operation using a ternary operator <mx:Text text="{(isMale.selected) ? 'Mr.' : 'Ms.'} {myTI.text}" /> // E4X expressions <mx:List dataProvider="{data.item.(@id=='36' || @id=='59').desc}"/>
A nice feature which is probably not typically used in your daily work, is the possibility to bind to functions. You can use a function as source for your Binding:
To make this work you have to mark the function with the Bindable metadata tag and define your custom event name. When you dispatch an Event with your selected event name the Binding is triggered and the destination gets the return value of your function.
<!--[CDATA[ [Bindable (event="checkBoxSelected")] private function isSelected():String { return cb.selected.toString(); } private function checkBoxSelected():void { dispatchEvent(new Event("checkBoxSelected")); } ]]> </mx:Script> <mx:CheckBox id="cb" click="checkBoxSelected()" /> <mx:TextArea id="myTA11" text="{isSelected()}" />
Another way is to use bindable properties as arguments to the function. When the property change the function is triggered.
<mx:CurrencyFormatter id="usdFormatter" precision="2" currencySymbol="$" alignSymbol="left" /> <mx:TextInput id="myTI10" text="Enter number here" /> <mx:TextArea text="{usdFormatter.format(myTI10.text)}" />
In an MXML file, you can make all public properties that you defined as variables usable as the source for data binding by including the [Bindable] metadata tag in an Metadata block. This is similar to the [Bindable] tag over the class definition in Actionscript.
<mx:Metadata> [Bindable] </mx:Metadata>
There is only one scenario where the curly brackets in MXML are not enough: If you want to use multiple sources for a destination.
If you need this you can move over to Binding tags:
<mx:Binding source="input2.text" destination="myTA3.text" /> <mx:Binding source="input1.text" destination="myTA3.text" /> <mx:TextInput id="input1" /> <mx:TextInput id="input2" /> <! you can also combine curly brackets with the Binding tag --> <!--<mx:TextArea id="myTA3" text="{input1.text}" />-->
As we have seen in the previous post Binding in MXML is not the fastest solution.
Your better choice for writing high performacne applications is BindingUtils:
You have 2 static methods: BindingUtils.bindProperty and BindingUtils.bindSetter
<mx:Script> <![CDATA[ private function onPreInitialize(event:FlexEvent):void { BindingUtils.bindProperty(myText5, "text", myTI5, "text"); } private function onPreInitialize2(event:FlexEvent):void { BindingUtils.bindSetter(setMyText6, myTI6, "text"); } private function setMyText6(value:String):void { myText6.text = value; } ]]> </mx:Script> <mx:TextInput id="myTI5"/> <mx:Text id="myText5" preinitialize="onPreInitialize(event);"/> <mx:TextInput id="myTI6"/> <mx:Text id="myText6" preinitialize="onPreInitialize2(event);"/>
The name of the arguments in the API are a little bit confusing (at least for me).
The first 2 parameters are the destination object and property name, and the last 2 the source object and property name.
- site:Object -> destination object
- prop:String -> destination property name
- host:Object -> source object
- chain:Object -> source property name(s)
Binding supports changes on every bindable element in the source chain. If one element is changing the Binding is triggered.
You can specify the chain as a single String for the name of your property (if there is only one chain element) or as Array of property name Strings for each bindable chain element.
// bind to object: user.setting.volume BindingUtils.bindProperty(myText0, "text", user, ["setting", "volume"]);
But there is another possibility to set the chain argument:
You can pass an Object in the form:
{ name: propertyName, getter: function(host) { return host[propertyName] } }
This Object must contain the name of the property and a function returning a value, which is normally a getter function for a public bindable property of the source object.
This feature opens interesting possibilities.
The function is basically a wrapper which returns the value of the source. I am not sure at the moment where to use this, but it´s good to know that there is an open door…
When using chains with BindingUtils you can choose if the chain elements in-between are bindable.
If an element in-between is not bindable, no Binding is triggered if this element changes.
This is not the case when you use the chains in MXML. You got a compiler warning and a runtime exception if one of the in-between elements are not marked as bindable.
Here is a code example for using chains:
<!-- Chains in MXML --> public class User { [Bindable] public var setting:Setting; public function User(setting:Setting) { this.setting = setting; } } public class Setting { [Bindable] public var title:String = "blabla"; public function Setting() { setInterval(function():void { title = int(Math.random()*10000).toString() }, 100); } }
The ChangeWatcher is the class used inside of BindingUtils. ChangeWatcher implements the event handling and the handling for the chains. BindingUtils are just a more convenience way and adds nearly no overhead compared to use ChangeWatcher directly.
Here are some additional snippets about DataBinding:
You can use multiple [Bindable] tags. Doing this lets you trigger Binding from different events.
When you use Binding in MXML the addEventListener method uses a weak reference.
This is important due the fact that you cannot unbind with the curly brackets syntax. Without weak reference Binding in MXML would be a potential memory leak.
When using BindingUtils you are responsible for calling the unwatch() method on your ChangeWatcher object (a ChangeWatcher object is the return value of BindingUtils.bindProperty). The addEventListener method in the ChangeWatcher is not using weak references, so if you don´t do your housekeeping carefully, this could lead to a memory leak.
In UIComponent there is the method executeBindings() which force a trigger to all Bindings on this component. This could help sometimes when a Binding is not firing by it´self correctly, but of course should be taken carefully because it´s probably just healing a symptom not the underlying problem.
When you use the [Bindable] metadata tag before a class definition, it only applies to public properties; it does not apply to private or protected properties, or to properties defined in any other namespace. You must insert the [Bindable] metadata tag before a nonpublic property to make it usable as the source for a data binding expression.
The DataBinding reference can be found at Adobe LiveDocs.
Category: Actionscript, Flash, Flex
No comments yet.