Home  >  

Building an Enterprise Framework - Enterprise Development with Flex, Part 3

Author photo
AddThis Social Bookmark Button

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.

Table of Contents, Part 3

Validation
Sample Application – DataFormValidator
Class ValidationRule Explained
Embedding Validation Rules into a DataGrid
Minimizing the Number of Custom Events
Summary

Validation

Like data forms and components in general, the Flex Validator could use some enhancement to make it more flexible for your application developers. In Flex validation seems to have been designed with an assumption that software developers will mainly use it with forms and each validator class will be dependent on and attached to only one field. Say, you have a form with two email fields. Flex framework forces you to create two instances of the EmailValidator object – one per field.

In the real life though, you may also need to come up with validating conditions based on relationships between multiple fields, as well as to highlight invalid values in more than one field. For example, you might want to set the date validator to a field and check if the entered date falls into the time interval specified in the start and end date fields. If the date is invalid, you may want to highlight all form fields.

In other words, you may need to do more than validate an object property. You may need the an ability to write validation rules in a function that can be associated not only with the UI control but also with the underlying data, i.e. with a data displayed in a row in a DataGrid.

Yet another issue of Flex Validator is its limitations regarding view states of automatically generated UI controls. Everything would be a lot easier if validators could live inside the UI controls, in which case they would be automatically added to view states along with the hosting controls.

Having convenient means of validation on the client is an important part of the enterprise Flex framework. Consider, for example, an RIA for opening new customer accounts in a bank or an insurance company. This business process often starts with filling multiple sections in a mile-long application form. In Flex, such an application may turn into a ViewStack of custom components with, say five forms totaling 50 fields. These custom components and validators are physically stored in separate files. Each section in a paper form can be represented as a content of one section in an Accordion or other navigator. Say you have total of 50 validators, but realistically, you'd like to engage only those validators that are relevant to the open section of the Accordion.

If an application developer decides to move a field from one custom component to another, he needs to make appropriate changes in the code to synchronize the old validators with a relocated field.

What some of the form fields are used with view states? How would you validate these moving targets? If you are adding three fields when the currentState="Details", you'd need manually write AddChild statements in the state section Details.

Say 40 out of these 50 validators are permanent, and the other 10 are used once in a while. But even these 40 you don't want to use simultaneously hence you need to create, say two arrays having 20 elements each, and keep adding/removing temporary validators to these arrays according to view state changes.

Even though it seems that Flex separates validators and field to validate, this is not a real separation but rather a tight coupling. What's the solution? For the customer accounts example, you want a ViewStack with five custom components, each of which has one DataForm whose elements have access to the entire set of 50 fields, but that validates only its own set of 10. In other words, all five forms will have access to the same 50-field dataProvider. If during account opening the user entered 65 in the field age on the first form, the fifth form may show fields with options to open a pension plan account, which won't be visible for young customers.

That's why each form needs to have access to all data, but when you need to validate only the fields that are visible on the screen at the moment, you should be able to do this on behalf of this particular DataForm. To accomplish all this, we created a new class called ValidationRule. Our goal is not to replace existing Flex validation routines, but rather offer you an alternative solution that can be used with forms and list-based controls. The next section demonstrates a sample application that uses the class ValidationRule. After that, you can take a look at the code under the hood.

Sample Application – DataFormValidator

The DataFormValidator.mxml application (Figure 3.5, “Running DataFormValidation application”) has two DataForm containers located inside the HBox. Pressing the button Save initiates the validation of both forms and displays the message if the entered data is valid or not.

Figure 3.5. Running DataFormValidation application

Running DataFormValidation application

Here's the code of the DataFormValidation.mxml application that created these forms:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application width="100" height="100" layout="vertical"
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:fx="http://www.faratasystems.com/2008/components"
    creationComplete="onCreationComplete()"
    >
    <mx:VBox width="100" height="100" backgroundColor="white">
         <mx:Label text="Submit Vacation Request"
               fontWeight="bold" fontSize="16" fontStyle="italic"
               paddingTop="10" paddingBottom="5" paddingLeft="10"
               />

        <mx:HBox width="100" height="100" >
          <fx:DataForm id="left" width="100" dataProvider="{vacationRequestDTO}">
            <fx:DataFormItem label="Employee Name: " fontWeight="bold"
             dataField="EMPLOYEE_NAME" required="true"
             validators="{[nameValidator, requiredValidator]}">
               <mx:TextInput  fontWeight="normal" />
            </fx:DataFormItem>
            <fx:DataFormItem label="Employee Email: " fontWeight="bold"
              dataField="EMPLOYEE_EMAIL" required="true"
              validators="{[emailValidator]}">
                     <mx:TextInput   fontWeight="normal"/>
             </fx:DataFormItem>
             <fx:DataFormItem label="Employee Email: " fontWeight="bold"
               dataField="MANAGER_EMAIL" required="true"
               validators="{[emailValidator]}">
                <mx:TextInput   fontWeight="normal"/>
             </fx:DataFormItem>
             <fx:DataFormItem label="Department: " fontWeight="bold"
              dataField="DEPARTMENT" required="true"
              validators="{[requiredValidator]}">
                <fx:TextInput fontWeight="normal"/>
             </fx:DataFormItem>
             <mx:Spacer height="10"/>
             <fx:DataFormItem label="Description: " fontWeight="bold"
               dataField="DESCRIPTION">
                <mx:TextArea width="200"  height="80" fontWeight="normal" />
             </fx:DataFormItem>
          </fx:DataForm>

          <fx:DataForm id="right" width="100" dataProvider="{vacationRequestDTO}">
               <fx:DataFormItem label="Start Date: " fontWeight="bold"
                dataField="START_DATE"  valueName="selectedDate" required="true">
                     <mx:DateField fontWeight="normal"/>
               </fx:DataFormItem>
               <fx:DataFormItem label="End Date: " fontWeight="bold"
                 dataField="END_DATE" valueName="selectedDate" required="true">
                     <fx:DateField  fontWeight="normal"/>
                       <fx:validators>
                          <mx:Array>
                           <fx:ValidationRule
                               rule="{afterStartDate}"
                                errorMessage="End Date ($[END_DATE]) must be later
                             than Start Date $[START_DATE]">
                          </fx:ValidationRule>
                          <fx:ValidationRule
                               rule="{afterToday}"
                                errorMessage="End Date ($[END_DATE]) must be later
                             than today">
                          </fx:ValidationRule>
                         </mx:Array>
                     </fx:validators>
               </fx:DataFormItem>
               <fx:DataFormItem label="Request Status: " fontWeight="bold"
                      dataField="STATUS">
                     <mx:Label  fontWeight="normal"/>
               </fx:DataFormItem>
             </fx:DataForm>
          </mx:HBox>
    </mx:VBox>
    <mx:Button label="Save" click="onSave()"/>

    <mx:Script>
          <![CDATA[
               import com.farata.datasource.dto.VacationRequestDTO;
               import mx.utils.UIDUtil;

               [Bindable] private var vacationRequestDTO:VacationRequestDTO ;
               private function afterToday( val: Object) : Boolean {
                     var b : Boolean = val.END_DATE > new Date();
                     return b;
               }
               private function afterStartDate( val: Object) : Boolean {
                     var b : Boolean = val.END_DATE > val.START_DATE;
                     return b;
               }

               private function onCreationComplete():void {
                     // create a new vacation request
                     vacationRequestDTO = new VacationRequestDTO;
                     vacationRequestDTO.REQUEST_ID = UIDUtil.
createUID();
vacationRequestDTO.STATUS = "Created";
                                       vacationRequestDTO.START_DATE =
                              new Date(new Date().time + 1000 * 3600 * 24);
                     vacationRequestDTO.EMPLOYEE_NAME = "Joe P";
                         vacationRequestDTO.EMPLOYEE_EMAIL =
 "jflexer@faratasystems.com";
                     vacationRequestDTO.VACATION_TYPE =
 "L"; //Unpaid leave - default
               }

               private function onSave():void       {
                     if (isDataValid()) {
                          mx.controls.Alert.show("Validation succedded");
                     } else {
                          mx.controls.Alert.show("Validation failed");
                     }
               }

               private function isDataValid():Boolean {
                     var failedLeft:Array = left.validateAll();
                     var failedRight:Array = right.validateAll();
                     return ((failedLeft.length == 0)&&(failedRight.length == 0));
               }
          ]]>
    </mx:Script>

    <mx:StringValidator id="nameValidator" minLength="6"
                  requiredFieldError="Provide your name, more than 5 symbols" />
    <mx:EmailValidator id="emailValidator"
                  requiredFieldError="Provide correct email" />
    <mx:StringValidator id="requiredValidator"
                  requiredFieldError="Provide non-empty value here" />
</mx:Application>

On creationComplete event this application creates an instance of the vacationRequestDTO that is used as a dataProvider for both left and right data forms.

This code uses a mix of standard Flex validators (StringValidator, EmailValidator) and subclasses of ValidatorRule. Note that both email fields use the same instance of the EmailValidator, which is not possible with regular Flex validation routines:

<fx:DataFormItem label="Employee Email: " fontWeight="bold"
              dataField="EMPLOYEE_EMAIL" required="true"
              validators="{[emailValidator]}">
                     <mx:TextInput   fontWeight="normal"/>
             </fx:DataFormItem>
             <fx:DataFormItem label="Employee Email: " fontWeight="bold"
               dataField="MANAGER_EMAIL" required="true"
               validators="{[emailValidator]}">
                <mx:TextInput   fontWeight="normal"/>
             </fx:DataFormItem>

Notice that these validators are encapsulated inside the DataFormItem. If application programmers decide to add or remove some of the form item when the view state changes, they don't need to program anything special to ensure that validators work properly! The form item end date encapsulates two validation rules that are given as a closures afterStartDate and afterToday.

<fx:DataFormItem label="End Date: " fontWeight="bold"
                 dataField="END_DATE" valueName="selectedDate" required="true">
                     <fx:DateField  fontWeight="normal"/>
                       <fx:validators>
                          <mx:Array>
                           <fx:ValidationRule
                               rule="{afterStartDate}"
                                errorMessage="End Date ($[END_DATE]) must be later
                             than Start Date $[START_DATE]">
                          </fx:ValidationRule>
                          <fx:ValidationRule
                               rule="{afterToday}"
                                errorMessage="End Date ($[END_DATE]) must be later
                             than today">
                          </fx:ValidationRule>
                         </mx:Array>
                     </fx:validators>
               </fx:DataFormItem>

...

private function afterToday( val: Object) : Boolean {
    var b : Boolean = val.END_DATE > new Date();
    return b;
}

private function afterStartDate( val: Object) : Boolean {
    var b : Boolean = val.END_DATE > val.START_DATE;
    return b;
}

The code above does not include standard Flex validators inside the <fx:validators>, but this is supported too. For example, you can add the line in the validators section of a DataFormItem right under the <mx:Array> tag.

<mx:StringValidator id="requiredValidator"
                  requiredFieldError="Provide non-empty value here" />

If you do it, you'll have three validators bound to the same form item End Date: one standard Flex validator and two functions with validation rules.

