43
Diving in the Data Binding Waters Michael Labriola Digital Primates

Diving in the Flex Data Binding Waters

Embed Size (px)

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

Page 1: Diving in the Flex Data Binding Waters

Diving in the

Data BindingWaters

Michael LabriolaDigital Primates

Page 2: Diving in the Flex Data Binding Waters

Who are you?

Michael LabriolaSenior Consultant at Digital Primates

Flex GeekComponent DeveloperFlex Team Mentor

Page 3: Diving in the Flex Data Binding Waters

Who were you?

Michael LabriolaSoftware Engineer

Embedded Systems DeveloperReverse Engineer

Page 4: Diving in the Flex Data Binding Waters

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

Page 5: Diving in the Flex Data Binding Waters

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

Page 6: Diving in the Flex Data Binding Waters

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

Page 7: Diving in the Flex Data Binding Waters

Data Binding Defined

Data Binding is the magical process by which changes in a data model are instantly propagated to views.

Page 8: Diving in the Flex Data Binding Waters

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

Page 9: Diving in the Flex Data Binding Waters

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.

Page 10: Diving in the Flex Data Binding Waters

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;}

}

Page 11: Diving in the Flex Data Binding Waters

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); }}

}

Page 12: Diving in the Flex Data Binding Waters

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));

}

}

Page 13: Diving in the Flex Data Binding Waters

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}"/>

Page 14: Diving in the Flex Data Binding Waters

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();

}

Page 15: Diving in the Flex Data Binding Waters

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.)

Page 16: Diving in the Flex Data Binding Waters

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

Page 17: Diving in the Flex Data Binding Waters

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;

Page 18: Diving in the Flex Data Binding Waters

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

Page 19: Diving in the Flex Data Binding Waters

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.

Page 20: Diving in the Flex Data Binding Waters

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]);

Page 21: Diving in the Flex Data Binding Waters

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}"/>

Page 22: Diving in the Flex Data Binding Waters

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.

Page 23: Diving in the Flex Data Binding Waters

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?

Page 24: Diving in the Flex Data Binding Waters

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

Page 25: Diving in the Flex Data Binding Waters

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

Page 26: Diving in the Flex Data Binding Waters

Also

MXML provides better warning and error detection than any of the runtime methods

Page 27: Diving in the Flex Data Binding Waters

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.

Page 28: Diving in the Flex Data Binding Waters

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");

}

Page 29: Diving in the Flex Data Binding Waters

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");}

Page 30: Diving in the Flex Data Binding Waters

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"], ...).

Page 31: Diving in the Flex Data Binding Waters

ChangeWatcher

public function setup():void {

ChangeWatcher.watch(textarea, "text", watchMeAndReact);

}

public function watchMeAndReact(event:Event):void{

myTA1.text="done";

}

You can also unwatch() something..

Page 32: Diving in the Flex Data Binding Waters

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

Page 33: Diving in the Flex Data Binding Waters

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

Page 34: Diving in the Flex Data Binding Waters

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:

Page 35: Diving in the Flex Data Binding Waters

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') );

}

Page 36: Diving in the Flex Data Binding Waters

Not getter/Setter

You will need to broadcast this event somewhere else.

[Bindable(event='myProductNameChanged')]

public var productName:String;

Page 37: Diving in the Flex Data Binding Waters

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') );

}

Page 38: Diving in the Flex Data Binding Waters

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?

Page 39: Diving in the Flex Data Binding Waters

Binding to Unbindable

Putting some of this to use

Page 40: Diving in the Flex Data Binding Waters

Lazy Load

Putting some of this to use

Page 41: Diving in the Flex Data Binding Waters

Random Closing Tips

Any users of describeType out there…. Make sure you use the DescribeTypeCache

var info:BindabilityInfo =

DescribeTypeCache.describeType(parentObj).

bindabilityInfo;

Page 42: Diving in the Flex Data Binding Waters

Q & A

Seriously? You must have some questions by now?

Page 43: Diving in the Flex Data Binding Waters

Resources

Blog Aggregator (All of the Digital Primates)http://blogs.digitalprimates.net/

My Blog Specificallyhttp://blogs.digitalprimates.net/codeSlinger/