Home >
Note: This is excerpted from Chapter 3 of the Rough Cuts version of Enterprise Development with Flex. This book is still in progress, and you can get access to it now.
Rough Cuts is a service from Safari Books Online that gives you early access to content on cutting-edge technologies -- before it's published. It lets you literally read the book as it is being written.
Check out Part 1 of this chapter.
Table of Contents, Part 2
The Base Class for Resources
Example 3.11,
“Class ResourceBase” depicts the class ResourceBase, which serves as a base class for all
resources for all components. This class can tell properties from
styles. In Chapter 2,
Selected Design Patterns you learned about a class
factory that accepts a class or a function name to create instances
of objects. We applied that same technique here: With ResourceBase, resource instance can be created
from a class factory or a class.
Technically, the ResourceBase class
applies specified values as either properties or resources.
Example 3.11. Class ResourceBase
package com.farata.resources {
import com.farata.controls.TextInput;
import flash.system.ApplicationDomain;
import mx.core.ClassFactory;
import mx.core.UIComponent;
import mx.utils.StringUtil;
public dynamic class ResourceBase {
public var resourceProps:Array = [];
public var resourceStyles:Array = [];
public function load(source:Object):void {
for each(var propName:String in resourceProps) {
try {
if( source[propName])
this[propName]= source[propName] ;
}
catch (e:Error) {}
}
for each(var styleName:String in resourceStyles){
try {
if(source.getStyle(styleName))
this[styleName] = source.getStyle(styleName);
}
catch (e:Error){}
}
}
public function apply(target:Object):void {
try {
for each(var propName:String in resourceProps)
if (this[propName]!=undefined)
target[propName] = this[propName];
} catch (e:Error) {
var error:String = mx.utils.StringUtil.substitute(
"Incompatible resource class. Can not apply
property {0} of {1} to {2}",
[propName,this.toString(), target.toString()] );
throw new Error(error);
}
try {
for each(var styleName:String in resourceStyles)
if(this[styleName])
target.setStyle(styleName, this[styleName]);
}
public static function getResourceInstance(value:Object,
styleOwner:Object=null):* {
var resClass:Object;
if(value is Class) {
resClass = Class(value);
if (styleOwner) {
try {
var result:* = new resClass(styleOwner);
return result;
}
catch (e:Error) {
return new resClass();
}
}
else
return new resClass();
}
else if(value is ResourceBase)
return value;
else if(value is ClassFactory)
return ClassFactory(value).newInstance();
else if (value != null) {
var v:String = String(value).replace(/{/,"");
v = v.replace(/}/,"");
resClass = ApplicationDomain.currentDomain.getDefinition(v);
if (styleOwner) {
try {
var result2:* = new resClass(styleOwner);
return result2;
}
catch (e:Error) {
return new resClass();
}
}
else
return new resClass();
}
}
public function get itemEditor() : UIComponent {
return new TextInput();
}
}
}
When application programmers design a resource for a particular
type of Flex UI control, they simply extend it from a ResourceBase class (or build a MXML component
based on it) and specify the names of the variables and their
default values, if need be.
The ResourceBase class relies on
two arrays: resourceProps and
resourceStyles. When application
developers create concrete resources, they also must populate these
arrays. Example 3.12,
“Sample ComboBoxResource” illustrates
implementation of a sample class called ComboBoxResource. Note how the array resourceProps is populated with the data in the
constructor.
Example 3.12. Sample ComboBoxResource
package com.farata.resources {
import mx.core.IFactory;
import mx.core.UIComponent;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;
import com.farata.controls.ComboBox;
dynamic public class ComboBoxResource extends ResourceBase {
public var autoFill :Boolean = false;
public var keyField : String = "data";
public var destination:String=null;
public var dropdownWidth : int = 0;
public var editable:Boolean = false;
public var itemRenderer:IFactory = null;
public var labelFunction : Function = null;
public var labelField : String = "label";
public var dataField : String = "label";
public var method : String = null;
public var width:int=−1;
public var dataProvider : Object;
public function ComboBoxResource(styleOwner:Object=null) {
resourceProps.push("autoFill", "keyField", "destination",
"dropdownWidth", "editable","itemRenderer", "labelField",
"labelFunction","method", "dataProvider", "width");
var sd:CSSStyleDeclaration =
StyleManager.getStyleDeclaration(".comboBoxResource");
if (!sd) {
sd = new CSSStyleDeclaration();
StyleManager.setStyleDeclaration(".comboBoxResource",
sd, false);
sd.setStyle("paddingBottom", 0);
sd.setStyle("paddingTop", 0);
}
if ( styleOwner!= null )
load( styleOwner );
}
override public function get itemEditor() :UIComponent {
return new ComboBox();
}
}
}
This class has to be written once for your enterprise framework,
and after that any junior programmer can easily create and update
resources such as DepartmentComboResource or StateComboResource shown earlier in this chapter
in
Example 3.9, “StateComboBoxResource with hard-coded
states” and
Example 3.10, “Sample DepartmentComboResource configured
for a remote destination”.
Similarly to CSS, resources should be compiled into a separate SWF file. They can be loaded and reloaded during the runtime, and you can find out more about class loaders in Chapter 7.
DataGrid with Resources
The most interesting part about these resources is that you can
attach them not only to regular, but also to such dynamic controls
as DataGridColumn. For example, the
next code snippet instructs the DataGridColumn (it was also enhanced and is
available in clear.swc) to turn itself into a ComboBox and populate itself based on the
configured resource DepartmentComboResource shown in
Example 3.9, “StateComboBoxResource with hard-coded
states”.
<fx:DataGridColumn dataField="DEPT_ID" editable="false"
headerText="Department"
resource="{com.farata.resources.DepartmentComboResource}"/>
A resource attached to a DataGridColumn not only sets a column's properties
but also identifies the item renderer and editor for this
column.
As discussed in Chapter 2, Selected Design Patterns, class factories become
extremely powerful if you use them as item renderer for a data grid
column. Using this methodology, you can also encapsulate a number
of properties and styles in the object provided by the factory. For
example, you can enable the support of resources on the enhanced
DataGridColumn object by adding the
code fragment in
Example 3.13, “Enabling resources support in
DataGridColumn”:
Example 3.13. Enabling resources support in DataGridColumn
private var _resource:Object;
public function set resource(value:Object):void{
_resource = ResourceBase.getResourceInstance(value, this);
if(labelFunction==null) {
getLabelFunctionByResource(_resource, this);
}
}
public function get resource():Object{
return _resource;
}
public static function getLabelFunctionByResource(resourceRef:Object,
column:Object):void {
var resource:ResourceBase = resourceRef as ResourceBase;
if (resource) {
if(resource.hasOwnProperty("destination") &&
resource["destination"])
CollectionUtils.getCollection(
function(ev:Event, collection:Object):void {
collectionLoaded(collection, column);
},
resource.destination,
resource.method
);
else if (resource.hasOwnProperty("dataProvider") &&
resource["dataProvider"]) {
collectionLoaded(
resource.dataProvider,
column,
safeGetProperty(resource, "labelField", "label"),
safeGetProperty(resource, "keyField", "data")
);
}
}
}
private static function collectionLoaded(collection:Object, column:Object,
labelField:String = null, dataField:String = null):
void {
if (null == collection) return;
labelField =
labelField ?
labelField :
(column["labelField"] != null ?
column.labelField :
(column.resource.labelField ?
column.resource.labelField : "label"));
if (!dataField)
dataField = column.resource.keyField ?
column.resource.keyField : column.dataField;
collection = CollectionUtils.toCollection(collection);
const options:Dictionary = new Dictionary();
// copy only when collection is non empty
if (collection != null && collection.length > 0 ) {
const cursor:IViewCursor = collection.createCursor();
do {
options[cursor.current[dataField]] =
cursor.current[labelField];
} while(cursor.moveNext())
}
column.labelFunction = function(data:Object, col:Object):String {
var key:* = data is String || data is Number ? data :
data[col.dataField];
var res:String = options[key];
return res != null ? res : '' + key;
};
}
Suppose you have a DataGrid and a
ComboBox with the values 1, 2, and 3
that should be displayed as John, Paul, and Mary. These values are
asynchronously retrieved from a remote DBMS. You can't be sure,
however, if John, Paul, and Mary will arrive before or after the
DataGrid getst populated. The code
above extends the DataGridColumn with
the property resource and checks if
the application developer supplied a labelFunction. If not, the code tries to "figure
out" the labelFunction from the
resource itself.
If resource has the destination set
and the method is defined as in
DepartmentComboResource in
Example 3.9, “StateComboBoxResource with hard-coded
states”, the code loads the Collection and after that, it creates the
labelFunction (see collectionLoaded() method) based on the loaded
data.
The resource may either come with a populated dataProvider as in Example 3.8,
“A CheckBox resource”, or the data for the
dataProvider may be loaded from the
server. When the dataProvider is
populated, the collectionLoaded()
method examines the dataProvider's
data and creates the labelFunction.
The following code attaches a labelFunction on the fly as a dynamic function
that gets the data and by the key finds the text to display on the grid.
column.labelFunction = function(data:Object, col:Object):String {
var key:* = data is String || data is Number ? data :
data[col.dataField];
var res:String = options[key];
return res != null ? res : '' + key;
};
This closure uses the dictionary options defined outside. The code above this
closure traverses the data provider and creates the following
entries in the dictionary:
1, John
2, Paul
3, Mary
Hence the value of the res returned
by this label function will be John, Paul, or Mary.
These few lines of code provide a generic solution for the
real-life situations that benefit from having asynchronously loaded
code tables that can be programmed by junior developers. This code
works the same way translating the data value into John and Mary, Alaska and
Pennsylvania, or department names.
Note
With resources, the properties and styles of UI controls become available not only to developers who write these classes, but to outsiders in a similar to CSS fashion. The examples of resources from the previous section clearly show that they are self-contained easy to understand artifacts that can be used by anyone as a business style sheets.
You can create a resource as a collection of styles, properties, event listeners that also allow providing a class name to be used with it. You can also create a class factory that will be producing instances of such resources.
Technically, any resource is an abstract class factory that can play the same role as XML-based configurable properties play in the Java EE world. But this solution requires compilation and linkage of all resources, which makes it closer to configuring Java objects using annotations. Just to remind you, in Flex, CSS also get compiled.
To summarize, resources offer the following advantages:
-
They are compiled and work fast.
-
Because they are simple to understand, junior programmers can work with them.
-
You can inherit one resource from another, and Flex Builder will offer you context-sensitive help and Flex compiler will help you to identify data type errors.
-
You can attach resources to a
DataGridColumnand use them as a replacement of item renderers.
Resources are a good start for automation of programming. In
Chapter 6, you'll get familiar with yet another useful Flex
component called DataCollection, a
hybrid of ArrayCollection and
RemoteObject, which yet another step
toward reducing manual programming.
Data Forms
In this section you'll continue adding components to the
enterprise framework. It's hard to find an enterprise application
that does not use forms, which makes the Flex Form component a
perfect candidate for possible enhancements. Each form has some
underlying model object, and the form elements are bound to the
data fields in the model. Flex 3 supports only one-way data
binding: Changes on a form
automatically propagate to the fields in the data model. But if you
wanted to update the form when the
data model changes, you had to
manually program it using the curly braces syntax in one direction
and BindingUtils.bindProperty() in
another.
Flex 4 introduces new feature: two-way binding. Add an @ sign to
the binding expression @{expression}
and notifications about data modifications are sent in both directions—from form to the model and back.
Although this helps in basic cases
where a text field on the form is bound to a text property in a
model object, two-way binding has not much use if you'd like to use
data types other than String.
For example, two-way binding won't help that much in forms that
use standard Flex <mx:CheckBox>
component. What are you going to bind here? The server side
application has to receive 1 if the CheckBox was selected and 0 if not. You can't just
bind its property selected to a
numeric data property on the underlying object. To really
appreciate two-way binding, you need to use different set of
components, similar to the ones that you have been building in this
chapter.
Binding does not work in cases when the model is a moving
target. Consider a typical master-detail scenario: The user
double-clicks on a row in a DataGrid
and details about selected row are displayed in a form. Back in Chapter 1,
Comparing Selected Flex Frameworks you saw an example of
this: Doubling-clicking a grid row in Figure 1.19, “Removing the filter to keep generated Java
code” opened up a form that displayed details of the
employee selected in a grid. This magic was done with the enhanced
form component that you are about to review.
The scenario with binding a form to a datagrid row has to deal with a moving model; the user selects another row. Now what? The binding source is different now and you need to think of another way of refreshing the form data.
When you define data binding using an elegant and simple notation with curly braces, the compiler generates additional code to support it. But in the end, an implementation of the Observer design pattern is needed, and "someone" has to write the code to dispatch events to notify registered dependents when the property in the object changes. In Java, this someone is a programmer, in Flex it's the compiler that also registered event listeners with the model.
Flex offers the Form class that an
application programmer bind to an object representing the data
model. The user changes the data in the UI form, and the model gets
changed too. But the original Form
implementation does not have means of tracking the data
changes.
It would be nice if the Form
control (bound to its model of type a DataCollection) could support similar
functionality, with automatic tracking of all changes compatible
with ChangeObject class that is
implemented with remote data service. Implementing such
functionality is the first of the enhancements you'll make.
The second improvement belongs to the domain of data validation. The enhanced data form should be smart enough to be able to validate not just individual form items, but the form in its entirety too. The data form should offer an API for storing and accessing its validators inside the form rather than in an external global object. This way the form becomes a self-contained black box that has everything it needs. (For details of what can be improved in the validation process, see the section called “Validation”.)
During the initial interviewing of business users, software developers should be able to quickly create layouts to demonstrate and approve the raw functionality without waiting for designers to come up with the proper pixel-perfect controls and layouts.
Hence, your third target will be making the prototyping of the
views developer friendly. Beside the need to have uniform controls,
software developers working on prototypes would appreciate if they
should not be required to give definitive answers as to which
control to put on the data form. The first cut of the form may use
a TextInput control, but the next
version should use a ComboBox instead.
You want to come up with some UI-neutral creature (call it a
data form item) that will
allow not be specific, like, "I'm a TextInput", or "I'm a ComboBox". Instead, developers will be able to
create prototypes with generic data items with easily attachable
resources.
The DataForm Component
The solution that addresses your three improvements is a new
component called DataForm (Example 3.14,
“Class DataForm”). It's a subclass of a Flex
Form, and its code implements two-way
binding and includes a new property dataProvider. Its function validateAll()supports data validation explained in
the next sections. This DataForm
component will properly respond to data changes propagating them to
its data provider.
Example 3.14. Class DataForm
package com.farata.controls{
import com.farata.controls.dataFormClasses.DataFormItem;
import flash.events.Event;
import mx.collections.ArrayCollection;
import mx.collections.ICollectionView;
import mx.collections.XMLListCollection;
import mx.containers.Form;
import mx.core.Container;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.FlexEvent;
import mx.events.ValidationResultEvent;
public dynamic class DataForm extends Form{
use namespace mx_internal;
private var _initialized:Boolean = false;
private var _readOnly:Boolean = false;
private var _readOnlySet:Boolean = false;
public function DataForm(){
super();
addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
}
private var collection:ICollectionView;
public function get validators() :Array {
var _validators :Array = [];
for each(var item:DataFormItem in items)
for (var i:int=0; i < item.validators.length;i++) {
_validators.push(item.validators[i]);
}
return _validators;
}
public function validateAll(suppressEvents:Boolean=false):Array {
var _validators :Array = validators;
var data:Object = collection[0];
var result:Array = [];
for (var i:int=0; i < _validators.length;i++) {
if ( _validators[i].enabled ) {
var v : * = _validators[i].validate(data, suppressEvents);
if ( v.type != ValidationResultEvent.VALID)
result.push( v );
}
}
return result;
}
[Bindable("collectionChange")]
[Inspectable(category="Data", defaultValue="undefined")]
/**
* The dataProvider property sets of data to be displayed in the form.
* This property lets you use most types of objects as data providers.
*/
public function get dataProvider():Object{
return collection;
}
public function set dataProvider(value:Object):void{
if (collection){
collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE,
collectionChangeHandler);
}
if (value is Array){
collection = new ArrayCollection(value as Array);
}
else if (value is ICollectionView){
collection = ICollectionView(value);
}
else if (value is XML){
var xl:XMLList = new XMLList();
xl += value;
collection = new XMLListCollection(xl);
}
else{
// convert it to an array containing this one item
var tmp:Array = [];
if (value != null)
tmp.push(value);
collection = new ArrayCollection(tmp);
}
collection.addEventListener(CollectionEvent.COLLECTION_CHANGE,
collectionChangeHandler);
if(initialized)
distributeData();
}
public function set readOnly(f:Boolean):void{
if( _readOnly==f ) return;
_readOnly = f;
_readOnlySet = true;
commitReadOnly();
}
public function get readOnly():Boolean{
return _readOnly;
}
/**
* This function handles CollectionEvents dispatched from the data provider
* as the data changes.
* Updates the renderers, selected indices and scrollbars as needed.
*
* @param event The CollectionEvent.
*/
protected function collectionChangeHandler(event:Event):void{
distributeData();
}
private function commitReadOnly():void{
if( !_readOnlySet ) return;
if( !_initialized ) return;
_readOnlySet = false;
for each(var item:DataFormItem in items)
item.readOnly = _readOnly;
}
private function distributeData():void {
if((collection != null) && (collection.length > 0)) {
for (var i:int=0; i<items.length; i++) {
DataFormItem(items[i]).data = this.collection[0];
}
}
}
private var items:Array = new Array();
private function creationCompleteHandler(evt:Event):void{
distributeData();
commitReadOnly();
}
override protected function createChildren():void{
super.createChildren();
enumerateChildren(this);
_initialized = true;
commitReadOnly();
}
private function enumerateChildren(parent:Object):void{
if(parent is DataFormItem){
items.push(parent);
}
if(parent is Container){
var children:Array = parent.getChildren();
for(var i:int = 0; i < children.length; i++){
enumerateChildren(children[i]);
}
}
}
}
}
Let's walk through the code of the class DataForm. Examine the setter dataProvider in the code above. It always wraps up
the provided data into a collection. This is needed to ensure that
the DataForm supports working with
remote data services the same way as DataGrid does. It checks the data type of the
value. It wraps an Array into an
ArrayCollection, and XML turns into
XMLListCollection. If you need to
change the backing collection that stores the data of a form, just
point the collection variable at the new data.
If a single object is given as a dataProvider, turn it into a one-element array and
then into a collection object. A good example of such case is an
instance of a Model, which is an
ObjectProxy (see Chapter 2,
Selected Design Patterns) that knows how to dispatch
events about changes of its properties.
Once in a while, application developers need to render
non-editable forms, hence the DataForm
class defines the readOnly
property.
The changes of the underlying data are propagated to the form in
the method collectionChangeHandler().
The data can be modified either in the dataProvider on from the UI and the DataForm ensures that each visible DataFormItem object (items[i]) knows about it. This is done in the
function distributeData():
private function distributeData():void {
if((collection != null) && (collection.length > 0)) {
for (var i:int=0; i<items.length; i++) {
DataFormItem(items[i]).data = this.collection[0];
}
}
}
This code always works with the element zero of the collection,
because the form has always one object with data that is bound to
the form. Such a design resembles the functionality of the
data variable of the Flex DataGrid that for each column provides a reference
to the object that represents the entire row.
Again, we need the data to be wrapped into a collection to
support DataCollection or DataService from LCDS.
Technically, a DataForm class is a
VBox that lays out its children
vertically in two columns and automatically aligns the labels of
the form items. This DataForm needs to
allow nesting – containing items that are also instances of
the DataForm object. A recursive
function enumerateChildren() loops
through the children of the form, and if it finds a DataFormItem, it just adds it to the array
items. But if the child is a
container, the function loops through its children and adds them to
the same items array. In the end, the
property items contains all
DataFormItems that have to be
populated.
Notice that the function validateAll()is encapsulated inside the
DataForm, while in Flex framework is
located in the class Validator. There,
the validation functionality was external to Form elements and you'd need to give an array of
validators that were tightly coupled with specific form fields.
Our DataForm component is self
sufficient – its validators are embedded inside and reusing
the same form in different views or applications is easier
comparing to original Flex Form object
that relies on external validators.
The DataFormItem Component
The DataFormItem, an extension of
Flex FormItem, is the next component
of the framework. This component should be a bit more humble than
its ancestor though. The DataFormItem
should not know too much about its representation and should be
able to render any UI component. Design of new Flex 4 components
has also been shifted toward separation between their UI and
functionality.
At least a half of the controls on a typical form are text
fields. Some of them use masks to enter formatted values like phone
numbers. The rest of the form items most likely are nothing else
but check boxes, and radio buttons. For these controls (and
whatever else you may need) just use resources. Forms also use
combo boxes. The "the section called “DataGrid
with Resources”" section showned you how class factory
based resources can be used to place combo boxes and other
components inside the DataGrid. Now
you'll see how to enable forms to have flexible form items using
the same technique.
The DataFormItem is a binding
object that is created for each control placed inside the
DataForm. It has somewhat similar to
BindingUtils functionality to support
two-way binding and resolves circular references. The DataFormItem has two major functions:
-
Attach an individual control inside to the instance of
DataFormItemEditorto listen to the changes in the underlying control. -
Create a UI control (either a default one, or according to requested masked input or resource).
The first function requires the DataFormItem control to support the syntax of
encapsulating other controls as it's implemented in FormItem, for example:
<lib:DataFormItem dataField="EMP_ID" label="Emp Id:">
<mx:TextInput/>
</lib:DataFormItem>
In this case the DataFormItem
performs binding functions; in Flex framework, <mx:FormItem> would set or get the value in
the encapsulated UI component, but now the DataFormItem will perform the binding duties.
Assignment of any object to the dataField property item of the DataFormItem will automatically pass this value to
the enclosed components. If, say an application developer decides
to use a chart as a form item, the data assigned to the
DataFormItem will be given for
processing to the chart object. The point is that application
developers would use this control in a uniform way regardless of
what object is encapsulated in the DataFormItem.
The second function, creating a UI control, is implemented with
the help of resources, which not only allow specifying the styling
of the component, but also can define what component to use. If you
go back to the code of the class ResourceBase, you'll find there a getter
itemEditor that can be used for
creation of controls. Actually, this gives you two flexible ways of
creating controls for the form: either specify a resource name, or
specify a component as itemEditor=myCustomComponent. If none of these
ways is engaged a default TextInput
control will de created.
The code above looks somewhat similar to the original
FormItem, but it adds new powerful
properties to the component that represents form item. The data of
the form item is stored in the EMP_ID
property of the data collection specified in the dataProvider of the DataForm. The label
property plays the same role as in FormItem.
The source code of the DataFormItem
component is shown in Example 3.15,
“Class DataFormItem”. It starts with defining
properties as in DataGrid –
dataField, valueName and itemEditor. The DataGridItem can create an itemEditor from a String, an Object or
a class factory. It also defines an array validator that will be described later in this
chapter.
Example 3.15. Class DataFormItem
package com.farata.controls.dataFormClasses {
import com.farata.controls.DataForm;
import csom.farata.controls.MaskedInput;
import com.farata.core.UIClassFactory;
import com.farata.resources.ResourceBase;
import com.farata.validators.ValidationRule;
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.utils.getDefinitionByName;
import mx.containers.FormItem;
import mx.events.FlexEvent;
import mx.validators.Validator;
dynamic public class DataFormItem extends FormItem {
public function DataFormItem() {
super();
}
private var _itemEditor:IEventDispatcher; //DataFormItemEditor;
[Bindable("itemEditorChanged")]
[Inspectable(category="Other")]
mx_internal var owner:DataForm;
private var _dataField:String;
private var _dataFieldAssigned:Boolean = false;
private var _labelAssigned:Boolean = false;
private var _valueName:String = null;
private var _readOnly:Boolean = false;
private var _readOnlySet:Boolean = false;
public function set readOnly(f:Boolean):void{
if( _readOnly==f ) return;
_readOnly = f;
_readOnlySet = true;
commitReadOnly();
}
public function get readOnly():Boolean {
return _readOnly;
}
public function set dataField(value:String):void{
_dataField = value;
_dataFieldAssigned = true;
}
public function get dataField():String{
return _dataField;
}
override public function set label(value:String):void {
super.label = value;
_labelAssigned = true;
}
public function set valueName(value:String):void{
_valueName = value;
}
public function get valueName():String {
return _valueName;
}
override public function set data(value:Object):void{
super.data = value;
if(_itemEditor)
if (_itemEditor["data"] != value[_dataField])
_itemEditor["data"] = value[_dataField];
for ( var i : int = 0; i < validators.length; i++) {
if ( validators[i] is ValidationRule && data)
validators[i]["data"]= data;
validators[i].validate();
}
}
override protected function createChildren():void{
super.createChildren();
if(this.getChildren().length > 0) {
_itemEditor = new DataFormItemEditor(this.getChildAt(0), this);
_itemEditor.addEventListener(Event.CHANGE, dataChangeHandler);
_itemEditor.addEventListener(FlexEvent.VALUE_COMMIT,
dataChangeHandler);
}
}
public function get itemEditor():Object {
return _itemEditor;
}
private var _validators :Array = [];
public function get validators() :Array {
return _validators;
}
public function set validators(val :Array ): void {
_validators = val;
}
public var _dirtyItemEditor:Object;
public function set itemEditor(value:Object):void{
_dirtyItemEditor = null;
if(value is String){
var clazz:Class = Class(getDefinitionByName(value as String));
_dirtyItemEditor = new clazz();
}
if(value is Class)
_dirtyItemEditor = new value();
if(value is UIClassFactory)
_dirtyItemEditor = value.newInstance();
if(value is DisplayObject)
_dirtyItemEditor = value;
}
private function dataChangeHandler(evt:Event):void{
if (evt.target["data"]!==undefined) {
if (data != null) {
data[_dataField] = evt.target["data"];
}
}
}
private var _resource:Object;
public function set resource(value:Object):void{
_resource = ResourceBase.getResourceInstance(value);
invalidateProperties();
}
public function get resource():Object{
return _resource;
}
private function commitReadOnly():void{
if( _itemEditor==null ) return;
if( !_readOnlySet ) return;
if( Object(_itemEditor).hasOwnProperty("readOnly") )
{
Object(_itemEditor).readOnly = _readOnly;
_readOnlySet = false;
}
}
override protected function commitProperties():void{
super.commitProperties();
if(itemEditor == null) //no child controls and no editor from resource
{
var control:Object = _dirtyItemEditor;
if(!control && getChildren().length > 0)
control = getChildAt(0); //user placed control inside
if(!control)
control = itemEditorFactory(resource as ResourceBase);
if(resource)
resource.apply(control);
if( (control is MaskedInput) && hasOwnProperty
("formatString"))
control.inputMask = this["formatString"];
addChild(DisplayObject(control));
//Binding wrapper to move data back and force
_itemEditor = new
DataFormItemEditor(DisplayObject(control),this);
_itemEditor.addEventListener(Event.CHANGE, dataChangeHandler);
_itemEditor.addEventListener(FlexEvent.VALUE_COMMIT,
dataChangeHandler);
} else
control = itemEditor.dataSourceObject;
commitReadOnly();
for ( var i : int = 0; i < validators.length; i++) {
var validator : Validator = validators[i] as Validator;
validator.property = (_itemEditor as DataFormItemEditor).valueName;
validator.source = control;
if ( validator is ValidationRule && data)
validator["data"]= data;
validator.validate();
}
}
protected function itemEditorFactory(resource : ResourceBase =
null):Object{
var result:Object = null;
if (resource && ! type)
result = resource.itemEditor;
else {
switch(type) {
case "checkbox":
result = new CheckBox();
if (!resource) {
resource = new CheckBoxResource(this);
resource.apply(result);
}
break;
case "radiobutton":
result = new RadioButtonGroupBox();
if (!resource) {
resource = new RadioButtonGroupBoxResource(this);
resource.apply(result);
}
break;
case "combobox":
result = new ComboBox();
if (!resource) {
resource = new ComboBoxResource(this);
resource.apply(result);
}
break;
case "date":
result = new DateField();
if (formatString) (result as DateField).formatString =
formatString;
break;
case "datetime":
result = new DateTimeField();
if (formatString) (result as DateTimeField).formatString =
formatString;
break;
case "mask":
result = new MaskedInput();
break;
}
}
if(result == null && formatString)
result = guessControlFromFormat(formatString);
if(result == null)
result = new TextInput();
return result;
}
protected function guessControlFromFormat(format:String):Object{
var result:Object = null;
if(format.toLowerCase().indexOf("currency") != −1)
result = new NumericInput();
else if(format.toLowerCase().indexOf("date") != −1){
result = new DateField();
(result as DateField).formatString = format;
}
else{
result = new MaskedInput();
(result as MaskedInput).inputMask = format;
}
return result;
}
}
}
Read the code above, and you'll see that you can use an instance
of a String, an Object, a class factory or a UI control as an
itemEditor property of the
DataFormItem. The function
createChildren() adds event listeners
for CHANGE and VALUE_COMMIT events, and when any of these events
is dispatched, the dataChangeHandler()
pushes provided value from the data attribute of the UI control use
in the form item into the data.dataField property of the object in the
underlying collection.
The resource setter allows
application developers to use resources the same way as it was done
with a DataGrid earlier in this
chapter.
The function commitReadonly()
ensures that the readOnly property on
the form item can be set only after the item is created.
The function itemEditorFactory()
supports creation of the form item components from a resource,
based on the specified type property, requests a control to be
created based on a format string. The guessControlFromFormat() is a function that can be
extended based on the application needs, but in the code above it
just uses a NumericInput component if
the currency format was
requested and DateField if the
date format has been
specified. If unknown format was specified, this code assumes that
the application developer needs a mask hence the MaskedInput will be created.
Remember that Flex schedules a call to the function commitProperty() to coordinate modifications to
component properties when a component is created. It's also called
as a result of the application code calling invalidateProperties(). The function commitProperties() checks if the itemEditor is not defined. If it is not, it'll be
created and the event listeners will be added. If the itemEditor exists, the code extracts from it the
UI control used with this form item.
Next, the data form item instantiates the validators specified by the application developers. This code binds all provided validators to the data form item.
for ( var i : int = 0; i < validators.length; i++) {
var validator : Validator = validators[i] as Validator;
validator.property = (_itemEditor as DataFormItemEditor).valueName;
validator.source = control;
if ( validator is ValidationRule && data)
validator["data"]= data;
validator.validate();
}
The next section discusses the benefits of hiding validators
inside the components and offers a sample application that shows
how to use them and the functionality of the ValidationRule class. Meanwhile,
Example 3.16, “Code fragment that uses DataForm and
DataFormItem” demonstrates how an application developer
could use the DataForm, DataFormItem, and resources. Please note that by
default, DataFormItem renders a
TextInput component.
Example 3.16. Code fragment that uses DataForm and DataFormItem
<lib:DataForm dataProvider="employeeDAO">
<mx:HBox>
<mx:Form>
<lib:DataFormItem dataField="EMP_ID" label="Emp Id:"/>
<lib:DataFormItem dataField="EMP_FNAME" label="First Name:"/>
<lib:DataFormItem dataField="STREET" label="Street:"/>
<lib:DataFormItem dataField="CITY" label="City:"/>
<lib:DataFormItem dataField="BIRTH_DATE" label="Birth Date:"
formatString="shortDate"/>
<lib:DataFormItem dataField="BENE_HEALTH_INS" label="Health:"
resource="{com.farata.resources.YesNoCheckBoxResource}"/>
<lib:DataFormItem dataField="STATUS" label="Status:"
resource="{com.farata.resources.StatusComboResource}"/>
</mx:Form>
<mx:Form>
<lib:DataFormItem dataField="MANAGER_ID" label="Manager Id:"/>
<lib:DataFormItem dataField="EMP_LNAME" label="Last Name:"/>
<lib:DataFormItem dataField="STATE" label="State:"
resource="com.farata.resources.StateComboResource"/>
<lib:DataFormItem dataField="SALARY" label="Salary:"
formatString="currency" textAlign="right"/>
<lib:DataFormItem dataField="START_DATE" label="Start Date:"
formatString="shortDate"/>
<lib:DataFormItem dataField="BENE_LIFE_INS" label="Life:"
resource="{com.farata.resources.YesNoCheckBoxResource}"/>
<lib:DataFormItem dataField="SEX" label="Sex:"
resource="{com.farata.resources.SexComboResource}"/>
</mx:Form>
<mx:Form>
<lib:DataFormItem dataField="DEPT_ID" label="Department:"
resource="{com.farata.resources.DepartmentComboResource}"/>
<lib:DataFormItem dataField="SS_NUMBER" label="Ss Number:"
itemEditor="{com.theriabook.controls.MaskedInput}" formatString="ssn"/>
<lib:DataFormItem dataField="ZIP_CODE" label="Zip Code:"
formatString="zip"/>
<lib:DataFormItem dataField="PHONE" label="Phone Number:"
itemEditor="{com.theriabook.controls.MaskedInput}" formatString="phone">
<lib:validators>
<mx:Array>
<mx:PhoneNumberValidator wrongLengthError="keep typing"/>
</mx:Array>
</lib:validators>
</lib:DataFormItem>
<lib:DataFormItem dataField="TERMINATION_DATE"
label="Termination Date:" formatString="shortDate"/>
<lib:DataFormItem dataField="BENE_DAY_CARE" label="Day Care:"
resource="{com.farata.resources.YesNoCheckBoxResource}"/>
</mx:Form>
</mx:HBox>
</lib:DataForm>
This code is an extract from the Café Townsend (Clear
Data Builder's version) from Chapter 1,
Comparing Selected Flex Frameworks. Run the application
Employee_getEmployees_GridFormTest.mxml,
double-click on a grid row and you'll see the DataForm in action. In the next section of this
chapter you'll see other working examples of the DataForm and DataGrid
with validators.
The final section of this chapter, part 3, will be published next week.





Facebook Application Development
This may surprise you to know that the Pacific region has its fair share of web talent. Having just been surrounded by most of them for the past couple of days I can attest to the fact This year was my first WebDU, and I'm still learning Courses from home
The best way of course would be to build test them through the learning process