From the application programmer's perspective, using such validation rules is simple. It allows reusing validators, which can be nicely encapsulated inside the form items.

For brevity, the function onSave() just displays a message box stating that the validation failed:

mx.controls.Alert.show("Validation failed");

But if you run this application through a debugger and place a breakpoint inside the function isDataValid(), you'll see that all validation errors in the failedLeft and failedRight arrays (Figure 3.6, “Debugger's View of validation errors”).

Figure 3.6. Debugger's View of validation errors

Debugger's View of validation errors

The next question is, "How does all this work?"

Class ValidationRule Explained

Enhancing the original Flex validators, the new ValidationRule extends Flex Validator and is known to clear.swc's UI controls. With it, developers can attach any number of validation rules to any field of a form or a list-based component. This means you can attach validation rules not only on the field level, but also on the parent level, such as to a specific DataGrid column or to an entire row.

When we designed the class, our approach was to separate (for real) validation rules from the UI component they validate. We also made them reusable to spare application developers from copy/pasting the same rule repeatedly. With the ValidationRule class you can instantiate each rule once and reuse it across the entire application. Our goal was to move away from one-to-one relations between a validator and a single property of a form field, to many-to-many relations where each field can request multiple validators and vice versa.

If you don't need to perform cross-field validation in the form, you can continue using the original Flex validator classes. If you need to validate, interdependent fields—say if the amount field has the value greater than $10K you need to block overnight delivery of the order field until additional approval is provided—use our more flexible extension, ValidationRule.

We still want to be able to reuse the validators (EmailValidator, StringValidator et al.) that come with Flex, but they should be wrapped in our class ValidationRule. On the other hand, with the class ValidationRule the application developers should also be able to write validation rules as regular functions, which requires less coding.

The source code of the class ValidationRule that supports all this functionality is listed in Example 3.17, “Class ValidationRule”.

Example 3.17. Class ValidationRule

package com.farata.validators{
    import mx.controls.Alert;
    import flash.utils.describeType;

    import mx.events.ValidationResultEvent;
    import mx.validators.ValidationResult;
    import mx.validators.Validator;

    public class ValidationRule   extends Validator{
        public var args:Array = [];
        public var wrappedRule:Function ;
        public var errorMessage : String = "[TODO] replace me";
        public var data:Object;

          public function ValidationRule() {
               super();
               required = false;
          }
        private function combineArgs(v:Object):Array {
               var _args:Array = [v];
                     if( args!=null && args.length>0 )
                          _args["push"].apply(_args, args);
                     return  _args;
        }

        public function set rule(f:Object) : void {
            if (!(f is Function)){
               Alert.show(""+f, "Incorrect Validation Rule" );
              return; // You may throw an exception here
           }

            wrappedRule = function(val:Object) :Boolean {
               return f(val);
            }
       }

        private function substitute(...rest):String {
            var len:uint = rest.length;
            var args:Array;
            var str:String = "" + errorMessage;
            if (len == 1 && rest[0] is Array){
                args = rest[0] as Array;
                len = args.length;
            }
            else{
                args = rest;
            }

            for (var i:int = 0; i < len; i++){
                str = str.replace(new RegExp("\\$\\["+i+"\\]", "g"), args[i]);
            }
               if ( args.length == 1 && args[0] is Object) {
               var o:Object = args[0];
                  for each (var s:*  in o){
                      str = str.replace(new RegExp("\\$\\["+s+"\\]",
 "g"), o[s]);
                  }

                  var classInfo:XML = describeType(o);
                // List the object's variables, their values, and their types.
             for each (var v:XML in classInfo..variable) {
                      str = str.replace(new RegExp("\\$\\["+v.@name+
"\\]", "g"),
                                                                    o[v.@name]);
                }

            // List accessors as properties.
                for each (var a:XML in classInfo..accessor) {
                // Do not get the property value if it is write only.
                 if (a.@access != 'writeonly') {
                           str = str.replace(new RegExp("\\$\\["+a.@name+"\\]",
                                                            "g"), o[a.@name]);
                 }
                }
               }
            return str;
        }

    override protected function doValidation(value:Object):Array{
             var results:Array = [];

            if (!wrappedRule(data))
                 results.push(new ValidationResult(true, null, "Error",
                          substitute(combineArgs(data))));

            return results;
        }
    override public function validate(value:Object = null,
                   suppressEvents:Boolean = false):ValidationResultEvent{
        if (value == null)
            value = getValueFromSource();

        // if required flag is true and there is no value
        // we need to generate a required field error
        if (isRealValue(value) || required){
            return super.validate(value, suppressEvents);
        }
        else {
            // Just return the valid value
            return new ValidationResultEvent(ValidationResultEvent.VALID);
        }
      }
    }
}

The superclass Validator has two methods that will be overridden in its descendents: doValidation(), which initiates and performs the validation routine, and the function validate(), which watches required arguments and gets the values from the target UI control.

Notice this code fragment from the DataFormValidation application:

<fx:ValidationRule rule="{afterStartDate}"
 errorMessage="End Date ($[END_DATE]) must be later than Start Date $[START_DATE]">
</fx:ValidationRule>

mentions the name of the function afterStartDate that alternatively could have been declared inline as a closure. The function ensures that the date being validated is older than the END_DATE.

private function afterToday( val: Object) : Boolean {
    var b : Boolean = val.END_DATE > new Date();
    return b;
}

In this code val points at the dataProvider of the form, which, in the sample application, is an instance of the vacationRequestDTO. An important point is that both the DataForm and the ValidationRule see the same dataProvider.

The value of the errorMessage attribute includes something that looks like a macro language: ($[END_DATE]). The function substitute() finds and replaces via regular expression the specified name (i.e. END_DATE) in all properties in the dataProvider with their values.

