Home > Series > Data Binding Series

Data Binding Series

Ever {wonder} how data binding works? The magic of data binding is a combination of code generation by the flex pre-compiler and the Flex event model, which is an implementation of the observer pattern.

Lets start with a short description and overview of the observer pattern and events:

The observer pattern has two actors: the publisher, also known as the subject, and one or more subscribers, also known as the dependents, who register one or more methods to be invoked by the publisher at some point in the future.

In Flex terms, this would be any IEventDispatcher. When you say obj.addEventListener(blah, onWhatever), obj is the publisher of blah and the class adding the event listener (onWhatever callback) with obj is the dependent. An example:

Car.as, the subscriber or dependent, manually set up to listen for an event.

package examples
{
	import flash.events.Event;
	
	public class Car
	{
		private var _wheels:Vector.<Wheel>;
		
		public function Car()
		{
			_wheels = new Vector.<Wheel>();
			for ( var i:int = 0; i < 4; i++ )
			{
				var wheel:Wheel = new Wheel();
				wheel.addEventListener(Wheel.ROTATE, onWheelRoll)
				_wheels.push( wheel );
			}
		}
		
		private function onWheelRoll(e:Event):void
		{
			
		}
	}
}

Wheel.as, the publisher or subject, manually set up to dispatch an event.

package examples
{
	import flash.events.Event;
	import flash.events.EventDispatcher;

	[Event(name="rotate", type="flash.events.Event")]
	public class Wheel extends EventDispatcher
	{
		public static const ROTATE:String = "rotate";
		
		private var _rotation:Number;
		
		public function Wheel()
		{
		}

		public function get rotation():Number
		{
			return _rotation;
		}
		public function set rotation(value:Number):void
		{
			_rotation = value;
			dispatchEvent( new Event(ROTATE) );
		}
	}
}

Now that we have understanding of Flex events as an implementation of the Observer pattern, you will see that data binding is nothing more than a series of callbacks getting notified when an interest changes.

Just as there are two parts to the observer pattern, there are two parts to data binding; the subject and dependent. So how does the {binding syntax} get turned into something similar of the manual job seen in Car and Wheel above? Lets start with the subject.

Revised Wheel to make it bindable:

package examples.two
{
	[Bindable]
	public class Wheel
	{	
		public var rotation:Number = 0;
		
		public function Wheel()
		{
		}
	}
}

A revised Car implementation as a bindable dependent.

<?xml version="1.0" encoding="utf-8"?>
<s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" 
		 xmlns:s="library://ns.adobe.com/flex/spark" 
		 xmlns:mx="library://ns.adobe.com/flex/mx"
		 xmlns:two="examples.two.*">
	
	<fx:Declarations>
		<two:Wheel id="one"/>
		<two:Wheel id="two"/>
		<two:Wheel id="three"/>
		<two:Wheel id="four"/>
	</fx:Declarations>
	
	<fx:Script>
		<![CDATA[
			public function moveForward():void
			{
				for each ( var wheel:Wheel in [one, two, three, four] )
				{
					wheel.rotation++;
				}
			}
		]]>
	</fx:Script>
	
	<s:Label text="Wheel 1 rotation: {one.rotation}, Wheel 2 rotation: {two.rotation}, Wheel 3 rotation: {three.rotation}, Wheel 4 rotation: {four.rotation}"/>
	<s:Button label="Move Forward" click="moveForward();"/>
</s:VGroup>

Lets use the -keep compiler flag and see the generated code. I’ve removed lots of comments and cut out some things just to make it readable here.

class BindableProperty implements flash.events.IEventDispatcher
{
	
    [Bindable(event="propertyChange")]
    public function get rotation():Number
    {
        return this._rotation;
    }
    public function set rotation(value:Number):void
    {
    	var oldValue:Object = this._rotation;
        if (oldValue !== value)
        {
            this._rotation = value;
           if (this.hasEventListener("propertyChange"))
               this.dispatchEvent(mx.events.PropertyChangeEvent.createUpdateEvent(this, "rotation", oldValue, value));
        }
    }
	
	//Removed implementation of IEventDispatcher

}

There are a few things going on here, first of which is that a PropertyChangeEvent is dispatched when the value of rotation changes. If you’ve ever wondered why when you set a property on a bindable class that didn’t get updated in the view, 99% of the time, this is the reason. There are performance implications (in addition to the implications we will go over later) if the event was fired every time the property was set regardless of whether or not it was different, but if you know how binding works and set up the piping manually, as opposed to the binding syntax, you can manipulate “rules” such as these.

Next in the series we will learn about the overhead and some of the pitfalls of using binding, tools that binding uses such as ChainWatcher and BindingUtils, how binding works on properties or objects that simply don’t exist or are invalid, and how to customize the events that are dispatched by the subject when a bindable property is set.

Just for fun and if you don’t feel like browsing through the generated code, here is the Car MXML after my cleanup of the pre-compiler:


public class Car extends spark.components.VGroup implements mx.binding.IBindingClient
{

    public var _Car_Label1 : spark.components.Label;

    [Bindable] public var four : examples.two.Wheel;

    [Bindable] public var one : examples.two.Wheel;

    [Bindable] public var three : examples.two.Wheel;

    [Bindable] public var two : examples.two.Wheel;

	public function Car()
    {
        super();
		var bindings:Array = _Car_bindingsSetup();
		var watchers:Array = [];

		var target:Object = this;

		if (_watcherSetupUtil == null)
		{
			var watcherSetupUtilClass:Object = getDefinitionByName("_examples_two_CarWatcherSetupUtil");
			watcherSetupUtilClass["init"](null);
		}

		_watcherSetupUtil.setup(this,
					function(propertyName:String):* { return target[propertyName]; },
					function(propertyName:String):* { return Car[propertyName]; },
					bindings,
					watchers);

		mx_internal::_bindings = mx_internal::_bindings.concat(bindings);
		mx_internal::_watchers = mx_internal::_watchers.concat(watchers);
		
        this.mxmlContent = [_Car_Label1_i(), _Car_Button1_c()];
        _Car_Wheel4_i();
        _Car_Wheel1_i();
        _Car_Wheel3_i();
        _Car_Wheel2_i();

		for (var i:uint = 0; i < bindings.length; i++)
		{
			Binding(bindings[i]).execute();
		}


    }
 
    private var __moduleFactoryInitialized:Boolean = false;
 
    override public function set moduleFactory(factory:IFlexModuleFactory):void
    {
        super.moduleFactory = factory;
        
        if (__moduleFactoryInitialized)
            return;

        __moduleFactoryInitialized = true;                         
    }

	public function moveForward():void
	{
		for each ( var wheel:Wheel in [one, two, three, four] )
		{
			wheel.rotation++;
		}
	}

	private function _Car_Wheel4_i() : examples.two.Wheel
	{
		var temp : examples.two.Wheel = new examples.two.Wheel();
		four = temp;
		mx.binding.BindingManager.executeBindings(this, "four", four);
		return temp;
	}
	
	private function _Car_Wheel1_i() : examples.two.Wheel
	{
		var temp : examples.two.Wheel = new examples.two.Wheel();
		one = temp;
		mx.binding.BindingManager.executeBindings(this, "one", one);
		return temp;
	}
	
	private function _Car_Wheel3_i() : examples.two.Wheel
	{
		var temp : examples.two.Wheel = new examples.two.Wheel();
		three = temp;
		mx.binding.BindingManager.executeBindings(this, "three", three);
		return temp;
	}
	
	private function _Car_Wheel2_i() : examples.two.Wheel
	{
		var temp : examples.two.Wheel = new examples.two.Wheel();
		two = temp;
		mx.binding.BindingManager.executeBindings(this, "two", two);
		return temp;
	}
	
	private function _Car_Label1_i() : spark.components.Label
	{
		var temp : spark.components.Label = new spark.components.Label();
		temp.id = "_Car_Label1";
		if (!temp.document) temp.document = this;
		_Car_Label1 = temp;
		mx.binding.BindingManager.executeBindings(this, "_Car_Label1", _Car_Label1);
		return temp;
	}
	
	private function _Car_Button1_c() : spark.components.Button
	{
		var temp : spark.components.Button = new spark.components.Button();
		temp.label = "Move Forward";
		temp.addEventListener("click", ___Car_Button1_click);
		if (!temp.document) temp.document = this;
		mx.binding.BindingManager.executeBindings(this, "temp", temp);
		return temp;
	}

	public function ___Car_Button1_click(event:flash.events.MouseEvent):void
	{
		moveForward();
	}

    private function _Car_bindingsSetup():Array
    {
        var result:Array = [];

        result[0] = new mx.binding.Binding(this,
            function():String
            {
                var result:* = "Wheel 1 rotation: " + (one.rotation) + ", Wheel 2 rotation: " + (two.rotation) + ", Wheel 3 rotation: " + (three.rotation) + ", Wheel 4 rotation: " + (four.rotation);
                return (result == undefined ? null : String(result));
            },
            null,
            "_Car_Label1.text"
            );
        return result;
    }

    public static function set watcherSetupUtil(watcherSetupUtil:IWatcherSetupUtil2):void
    {
        (Car)._watcherSetupUtil = watcherSetupUtil;
    }

    private static var _watcherSetupUtil:IWatcherSetupUtil2;
    mx_internal var _bindings : Array = [];
    mx_internal var _watchers : Array = [];
    mx_internal var _bindingsByDestination : Object = {};
    mx_internal var _bindingsBeginWithWord : Object = {};
}
Advertisements
  1. March 16, 2011 at 3:48 PM

    My understanding is that [Binding] will, in fact, test for the new value == the existing value. It does this for bound variables as well as bound properties (i.e. accessors and mutators).

    Important tip: do not reference the mutator (e.g. to set a default value) from an accessor: the mutator calls the accessor to make this optimization comparison, so going into a loop is easy.

    Coming from an old-school background (i.e. Delphi), I tend to put the optimization test into mutators myself:

    public function set foo(value : String) : void
    {
    if (_foo != value)
    {
    _foo = value;
    /// side effect of change
    }
    }

    so dispatching my own event is easy if I want to make the [Binding(“change”)] myself.

    Cheers

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: