home
products
education
support
partners
company

introduction

get started

articles

manual

tutorials

reference docs

case studies

knowledge base

goodies

screenshots

demos

echoes

Carousel zone


SourceForge.net Logo

home

forums

downloads

bugs

mailing list
XUI Zone - manual

number of ratings 0
average rating 0.00

17 Input Validation

Carousel 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.

Code Sample 17-1 - A simple validation file

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.

Code Sample 17-2 - 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.

Code Sample 17-3 - The validated page

alignment="Left" opaque="false"/>

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 ' net.xoetrope.test.Personal '. For now, simply insert the following function into the class:

Code Sample 17-4 - 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 net.xoetrope.xui.validation.XValidator interface.

This code works but it is not a particularly elegant way of handling the validations and the section "Writing a custom exception handler" on page 165 , 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

Code Sample 17-5 - An in-line validation

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.

Code Sample 17-6 -

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 age edit field and click the button. No exception is output as the mandatory attribute is set to false .

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 true and 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.

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 XExceptionHandler interface is created.

The XExceptionHandler interface contains two methods.

Code Sample 17-7 - 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.

Code Sample 17-8 - The accumulateMessages method definition

public int accumulateMessages( boolean accumulate, int level );

Another method is now required, the accumulateMessages method is called twice when the XPage.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.

Code Sample 17-9 - The entire ExceptionHandler class

package net.xoetrope.test;

import java.util.*;

import java.awt.*;

import net.xoetrope.xui.*;

import net.xoetrope.xui.exception.*;

import net.xoetrope.xui.validation.*;

public class ExceptionHandler implements XExceptionHandler

{

boolean pageValidation = false;

private XPage currentPage;

Vector errors, warnings;

public ExceptionHandler( XPage page )

{

currentPage = page;

}

public boolean handleException( Object comp, Exception ex, Object xvalidator )

{

XValidator validator = ( XValidator ) xvalidator;

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.

Code Sample 17-10 - The ExceptionHandler constructor

public ExceptionHandler( XPage 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.

Code Sample 17-11 - Implementing the handleException method

public boolean handleException( Object comp, Exception ex, Object xvalidator )

{

XValidator validator = ( XValidator ) xvalidator;

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

Code Sample 17-12 - 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 XValidationFactory 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 ValidationFactory which extends the XValidationFactory class.

Code Sample 17-13 - The custom XValidationFactory

package net.xoetrope.test;

import java.awt.*;

import net.xoetrope.debug.*;

import net.xoetrope.xml.*;

import net.xoetrope.xui.build.*;

import net.xoetrope.xui.validation.*;

public class ValidationFactory extends XValidationFactory {

public XValidator 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

Code Sample 17-14 - The startup.properties line

ValidationFactory=net.xoetrope.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 XBaseValidator 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.

Code Sample 17-15 - The NumberValidation class

package net.xoetrope.test;

import java.awt.*;

import net.xoetrope.xui.validation.*;

public class NumberValidation extends XBaseValidator {

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.

Code Sample 17-16 - The modified getValidation method

public XValidator 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 '

Code Sample 17-17 - Declaring a new number validation

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

Code Sample 17-18 -

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.

Code Sample 17-19 -

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 XBaseValidator class as mentioned earlier.

Code Sample 17-20 - The overloaded setup class

public void setup( XmlElement ele )

{

String value = ele.getAttribute( "mandatory" );

mandatory = value.compareTo( "true" ) == 0 ? true : false;

super.setup( ele );

}

This is wherethe attributes defined in the validations.xml file can be used. Set the mandatory attribute to true and run the application again.

Code Sample 17-21 - 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.

Getting values from the XPage

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

Code Sample 17-22 - Modifiied age validation

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.

Code Sample 17-23 - Create the getAge method

public String getAge()

{

System.out.println( "In the getAge() function" );

XEdit ageText = ( XEdit )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 XBaseValidator class

Code Sample 17-24 - The new FunctionValidation

package net.xoetrope.test;

import java.awt.*;

import net.xoetrope.xml.*;

import net.xoetrope.xui.validation.*;

public class FunctionValidation extends XBaseValidator {

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

Code Sample 17-25 - The agefunction validation declared

Now the custom validation factory needs to be amended to construct the new validation. The getValidation function should now look like this

Code Sample 17-26 - The modified getValidation function

public XValidator 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.

Code Sample 17-27 - Implementation of the function validation

The method being called in this case is the 'doAgeValidation' method so that function needs to be defined within the page.

Code Sample 17-28 - The doAgeValidation function

public Integer doAgeValidation()

{

return new Integer( XValidator.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.

comments


If you were logged in you could rate this article or add a comment.