If dataProvider is a dynamic object, the function ValidationRule.substitute() enumerates all its properties via or for each loop. For regular classes, Flex offers a reflection mechanism using the function describeType() – give it a class name and it'll return a definition of this class in a form of XML. Then, the function substitute() gets all class variables and accessors (getters and setters) and applies the regular expression to the errorMessage text.

For example, if you deal with a dynamic object o that has a property END_DATE, the following line will replace ($[END_DATE]) in the error text with the value of this property – o[s]:

str = str.replace(new RegExp("\\$\\["+s+"\\]", "g"), o[s]);

The method substitute() is called from doValidate(), and if the user entered invalid dates (i.e. the start date is 12/10/2008 and the end date 12/06/2008), the validator will find the properties called END_DATE and START_DATE and turn this error text:

"End Date ($[END_DATE]) must be later than Start Date $[START_DATE]"

into this one:

"End Date (12/06/2008) must be later than Start Date 12/10/2008"

In Chapter 2, Selected Design Patterns you learned how to write class factories that can wrap functions and return them as objects. This technique is applied in the ValidationRule class too, which supports functions as validators. If the application code uses the setter rule the function with business-specific validation rules is expected.

The class ValidationRule has this setter:

public function set rule(f:Object) : void {
          if (!(f is Function)){
             Alert.show(""+f, "Incorrect Validation Rule" );
            return;
         }

          wrappedRule = function(val:Object) :Boolean {
          return f(val);
          }
}

