Click here to Skip to main content
Click here to Skip to main content
Go to top

Flex Style Programming: Handling Dynamically Created Controls

, , 16 Mar 2010
Rate this:
Please Sign up or sign in to vote.
This article demonstrates how to ease the work with dynamically created Flex controls using interfaces and data binding

Introduction

When designing your own Flex applications, you deal with different types of object's attributes, like string, date, enumeration, number and boolean. Each of the types can be visualized in different ways in the user interface, for example a boolean type can be represented as a label, as a checkbox, as a pair of radio buttons or as a dropdown list. For these different visualizations, the different controls are used.

In this article, we will get through some examples and learn how the tasks, we deal with, when working with dynamically created controls in Flex, could be simplified with the usage of interfaces and data binding.

Imagine we have a panel that contains several controls, which display some property of the object. The controls are dynamically loaded during startup of the panel. Each of the controls may provide its own style of property visualization.

Creating a Control

Firstly we will create a common interface for the components that will display values (the interface will ease our future work, when we will need to add new types of the controls). To simplify the example, we assume all the components operate with a single value:

package controls
{
	public interface IDetailsControl
	{
		function set value(value: Object): void;	
	}
}

Now we create a control, which implements that interface and displays the supplied value. This will be a simple control, which extends the mx.controls.Label functionality:

package controls
{
	import mx.controls.Label;
	public class ELable extends Label implements IDetailsControl
	{
		public function set value(value: Object): void
		{
			if (value is String)
			{
				text = value as String;
			}
			else if (value != null)
			{
				text = "#object#"; 	
			}
			else
			{
				text = "#null value#"	
			}
		}
	}
}

We will also make a factory, which will provide us with the controls. This factory will operate only with the Elable control at the moment:

package controls
{
	public class ControlFactory
	{
		public static function getControl(type: String): IDetailsControl
		{
			// receiving control class
			var ctrlClass: Class = null;
			switch (type)
			{
				case "label": ctrlClass= ELable;break;
			}
			// instantiating control class
			var ctrl: IDetailsControl = 
				ctrlClass != null ? new ctrlClass : null;
			
			return ctrl;
		}
	}
}

Making Visualization

As soon as we have an interface, a control and a factory, we may start to create an application. We create a property obj, which will act as a value to be displayed by controls. We will also add a VBox with the number of buttons, which will make the changes to the obj property:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
	<mx:Script>
		<![CDATA[
			public var obj: Object = "initial value";	
		]]>
		
	</mx:Script>
	<mx:VBox id="target" width="100%" height="100%" horizontalAlign="center">
		<mx:Button label="String" click="obj = Math.random().toString()"/>
		<mx:Button label="Object" click="obj = {}"/>
		<mx:Button label="Null" click="obj = null"/>
	</mx:VBox>

</mx:WindowedApplication>

Our application will display a number of controls. The controls to be shown will be defined in the conf string array by their names. We will initialize the controls using our ControlFactory:

var conf: Array = ["label", "label", "unknown control"];
for each (var controlType: String in conf)
{
	var ctrl: IDetailsControl = ControlFactory.getControl(controlType);
}

We will then add controls to a VBox layout and will also set the obj property to be the value for the controls (for simplicity, we will use the same value for all of the controls):

if (ctrl is DisplayObject)
	{
		target.addChild(ctrl as DisplayObject);
		ctrl.value=obj;
}

All the code will be placed in the creationComplete handler, which will be invoked after the application will be initialized:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
	creationComplete="onCreationComplete(event)">
	<mx:Script>
		<![CDATA[
			import mx.events.FlexEvent;
			import controls.IDetailsControl;
			import controls.ControlFactory;

			public var obj: Object = "initial state";

			private function onCreationComplete(event: FlexEvent): void
			{
				// specify controls we want to display
				var conf: Array = 
					["label", "label", "unknown control"];
				 
				for each (var controlType: String in conf)
				{
					// receiving control
					var ctrl: IDetailsControl = 
					    ControlFactory.getControl(controlType);
					
					// we work with DisplayObject only
					if (ctrl is DisplayObject)
					{
						//adding a control to layout
						target.addChild(ctrl as 
							DisplayObject);

						//setting the value 
						//for the control
						ctrl.value=obj;		
					}
				}
			}
		]]>
		
	</mx:Script>
	<mx:VBox id="target" width="100%" height="100%" horizontalAlign="center">
		<mx:Button label="String" click="obj = Math.random().toString()"/>
		<mx:Button label="Object" click="obj = {}"/>
		<mx:Button label="Null" click="obj = null"/>
	</mx:VBox>
</mx:WindowedApplication>

We may start our application now.

As you see, the two Label controls show the initial value of obj variable.

Applying Bindings

Now by the use of data binding, we will make the labels listen to the changes made with obj variable, so when some button is pressed, we will be able to see on the labels the up to date value of obj property.

First, we will make our obj property to be bindable:

[Bindable]
public var obj: Object = "initial state";

Then we will replace the simple setting of ctrl.value=obj; with the binding to the setter function so this function will be invoked every time an obj property changes. For that, we will use the bindSetter method from mx.binding.utils.BindingUtils:

BindingUtils.bindSetter(createSetter(lbl),this,"obj");

The setter function, which we pass to the bindSetter method, will look like follows:

private function createSetter(ctrl: IDetailsControl): Function
{
	return function (value: Object): void
	{
		ctrl.value = value;
	}
}

As you see, we enclosed a setter function within another function closure. Due to this, the reference to the ctrl control will be stored in a function closure and during binding process, the setter will be correctly invoked once per control. If we would bind a setter directly, ctrl variable, which is stored within the setter scope and changes in the previously defined "for" loop, will contain a reference to the last control in all the scopes, which is not what we really want to have.

Our full application code looks as follows:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
	creationComplete="onCreationComplete(event)">
	<mx:Script>
		<![CDATA[
			import controls.IDetailsControl;
			import controls.ControlFactory;
			import mx.events.FlexEvent;
			import mx.binding.utils.BindingUtils;
			
			[Bindable]
			public var obj: Object = "initial state";
			private function onCreationComplete(event: FlexEvent): void
			{
				// specify controls we want to display
				var conf: Array = 
					["label", "label", "unknown control"];
				 
				for each (var controlType: String in conf)
				{

					// receiving control
					var ctrl: IDetailsControl = 
					    ControlFactory.getControl(controlType);
					
					// adding a control to layout, 
					// we work with DisplayObject only
					if (ctrl is DisplayObject)
					{
						target.addChild
						(ctrl as DisplayObject);

						// applying binding
						BindingUtils.bindSetter(
							createSetter(ctrl),
							this,
							"obj"
						);
					}
				}
			}
			
			private function createSetter(ctrl: IDetailsControl): Function
			{
				return function (value: Object): void
				{
					ctrl.value = value;
				}
			}			
		]]>
		
	</mx:Script>
	<mx:VBox id="target" width="100%" height="100%" horizontalAlign="center">
		<mx:Button label="String" click="obj = Math.random().toString()"/>
		<mx:Button label="Object" click="obj = {}"/>
		<mx:Button label="Null" click="obj = null"/>
	</mx:VBox>

</mx:WindowedApplication>

Now we may start our application, click on the buttons and see that the labels will react to the changes of obj property:

Adding More Controls

So, when an object is changed, a setter is executed for each of the controls. When there is a need to introduce a new control, we only should make it implement the IDetailsControl interface, register it in a ControlFactory and define it in the configuration. Let us create one more control, which this time will extend the mx.controls.TextInput:

package controls
{
	import mx.controls.TextInput;

	public class ETextField extends TextInput implements IDetailsControl
	{
		public function set value(value: Object): void
		{
			if (value is String)
			{
				text = value as String;
			}
			else if (value != null)
			{
				text = "#object#"; 	
			}
			else
			{
				text = "#null value#"	
			}	
		}
	}
}

Now, we add a control to the ControlFactory:

// ...
switch (type)
{
	case "text":
		ctrlClass= ETextField; break;
	case "label":
		ctrlClass= ELable;break;
} 
// ...

And finally we define it in a configuration conf array:

var conf: Array = ["label", "text", "label", "unknown control"];

We may now open the application, click on some button and see the result:

Everything is fine, all the controls including new text control display up to date values for the obj property.

Extending Setter Logic

The previous screenshot shows the application state, when we clicked on a "Null" button. The displayed values are not very nice to see, so let us now implement the logic to hide the controls, when the obj property is set to the null value.

Since we work with the IDetailsControl interface, we will add a new visible method there so the interface will look as shown below:

package controls
{
	public interface IDetailsControl
	{
		function set value(value: Object): void;
		
		function set visible(value: Boolean): void;
	}
}

Now we need to implement this visible method in our Elable and ETextField controls. The good thing is that this method was already implemented in the classes we extended our controls from (mx.controls.Label and mx.controls.TextInput).

So, we may just start using this new interface method. We will add it to the createSetter function:

private function createSetter(ctrl: IDetailsControl): Function
{
	return function (value: Object): void
	{
		ctrl.visible = value != null;
		ctrl.value = value;
	}
}

And that’s it. When we click on a "Null" button, the components will hide:

When dealing with dynamically created controls, the usage of interfaces and data binding simplifies the developer tasks, reduces the code size, and makes the model to be easily extendable.

History

  • 16th March, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Authors

IntexSoft
IntexSoft
Belarus Belarus
IntexSoft (Intellectual Export Software) is dynamic software development company offering high quality cost-effective IT services to customers worldwide. We develop custom software across a range of industry, such as Media Production, Product Information Management, Translation Management.
 
The company possesses high expertise in J2EE, Adobe Flex/AIR and also in software integration with products like Adobe Indesign Server, Quark Xpress Server, Across Server and others.
 
Founded in 1999, our development centre is located in Grodno, Belarus - historically the most technically advanced region in the former USSR.
Group type: Organisation

4 members


Eugene Dubrovka
Software Developer (Senior) EPAM Systems
Belarus Belarus
No Biography provided
Follow on   Twitter

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web04 | 2.8.140905.1 | Last Updated 16 Mar 2010
Article Copyright 2010 by IntexSoft, Eugene Dubrovka
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid