Diving in the Flex Data Binding Waters

  • View
    17.060

  • Download
    1

  • Category

    Business

Preview:

DESCRIPTION

In depth overview of the Flex data binding code generation. Provides info on accomplish data binding through actionscript as well as limitations of the process.

Citation preview

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/

Recommended