In the application DataFormValidation you can easily find this setter has been used (we've already discussed the function afterStartDate above):

<fx:ValidationRule
    rule="{afterStartDate}"
    errorMessage="End Date ($[END_DATE]) must be later
                    than Start Date $[START_DATE]">
</fx:ValidationRule>

We hope you like the simplicity that ValidationRule offers to application developers that have to validate forms. The next section examines a more sample application that demonstrates the use of this class in a DataGrid control.

Embedding Validation Rules into a DataGrid

As opposed to component libraries, classes in a framework depend on each other. In this context is means that the ValidationRule class requires an enhanced DataGrid component.

Note

Please note that sample application shown below does uses DataGrid and DataGridItem from a different namespace. These classes are included in the clear.swc library and come with the source code accompanying the book, but due to space constraints, we won't include the source code of these objects here.

This example is yet another version of the Café Townsend from Chapter 1, Comparing Selected Flex Frameworks. For simplicity, the employee data are hard-coded and to run this application you don't need to do any server-side setup.

This application is an example of a master-detail window with validators embedded inside a data grid. Figure 3.6, “Debugger's View of validation errors” shows the phone number having the wrong number of digits in the first row of our DataGrid component. Embedded validation rule properly reports an error message that reads "Wrong length, need 10 digit number".

Figure 3.7. Validating the phone DataGridColumn

Validating the phone DataGridColumn

You can also assign validation rules to the form items that show details of the selected row. In Figure 3.8, “Validating the salary DataGridColumn” you can see a validation error message stating that "Salary(9.95) is out of reasonable range". All fields that have invalid values have red borders. While examining the source code, please note, the dropdown box Department that was populated using a resource file.

Figure 3.8. Validating the salary DataGridColumn

Validating the salary DataGridColumn

This version of the Café Townsend application uses the following custom object Employee_getEmployees_gridFormTest:

Example 3.18. Code of Café Townsend with validations

<?xml version="1.0" encoding="UTF-8"?>
<mx:ViewStack height="100%" width="100%" xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:fx="http://www.faratasystems.com/2008/components"  creationPolicy="all"
    creationComplete="fill_onClick()">
<fx:DataCollection id="collection" destination="com.farata.datasource.Employee"
          method="getEmployees"  collectionChange="trace(event)"
                                                     fault="trace(event)" />
<mx:Canvas height="100%" width="100%">
    <mx:Panel title="Employee List" width="100%" height="100%">
          <fx:DataGrid id="dg"
               itemRenderer="{new
      UIClassFactory(com.farata.controls.dataGridClasses.DataGridItemRenderer)}"
          horizontalScrollPolicy="auto" width="100%" dataProvider="{collection}"
               editable="true" height="100%" rowHeight="25">
             <fx:columns>
               <fx:DataGridColumn dataField="EMP_FNAME"  headerText="First Name"/>
               <fx:DataGridColumn dataField="EMP_LNAME"  headerText="Last Name"/>
               <fx:DataGridColumn dataField="DEPT_ID"   editable="false"
                  headerText="Department"
                  resource="{com.farata.resources.DepartmentComboResource}"/>
               <fx:DataGridColumn dataField="STREET"  headerText="Street"/>
               <fx:DataGridColumn dataField="CITY"  headerText="City"/>
               <fx:DataGridColumn dataField="STATE"  editable="false"
                  headerText="State"
                  resource="{com.farata.resources.StateComboResource}"/>
               <fx:DataGridColumn dataField="ZIP_CODE"  headerText="Zip Code"
                  formatString="zip" >
                     <fx:validators>
                          <mx:ZipCodeValidator />
                     </fx:validators>
               </fx:DataGridColumn>
               <fx:DataGridColumn dataField="PHONE" headerText="Phone Number"
                  formatString="phone"  >
                     <fx:validators>
                          <mx:Array>
                                <mx:PhoneNumberValidator  wrongLengthError="Wrong
                                length, need 10 digit number"/>
                          </mx:Array>
                     </fx:validators>
               </fx:DataGridColumn>

               <fx:DataGridColumn dataField="STATUS"  headerText="Status"/>

               <fx:DataGridColumn dataField="SS_NUMBER" headerText="Ss Number"
                                                        formatString="ssn" >
                     <fx:validators>
                          <mx:SocialSecurityValidator/>
                     </fx:validators>
               </fx:DataGridColumn>

               <fx:DataGridColumn dataField="SALARY" headerText="Salary"
                                 formatString="currency(2)">
                     <fx:validators>
                          <mx:Array>
                            <fx:ValidationRule
                            rule="{function(data:Object):Boolean
                                  { return (data &amp;&amp;data.SALARY > 10000
                                  &amp;&amp; data.SALARY &lt; 500000);}}"
                               errorMessage="Salary ($[SALARY]) is out of reasonable
                                                                          range"/>
                          </mx:Array>
                     </fx:validators>
               </fx:DataGridColumn>

               <fx:DataGridColumn dataField="START_DATE"  headerText="Start Date"
                itemEditor="mx.controls.DateField" editorDataField="selectedDate"
                formatString="shortDate"/>

               <fx:DataGridColumn dataField="TERMINATION_DATE"
                  headerText="Termination Date" itemEditor="mx.controls.DateField"
                  editorDataField="selectedDate" formatString="shortDate">
                     <fx:validators>
                          <fx:ValidationRule
                                rule="{afterStartDate}"
                                errorMessage="End Date ($[TERMINATION_DATE]) must be
                                            later than Start Date $[START_DATE]">
                          </fx:ValidationRule>
                     </fx:validators>
               </fx:DataGridColumn>

               <fx:DataGridColumn dataField="BIRTH_DATE" headerText="Birth Date"
                  itemEditor="mx.controls.DateField" editorDataField="selectedDate"
                  formatString="shortDate"/>

               <fx:DataGridColumn dataField="BENE_HEALTH_INS"  headerText="Health"
                  resource="{YesNoCheckBoxResource}" rendererIsEditor="true"/>

               <fx:DataGridColumn dataField="BENE_LIFE_INS"  headerText="Life"
                  resource="{YesNoCheckBoxResource}"   rendererIsEditor="true"/>

                <fx:DataGridColumn dataField="BENE_DAY_CARE"  headerText="Day Care"
                  resource="com.farata.resources.YesNoCheckBoxResource"
                  rendererIsEditor="true"/>

                <fx:DataGridColumn dataField="SEX"  headerText="Sex"
                  resource="{SexRadioResource}" rendererIsEditor="true"/>
            </fx:columns>
    </fx:DataGrid>

    <fx:DataForm dataProvider="{dg.selectedItem}">
          <mx:HBox>
               <mx:Form>
                     <fx:DataFormItem dataField="EMP_ID" label="Emp Id:"/>
                     <fx:DataFormItem dataField="EMP_FNAME" label="First Name:"/>
                     <fx:DataFormItem dataField="STREET" label="Street:"/>
                     <fx:DataFormItem dataField="CITY" label="City:"/>
                     <fx:DataFormItem dataField="BIRTH_DATE" label="Birth Date:"
                       formatString="shortDate" required="true"/>
                     <fx:DataFormItem dataField="BENE_HEALTH_INS" label="Health:"
                       resource="{com.farata.resources.YesNoCheckBoxResource}"/>
                     <fx:DataFormItem dataField="STATUS" label="Status:"
                       resource="{com.farata.resources.StatusComboResource}"
                       required="true"/>
               </mx:Form>

               <mx:Form>
                     <fx:DataFormItem dataField="MANAGER_ID" label="Manager Id:"/>
                     <fx:DataFormItem dataField="EMP_LNAME" label="Last Name:"/>
                     <fx:DataFormItem dataField="STATE" label="State:"
                       resource="com.farata.resources.StateComboResource"/>
                     <fx:DataFormItem dataField="SALARY" label="Salary:"
                       formatString="currency" textAlign="right">
                         <fx:validators>
                           <fx:ValidationRule rule="{function(data:Object):Boolean {
                            return (data &amp;&amp;data.SALARY > 10000 &amp;&amp;
                                   data.SALARY &lt; 500000);}}"
                              errorMessage="Salary ($[SALARY]) is out
                                                           of reasonable range"/>
                          </fx:validators>
                     </fx:DataFormItem>
                     <fx:DataFormItem dataField="START_DATE" label="Start Date:"
                                    formatString="shortDate"/>
                     <fx:DataFormItem dataField="BENE_LIFE_INS" label="Life:"
                          resource="{YesNoCheckBoxResource}"/>
                     <fx:DataFormItem dataField="SEX" label="Sex:"
                          resource="{SexRadioResource}"/>
               </mx:Form>
               <mx:Form>
                     <fx:DataFormItem dataField="DEPT_ID" label="Department:"
                          resource="{DepartmentComboResource}"/>
                     <fx:DataFormItem dataField="SS_NUMBER" label="Ss Number:"
                          itemEditor="{com.farata.controls.MaskedInput}"
                                                         formatString="ssn">
                          <fx:validators>
                                <mx:SocialSecurityValidator/>
                          </fx:validators>
                     </fx:DataFormItem>
                     <fx:DataFormItem dataField="ZIP_CODE" label="Zip Code:"
                                    formatString="zip">
                          <fx:validators>
                                <mx:ZipCodeValidator />
                          </fx:validators>
                     </fx:DataFormItem>
                     <fx:DataFormItem dataField="PHONE" label="Phone Number:"
                        itemEditor="{com.farata.controls.MaskedInput}"
                                    formatString="phone">
                          <fx:validators>
                                <mx:PhoneNumberValidator
                                         wrongLengthError="keep typing"/>
                          </fx:validators>
                     </fx:DataFormItem>
                     <fx:DataFormItem dataField="TERMINATION_DATE"
                       label="Termination Date:" formatString="shortDate">
                          <fx:validators>
                                <fx:ValidationRule
                                rule="{afterStartDate}"
                                errorMessage="End Date ($[TERMINATION_DATE]) must be
                               later than Start Date $[START_DATE]">
                             </fx:ValidationRule>
                     </fx:validators>
                  </fx:DataFormItem>
                   <fx:DataFormItem dataField="BENE_DAY_CARE" label="Day Care:"
                       resource="{YesNoCheckBoxResource}"/>
               </mx:Form>
          </mx:HBox>
   </fx:DataForm>
