In Part I of this series, I talked about some of the generated code that supports data binding in Flex. In this part, I want to take a look at a few more facets of data binding, but mainly as they can be used within MXML.
So, without further ado, here’s the example I’ll be working with. This example includes all the generated code so everything I’ll be discussing should be available to follow along with.
I basically have two text input fields, src and dst, and a single label, lbl that will change based on the value of the src field. The code looks like this:
... [Bindable] public var bool:Boolean; ... <mx:HBox> <mx:Label text="Source Field:"/> <mx:TextInput id="src" width="219"/> </mx:HBox> <mx:HBox> <mx:Label text="Destination Field:"/> <mx:TextInput id="dst" width="219"/> </mx:HBox> ... <mx:Label id="lbl" text="[Source Length] {src.text.length} > 5 : {bool}"/>
That’s pretty simple. The last line above might raise some questions. It references two different bindable values, the first value src.text.length is a bindable property chain that will cause the label to change if any of the properties change. In addition, any changes to the bool property will cause lbl to be updated. I’ll come back to this in a little more detail.
I also have three data binding lines in the MXML:
<mx:Binding source="src.text" destination="dst.text"/> <mx:Binding source="dst.text" destination="src.text"/> <mx:Binding source="src.text.length > 5" destination="bool" />
The first data binding line above will cause any changes in either the src property or its text property to cause the destination to be changed. This happens by generating code that’s virtually identical to the generated code that we saw in Part I. In viewing the source for _DataBinding2WatcherSetupUtil.as we see the following:
// writeWatcher id=1 shouldWriteSelf=true class=flex2.compiler.as3.binding.PropertyWatcher shouldWriteChildren=true watchers[1] = new mx.binding.PropertyWatcher("src", { propertyChange: true } ); // writeWatcher id=2 shouldWriteSelf=true class=flex2.compiler.as3.binding.PropertyWatcher shouldWriteChildren=true watchers[2] = new mx.binding.PropertyWatcher("text", { textChanged: true, change: false } );
At this point, we have two property watchers that are monitoring the src and text properties, but now we need to know what happens once they have noticed a change:
// [... snip ...] tempWatcher = watchers[1]; tempWatcher.addListener(bindings[2]); tempWatcher.addListener(bindings[3]); tempWatcher.addListener(bindings[0]); // [... snip ...] tempWatcher = watchers[2]; tempWatcher.addListener(bindings[2]); tempWatcher.addListener(bindings[3]); tempWatcher.addListener(bindings[0]); watchers[1].addChild(watchers[2]);
Now we have added bindings 2, 3, and 0 as responders. Each of these bindings listens for property changes and, when they are notified of the property change update appropriately. But, what exactly are bindings 2, 3, and 0?
In the _DataBinding2_bindingsSetup() function in DataBinding2-generated.as we can see exactly what the bindings are.
bindings[0] sets the data binding on dst.text, bindings[1] sets the data binding on src.text.
One of the more interesting things about this is that at this point you might expect that a change to src.text would then ellicit a change in dst.text which would in turn cause another change in src.text thereby causing a circular loop. Luckily — this doesn’t happen.
Two protections are in place to prevent this. First, the automatically generated setters and getters check to see if the value really has changed before dispatching a property change event (see _DataBinding2-binding-generated.as, and second, setters are smart. To summarize the latter, when invoking a setter, the getter is called and it’s value compared to the new value. If the value has changed, then the setter code will be invoked, otherwise it will not. For more information see Good, Smart Setter.
The supporting generated code is almost immediately following the bindings[0] declaration:
binding.twoWayCounterpart = _bindings[0]; _bindings[0].twoWayCounterpart = binding; _bindings[1] = binding;
Thus, bindings[1] knows that it is a two-way binding and knows who it’s counterpart is. Right after the two-way binding is setup, bindings 2 and 3 are defined. bindings[2] assigns bool the value of src.text.length > 5, and bindings[3] assigns the generated string "[Source Length]..." to lbl.text.
In order for these to happen in the right order, some static analysis must take place, but as the generated code is correct, it will work perfectly. If the order had been reversed and the bindings[3] took place before bindings[2], lbl.text would display an incorrect value for bool.
To be a little more explicit about how lbl.text is set, let’s take a look at the generated ActionScript:
binding = new mx.binding.Binding(this, function():String { var result:* = "[Source Length] " + (src.text.length) + " > 5 : " + (bool); var stringResult:String = (result == undefined ? null : String(result)); return stringResult; }, function(_sourceFunctionReturnValue:String):void { lbl.text = _sourceFunctionReturnValue; }, "lbl.text"); _bindings[3] = binding;
To review what was described in Part I of this series, once the PropertyWatcher notices a change, it then calls execute for each Binding object that it knows about. Somewhere along this process, the first anonymous function is executed and it’s return value is passed as an argument to the second anonymous function, which then sets lbl.text to the correct value.
Finally, once all of the data binding has been setup it’s time to set the initial values on the bound properties, so execute() is called:
// [... snip ...] bindings[0].uiComponentWatcher = 1; bindings[0].execute(); bindings[1].uiComponentWatcher = 3; bindings[1].execute();
According to the documentation in Binding.as, uiComponentWatcher stores the id of the watcher to the binding expression so validators “can determine a default listener.” This is used by a Binding(id='NN') metadata tag where NN represents some id number as found in the generated/DataBinding2-generated.as file and seen below:
private function _DataBinding2_bindingExprs():void { var destination:*; [Binding(id='0')] dst.text = src.text; [Binding(id='1')] src.text = dst.text; [Binding(id='2')] bool = src.text.length > 5; [Binding(id='3')] destination = "[Source Length] " + (src.text.length) + " > 5 : " + (bool); }
I haven’t yet found where this is used. A quick search through the generated ActionScript yielded only the above function, so I can’t ( yet :) ) provide any more details. If anyone knows, please post a comment or send me an e-mail.
[Nov 6, 2007] – Fixed the broken link to the example app, sorry about that. –Kaleb
It is used nowhere (my oppinion). Data binding works without it. I’m sure.
Incoming Links
Trackback this post | Subscribe to the comments via RSS Feed