Flex 3 Custom validation of grouped input fields

Introduction

Flex 3 has nicely build-in validation mechanism, but sometimes you wish they had not embedded this so deep into the components, that customization becomes a laborish task. I have also read many articles on customizations of this mechanism, but I have not found a working solution to my specific wish. What I want is validation of single input fields, multiple input and arbitrary placement of errors
besides the tooltip method.

Requirements

Steps

Now I am going to describe the steps I took to create this validator.

Step 1

I have created a Flex (web) project in my workspace. In this project I have 1 mxml file (my application file). I have named this ValidationTutorial.mxml.

1
2
3
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:local="*">
</mx:Application>

In this mxml I will put 3 input field (day, month, year) in a hbox. Furthermore I will place a hidden Label
below the hbox. These are my basic components that I will be using to demonstrate the multiple input validator.

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:local="*">
  <mx:HBox>
      <mx:TextInput id="day" />
      <mx:TextInput id="month" />
      <mx:TextInput id="year" />
  </mx:HBox>
  <mx:Label id="errorMsgbirthdayValidationModel" visible="false" includeInLayout="false"/>
</mx:Application>

Step 2

Code our custom validator. Now we have to create the validator. file -> new -> actionscript class and call it BirthdayValidator (make it extend Validator). override the protected function doValidation, put a call to super.doValidation(value) in it and we have our foundation for the validator in place. Your actionscript class should look as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package {

    public class BirthdayValidator extends Validator {

        public function BirthdayValidator() {
            super();
        }
       
        override protected function doValidation(value:Object):Array {
            // Init the results array with the basic validation        
          var results:Array = super.doValidation(value);
          return results;
        }
 
    }
}

Step 3

This is a relatively small step, but contains the magic that will enable me to validate and control the output regarding 3 input fields. Make a mental note of the valError element, which I will use later on.

1
2
3
4
5
6
7
8
9
10
<mx:Model id="birthdayValidationModel">
     <date>
       <birthday>
         <day>{day.text}</day>
         <month>{month.text}</month>
         <year>{year.text}</year>
         <valError></valError>
       </birthday>
     </date>
  </mx:Model>

Step 4

We define the validator and link all components together. this means we are going to tell the validator that the source for validation is our model and the results will be displayed in the label. If you now run the application, nothing will happen when tabbing across the fields. This is because we have not defined the triggers for the validator and the actual implementation of the validator. We will put this on the last input with focusOut. The implementation for the validator is for tutorial purpose simple.

1
2
3
4
5
6
7
8
<local:BirthdayValidator
         id="birthdayVal"
         required="true"
         source="{birthdayValidationModel}"
         property="birthday"
         listener="{errorMsgbirthdayValidationModel}"
         trigger="{year}"
         triggerEvent="focusOut"/>

We are now close to a working example.

Step 5

Now we define the invalid handler and the valid handler. These methods will contain code to set/unset validation texts. Configure them into the validator and edit the label so that it responds to the errors in it. When you have done this, you will see validation results below the inputs when you enter no values and focusOut on the year input (last input field).

1
2
3
4
5
6
7
8
9
10
<local:BirthdayValidator
         id="birthdayVal"
         required="true"
         source="{birthdayValidationModel}"
         property="birthday"
         listener="{errorMsgbirthdayValidationModel}"
         trigger="{year}"
         triggerEvent="focusOut"
         invalid="onInvalidHandler(event)"
         valid="onValidHandler(event)"/>

