Joshua has posted 1 posts at DZone. View Full User Profile

Validators and Formatters in Flex 4

10.14.2010
| 19892 views |
  • submit to reddit

In Flex both validation and formatting are, at their root, a combination of string manipulation and visual feedback for that manipulation. It’s simply a matter of parsing strings to detect a certain pattern and then altering that string to fit a certain format and returning error messages to users. This gets important with user input like phone numbers, names, currency, zip codes, ISBN numbers; a whole host of different situations that user facing applications or applications that communicate with other services need. The Flex Framework has two classes that help meet these requirements: the Validator and Formatter classes.

 

Validator

The Validator is an event dispatcher object that checks a field within any Flex control to ensure that the value submitted falls within its set parameters. These parameters can indicate a certain format, whether a field is required, or a length of a field. The integration of the Validator with the Flex control means that displaying the result of a validation error simply requires setting the source of a Validator class to be the control where the user input will occur and indicating the property that the Validator should check. The Validator will dispatch the event to the control, and the control will display a custom error message that has been set in the Validator. There are many predefined Validator types in the Flex Framework for credit cards, phone numbers, email, and social security numbers and we’ll look at a quick example of using these before moving on to building custom Validators and integrating validators and validation events with controls.

 

Formatter

The Formatter class has a simple but important use: altering a given value to fit a prescribed format. You could do this with a string manipulation function as well, but it’s both cleaner and easier to connect to your UI if you use a Formatter in your code. Dates in particular can be much more easily manipulated using a Formatter, though other uses are just as important. The Formatter class defines a single method of importance to us: format() that takes the input and returns the proper string.

How to use Validators and Formatters together

To use validators and formatters together in a component, simply create multiple validators for each of the needed types of validation. When the focusOut event occurs on the appropriate TextInput, call the validate() method on the proper validator. To bind the validator to the correct TextInput, set the TextInput as the source of the validator and the text as the property of the TextInput:

<mx:NumberValidator id="numValidator" source="{inputCurrency}" property="text"/>

 

The formatter is called after the data has been validated. The base Formatter class accepts a formatting string consisting of hash marks that will be replaced by the digits or characters of the string that is being formatted. For a phone number, for example, the phone number formatting string is as follows:

