New topics: Your Pet, IOU, Baby IQ, The Poisons, Birther II, Games, Future Power

Welcome to the Tech Space!

Interested in Games?

Skip to end of metadata
Go to start of metadata

I spend an hour to discover what every Flex developer probably already knows: Curly bracket bindings in flex are not bidirectional. But when a control, its bound variable, and an initialize handler are all attempting to set an initial value for the bound variable, which one wins? I find out....

I must finally be getting the hang of Flex. I've went a whole day, (I think), since finding the last real head scratcher. But I just found another one. I started documenting what I thought was a bug in Flex, turns out it was just my lack of understanding. Bindings are really cool, but somehow I thought they were bi-directional. Turns out they aren't, at least not without a little work.

I did some google queries to try and figure out my problem, and I did run across other complaints about binding to checkboxes and datagrids:

  • flex binding a checkbox to a boolean variable
  • flex binding to a checkbox

But it wasn't until I wrote a small test app that I really figured out what I needed to know.

What I'm trying to accomplish

My first flex app was a Flash Universe Builder for space games. I spent several days creating an app that would grow a fun network of jumps between stars. My next Flex project was an AIR application to monitor websites

Right now I'm working on a graphical FTP client as an Adobe AIR application. The is the deepest app I've attempted in Flex/Air. It includes a function to mirror the contents of a directory from one FTP account to a directory in another FTP account. I've componentized several pieces of the UI, and spent time playing with the fun of canvases vs. vboxes and creating a layout that will properly stretch as the app window is resized. At this point my UI is mostly done and I've been able to concentrate on the logic of the app. Developing the low level FTP routines in flex has been fun, if not tedious. I started with someone else's open source code, then spent a week debugging and fixing all kinds of basic misconceptions it had about the FTP protocol. Oh and exploring the joys of IOError on sockets (uggh, so little information available to debug when these events occur): flex error #2031 Socket Error. A socket error occurred.

The amazing File Transfer Protocol

Do you know how different the responses are from various FTP servers? It's amazing FTP clients and servers can communicate at all! Fortunately, I've got the benefit of being able to bounce my code off of ProFTP running on a Linux box, Windows FTP servers, and Documentum File Transfer services. The original FTP open source code seems to have been based not only on mistaken notions about the protocol, it apparently had never been tested with anything other than a single FTP server. Still the FTP code was a useful skeleton, eventhough I now have it full of all kinds of extra prosthesises (prosthesii?) and titanium screws (or is it really just rubber bands and duct tape (duck tape?)?). You can always recognize a programmer by their punctuation habits....

Adding a global variable, then creating a UI element to control it

I wanted to add a global setting in my UI to skip files with spaces in the name, due to the crippled FTP server on Documentum which is unable to accept files with spaces in the names. So I first added the global boolean, wrote the logic, and tested it. I then decided to expose the boolean to the user via a checkbox. I wrote

  <mx:CheckBox id="settingsCBSkipSpaces" label="Skip files and directories with spaces" selected="{bSkipWithSpaces}" />

Seems pretty straightforward... I added the code to save the CheckBox.selected with the configurations. Then I edited a couple of configs, and saved one with the "skip spaces" set to true, and one with it set false. But on loading both, I discovered the checkbox didn't toggle like it should. I checked the loading of the configs, and discovered the boolean I was saving was always set to true.

Hmmmm. Changes to the control don't get sent to the boolean variable bound to the countrol. Seems like a binding problem.

Here's an example:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Accordion width="100%" height="100%">
	<mx:VBox label="Test bound checkbox">
		<mx:CheckBox id="cb" label="Test checkbox" selected="{bCheckBoxState}" />
		<mx:Button label="Get values" click="clickGet()" />
		<mx:Button label="Toggle boolean" click="toggleBool()" />
		<mx:TextArea id="textArea" width="361" height="178"/>
                  <mx:Script>
	           <![CDATA[
	  	  [Bindable]
		  private var bCheckBoxState:Boolean = true;
		  private function clickGet():void {
			textArea.text += "Checkbox:"+ cb.selected + " bCheckBoxState:"+bCheckBoxState+"\r";
		  }
		  private function toggleBool():void {
			bCheckBoxState = !bCheckBoxState;
		  }
	         ]]>
                  </mx:Script>

	</mx:VBox>
</mx:Accordion>	
</mx:WindowedApplication>

Table of Contents

Playing with Flex or Adobe Air?

You need this book!

Unknown macro: {asin}

Click "Get Values", and see:

  • Checkbox:true bCheckBoxState:true

Click the checkbox to unchecked, then click "Get Values", and see:

  • Checkbox:false bCheckBoxState:true