And the methods….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
         * Validationresult event handler. This function
         * places the validation results into the appropriate
         * label for error text placement beneath an input
         * text.
         *
         * @param event Results of the input box(es) validation
         */

        public static function onInvalidHandler( event:ValidationResultEvent ):void {
            var _error:String = "";
            var validator:Validator = event.target as Validator;
            for each ( var result:ValidationResult in event.results ) {
                _error+=result.errorCode;
            }
            // All validation results will be displayed in a label component
            var errorLabel:Label = validator.listener as Label;
            errorLabel.text = _error;
            event.stopImmediatePropagation();
        }

       /**
         * Validation result handler. This function removes all
         * error texts from the error label for the current input
         * component.
         *
         * @param event Results of the input box(es) validation
         */

        public static function onValidHandler( event:ValidationResultEvent ):void {
            var _error:String = "";
            var validator:Validator = event.target as Validator;
            // All validation results will be displayed in a label component
            var errorLabel:Label = validator.listener as Label;
            errorLabel.text = _error;
            event.stopImmediatePropagation();
        }

Step 6

Add styling. Just some nice sugar, but styling is everything. Add this styling to your ValidationTutorial.mxml And add the following method to the script to use this styling.
Add to the 3 input field the following property

1
styleName="{ValidatorUtil.showErrorBorder((birthdayValidationModel.birthday.valError as XML), this.day)}"

Step 7

Now comes the 4th element in the mx:model into focus. When you run the application you will see deep blue borders, but they do not change to red borders were there is an error detected by the validation. For this to work we need to programaticly fill the error element to reflect the inputs that are invalid.
This we will do in the validator and a helper method in our ValidationTutorial.mxml

The BirthdayValidator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package {
    import mx.validators.ValidationResult;
    import mx.validators.Validator;

    public class BirthdayValidator extends Validator {
     
      private static const ERROR_START_ELEMENT_TAG:String = "<errorElement>";
      private static const ERROR_END_ELEMENT_TAG:String = "</errorElement>";
      private static const ERROR_DAY_TAG:String = "<error>day</error>";
      private static const ERROR_MONTH_TAG:String = "<error>month</error>";
      private static const ERROR_YEAR_TAG:String = "<error>year</error>";

        public function BirthdayValidator() {
            super();
        }
       
        override protected function doValidation(value:Object):Array {
            var day:String = value.day;
            var month:String = value.month;
            var year:String = value.year;
            var errorString:String = ERROR_START_ELEMENT_TAG;

            // Init the results array with the basic validation        
          var results:Array = super.doValidation(value);
            if (day == "" || day == null) {
              // Texts and codes need to be refactered out of this util
              // into a text file
                results.push(new ValidationResult(true,
                    "day", "noDay", " No day"));
                errorString += ERROR_DAY_TAG;
            }
          // Check month field.
            if (month == "" || month == null) {
              // Texts and codes need to be refactered out of this util
              // into a text file
               results.push(new ValidationResult(true,
                    "month", "noMonth", " No month"));
                 errorString += ERROR_MONTH_TAG;
            }
          // Check month field.
            if (year == "" || year == null) {
              // Texts and codes need to be refactered out of this util
              // into a text file
                results.push(new ValidationResult(true,
                    "year", "noYear", " No year"));
                 errorString += ERROR_YEAR_TAG;
            }
            errorString += ERROR_END_ELEMENT_TAG;
          value.valError = new XML(errorString);
          return results;
        }
 
    }
}

The ValidatorTutorial.mxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:local="*">

    <mx:Style>

      .errorBorderStyle {
       
        border-color: red;
        border-style: solid;
        border-thickness: 2px;
        border-thickness-bottom: 2px;
        border-thickness-left: 2px;
        border-thickness-right: 2px;
        border-thickness-top: 2px;
       
      }
     
      .defaultBorderStyle {
       
        border-color: blue;
        border-style: solid;
        border-thickness: 2px;
        border-thickness-bottom: 2px;
        border-thickness-left: 2px;
        border-thickness-right: 2px;
        border-thickness-top: 2px;
       
      }

    </mx:Style>

  <mx:Script>
    <![CDATA[
      import mx.core.UIComponent;
      import mx.validators.ValidationResult;
      import mx.validators.Validator;
      import mx.events.ValidationResultEvent;
     
        /**
        * Validationresult event handler. This function
        * places the validation results into the appropriate
        * label for error text placement beneath an input
        * text.
        *
        * @param event Results of the input box(es) validation
        */
       public static function onInvalidHandler( event:ValidationResultEvent ):void {
           var _error:String = "";
           var validator:Validator = event.target as Validator;
           for each ( var result:ValidationResult in event.results ) {
               _error+=result.errorCode;
           }
           // All validation results will be displayed in a label component
           var errorLabel:Label = validator.listener as Label;
           errorLabel.text = _error;
           event.stopImmediatePropagation();
       }

      /**
        * Validation result handler. This function removes all
        * error texts from the error label for the current input
        * component.
        *
        * @param event Results of the input box(es) validation
        */
       public static function onValidHandler( event:ValidationResultEvent ):void {
           var _error:String = "";
           var validator:Validator = event.target as Validator;
           // All validation results will be displayed in a label component
           var errorLabel:Label = validator.listener as Label;
           errorLabel.text = _error;
           event.stopImmediatePropagation();
       }
     
       /**
        * Function reserved for complex objects. In this we mean mx:model
        * type objects for multi input validations. (dates where day,month,year
        * are seperated input controls).
        *
        * @param errors A xmllist of elements containing the controls that need
        *        or do not need error styling.
        * @param input the control we like to style.
        * @return  the new styling information.
        */
       public static function showErrorBorder( errors:XML, input:UIComponent ):String {
           var styleName:String  =  "defaultBorderStyle";
           // Check for not null, otherwise the flashplayer
           // will silently ignore the nullpointer and break off
           // any further styling for the target control
           if ( errors != null && errors.error != null ) {
               for each ( var error:XML in errors.error ) {
                   if ( error == input.id ) {
                       styleName  =  "errorBorderStyle";
                       break;
                   }
               }
           }
           return styleName;
       }
     
    ]]>
  </mx:Script>

  <mx:Model id="birthdayValidationModel">
     <date>
       <birthday>
         <day>{day.text}</day>
         <month>{month.text}</month>
         <year>{year.text}</year>
         <valError></valError>
       </birthday>
     </date>
  </mx:Model>

  <mx:HBox>
   
      <mx:TextInput id="day" styleName="{showErrorBorder((birthdayValidationModel.birthday.valError as XML), this.day)}"/>
      <mx:TextInput id="month" styleName="{showErrorBorder((birthdayValidationModel.birthday.valError as XML), this.month)}"/>
      <mx:TextInput id="year" styleName="{showErrorBorder((birthdayValidationModel.birthday.valError as XML), this.year)}"/>
   
  </mx:HBox>
 
  <mx:Label id="errorMsgbirthdayValidationModel" visible="{errorMsgbirthdayValidationModel.text!=''}" includeInLayout="{errorMsgbirthdayValidationModel.text!=''}"/>
 
  <local:BirthdayValidator
         id="birthdayVal"
         required="true"
         source="{birthdayValidationModel}"
         property="birthday"
         listener="{errorMsgbirthdayValidationModel}"
         trigger="{year}"
         triggerEvent="focusOut"
         invalid="onInvalidHandler(event)"
         valid="onValidHandler(event)"/>
 
</mx:Application>

Final note

And now the magic is complete. You have build a multiple input validator, with error message customization and placement. You can add many enhancement, that I will leave up to your imagination.

I hope you enjoy coding Flex/ActionScript as I do.

Kind regards,

Marc de Kwant

Tags: , , , , ,

2 Responses to “Flex 3 Custom validation of grouped input fields”

  1. RK says:

    Nice article

  2. JabbyPanda says:

    Good stuff, your effort nicely complements Flex 3 livedocs sample
    “Example: Validating multiple fields” ;)

    http://livedocs.adobe.com/flex/3/html/help.html?content=createvalidators_4.html

Leave a Reply