(###) ###-####

 

The formatter for the phone number is set up as here:

<mx:PhoneFormatter id="phoneFormatter" formatString="(###) ###-####" validPatternChars="#-() "/>

 

To use this formatter, call the format() method and pass the text property of the desired TextInput:

inputPhone.text = phoneFormatter.format(inputPhone.text);

 

In many situations you might want to create a custom formatter that will accept any appropriate string and return it with the correct formatting. To do that, you extend the Formatter class and override the format() method.

Within the format() method, create a SwitchSymbolFormatter instance and pass to SwitchSymbolFormatter’s formatValue() method a string of hash marks to represent the characters you want replaced with your original string. The formatValue() method, if provided the format ###-### and the source 123456, will return 123–456. Return this value from the format() method of your custom formatter.

The Formatter class uses a string of hash marks that will be replaced by all the characters in the string passed to the format() method. Replacing those characters is simply a matter of looping through the string and, character by character, building out the properly formatted string and then replacing it.

package oreilly.cookbook
{
   import mx.formatters.Formatter;
   import mx.formatters.SwitchSymbolFormatter;
 
   public class ISBNFormatter extends Formatter
   {

   public var formatString : String = "####-##-####"; 

      public function ISBNFormatter()
      {
         super();
      }
 
      override public function format(value:Object):String {
         // we need to check the length of the string
         // ISBN can be 10 or 13
            if( ! (value.toString().length == 10 || value.toString().length == 13) ) {
               error="Invalid String Length";
              return ""
            }
 
            // count up the number of hash marks passed into our format string
            var numCharCnt:int = 0;

            for( var i:int = 0; i<formatString.length; i++ ) {

                if( formatString.charAt(i) == "#" ) {
                   numCharCnt++;
                }
            }
 
            // if we don't have the right number of items in our format string
            // time to return an error

            if( ! (numCharCnt == 10 || numCharCnt == 13)  ) {
                error="Invalid Format String";
                return ""
            }

            // If the formatString and value are valid, format the number.
            var dataFormatter:SwitchSymbolFormatter = new SwitchSymbolFormatter();
            return dataFormatter.formatValue( formatString, value );
        }
 
   }
}

 

Creating Custom Validators

There are many times you’ll want to create a custom Validator. To demonstrate this, let’s begin putting together some custom Validators to check different types of controls. Let’s imagine that we want to validate groups of radio buttons and combo boxes to ensure that a value is selected and that one of a group of ToggleButtons are selected and that one of the radio buttons in a group is selected. To do this first we’ll create a custom validator that will look at all children of a Container and examine them to see if one of them is selected.

package com.dzone
{
  import mx.containers.VBox;
  import mx.core.Container;
  import mx.core.UIComponent;
  import mx.validators.ValidationResult;
  import mx.validators.Validator;
                  
  import spark.components.Group;
  import spark.components.ToggleButton;
  import spark.components.VGroup;
                  
   public class SelectedValidator extends Validator
   {                                       
     private var componentsToCheck:Array = [];                                            
   
     public function SelectedValidator() {
        super();
     }
                                            
     override public function set source(value:Object):void {
    
     var con:UIComponent = (value as UIComponent);
    
     for(var i:int = 0; i<con.numChildren; i++) {
           componentsToCheck.push(con.getChildAt(i));
     }
  
    super.source = value;
                                   }                                           
    //here we check for either a null value or the possibility that
    //the developer has added a custom prompt to the comboBox, in which
    //case we want to return an error
                            
    override protected function doValidation(value:Object):Array {
    var results:Array = [];
    var hasSelected:Boolean = false;

    for(var i:int = 0; i<componentsToCheck.length; i++) {                                                   
        var toggle:ToggleButton = componentsToCheck[i] as ToggleButton;
             if(toggle.selected) {
                  hasSelected = true;
         }
     }                                                 

    if(!hasSelected) {
      var res:ValidationResult = new ValidationResult(true, "Please select a button", "");
         results.push(res);
            }                                                 
        return results;
       }
    }
}

To return a ValidationResultEvent for a group of radio buttons, we simply need to check that the selectedIndex of the RadioButtonGroup is not −1, which would indicate that there is not a selected radio button. To validate a combo box, create a custom validator and check that the selectedItem of the ComboBox is not null, and is not a custom prompt or invalid value, if one is provided.

package com.dzone
{      
  import mx.validators.ValidationResult;
  import mx.validators.Validator;
                
  public class ComboValidator extends Validator
  {
    //this is the error message that is returned if an item in the ComboBox
    //is not selected

    public var error:String;

    //if the developer sets a manual prompt, but pushes something into the
    //array of the ComboBox (I've seen it many times for different reasons)
    // we want to check that against what the selected item in the CB is

    public var prompt:String;
                                           
    public function ComboValidator() {
        super();
    }

    //here we check for either a null value or the possibility that
    //the developer has added a custom prompt to the comboBox, in which
    //case we want to return an error

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

        if(value == -1 || value == null) {
            var res:ValidationResult = new ValidationResult(true, "", "", error);
            results.push(res);
         }
         return results;
     }
   }
}

 

One strategy for performing multiple validations is to use an array: Add to the array all of a component’s validators that need to be called, and then use the public static Validator.validateAll() method to validate all the validators in the array. This is particularly valuable when multiple fields need to be validated at the same time. If any of the validators return an error, all those errors are joined together and displayed in an Alert control:

<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
         xmlns:s="library://ns.adobe.com/flex/spark"
         xmlns:mx="library://ns.adobe.com/flex/mx"
         xmlns:dzone="com.dzone.*" width="600" height="400"
         creationComplete="init()">                  

         <s:layout>
           <s:VerticalLayout gap="50"/>
         </s:layout>                  

         <fx:Declarations>
            <mx:StringValidator id="rbgValidator" source="{rbg}" property="selectedValue"/>
            <dzone:SelectedValidator id="toggleValidator" property="width"/>
            <dzone:ComboValidator id="comboValidator" error="Please Select A State" source="{stateCB}" property="selectedItem"/>                                          

           <s:RadioButtonGroup id="rbg"/>
         </fx:Declarations>

         <fx:Script>
              <![CDATA[
                  import mx.collections.ArrayList;
                  import mx.controls.Alert;
                  import mx.events.ValidationResultEvent;
                  import mx.validators.Validator;                                                 

                  [Bindable]
                  private var validatorArr:Array;
 
                 //make an array of all the validators that we'll check with one method later
 
                 private function init():void {
                        validatorArr = new Array();

                       //push all the validators into the same array
                       validatorArr.push(rbgValidator);
                       validatorArr.push(toggleValidator);
                       validatorArr.push(comboValidator);
                  }
          
                // validate all the items in the validator array and show an alert if there
                // are any errors

                 private function validateForm():void {
  
                    //the validate all method will validate all the Validators in an array
                    //passed to the validateAll method

                    var validatorErrorArray:Array = Validator.validateAll(validatorArr);;
                    var isValidForm:Boolean = validatorErrorArray.length == 0;

                    if (!isValidForm) {
                        var err:ValidationResultEvent;
                        var errorMessageArray:Array = [];

                        for each (err in validatorErrorArray) {
                               errorMessageArray.push(err.message);
                        }
                        Alert.show(errorMessageArray.join("\n"), "Invalid form...", Alert.OK);
                        }
                    }
                                               
               ]]>
         </fx:Script>
                   
         <s:VGroup id="form" creationComplete="{toggleValidator.source = toggleButton}">
               <s:ComboBox id="stateCB" dataProvider="{new ArrayList([1, 2, 3, 4])}"/>
                  <s:HGroup>
                     <s:RadioButton group="{rbg}" label="first" id="first"/>
                     <s:RadioButton group="{rbg}" id="second" label="second"/>
                     <s:RadioButton id="third" label="third" group="{rbg}"/>
                  </s:HGroup>
        
                  <s:VGroup id="toggleButton" width="500" height="100">
                     <s:ToggleButton id="buttonOne"/>
                     <s:ToggleButton id="buttonTwo"/>
                     <s:ToggleButton id="buttonThree"/>
                     <s:ToggleButton id="buttonFour"/>
                     <s:ToggleButton id="buttonFive"/>
                  </s:VGroup>
         </s:VGroup>
      <s:Button label="validate" click="validateForm()"/>
 </s:Group>

 

Customizing Error Messages

An important part of validation is showing the user error message if the Validation doesn’t pass. You want to create and display multiple validation error results regardless of whether the user has the TextInput or another control focused. To place an error message use the ToolTipManager to create a new ToolTip class and position it over the control. Create a Style object and assign it to the ToolTip to give a red background and the correct font color.

The error tip that displays when a validator returns an error is simply a ToolTip component. You can use a style to represent all the necessary visual information for the ToolTip: backgroundColor, fontColor, fontType, and so forth. Use the setStyle() method of the ToolTip to apply this style to the new tooltips created for each validation error; for example:

errorTip.setStyle("styleName", "errorToolTip");

 

To display multiple tooltips, position them by using the stage position of the relevant control. For example:

var pt:Point = this.stage.getBounds(err.currentTarget.source);
var yPos:Number = pt.y * −1;
var xPos:Number = pt.x * −1;
//now create the error tip
var errorTip:ToolTip =
ToolTipManager.createToolTip(err.message, x
Pos + err.currentTarget.source.width, yPos) as ToolTip;

 

When the form validates all the tooltips are removed by using the ToolTipManager destroyToolTip() method. This method loops through each ToolTip added.