Doesn't matter how many times the checkbox is checked or unchecked, the boolean doesn't change.

If you click "Toggle boolean", the checkbox does change to match the new state of the boolean.

Question: Shouldn't the binding work in both directions?

The workaround is simple enough, add a click handler to the checkbox and manually set the value of the boolean.... sigh

		<mx:CheckBox id="cb" label="Test checkbox" selected="{bCheckBoxState}" click="clickCB()" />

...
				private function clickCB(evt:Event):void {
					bCheckBoxState = (evt.target as CheckBox).selected;
				}

Note: Maybe every Flex developer already knows this, but I like writing the above using evt.target so the code doesn't depend on the id of my checkbox, and the same handler can be used for every bound checkbox.

So I tried the same thing with a TextInput. And again, changes to the TextInput don't affect the bound variable. But changes to the variable get reflected in the control. Hmmm... Time to go back and read the books about binding....

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Accordion width="100%" height="100%">
	<mx:VBox label="Test bound checkbox">
		<mx:CheckBox id="cb" label="Test checkbox" selected="{bCheckBoxState}" click="clickCB(event)" />
		<mx:Button label="Get values" click="clickGet()" />
		<mx:Button label="Toggle boolean" click="toggleBool()" />
		<mx:TextArea id="textArea" width="361" height="178"/>
		<mx:Script>
			<![CDATA[
				[Bindable]
				private var bCheckBoxState:Boolean = true;
				private function clickGet():void {
					textArea.text += "Checkbox:"+ cb.selected + " bCheckBoxState:"+bCheckBoxState+"\r";
				}
				private function toggleBool():void {
					bCheckBoxState = !bCheckBoxState;
				}
				private function clickCB(evt:Event):void {
					bCheckBoxState = (evt.target as CheckBox).selected;
				}
			]]>
		</mx:Script>		

		<mx:TextInput id="ti" text="{sVal}"/>
		<mx:Button label="Get textinput values" click="clickGetTI()" />
		<mx:Button label="Set bound string to test" click="setTIBound()" />

		<mx:Script>
			<![CDATA[
				[Bindable]
				private var sVal:String = "Test me";

				private function clickGetTI():void {
					textArea.text += "TextInput:"+ ti.text + " sVal:"+sVal+"\r";
				}
				private function setTIBound():void {
					sVal = "test";
				}
			]]>
		</mx:Script>
	</mx:VBox>
</mx:Accordion>	
</mx:WindowedApplication>

Bidirectional binding of controls and variables in Flex is a do it yourself project

OK, Flex 3 cookbook, recipe 14.1 has some clues:

  • When you assign a property of one object (the destination object) to be bound to a property of another object (the source object), an event from the source object is dispatched to notify the destination object of an update to the value.... Though curly braces are easy, quick to develop, and have the same end result, choosing to use the \<mx:Binding\> tag may prove beneficial in your development efforts because the syntax is easy to read and also lets you define more than one source property to the same destination.

So, next to the checkbox, I added:

		<mx:Binding destination="bCheckBoxState" source="cb.selected" />
		<mx:Binding destination="cb.selected" source="bCheckBoxState" />

Ooops, another gotcha: You can't place bindings just anywhere

Here's a Flex/ActionScript gotcha:

  • Ooops. Can't put that inside a vbox. Compiler gives: "<mx:Binding> is not allowed here." I have to move those lines (far away) up to be under the application tag. Wouldn't it be more logical to allow me to place them next to the control(s) they affect? I suppose I could:
    • pull out the checkbox, and its boolean, and their bindings, and turn them into a component... But that sounds a lot like just a checkbox, what's the point?....
    • Or I could have replaced my global boolean with a call to CheckBox.selected......

So here is the app with both fields now doing bidirectional binding:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">

	<!-- Bidirectional binding for the checkbox: -->
		<mx:Binding destination="bCheckBoxState" source="cb.selected" />
		<mx:Binding destination="cb.selected" source="bCheckBoxState" />
	
	<!-- Bidirectional binding for the text box: -->
		<mx:Binding destination="ti.text" source="sVal" />
		<mx:Binding destination="sVal" source="ti.text" />

