Flex: understanding databinding: oneway and twoway

17 02 2009

Something that isn’t that well talked about in the Flex world is two way databinding, and it’s implications. 

I was working with an MVC project recently and I came up to an interesting problem with an ArrayCollection somewhere in the View that was bound oneway from the Model. When I changed a property of the object in the View, it changed in the Model. Huh? I didn’t use two-way databinding so why would this happen?

I realised this was due to the fact that while one-way databinding doesn’t change the source when the destination object changes, it DOES ensure that both objects have the same reference. The implication here is that if you change a property of the destination object, it will change in the source as well. They are the same object reference after all.

Let’s prove this with examples. Simple databinding in Flex is trivial. 

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

<mx:Script>
<![CDATA[

[Bindable]
public var someName:String;

]]>
</mx:Script>

<mx:TextInput text="{someName}" />

<mx:Label text="This is the local obj 'someName': {someName}" />

</mx:Application>

That’s all well and good but what if we want to change the someName variable back? We insert a Binding tag to bind back – two way binding.

<mx:Binding source="textInput.text" destination="someName" />

OK great. But something is happening here that needs to be discussed.  The binding is REPLACING the reference of “someName” to that of a new String. So it may be logical to assume that we need two-way binding when updating the destination of some binding to reflect the changes in the source. But, hang on:

Say I have this Model: 

package
{

	[Bindable]
	public class TestModel
	{
		public static var obj:TestObject = new TestObject("test via model","me via model");
	}

}

And say I have some Object: (TestObject.as)

package
{
	[Bindable]
	public class TestObject
	{

		public var name:String;

		public var desc:String;

		public function TestObject(n:String, d:String)
		{
			this.name = n;
			this.desc = d;
		}

	}
}

And this is my Application:

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

	<mx:Script>
		<![CDATA[

			[Bindable]
			public var someObj:TestObject;

		]]>
	</mx:Script>

	<mx:Binding source="TestModel.obj" destination="this.someObj" />

	<mx:Binding source="textInput.text" destination="someObj.name" />

	<mx:Label id="someLabel" text="From the MODEL: {TestModel.obj.name}" />

	<mx:TextInput id="textInput"  />

	<mx:Label text="This is the name locally: {someObj.name}" />

</mx:Application>

I’m simply binding the Model to the local “someObj” (one-way) and then ensuring that changing the TextInput will change the name property of “someObj”. What happens? Try it. 

testbinding

Notice how my Model is also changing? So, you can see that one-way binding actually ensures that both the source and the destination point to the same object reference. You use two-way binding if you need to change the entire destination object completely and want this to be reflected in the source.

Let’s have a look at the final example:

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

	<mx:Script>
		<![CDATA[

			[Bindable]
			public var someObj:TestObject;

			private function onEnter():void
			{
				someObj = new TestObject("created locally","local");
			}

		]]>
	</mx:Script>

	<mx:Binding source="TestModel.obj" destination="this.someObj" />

	<mx:Binding source="textInput.text" destination="someObj.name" />

	<mx:Label id="someLabel" text="From the MODEL: {TestModel.obj.name}" />

	<mx:TextInput id="textInput" enter="onEnter()"  />

	<mx:Label text="This is the name locally: {someObj.name}" />

</mx:Application>

Now, when you hit “ENTER” on your keyboard aftering entering some text, you’ll see the local object now points to a new object instance. And, you can see the Model doesn’t change from this point on, because it still points to the original instance. If you wanted the Model to update from this change, you would add:

<mx:Binding source="someObj" destination="TestModel.obj" />

 

Although you’d be careful to insert it AFTER the Binding tag that assigns someObj as the destination and TestModel as the source. Otherwise you’ll set the Model to null before you begin!

Make sense?


Actions

Information

5 responses

3 03 2009
flexer

I seen two way binding happen when the destination is null…

In your case, the following sequence will simulate a two way binding. Changes to destination will show in in src!!!

public var someName:String; // initialized to null by default

I think src and dest refer to the same object, resulting in two way binding – if you pass dest as null….

Underneath the covers I don’t think there is any real “binding” at all done here. Both dest and src refere to the same thing – giving you the best possible simulated two way binding ever possible!!!

3 03 2009
Justin J. Moses

Flexer – I disagree.

Try this below: When you click the button, the DESTINATION (text input’s text field) changes but the SOURCE remains the same. This is simple one-way binding. If they pointed to the same reference as you suggested, then when you click the button, you’d expect both strings to match.

If you changed the variable someName AFTER you’ve changed the textInput’s text field, the one way dataBinding will still activate. So manually overriding a binding destination will not prevent the databinding from re-executing when the source changes – as you’d expect.

16 03 2009
flexer

Justin,
I thought you were seeing the same behavior I was seeing as shown in the code below with XML binding. With just one mx binding, changes in src show in the destination and changes in Destination show in src… I tried the with strings but it does not show the same behavior…

<![CDATA[
[Bindable]
public var srcXML:XML;
[Bindable]
public var destXML:XML;

public var count:Number=1;
public function onCreationComplete() :void {
srcXML = new XML ();
}

public function changeSrc():void {
srcXML.appendChild(new XML( {count}));
trace (“src=” + srcXML.toXMLString());
trace (“dst=” + destXML.toXMLString());
count ++;
}

public function changeDst():void {
destXML.appendChild(new XML({count}));
trace (“src=” + srcXML.toXMLString());
trace (“dst=” + destXML.toXMLString());
count ++;
}
]]>

16 03 2009
flexer

here is the last part of the mxml code that got cut off. I’ve remove the characters that seem to confuse the “submit comment” process…

mx:Binding source=”srcXML” destination=”destXML”
mx:Button x=”10″ y=”23″ label=”chgSrc” click=”changeSrc()”
mx:Button x=”100″ y=”23″ label=”chgDst” click=”changeDst()”

16 03 2009
Justin J. Moses

Yes, XML is an Object. So when you databind from a source object to a destination object then both point to the same reference. IF you change a property of either the source or the destination – as you have by calling “appendChild” – then both will change, because both src & dest point to the same object.

However, this can lead to confusion, as it has to many people – myself included. The thing is, if you changed your method “changeDst” to

public function changeDst():void {
destXML = new XML();
destXML.appendChild(new XML({count}));
trace (”src=” + srcXML.toXMLString());
trace (”dst=” + destXML.toXMLString());
count ++;
}

then you would see a different XML object in the source and the destination. If you wanted that action to update the source, then you would need to databind the destination back to the source, in two-way databinding.

So, these things need to be kept in mind when databinding – whether to use one-way or two-way.

Leave a comment