</mx:Panel>

<mx:HBox horizontalScrollPolicy="off" verticalAlign="middle" height="30"
                                                        width="100%">
 <mx:Spacer width="100%"/>
 <mx:VRule strokeWidth="2" height="24"/>
 <mx:Button enabled="{dg.selectedIndex != −1}"
            click="collection.removeItemAt(dg.selectedIndex)" label="Remove"
            icon="@Embed('/assets/delete_16x16.gif')"/>
 <mx:Button click="addItemAt(Math.max(0,dg.selectedIndex+1)) " label="Add"
            icon="@Embed('/assets/add_16x16.gif')" />
 <mx:Label text="Deleted: {collection.deletedCount}"/>
 <mx:Label text="Modified: {collection.modifiedCount}"/>
 </mx:HBox>
</mx:Canvas>

<mx:Script>    <![CDATA[
    import com.farata.controls.dataGridClasses.DataGridItemRenderer;
    import com.farata.core.UIClassFactory;
    import com.farata.collections.DataCollection;
    import mx.collections.ArrayCollection;
    import mx.controls.dataGridClasses.DataGridColumn;
    import mx.events.CollectionEvent;
    import com.farata.datasource.dto.EmployeeDTO;
    import com.farata.resources.*;
    import mx.validators.*;

    private var linkage:EmployeeDTO = null;

    private function fill_onClick():void {
          collection.source = Test.data;
          dg.selectedIndex=0;
    }

    private function addItemAt(position:int):void   {
          var item:EmployeeDTO = new EmployeeDTO();
          collection.addItemAt(item, position);
          dg.selectedIndex = position;
    }

    import com.farata.resources.*;
    import com.farata.controls.*;
    private function afterStartDate( val: Object) : Boolean {
          return !val.TERMINATION_DATE || val.TERMINATION_DATE > val.START_DATE;
    }
    ]]>
 </mx:Script>
</mx:ViewStack>

When you review the code in Example 3.18, “Code of Café Townsend with validations”, you'll find different flavors of validation rules inside the data grid columns in this implementation of the Café. For example, the following rule is defined as an anonymous function for the data grid column SALARY:

<fx:DataGridColumn dataField="SALARY" headerText="Salary"
                                 formatString="currency(2)">
                     <fx:validators>
                          <mx:Array>
                            <fx:ValidationRule
                            rule="{function(data:Object):Boolean
                                  { return (data &amp;&amp;data.SALARY > 10000
                                  &amp;&amp; data.SALARY &lt; 500000);}}"
                               errorMessage="Salary ($[SALARY]) is out of reasonable
                                                                          range"/>
                          </mx:Array>
                     </fx:validators>
               </fx:DataGridColumn>

If the data grid is populated and the salary in a particular cell does not fall into the range between 10000 and 500000, this function returns false and this data value is considered invalid. Such cell(s) will immediately get the red border and the error message will report the problem in the red error tip right by this cell.

Some of the validation rules were repeated both in the DataGrid and DataForm, but this doesn't have to be the case. The same instances of the ValidationRule class can be reused as in the DataFormValidation application.

The data for this sample application are hardcoded in Test.as, which starts as follows:

public class Test{

          public function Test(){
          }
          static public function get data() : Array {
               var e : EmployeeDTO = new EmployeeDTO;
               e.EMP_FNAME = "Yakov";
               e.EMP_LNAME = "Fain";
               e.BENE_DAY_CARE = "Y";
               e.BENE_HEALTH_INS = "Y";
               e.BENE_LIFE_INS = "N";
                ...

If you'd like to have deeper understanding of how <fx:DataGridColumn> works with embedded validators, please examine the source code of the classes com.farata.controls.dataGridClasses.DataGridItem and com.farata.controls.DataGrid that are included with the source code of accompanying this chapter.

We had to jump through a number of hoops to allow Flex validators to communicate with the DataGrid as the class Validator expects to work only with sublcasses of the UIComponent that are focusable controls with borders. It's understandable – who needs to validate, say a Label?

But we wanted to be able to display a red border around the cell that has an invalid value and a standard error tip when the user hovers the mouse pointer over the DataGrid cell. Hence we had to make appropriate changes and replace the original DataGrid.itemRederer with our own that implements IValidatorListener interface. An itemRenderer on the DataGrid level affects all its columns.

<fx:DataGrid id="dg"
    itemRenderer="{new UIClassFactory(
 com.farata.controls.dataGridClasses.DataGridItemRenderer)}"

We've included this replacement of the DataGridItemRenderer in the demo application just for illustration purposes to show that you can substitute the base classes from Flex framework with your own. But as a developer of a business framework, you should hide such code in the base components, which in this case would have been a constructor of your enhanced DataGrid.

Besides validation rules, it worth noting how master-detail relations are implemented with just one line:

<fx:DataForm dataProvider="{dg.selectedItem}">

A selected row in a DataGrid (master) is a dataProvider for a DataForm (detail). With original Flex DataGrid and Form components it would take a lot more coding to properly re-bind the object representing a selected row that is changing whenever the user selects a different one.

Once again, a well designed framework should allow application developers to write less code. The code of this version of Café Townsend is an example of what can be done in only about 160 lines of code. It implements master-detail relations, performs lots of custom validations, uses business style sheets. Adding a couple of dozen lines of code can turn this application into a CRUD built on the powerful DataCollection class that will be discussed in Chapter 6.

Minimizing the Number of Custom Events

Up till now, you've concentrated on building rich components for a business framework. We Flex architects should also recommend some coding techniques that serve the same goal as these components: enabling application developers to write less code. In this section you'll see how to minimize the number of custom event classes in any application.

Flex is all about event-driven development. Create loosely coupled custom components and let them send events to each other as in the Mediator pattern example from Chapter 2, Selected Design Patterns. You can create new events for every occasion. If an event does not need to carry any additional data, just give it a name, specify its type as flash.events.Event, and define the meta-tag to help Flex Builder list this event in its type-ahead prompts and dispatch it when appropriate. If your new event needs to carry some data, create an ActionScript class extending flash.events.Event, define a variable in this subclass to store application data, and override the method clone().

Currently, for a mid-size Flex application that includes about 30 views, where each view has two components that can send/receive just one custom event, for example, you face the need to write 60 custom event classes that look pretty much the same. We'll show you how to get away with just one custom event class for the entire application.

To illustrate the concept, we've created a simple application that defines one event class that can serve multiple purposes. This application consists of two modules (GreenModule, shown in Figure 3.9, “The GreenModule is loaded”, and RedModule) that are loaded in the same area of the main application on the click of one of the load buttons. It also has one universal event class called ExEvent.

Figure 3.9. The GreenModule is loaded

The GreenModule is loaded

Clicking any Send button creates an instance of this event that's ready to carry an application-specific pay load: a DTO, a couple of String variables, or any other object.

Figure 3.9, “The GreenModule is loaded”'s example uses an ActionScript class called GirlfriendDTO. No Cairngorm-style mapping is required between the event being sent and the modules. For example, if you send Green event to the RedModule, nothing happens since the latter is not listening to Green event.

This application and its source code are deployed at http://tinyurl.com/5n5qkg.

Flex Builder's project has a folder called modules that contains two modules: RedModule and GreenModule. The red one is listening for the arrival of the girlfriend's first and last name packaged in our single event class as the two separate strings listed in Example 3.19, “RedModule.mxml”.

Example 3.19. RedModule.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    width="100%" height="100%" creationComplete="onCreationComplete(event)">
    <mx:TextArea id="display" backgroundColor="#FF4949"  width="100%" height="100%"
                                                      fontSize="28"/>
        <mx:Script>
        <![CDATA[
    private function onCreationComplete(evt:Event):void{
        this.addEventListener("RedGirlfriend", onRedGirlfriend);
        }

    private function onRedGirlfriend(evt:ExEvent):void{
        display.text="My girlfriend is "+ evt.fName+ " " + evt.lName ;
        }
        ]]>
    </mx:Script>
</mx:Module>

The green module expects the girlfriend's name in a form of GirlfriendDTO.

Example 3.20. GreenModule

<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
       width="100%" height="100%" creationComplete="onCreationComplete(event)">
    <mx:TextArea id="display" backgroundColor="#9CE29C" width="100%"
                   height="100%" color="#070707" fontSize="28"/>
    <mx:Script>
        <![CDATA[
        import dto.GirlfriendDTO;

        private function onCreationComplete(evt:Event):void{
          this.addEventListener("GreenGirlfriend", onGreenGirlfriend);
        }

        private function onGreenGirlfriend(evt:ExEvent):void{
         var myGirlfriend:GirlfriendDTO=evt["girlfriend"];

         display.text="My girlfriend is "+ myGirlfriend.fName+ " " +
                                                 myGirlfriend.lName ;
         }
        ]]>
    </mx:Script>
</mx:Module>

The GirlfriendDTO is pretty straightforward too, as Example 3.21, “GirlFriendDTO” shows.

Example 3.21. GirlFriendDTO

package dto
/**
 * This is a sample data transfer object (a.k.a. value object)
 */
{
    public class GirlfriendDTO {
         public var fName:String; // First name
         public var lName:String; // Last name
    }
}

The next step is to create a single but universal event class. It will be based on the DynamicEvent class, which allows you to add any properties to the event object on the fly. For the example, GirlfriendDTO is the object. Here's how dynamic event can carry the GirlfriendDTO:

var myDTO:GirlfriendDTO=new GirlfriendDTO();
            myDTO.fName="Mary";
            myDTO.lName="Poppins";

            var greenEvent:ExEvent=new ExEvent("GreenGirlfriend");
            greenEvent.girlfriend=myDTO;
            someObject.dispatchEvent(greenEvent);

Sending any arbitrary variables with this event will be straightforward:

var redEvent:ExEvent=new ExEvent("RedGirlfriend");

            redEvent.fName="Mary";
            redEvent.lName="Poppins";
            someObject.dispatchEvent(redEvent);


The ExEvent is a subclass of DynamicEvent, which has a little enhancement eliminating manual programming of the property Event.preventDefault:

package{
    import mx.events.DynamicEvent;
    public dynamic class ExEvent extends DynamicEvent{
        private var m_preventDefault:Boolean;

        public function ExEvent(type:String, bubbles:Boolean = false,
                                         cancelable:Boolean = false)     {
            super(type, bubbles, cancelable);
            m_preventDefault = false;
        }

         public override function preventDefault():void         {
            super.preventDefault();
            m_preventDefault = true;
        }

        public override function isDefaultPrevented():Boolean     {
            return m_preventDefault;
        }
    }
}

