Diving in the Flex Data Binding Waters
-
Upload
michaellabriola -
Category
Business
-
view
17.060 -
download
1
description
Transcript of Diving in the Flex Data Binding Waters
Diving in the
Data BindingWaters
Michael LabriolaDigital Primates
Who are you?
Michael LabriolaSenior Consultant at Digital Primates
Flex GeekComponent DeveloperFlex Team Mentor
Who were you?
Michael LabriolaSoftware Engineer
Embedded Systems DeveloperReverse Engineer
What is this session about?
This session is part of my continuing quest to teach Flex from the inside out.
Learn what the Flex framework is really doing and you are more likely to use it successfully, respect its boundaries and extend it in useful ways
One more reason
Let’s call it “Game Theory”.
If you know how something works really well, you know which rules you can bend and just how far you can bend them before they break.
Sometimes you can even find really creative ways out of difficult situations
Standard Disclaimer
I am going to lie to you a lot… a whole lot
Even at this ridiculous level of detail, there is much more
All of this is conditional. So, we are just going to take one route and go with it
Data Binding Defined
Data Binding is the magical process by which changes in a data model are instantly propagated to views.
Now Really Defined
Data Binding is not magic
It is a relatively complicated combination of generated code, event listeners and handlers, error catching and use of meta data through object introspection
Still on the Soap Box
Data Binding works because Flex (which I am generically using here to mean precompiler, compiler and framework) generates a lot of code on your behalf.
Transformation
When you use Data Binding, the Flex compiler generates code for you.. A lot of code
So, the following example becomes..
package valueObject {[Bindable]public class Product {
public var productName:String;}
}
Generated Codepackage valueObject {
import flash.events.IEventDispatcher;
public class ProductManualBinding implements IEventDispatcher { private var dispatcher:flash.events.EventDispatcher = new flash.events.EventDispatcher(flash.events.IEventDispatcher(this));
[Bindable(event="propertyChange")] public function get productName():String { return _productName; }
public function set productName(value:String):void { var oldValue:Object = _productName; if (oldValue !== value) { _productName = value; dispatchEvent(mx.events.PropertyChangeEvent.createUpdateEvent(this, "productName", oldValue, value)); } }
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, weakRef:Boolean = false):void { dispatcher.addEventListener(type, listener, useCapture, priority, weakRef); }
public function dispatchEvent(event:flash.events.Event):Boolean { return dispatcher.dispatchEvent(event); }
public function hasEventListener(type:String):Boolean { return dispatcher.hasEventListener(type); }
public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { dispatcher.removeEventListener(type, listener, useCapture); }
public function willTrigger(type:String):Boolean { return dispatcher.willTrigger(type); }}
}
Most Importantlypublic class ProductManualBinding implements IEventDispatcher {
private var dispatcher:EventDispatcher = new EventDispatcher(IEventDispatcher(this));
[Bindable(event="propertyChange")]
public function get productName():String {
return _productName;
}
public function set productName(value:String):void {
var oldValue:Object = _productName;
if (oldValue !== value) {
_productName = value;
dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "productName", oldValue, value));
}
}
Only Half of the Equation
Data Binding is about changing a model and having the view react
So, the generated code for the following view becomes…
[Bindable]private var product:Product;<mx:Label id="myLbl"
text="{product.productName}"/>
The Other 7/8override public function initialize():void {
var bindings:Array = [];
var binding:Binding;
binding = new mx.binding.Binding(this,
function():String {
var result:* = (product.productName);
var stringResult:String = (result == undefined ? null : String(result));
return stringResult;
},
function(_sourceFunctionReturnValue:String):void {
myLabel.text = _sourceFunctionReturnValue;
},
"myLabel.text");
bindings[0] = binding;
var watchers:Array = [];
watchers[0] = new mx.binding.PropertyWatcher("product",{propertyChange: true},[ bindings[0] ], function(propertyName:String):* { return target[propertyName]; } );
watchers[1] = new mx.binding.PropertyWatcher("productName",{productNameChanged: true}, [bindings[0]],null);
watchers[0].updateParent(target);
watchers[0].addChild(watchers[1]);
for (var i:uint = 0; i < bindings.length; i++) {
Binding(bindings[i]).execute();
}
mx_internal::_bindings = mx_internal::_bindings.concat(bindings);
mx_internal::_watchers = mx_internal::_watchers.concat(watchers);
super.initialize();
}
Starting at the Top
The generated code overrides the initialization function to add all of the generated code into startup
The first relevant thing it does for us it to create an Array of mx.binding.Binding objects. These objects are responsible for executing bindings.. (moving values from the binding source to the destination.)
mx.binding.Binding
Instances of this class accept a document, srcFunc, destFunc and destString as parameters.
The document is the target of the work. The srcFunc returns the value used in the binding. The destFunc assigns it to the destination. The destString is the destination represented as a String… more on that later
Binding in our Example
var bindings:Array = [];var binding:Binding;
binding = new mx.binding.Binding(this, function():String { var result:* = (product.productName); var stringResult:String = (result == undefined ? null : String(result)); return stringResult; }, function(_sourceFunctionReturnValue:String):void { myLabl.text = _sourceFunctionReturnValue; }, “myLbl.text");bindings[0] = binding;
Watchers
Still in the initialize method, the generated code creates an array of mx.binding.PropertyWatcher objects
The objects are responsible for noticing a change and, among other things, notifying the binding objects that they should execute
mx.binding.PropertyWatcher
Instances of this class accept the propertyName, an object that indicates which events are broadcast when the property has changed, an array of listeners and a propertyGetter function
The listeners are any Binding instances created for the property. In this case, the property getter is an anonymous function that returns the value of the property binding.
Watchers in our Example
watchers[0] = new mx.binding.PropertyWatcher("product",
{propertyChange: true},[ bindings[0] ], propertyGetter );
watchers[1] = new mx.binding.PropertyWatcher("productName",
{productNameChanged: true}, [bindings[0]],null);
watchers[0].updateParent(target);
watchers[0].addChild(watchers[1]);
Chains
Data Binding Expressions are rarely simple names of properties. They are often chains.
For example:<mx:Text id="myText" text="{user.name.firstName.text}"/>
Execution
After the watchers are setup, the generated initialize function loops through all of the Binding objects and calls their execute() method.
This method cautiously attempts to set the destination value to the source value, first ensuring that we aren’t already in process or an in an infinite loop.
Value Changed
One important thing to note about this process which often trips up new users to databinding:
A value on an object is only set if it is differnet(oldValue !== value)
What impact does this have on Objects? Arrays?
Ways to Bind
This explains how binding is setup if the bindings are declared in MXML. There are ways to handle binding in ActionScript:
mx.binding.utils.BindingUtilsmx.binding.utils.ChangeWatcher.Manually adding event listeners
The differences
You cannot: include ActionScript code in a data binding expression defined in ActionScript.
include an E4X expression in a data binding expression defined in ActionScript.
include functions or array elements in property chains in a data binding expression defined this way
Also
MXML provides better warning and error detection than any of the runtime methods
BindingUtils
BindingUtils provides two methods which do this work for you at runtime
bindProperty and bindSetter
The first one is used with public properties. The second is used with getter/setters.
bindProperty Syntax
public static function bindProperty(site:Object, prop:String, host:Object, chain:Object, commitOnly:Boolean = false):ChangeWatcher
For example:
public function setup():void {
BindingUtils.bindProperty(someOtherTextFiled, “text”, someTextInput, "text");
}
bindSetter Syntax
public static function bindSetter(setter:Function, host:Object, chain:Object, commitOnly:Boolean = false):ChangeWatcher
For example:public function updateIt(val:String):void { someOtherTextFiled.text = val.toUpperCase();}
public function setup():void { BindingUtils.bindSetter(updateIt, someTextInput,
"text");}
The ChainThe chain is a rather complex parameter that can take on
multiple forms… for instance, it can be a:String containing the name of a public bindable property
of the host object.
An Object in the form: { name: property name, getter: function(host) { return host[property name] } }.
A non-empty Array containing a combination of the first two options that represents a chain of bindable properties accessible from the host. For example, to bind the property host.a.b.c, call the method as: bindProperty(host, ["a","b","c"], ...).
ChangeWatcher
public function setup():void {
ChangeWatcher.watch(textarea, "text", watchMeAndReact);
}
public function watchMeAndReact(event:Event):void{
myTA1.text="done";
}
You can also unwatch() something..
Manual Event Listeners
You could, but…
The data binding code swallows a bunch of errors on your behalf, to handle things like null values, etc… your code will crash if you don’t take the same care
What does this mean?
Binding is just a contract between two objects.
One object explains that it will broadcast an event when it changes and details what event that will be
Another object waits for that event to occur and updates the destination when it occurs
propertyChange
Even though the propertyChange is the default event that Flex uses when you auto-generate binding code, you can change it if you use your own getters and setters.
For example:
Not propertyChangeprivate var _productName:String;
[Bindable(event='myProductNameChanged')]public function get productName():String {
return _productName;}
public function set productName( value:String ):void {_productName = value;dispatchEvent( new Event('myProductNameChanged') );
}
Not getter/Setter
You will need to broadcast this event somewhere else.
[Bindable(event='myProductNameChanged')]
public var productName:String;
Double Downprivate var _productName:String;
[Bindable(event='serverDataChanged')][Bindable(event='myProductNameChanged')]public function get productName():String {
return _productName;}
public function set productName( value:String ):void {_productName = value;dispatchEvent( new Event('myProductNameChanged') );
}
Models, Oh Models
The propertyChange event is broadcast by default for every property setter that is auto-generated
How do you think that scales in a giant application model?
What happens?
Binding to Unbindable
Putting some of this to use
Lazy Load
Putting some of this to use
Random Closing Tips
Any users of describeType out there…. Make sure you use the DescribeTypeCache
var info:BindabilityInfo =
DescribeTypeCache.describeType(parentObj).
bindabilityInfo;
Q & A
Seriously? You must have some questions by now?
Resources
Blog Aggregator (All of the Digital Primates)http://blogs.digitalprimates.net/
My Blog Specificallyhttp://blogs.digitalprimates.net/codeSlinger/