You are hereAria User Guide / Section III: In Depth / Input Validation
Input Validation
Aria input validation allows you to define your validations in a single file so that the validation rules contained in the file can be used application wide or even across applications. Built-in validations provide common validations such as `mandatory' and `min-max' validations. The validation rules provide predictable and consistent behavior making your code much easier to maintain and extend than the alternative of checking individual inputs on an ad hoc basis.
The validation mechanism can also make it easier to localize an application or customize things like input limits for different environments or contexts.
In this section you will see how to implement the simple validations and how to handle validation errors and exceptions. The chapter will also show how to build customized validations.
Adding Basic Validations
The first step in adding input validations is to define our validation rules in an XML file and amend the
startup.properties
file to refer to the file.
Create a new file and name it `
validations.xml
'. In the file create a simple validation file in the
resources
directory of your project.
A simple validation file
<Validations> <validation name="firstname" type="mandatory" msg="Firstname cannot be blank"/> </Validations>
The
name
attribute is what we refer to when referencing the validation from a page. The
type
attribute defines the validation type and the
msg
attribute is the message which is shown should the validation fail.
The
startup.properties
file needs to refer to the new validations file. Setting the validations file can be carried out through the project properties page within the editor or manually by adding the following line to the
startup.properties
file. If no `Validations' property is specified the application will default to `validations.xml' and use it if found.
New line in the startup.properties
Validations=validations.xml
Now a page can be created with an edit field which needs to be validated. To provide data for the field and to map the input field to the data model a binding can be added within the data section. A button has also been added which will be used to trigger the validation.
The validated page
<Page class="org.formaria.test.Personal" style="prompt"> <Components> <Label x="144" y="110" w="100" h="20" style="prompt" content="Firstname:" alignment="Left" opaque="false"/> <Edit name="firstnameText" x="294" y="108" w="178" h="20" style="data" alignment="Leading"/> <Button name="validateBtn" x="220" y="250" w="200" h="30" content="Validate"/> </Components> <Events> <Event method="doValidation" target="validateBtn" type="ActionHandler"/> </Events> <Validations> <Validation rule="firstname" target="firstnameText"/> </Validations> <Data> <Bind target="firstnameText" source="customer/firstname" output="customer/firstname"/> </Data> </Page>
Once the validation rule has been setup and bound to a component, the validation needs to be triggered and validation error messages also need to be presented to the user.
From the page's XML file it can be seen that the class which implements the event handlers is `
org.formaria.test.Personal
'. For now, simply insert the following function into the class:
Prepare to handle validations with an event handler function
public void doValidation() { int ret = checkValidations(); }
If the application is run and the button clicked, the Java console will show the exceptions being thrown with the text of the validation. If some text is entered into the textbox and the button clicked again no exception is now thrown indicating that everything is OK.
The
checkValidations
function returns a value indicating the status. A decision can be made what to do depending on the value returned from the function. The return values are defined as constants in the
org.formaria.aria.validation.Validator
interface.
This code works but it is not a particularly elegant way of handling the validations and the section Writing a custom exception handler., will also look at how to handle and display the validations in a more programmer friendly way.
Adding component validations
The validation described above was triggered by a programmed event and the same validation would also be triggered upon a page transition. Component validations on the other hand are triggered when the user interface component being validated has lost focus. Start by opening the
validations.xml
file and adding a new minmax validation. The new validation appears as below
An in-line validation
<validation name="age" type="minmax" min="18" max="65" msg="Customer age must be between {min} and {max}" mandatory="false"/>
In this case the type is `
minmax
' indicating that this validation is to be used with numeric data. The `
min
' and `
max
' attributes contain the limits of the input. The message contains the two pieces of text `
{min}
' and `
{max}
'. These values will be substituted with the values contained in the `
min
' and `
max
' attributes whenever the validation is triggered. The `
mandatory'
attribute indicates whether or not the field must contain data when the page level validations are being carried out.
Next add another edit component to the `
personal
' page and add the validation below.
<Validation rule="age" target="ageText"/>
Start the application once more and enter a value outside the specified range into the new edit field. Hit the tab key and you will notice an exception being output in the Java console. Do the same with a value within the range and no exception is output.
To highlight the behavior of the validation a few input combinations are listed. For clarity enter some data into the
firstname
edit so that it does not trigger the mandatory validation.
-
Clear the data in the
ageedit field and click the button. No exception is output as the mandatory attribute is set tofalse. -
Enter some invalid data into the edit field, hit the tab field to generate the exception and click the button. In this case the page level validation triggers an exception as the text entered into the field is outside the permissible range even though the mandatory flag is
false. -
Change the mandatory flag to
trueand restart the application. Leave the age field blank and click the button. In this case an exception is triggered as the field cannot be blank.
Validation attribute evaluation
Aria 3.0 extends the use of evaluated attributes to most areas of the framework, including validations. Evaluated attributes allow you to change the behavior of a validation at runtime by way of callbacks to your code or to library code.
Handling exceptions
The attribute evaluator contains an exception handler that gets called in case of an exception and can override the result returned by the attribute evaluator. The evaluator may be of use in case, for example, an evaluation depends on a list selection and where that list may not have a selected values - the list would otherwise return a value such as null or -1 to indicate the error and this is probably not a valid value for the evaluated attribute. Say a path of
a/b/${c}/d/e
is enetered and
${c}
depends on say a list selection and that list is not fully initialized.
Validation lifecycles
Validation can be invoked at various times within an application. For the most part the lifecylce of an individual validation instance is tied to the lifecycle of the component and page to which it is bound.
At the finest level a validation may be triggered by a change in a components data, or by intervention by the programmer, while at a courser level the validations can be triggered prior to page transition.
Validation on Page Transition
Normally validations are checked when a page transition occurs if the
TriggerValidations
startup property is set, however on versions of Aria prior to 3.2 this value was not added to the startup file by the
New Aria Project wizard
, so it wass necessary to set the value manually.
Once the
TriggerValidations
flag is set a validation error will prevent the page transition from occuring.
Writing a custom exception handler
The method of reporting the validations up to now is crude and does not give you much control of error reporting. In order to manage the validations a custom exception handler can be written.
Writing a custom exception handler is straightforward and allows you the same code to be used for error handling on all of pages in an application. A new class named `
ExceptionHandler
` which provides an implementation of the
ExceptionHandler
interface is created.
The
ExceptionHandler
interface contains two methods.
The handleException method definition
public boolean handleException( Object c, Exception ex, Object checker );
The
handleException
method takes three parameters and returns a
boolean
value. The first parameter is the component being validated, the second the exception, and the third is the validator which is carrying out the validation. The method should return
true
if further validations are to be carried out and
false
if no more validations are required. This chaining of validations allows you to control how the validations are used and how error messages are displayed. Stopping a chain of validations allows you to prevent the user being bombarded with error messages if, for instance, a single input field triggers multiple validation errors.
The accumulateMessages method definition
public int accumulateMessages( boolean accumulate, int level );
Another method is now required, the
accumulateMessages
method is called twice when the
Page.checkValidation
s method is invoked. The first time is before any validations have been done so as to inform the
ExceptionHandler
that page level validations have begun. In this case the first parameter is
true
. Then, it is called once more when all of the validations have completed and in this case the first parameter is
false
.
The second parameter indicates the most serious level of error encountered so far. the value of this parameter can be overridden within the implementation of the
accumulateMessages
method by returning a different value from the method. Otherwise it is expected that the second parameter is returned.
Now we will implement our custom
ExceptionHandler
class as shown below. The class is shown in its entirety as it is to explain it section by section when the context of each section can be seen.
The entire ExceptionHandler class
package org.formaria.test; import java.util.*; import java.awt.*; import org.formaria.aria.*; import org.formaria.aria.exception.*; import org.formaria.aria.validation.*; public class ExceptionHandler implements ExceptionHandler { boolean pageValidation = false; private Page currentPage; Vector errors, warnings; public ExceptionHandler( Page page ) { currentPage = page; } public boolean handleException( Object comp, Exception ex, Object xvalidator ) { Validator validator = ( Validator )validator; if ( ( validator.getLevel() == validator.LEVEL_ERROR ) && ( ! pageValidation ) ){ currentPage.showMessage( "Input error", ex.getMessage() ); return true; } String msg = validator.getMessage(); if ( validator.getLevel() == validator.LEVEL_ERROR ) errors.add( msg ); else warnings.add( msg ); return true; } public int accumulateMessages( boolean start, int level ) { pageValidation = start; if ( pageValidation ){ errors = new Vector(); warnings = new Vector(); } else { if ( errors.size() > 0 || warnings.size() > 0 ) { Toolkit.getDefaultToolkit().beep(); currentPage.showMessage( "Error", formatMessages() ); } } return level; } private String formatMessages() { String msg = ""; for ( int i = 0; i < errors.size(); i++ ) msg += "error :" + errors.elementAt( i ) + "\n"; for ( int i = 0; i < warnings.size(); i++ ) msg += "warning :" + warnings.elementAt( i ) + "\n"; return msg; } }
The constructor takes the page being validated as a parameter. This is stored in a member variable for use when the validations are being done.
The ExceptionHandler constructor
public ExceptionHandler( Page page ) { currentPage = page; }
The
handleException
method handles component and page level validations. In the case of component validations the level will be
XValidator.LEVEL_ERROR
passed exception is simply displayed.
In the case where the
pageValidation
flag is
true
the message is added to the appropriate storage. The idea behind this is that the reporting of the errors can be controlled instead of having to display individual messages (and require the user to dismiss multiple error messages) or only most severe errors need be displayed.
Implementing the handleException method
public boolean handleException( Object comp, Exception ex, Object xvalidator ) { Validator validator = ( Validator )validator; if ( ( validator.getLevel() == validator.LEVEL_ERROR ) && ( ! pageValidation ) ){ currentPage.showMessage( "Input error", ex.getMessage() ); return true; } String msg = validator.getMessage(); if ( validator.getLevel() == validator.LEVEL_ERROR ) errors.add( msg ); else warnings.add( msg ); return true; }
The
accumulateMessages
method is shown below
Implementing the accumulateMessages method
public int accumulateMessages( boolean start, int level ) { pageValidation = start; if ( pageValidation ){ errors = new Vector(); warnings = new Vector(); } else { if ( errors.size() > 0 || warnings.size() > 0 ) { Toolkit.getDefaultToolkit().beep(); currentPage.showMessage( "Error", formatMessages() ); } } return level; }
The first line sets the member variable `
pageValidation
' to the value passed in the `
start
' parameter.
If
start
is
true
the storage vectors are initialized; one to store warnings and the other to store errors.
Alternatively, if
start
is
false
something needs to be done with the errors and warnings that have been accumulated. In this case, the
showMessage
method of the currentPage is called which will display the errors and warnings in a simple text dialog with an OK button. A decision on what to do next can be made in the `
personal
' page where the
checkValidations
method was called depending on the values returned from the
accumulateMessages
method.
As mentioned above this error handling mechanism provides control over the error reporting mechanism and another way to handle the validations in the case where there are only warnings is to display a dialog listing the warnings and asking `Do you wish to continue?' along with `
Yes
' and `
No
' buttons. If the Yes button is clicked then the value
XValidator.LEVEL_IGNORE
can be returned which will give the page the impression that no validations were generated.
Writing a custom validation factory and custom validations
The basic
ValidationFactory
class can construct the validation types discussed so far but custom validations might be required, and this is done by means of writing a custom validation factory.
Start by creating the class AValidationFactory which extends the ValidationFactory class.
The custom AValidationFactory
package org.formaria.test; import java.awt.*; import org.formaria.debug.*; import org.formaria.xml.*; import org.formaria.aria.build.*; import org.formaria.aria.validation.*; public class AValidationFactory extends ValidationFactory { public Validator getValidation( String validationName, Method m, int mask, Object page ) { return super.getValidation( validationName, mask, page ); } }
For now the new class simply implements the
getValidation
method and returns the call to the
super
method.
In order to start using this custom class the following line needs to be added to the
startup.properties
file
The startup.properties line
ValidationFactory=org.formaria.test.ValidationFactory
If the application is run now, it will work exactly as before except that our new validation factory is now controlling the construction of the validation rules.
Now a custom validation class can be created. As an example a simple number validation class can be created which will make sure that entered data is numeric. Create a class called
NumberValidation
which extends the
BaseValidator
class. Create the validate method which carries out the validation. In the
NumberValidator
class the
Double.parseDouble
method is used to parse the text contained in the component. If an exception occurs it can be assumed that the validation has failed, the
errorLevel is set
and an exception is thrown.
The NumberValidation class
package org.formaria.test; import java.awt.*; import org.formaria.aria.validation.*; public class NumberValidation extends BaseValidator { public void validate(Object c, boolean forceMandatory) throws Exception { String text = getText(c); if (text.trim().length() > 0) { try { Double.parseDouble(text); } catch (Exception ex) { errorLevel = LEVEL_ERROR; throwException(); } } }
Now the custom validation factory class needs to be amended to create and return the
NumberValidator
class whenever the text `
number
' is passed in the
validationName
parameter of the
getValidation
function.
The modified getValidation method
public Validator getValidation( String validationName, Method m, int mask, Object page ) { XmlElement ele = ( XmlElement ) validations.get( validationName ); String type = ele.getAttribute( "type" ); if ( type.compareTo( "number" ) == 0 ) { NumberValidation validator = new NumberValidation(); validator.setName( validationName ); validator.setMask( mask ); validator.setup( ele ); return validator; } else { return super.getValidation( validationName, mask, page ); } }
The line `
validator.setup(ele)
' deserves some explanation as this allows whatever arbitrary attributes might be required for the custom validator to be set. The
ele
variable is the entire validation node as defined in the validations.xml file and can be interrogated by the custom validator for whatever tags are used.
Now open the validations file and enter a new validation of type `
number
'
Declaring a new number validation
<validation name="price" type="number" msg="The price must be a number"/>
Next, open the
personal.xml
file and enter a definition for a new edit component and name it
priceText
. Now enter a new validation for the component as follows
<Validation rule="price" target="priceText"/>
If the application is run now it can be seen that the new field has page level as well as component validations. You can remove the component validation by changing the
validate
function as follows.
public void validate(Object c, boolean forceMandatory) throws Exception { if (forceMandatory) { String text = getText(c); if (text.trim().length() > 0) { try { Double.parseDouble(text); } catch (Exception ex) { errorLevel = LEVEL_ERROR; throwException(); } } } }
The
forceMandatory
flag is set to
true
when the validation is page level so by doing nothing when it is
false
removes the component validation.
Finally the new validation class is changed to handle the
mandatory
attribute. This is achieved by overloading the setup method of the
BaseValidator
class as mentioned earlier.
The overloaded setup class
public void setup( XmlElement ruleConfig, XmlElement instanceConfig ) { String value = ruleConfig.getAttribute( "mandatory" ); mandatory = value.compareTo( "true" ) == 0 ? true : false; super.setup( ruleConfig, instanceConfig ); }
This is wherethe attributes defined in the
validations.xml
file can be used. Set the mandatory attribute to
true
and run the application again.
Handling the mandatory flag
public void validate(Component c, boolean forceMandatory) throws Exception { if (forceMandatory) { String text = getText(c); if (text.trim().length() > 0) { try { Double.parseDouble(text); } catch (Exception ex) { errorLevel = LEVEL_ERROR; throwException(); } } else if ( mandatory ){ errorLevel = LEVEL_ERROR; throwException(); } } }
Simply throw the exception if the
mandatory
flag is
true
and the text is zero length.
Registering Validations
Aria 3.0 introduced a new mechanism for registering validations and this mechanism falls into line with the other registrations used by Aria. Instead of using a validation factory as described above a validation can be added by adding a validations xml file. By default the project will look for a
validations.xml
file in a project (or a file named by the
Validations
startup property. An example of such a file is shown below:
A sample validations file
<Validations> <validation name="firstname" type="mandatory" msg="VALID_FNAME" first="true"/> <validation name="surname" type="mandatory" msg="VALID_SNAME"/> <validation name="dob" type="mandatory" min="sixtyYearsAgo()" max="eighteenYearsAgo()" msg="Date of birth must be a valid date, and the applicant must be between the ages of 18 and 60"/> <validation name="propvalue" class="org.formaria.mortgage.MinMaxValidator" min="2000" max="2000000" msg="VALID_PROPVALUE" mandatory="true"/> <validation name="mortamt" class="org.formaria.mortgage.MinMaxValidator" min="2000" max="1000000" msg="VALID_MORT_AMT" mandatory="true"/> <validation name="createApp" type="function" msg="Please select 'Sole' or 'Joint' in order to proceed!"/> <validation name="openApp" type="function" msg="Please select a file to proceed!"/> <validation name="filename" type="mandatory" msg="Please enter a filename!"/> <validation name="mortratio" type="function" msg="Mortgage amount is greater than property value!"/> </Validations>
This is now the preferred mechanism for adding validations, but the old method should still work.
Getting values from the Page
There are a number of ways of getting validation information from the page being validated which can be very useful
First open the
personal.xml
file which has been used up to now and change the minmax validation using the `age' validation to the following
Modifiied age validation
<Validation rule="age" target="ageText" method="getAge"/>
The new `
method
' attribute declares the name of the method within the page which is to be used to retrieve the value being validated. Now the
getAge
method is created in the page.
Create the getAge method
public String getAge() { System.out.println( "In the getAge() function" ); Edit ageText = ( Edit )findComponent( "ageText" ); return ageText.getText(); }
If the application is run now the first line is output to the console when the age is being validated. Now, this does exactly the same as previously but this way of getting values can be useful where more behind the scenes work needs to be done in order to carry out the validation.
This leads to the next type of validation for which another custom validation can be written. The validation is a FunctionValidation which will simply call a function within the page. The specified function will return the error level. Start by creating the following class which extends the BaseValidator class
The new FunctionValidation
package org.formaria.test; import java.awt.*; import org.formaria.xml.*; import org.formaria.aria.validation.*; public class FunctionValidation extends BaseValidator { public void validate(Object c, boolean forceMandatory) throws Exception { Integer ret = ( Integer ) invokeMethod(); if ( ret.intValue() > LEVEL_IGNORE ) { errorLevel = ret.intValue(); throwException(); } } public void setup( XmlElement element ) { message = element.getAttribute( "msg" ); super.setup( element ); } }
Within the setup method the validator setting the message which needs to be used with this validator.
The validate function invokes the registered method. It expects a return type of Integer which it can then throw an exception for if the error level is greater than
LEVEL_IGNORE
.
Next define a validation for this type in the
validation.xml
file
The agefunction validation declared
<validation name="agefunction" type="function" msg="The agefunction validation triggered this"/>
Now the custom validation factory needs to be amended to construct the new validation. The
getValidation
function should now look like this
The modified getValidation function
public Validator getValidation( String validationName, Method m, int mask, Object page ) { XmlElement ele = ( XmlElement ) validations.get( validationName ); String type = ele.getAttribute( "type" ); if ( type.compareTo( "number" ) == 0 ) { NumberValidation validator = new NumberValidation(); validator.setName( validationName ); validator.setMask( mask ); validator.setup( ele ); return validator; } else if ( type.compareTo( "function" ) == 0 ) { FunctionValidation validator = new FunctionValidation(); validator.setName( validationName ); validator.setup( ele ); return validator; } else { return super.getValidation( validationName, mask, page ); } }
Reopen the
personal.xml
file and insert the following validation.
Implementation of the function validation
<Validation rule="agefunction" target="ageText" method="doAgeValidation"/>
The method being called in this case is the `doAgeValidation' method so that function needs to be defined within the page.
The doAgeValidation function
public Integer doAgeValidation() { return new Integer( Validator.LEVEL_ERROR ); }
As mentioned earlier the method needs to return an
Integer
object which contains the error level. The reason for using this type of validation is that any type of complex checking can be carried out and the appropriate level can be returned. These validations can be quite complex and would be very difficult to describe in XML. If fact an XML syntax would probably have to be developed in order to describe this type of validation so it's just easier to have Java do the work for us.
From the examples here it might seem that quite a lot of work needs to be done in order to setup custom validations but it is done in this way so that a more maintainable codebase and consistent way of handling validations can be achieved.
Adding validation feedback
Limited visual feedback can be provided by the validations as they are evaluated, so that special colors are used for failed validations. Adding such feedback makes it easy for the user to locate the inputs that block progress within an application due to failed validations.
The normal, warning and failure colors used for these validations can be set using the
BaseValidator.setValidationColors
, however the validations can also be set using an extended style reference in the validations file
Add a style parameter to the validations file
<Validations style="validationStyle"> <validation name="firstname" type="mandatory" msg="Firstname cannot be blank"/> </Validations>
The style attributes are then specified in the styles file as normal:
Add the styles to the styles file
<style name="validationStyle"> <colorNormal value="ffffff"/> <colorWarn value="ffffd5"/> <colorFail value="ffe6d5"/> </style>
- Printer-friendly version
- Login or register to post comments