The function preventDefault() is overridden because the class DynamicEvent does not automatically process preventDefault in cloned events

The code of the test application below loads modules, and then the user can send any event to whatever module is loaded at the moment. Of course, if the currently loaded module does not have a listener for the event you're sending, tough luck. But the good news is that it won't break the application either, as shown in Example 3.22, “An application that tests generic event ExEvent”.

Example 3.22. An application that tests generic event ExEvent

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
 layout="vertical" viewSourceURL="srcview/index.html">
 <mx:HBox>
    <mx:Button label="Load the Green Module"
          click="loadMyModule('modules/GreenModule.swf')"/>
    <mx:Button label="Load the Red module"
          click="loadMyModule('modules/RedModule.swf')"/>
    <mx:Button label="Send Green Event with Object" click="sendGreen()"/>
    <mx:Button label="Send Red Event Event with two strings" click="sendRed()"/>

 </mx:HBox>

<mx:Panel width="100%" height="100%" title="A module placeholder"
                                                    layout="absolute">
  <mx:ModuleLoader id="theModulePlaceholder" width="100%" height="100%"/>
</mx:Panel>
<mx:Script>
    <![CDATA[
        import dto.GirlfriendDTO;
        //Load the module specified in the moduleURL
        private function loadMyModule(moduleURL:String):void{
            theModulePlaceholder.url=moduleURL;
            theModulePlaceholder.loadModule();
        }

        // Sending generic  ExEvent adding an object that contains
        // the name of the girlfriend
        private function sendGreen():void{

            // Strongly typed DTO - better performance and readability,
            // but its structure has to be known for both parties -
            // the main application and the module
            var myDTO:GirlfriendDTO=new GirlfriendDTO();
            myDTO.fName="Mary";
            myDTO.lName="Poppins";

            if (theModulePlaceholder.child !=null){
                var greenEvent:ExEvent=new
                                          ExEvent("GreenGirlfriend");
                greenEvent.girlfriend=myDTO;

                theModulePlaceholder.child.dispatchEvent(greenEvent);
            }
        }

    // Sending a generic ExEvent that holds the name of the girlfriend
    // as two separate variables
        private function sendRed():void{
                var redEvent:ExEvent=new ExEvent("RedGirlfriend");

                redEvent.fName="Angelina";
                redEvent.lName="YouKnowWho";

                if (theModulePlaceholder.child !=null){
                theModulePlaceholder.child.dispatchEvent(redEvent);
                }
        }
    ]]>
</mx:Script>
</mx:Application>

The function sendGreen() sends an instance of ExEvent carrying the DTO inside, while the sendRed() just adds two properties fName and lName to the instance of ExEvent.

Instead of using a DTO, you could've used a weakly typed data transfer object:

var myDTO:Object={fname:"Mary",lname:"Poppins"};

But this may result in a tiny bit slower performance and the code would be less readable. On the positive side, there would be no need to explicitly define and share the class structure of the DTO between the application (the mediator) and the module. You can use this technique for creating quick and dirty prototypes.

To summarize, using a single dynamic event spares you from tedious coding of dozens of similar event classes. On the negative side, because this solution does not use the metatag Event declaring the names of the events, Flex Builder won't be able to help you with the name of the event in its type-ahead help.

In vast majority of RIAs you can afford to lose a couple of milliseconds caused by using dynamic event. Using a single dynamic event is one more step toward minimizing the code to be written for your project.

Summary

In this chapter you learned by example how to start enhancing the Flex framework with customized components and classes, such as CheckBox, ComboBox, DataGrid, DataForm, DataFormItem, ValidationRule. You also saw how to use these components in your applications. The source code for this chapter comes as two Flex Builder projects – Business Framework that includes sample applications discussed in this chapter and Business Framework Library that includes a number of enhanced Flex components (some of them were shown here in simplified form) that can be used in your projects too.

The clear.swc component library is offered for free under MIT license as a part of the open source framework Clear Toolkit– just keep the comments in the source code giving credits to Farata Systems as the original creator of this code. You can find out the up to date information about all components included into Clear Toolkit by visiting popular open source repository Sourceforge, or to be more specific, the following URL: https://sourceforge.net/projects/cleartoolkit. Make sure that you've tested theses components thoroughly before using them in production systems.

In this chapter we reviewed and explained why and how we extended several Flex components. We started with simpler CheckBox and ComboBox components just because it was easier to illustrate the process of extending of components. But then we did some heavy lifting and extend such important for every enterprise developer Flex components as Form and Vailidator. You've seen working example application that would integrate validators into a DataForm and DataGrid componens.

Besides extending components, we've shown you some best practices (using resources and writing single-even applications) that greatly minimize the amount of code that application developers have to write.

You'll see more of extended components in Chapters 6, 9, and 11. But now let's discuss convenient third party tools that can be handy for any Flex team working on an enterprise project.

Read more from Yakov Fain. Yakov Fain's Atom feed

  • comments: 2

Comments

2 Comments

George said:

I am facing a problem about cross field validation across 3 text fields, say TEXT_1, TEXT_2 and TEXT_3.

The validation rule is that:

Either
TEXT_1 must be input
Or
(TEXT_2 Or TEXT_3) must be input.

I want to know how to use the Class ValidationRule to do that?

thanks
George


David Arakea said:

Got a little confused at embedding validation in data grid. Thanks for the info. It is very handy for my work!

Skull and Crossbones t-shirt

Leave a comment


Tag Cloud

Poll: Mobile Features

What feature do you use most on your mobile phone?

Vote | View Poll Results | Read Related Blog Entry

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.