<mx:Accordion width="100%" height="100%">

	<mx:VBox label="Test bound checkbox">
		<mx:CheckBox id="cb" label="Test checkbox"  />
		<mx:Button label="Get values" click="clickGet()" />
		<mx:Button label="Toggle boolean" click="toggleBool()" />
		<mx:TextArea id="textArea" width="361" height="178"/>
		<mx:Script>
			<![CDATA[
				[Bindable]
				private var bCheckBoxState:Boolean = true;
				private function clickGet():void {
					textArea.text += "Checkbox:"+ cb.selected + " bCheckBoxState:"+bCheckBoxState+"\r";
				}
				private function toggleBool():void {
					bCheckBoxState = !bCheckBoxState;
				}
			]]>
		</mx:Script>		

		<mx:TextInput id="ti" text="{sVal}"/>
		<mx:Button label="Get textinput values" click="clickGetTI()" />
		<mx:Button label="Set bound string to test" click="setTIBound()" />

		<mx:Script>
			<![CDATA[
				[Bindable]
				private var sVal:String = "Test me";

				private function clickGetTI():void {
					textArea.text += "TextInput:"+ ti.text + " sVal:"+sVal+"\r";
				}
				private function setTIBound():void {
					sVal = "test";
				}
			]]>
		</mx:Script>
	</mx:VBox>
</mx:Accordion>	
</mx:WindowedApplication>

Where did my default initial values go?

Notice a small quirk of the above:

  • I forgot to remove text="{sVal}" from the TextInput. It seems to be unimportant in this case.
  • Click "Get textinput values" immediately after startup and notice that the empty value of the TextInput erases the initial value of sVal.
    • Setting up an application.initialize handler and setting the value of sVal won't work either. sVal still gets erased.
    • I just noticed that eventhough I initialized bCheckBoxState = true in its declaration, it has also been set to false. So, bound controls clobber the initial values of the variables they are bound to.
    • I thought about setting these variables in an initialize handler. Unfortunately this value gets lost too!
      				private function doInit():void {
      					// This will get clobbered later too!
      					sVal = "Test me set in initialize";
      				}
      
    • The cure is setting the initial text attribute in the TextInput, like this: text="Test me text in ti" This points out one of the possible benefits of using the mx:Binding tags: If you move the bindings from a curly brace to an mx:Binding tag, then you are free to use the text field for a default value.

Tada! The final app with bidirectional bindings and default values

So here is the final app:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="doInit()">

	<!-- Bidirectional binding for the checkbox: -->
		<mx:Binding destination="bCheckBoxState" source="cb.selected" />
		<mx:Binding destination="cb.selected" source="bCheckBoxState" />
	
	<!-- Bidirectional binding for the text box: -->
		<mx:Binding destination="ti.text" source="sVal" />
		<mx:Binding destination="sVal" source="ti.text" />

<mx:Accordion width="100%" height="100%">

	<mx:VBox label="Test bound checkbox">
		<mx:CheckBox id="cb" label="Test checkbox"  />
		<mx:Button label="Get values" click="clickGet()" />
		<mx:Button label="Toggle boolean" click="toggleBool()" />
		<mx:TextArea id="textArea" width="361" height="178"/>
		<mx:Script>
			<![CDATA[
				[Bindable]
				private var bCheckBoxState:Boolean = true;
				private function clickGet():void {
					textArea.text += "Checkbox:"+ cb.selected + " bCheckBoxState:"+bCheckBoxState+"\r";
				}
				private function toggleBool():void {
					bCheckBoxState = !bCheckBoxState;
				}
			]]>
		</mx:Script>		

		<!-- The value of the text field here will determine the value of this textbox
		     and the bound variable after the app starts up. -->
		<mx:TextInput id="ti" text="Test me text in ti" />
		<mx:Button label="Get textinput values" click="clickGetTI()" />
		<mx:Button label="Set bound string to test" click="setTIBound()" />

		<mx:Script>
			<![CDATA[
				[Bindable]
				// This gets clobbered later 
				private var sVal:String = "Test me";

				private function doInit():void {
					// This will get clobbered later too!
					sVal = "Test me set in initialize";
				}

				private function clickGetTI():void {
					textArea.text += "TextInput:"+ ti.text + " sVal:"+sVal+"\r";
				}
				private function setTIBound():void {
					sVal = "test";
				}
			]]>
		</mx:Script>
	</mx:VBox>
</mx:Accordion>	
</mx:WindowedApplication>

That was a fun 90 minute diversion. It's now 7am, time for some more sleep.... Hope you find it useful to visit some of the ads on this page....

Unknown macro: {asin}
Labels:
umeli umeli Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Oct 06, 2009

    Anonymous

    Hey,

    Did you ever fix that 2031 error on the Socket, i'm hitting my head against the walls for the last week over a similar error, on a modified version of the flexftp code.

    It works fine with most ftp servers, but this one installation is causing me huge issues, with #2031 on opening the passive connection.

  2. Mar 19, 2010

    Anonymous

    using BindingUtils.bindProperty you could have solved this too

  3. Feb 23, 2011

    Anonymous

    Thank you very much. I was banging my head why the bidirectional thing was not working. You save a lot of effort. Thanks again

Add Comment