<?xml version="1.0" encoding="utf-8"?>
<s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009"
          xmlns:s="library://ns.adobe.com/flex/spark"
          xmlns:mx="library://ns.adobe.com/flex/halo"
          width="500" height="400"
          xmlns:cookbook="oreilly.cookbook.flex4.*"
          creationComplete="init();" xmlns:dzone="com.dzone.*">
          
         <fx:Declarations>
             <!-- our two validators -->
             <dzone:ComboValidator id="comboValidator1" error="Please Select A State" source="{stateCB1}" property="selectedItem"/>
             <dzone:ComboValidator id="comboValidator2" error="Please Select A State" source="{stateCB2}" property="selectedItem"/>
         </fx:Declarations>

         <fx:Style>
             /* here's our CSS class that we'll use to give our tooltip the appearance
             of an error message */
            .errorToolTip {
                            color: #0000FF;
                            fontSize: 9;
                            fontWeight: "bold";
                            shadowColor: #000000;
                            borderColor: #CE2929;
                            borderStyle: "errorTipRight";
                            paddingBottom: 4;
                            paddingLeft: 4;
                            paddingRight: 4;
                            paddingTop: 4;
              }                                          

           </fx:Style>
          
           <fx:Script>
                <![CDATA[
                   import mx.collections.ArrayList;
                   import mx.controls.Alert;
                   import mx.controls.ToolTip;
                   import mx.events.ValidationResultEvent;
                   import mx.managers.ToolTipManager;
                   import mx.validators.Validator;                                               

                   [Bindable]
                   private var validatorArr:Array;                                                 
                   private var allErrorTips:Array;                                                 

                   private function init():void {
                           validatorArr = new Array();
                           validatorArr.push(comboValidator1);
                           validatorArr.push(comboValidator2);
                   }

 

 

Here’s where the actual validation occurs:

private function validateForm():void {

    // if we have error tips already, then we want to remove them
    if(!allErrorTips) {
                            allErrorTips = new Array();
    } else {
                           for(var i:int = 0; i<allErrorTips.length; i++) {
                                        // remove the tooltip
                                        ToolTipManager.destroyToolTip(allErrorTips[i]);
                           }
               //empty our array
             allErrorTips.length = 0;
    }

                   var validatorErrorArray:Array = Validator.validateAll(validatorArr); 

 

If nothing has been pushed into the validatorErrorArray, you know that no validation error have been thrown; otherwise, you want to go about creating those error tips and placing them:

var isValidForm:Boolean = validatorErrorArray.length == 0;

     if (!isValidForm) {
             var err:ValidationResultEvent;

             for each (err in validatorErrorArray) {
            // Use the target's x and y positions to set position of
            // error tip. We want their actual stage positions
            // in case there's some layout management going on so we use
            //  the getBounds method

 

Because the ErrorEvent’s target property is the control or component that threw the event, use that property to place the error tip:

var pt:Rectangle = stage.getBounds(err.currentTarget.source);

                   var yPos:Number = -pt.y;
                   var xPos:Number = -pt.x;
                   var errorTip:ToolTip = ToolTipManager.createToolTip(err.message, xPos + err.currentTarget.source.width, yPos) as ToolTip;

                    errorTip.setStyle("styleName", "errorToolTip");
                    allErrorTips.push(errorTip);
                      }
                 }
             }
         ]]>
</fx:Script>

              <s:VGroup id="form">
                     <s:ComboBox id="stateCB1" dataProvider="{new ArrayList([1, 2, 3, 4, 5])}"/>
                     <s:ComboBox id="stateCB2" dataProvider="{new ArrayList([1, 2, 3, 4, 5])}"/>
              </s:VGroup >

              <s:Button label="validate" click="validateForm()"/>

             </s:VGroup>

 

This shows you some of the ways that you can integrate formatting and validation easily into your application to check user input and show feedback to any users.

 

About the Author

Joshua Noble is a programmer and interaction designer based in New York City and Portland, Oregon. He's the lead author of Flex 3 Cookbook, Flex 4 Cookbook, and Programming Interactivity, all published by O'Reilly. He has taught at Tufts University, taught workshops and spoken at conferences around the world, and worked as a consultant on a wide range of web projects and physical interaction projects using ActionScript/AIR, Java, Python, C++, and embedded C. He keeps a website at http://thefactoryfactory.com and can be found on Twitter at @factoryfactory

 

 

Published at DZone with permission of its author, Joshua Noble.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Fandy Nur Azizi replied on Sun, 2011/01/30 - 10:56pm

membuat compression ZIP berbasis java This my blog, I make java project from neatbeans IDE. I want to lern about Adobe Flex...

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.