User Input Validation in Tapestry 5

Exclusive offer: get 50% off this eBook here
Tapestry 5: Building Web Applications

Tapestry 5: Building Web Applications — Save 50%

A step-by-step guide to Java Web development with the developer-friendly Apache Tapestry framework

$23.99    $12.00
by Alexander Kolesnikov | January 2009 | Content Management Java Open Source

One of the benefits of having a web application is that it can be very easily accessed by everyone around the world. One of the downsides of this is that when so many people use your application, they are going to have errors in their input. Some people are not attentive, others are tired and, finally, everyone in this world has his or her individual style of thinking, so something that seems obvious to the developers of the application might puzzle someone else.

A well-designed web application should immediately be able to define that the input is wrong and stop—otherwise all kinds of errors can happen inside of the application. If this application is user-friendly, it should:

  • Clearly and unambiguously inform the user that some part of the input is erroneous, and should be corrected.
  • Identify the field that is erroneous and mark it in some way.
  • If possible, display the erroneous value, and maybe even explain why exactly it is wrong.

In this article by Alexander Kolesnikov, we will see how Tapestry 5, being a highly efficient and user-friendly framework, handles these issues. For the purpose of this article we will use a Tapestry web application named Celebrity Collector.

Adding Validation to Components

The Start page of the web application Celebrity Collector has a login form that expects the user to enter some values into its two fields. But, what if the user didn't enter anything and still clicked on the Log In button? Currently, the application will decide that the credentials are wrong and the user will be redirected to the Registration page, and receive an invitation to register. This logic does make some sense; but, it isn't the best line of action, as the button might have been pressed by mistake.

These two fields, User Name and Password, are actually mandatory, and if no value was entered into them, then it should be considered an error. All we need to do for this is to add a required validator to every field, as seen in the following code:

<tr>
<td>
<t:label t:for="userName">
Label for the first text box</t:label>:
</td>
<td>
<input type="text" t:id="userName" t:type="TextField" t:label="User Name" t:validate="required"/>
</td>
</tr>
<tr>
<td>
<t:label t:for="password">
The second label</t:label>:
</td>
<td>
<input type="text" t:id="password" t:label="Password" t:type="PasswordField" t:validate="required"/>
</td>
</tr>

Just one additional attribute for each component, and let's see how this works now. Run the application, leave both fields empty and click on the Log In button. Here is what you should see:

User Input Validation in Tapestry 5

Both fields, including their labels, are clearly marked now as an error. We even have some kind of graphical marker for the problematic fields. However, one thing is missing—a clear explanation of what exactly went wrong. To display such a message, one more component needs to be added to the page. Modify the page template, as done here:

<t:form t:id="loginForm">
<t:errors/>
<table>

The Errors component is very simple, but one important thing to remember is that it should be placed inside of the Form component, which in turn, surrounds the validated components. Let's run the application again and try to submit an empty form. Now the result should look like this:

User Input Validation in Tapestry 5

This kind of feedback doesn't leave any space for doubt, does it?

If you see that the error messages are strongly misplaced to the left, it means that an error in the default.css file that comes with Tapestry distribution still hasn't been fixed. To override the faulty style, define it in our application's styles.css file like this:

DIV.t-error LI
{
margin-left: 20px;
}

Do not forget to make the stylesheet available to the page.

I hope you will agree that the efforts we had to make to get user input validated are close to zero. But let's see what Tapestry has done in response to them:

  • Every form component has a ValidationTracker object associated with it. It is provided automatically, we do not need to care about it. Basically, ValidationTracker is the place where any validation problems, if they happen, are recorded.
  • As soon as we use the t:validate attribute for a component in the form, Tapestry will assign to that component one or more validators, the number and type of them will depend on the value of the t:validate attribute (more about this later).
  • As soon as a validator decides that the value entered associated with the component is not valid, it records an error in the ValidationTracker. Again, this happens automatically.
  • If there are any errors recorded in ValidationTracker, Tapestry will redisplay the form, decorating the fields with erroneous input and their labels appropriately.
  • If there is an Errors component in the form, it will automatically display error messages for all the errors in ValidationTracker. The error messages for standard validators are provided by Tapestry while the name of the component to be mentioned in the message is taken from its label.

A lot of very useful functionality comes with the framework and works for us "out of the box", without any configuration or set-up!

Tapestry comes with a set of validators that should be sufficient for most needs. Let's have a more detailed look at how to use them.

Validators

The following validators come with the current distribution of Tapestry 5:

  • Required—checks if the value of the validated component is not null or an empty string.
  • MinLength—checks if the string (the value of the validated component) is not shorter than the specified length. You will see how to pass the length parameter to this validator shortly.
  • MaxLength—same as above, but checks if the string is not too long.
  • Min—ensures that the numeric value of the validated component is not less than the specified value, passed to the validator as a parameter.
  • Max—as above, but ensures that the value does not exceed the specified limit.
  • Regexp—checks if the string value fits the specified pattern.

We can use several validators for one component. Let's see how all this works together.

First of all, let's add another component to the Registration page template:

<tr>
<td><t:label t:for="age"/>:</td>
<td><input type="text" t:type="textfield" t:id="age"/></td>
</tr>

Also, add the corresponding property to the Registration page class, age, of type double. It could be an int indeed, but I want to show that the Min and Max validators can work with fractional numbers too. Besides, someone might decide to enter their age as 23.4567. This will be weird, but not against the laws.

Finally, add an Errors component to the form at the Registration page, so that we can see error messages:

<t:form t:id="registrationForm">
<t:errors/>
<table>

Now we can test all the available validators on one page. Let's specify the validation rules first:

  1. Both User Name and Password are required. Also, they should not be shorter than three characters and not longer than eight characters.
  2. Age is required, and it should not be less than five (change this number if you've got a prodigy in your family) and not more than 120 (as that would probably be a mistake).
  3. Email address is not required, but if entered, should match a common pattern.

Here are the changes to the Registration page template that will implement the specified validation rules:

<td>
<input type="text" t:type="textfield" t:id="userName" t:validate="required,minlength=3,maxlength=8"/>
</td>
...
<td>
<input type="text" t:type="passwordfield" t:id="password" t:validate="required,minlength=3,maxlength=8"/>
</td>
...
<td>
<input type="text" t:type="textfield" t:id="age" t:validate="required,min=5,max=120"/>
</td>
...
<input type="text" t:type="textfield" t:id="email" t:validate="regexp"/>

As you see, it is very easy to pass a parameter to a validator, like min=5 or maxlength=8. But, where do we specify a pattern for the Regexp validator? The answer is, in the message catalog. Let's add the following line to the app.properties file:

email-regexp=^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$

This will serve as a regular expression for all Regexp validators applied to components with ID email throughout the application.

Run the application, go to the Registration page and, try to submit the empty form. Here is what you should see:

User Input Validation in Tapestry 5

Looks all right, but the message for the age could be more sensible, something like You are too young! You should be at least 5 years old. We'll deal with this later. However for now, enter a very long username, only two characters for password and an age that is more than the upper limit, and see how the messages will change:

User Input Validation in Tapestry 5

Again, looks good, except for the message about age. Next, enter some valid values for User Name, Password and Age. Then click on the check box to subscribe to the newsletter. In the text box for email, enter some invalid value and click on Submit. Here is the result:

User Input Validation in Tapestry 5

Yes! The validation worked properly, but the error message is absolutely unacceptable. Let's deal with this, but first make sure that any valid email address will pass the validation.

 

Providing Custom Error Messages

We can provide custom messages for validators in the application's (or page's) message catalog. For such messages we use keys that are made of the validated component's ID, the name of validator and the "message" postfix. Here is an example of what we could add to the app.properties file to change error messages for the Min and Max validators of the Age component as well as the message used for the email validation:

email-regexp-message=Email address is not valid.
age-min-message=You are too young! You should be at least 5 years old.
age-max-message=People do not live that long!

Still better, instead of hard-coding the required minimal age into the message, we could insert into the message the parameter that was passed to the Min validator (following the rules for java.text.Format), like this:

age-min-message=You are too young! You should be at least %s years old.

If you run the application now and submit an invalid value for age, the error message will be much better:

User Input Validation in Tapestry 5

You might want to make sure that the other error messages have changed too.

We can now successfully validate values entered into separate fields, but what if the validity of the input depends on how two or more different values relate to each other? For example, at the Registration page we want two versions of password to be the same, and if they are not, this should be considered as an invalid input and reported appropriately. Before dealing with this problem however, we need to look more thoroughly at different events generated by the Form component.

 

Tapestry 5: Building Web Applications A step-by-step guide to Java Web development with the developer-friendly Apache Tapestry framework
Published: January 2008
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

Handling Validation-Related Form Events

Previously, while writing a Form submission handler, we used either an annotation like this:

@OnEvent(value="submit", component="registrationForm")

or an appropriately named method like onSubmitFromRegistrationForm()

As you already know, this means that the event handler method should be invoked in response to the submit event generated by the component with a specified ID. The Form component generates a few other events too. There are three events related to validation—validate, success, and failure. Let's implement event handler methods for all these events.

This is where following the naming convention for event handlers, as opposed to using the @OnEvent annotation, will save us some effort. If Tapestry will find a method named onSuccess() in the page class, it will understand that this is the event handler for any success event that happens on the page. Let's modify the existing handler for the submit event and add handlers for the other events mentioned above. Here is how these methods should look in the Registration class:

String onSubmit()
{
System.out.println("The form was submitted!");
if (unsubscribe) subscribe = false;
return nextPage;
}
void onValidate()
{
System.out.println("In onValidate.");
}
void onSuccess()
{
System.out.println("In onSuccess.");
}
void onFailure()
{
System.out.println("In onFailure.");
}

As usual, we have inserted some output statements to have a glimpse into the inner life of Tapestry.

If you run the application now, enter some invalid values and then click on the newsletter checkbox (use the Reset button to return the form to its initial state when needed) you should see the following output:

In onValidate.
In onFailure.
The form was submitted!

From this we can see that the validate event was fired first, then failure, and finally submit.

If however, we provide correct input, the output will be different:

Setting user name: John
Setting password: Smith
In onValidate.
Submit button was pressed!
In onSuccess.
The form was submitted!

Here is how all this works.

In the very beginning, as soon as we click on the Submit button, the Form component performs an automatic single field validation of the values using the validators we have specified. You might have noticed that if you click on the Submit button in an empty form no event handlers are invoked at all. This is because the initial stage of validation happens on the client side, without submitting the form to the server. However, for client side validation to work, the components used on the page should provide some appropriate JavaScript (those components that come with Tapestry do provide such JavaScript), and of course JavaScript should be enabled in the user's browser.

If this first stage of validation was successful, the Form component sends the values provided by the user to the server, so they can be set to the appropriate properties of the page class. This is why we saw the following lines of output:

Setting user name: John
Setting password: Smith

If however the first stage revealed a problem, the values submitted by the user are not set to the properties of the page class, and none of the event handlers get invoked. On the next step, the Form component fires the validate event, and Tapestry invokes our onValidate method. This is where we should put any custom validation logic, additional to that performed automatically by the form on single fields. If our additional logic defines a problem, we can record a validation error with a message of our choice. This is what we'll be doing in the next section.

After the onValidate method does its job, Tapestry checks if there are any errors recorded in ValidationTracker—either automatically or by ourselves. If there are, then the failure event is generated and the onFailure method is invoked, otherwise the success event is fired and the onSuccess method runs. Finally, the submit event is generated, and so the onSubmit method runs no matter what.

From this logic, we can make an inference that the proper place for any code that should run only in case of successful validation is the onSuccess method, and so we need to change the code of the Registration class like this:

void onSubmit()
{
System.out.println("The form was submitted!");
}
String onSuccess()
{
System.out.println("In onSuccess.");
if (unsubscribe) subscribe = false;
return nextPage;
}

Another conclusion is that if we want to perform any additional validation, this should be done in the onValidate method.

Cross-Form Validation

We do need to perform custom validation in a couple of places. First of all, at the Registration page we should check whether the two versions of password are identical, and if not, we should report a problem.

To begin with, we need to have a reference to the Form component to record any eventual error in the page class code, and it is very easy to get:

@Component
private Form registrationForm;

Here the name of the private class member is the same as the ID of the Form component, so we do not need to clarify exactly which form we mean here. We can record an error in the form in two ways—specifying the component which is in error and without mentioning any components. In the first case, the specified component will be marked as an error. Let's say that if passwords do not match, we want to mark the password component as an error. For this, we need to have a reference to this component in the page code:

@Component(id="password")
private PasswordField passwordField;

Here we couldn't give the reference the same name as the ID of the component, since there is already a class member named password. So we used a different name for the reference (passwordField) and specified exactly which component we meant in the id parameter passed to the @Component annotation.

Also, we'll want to provide an error message to be automatically displayed by the Errors component. The proper place for such a message is the application's (or page's) message catalog, so let's add some error message to the app.properties file:

passwords-dont-match=Two versions of password do not match.

And then we need to provide a reference to this catalog in the page class:

@Inject
private Messages messages;

Finally, we can write the contents for the onValidate method:

void onValidate()
{
System.out.println("In onValidate.");
if (!password.equals(password2))
{
password = null;
registrationForm.recordError(passwordField,
messages.get("passwords-dont-match"));
}
}

We are setting the password property to null if passwords do not match so that previously implemented logic (that hides password fields after password was submitted) worked properly. Then we are recording an error into the form, specifying the component in error and providing an error message. If some errors were recorded into the form, Tapestry will always redisplay the same page, so we don't need to bother about any navigation issues. Now, if you run the application and enter two different values for password you should see something like this:

User Input Validation in Tapestry 5

The second place in the application where the onValidate method becomes useful is the Start page. If you remember, in the case of failed authentication we are simply redirecting the user to the Registration page. It would be much better to just display an error message in this case. As we are not going to associate this error with any component, here is the addition to the Start page class that will do the job:

@Component
private Form loginForm;

@Inject
private Messages messages;

Object onSuccess()
{
return ShowAll.class;
}
void onValidate()
{
User authenticatedUser =
Security.authenticate(userName, password);
if (authenticatedUser != null)
{
user = authenticatedUser;
}
else
{
loginForm.recordError(
messages.get("authentication-failed"));
}
}

Of course, you will need to remove the listener for the submit event that was used on this page before. Also, some appropriate error message should be added to the message catalog:

authentication-failed=We couldn't authenticate you. Try again or register.

Run the application and try to log in using wrong credentials. You should see something similar to this:

User Input Validation in Tapestry 5

So far, we've successfully managed different cases of validation by dealing directly with components in the form, or by recording errors directly to the Form component; but, how about validation in BeanEditForm? This sophisticated component does so much for us automatically that it doesn't make sense to use many of the skills we've just learned.

BeanEditForm Validation

Let's say that when we are adding a new celebrity to our collection the first name should be mandatory. This can be easily achieved by adding a @Validate annotation to either getter or setter method of the Celebrity class, like this:

@Validate("required")
public String getFirstName()
{
return firstName;
}

If you try to add a celebrity without specifying a first name, you will see an appropriate error message, as the Errors component is already incorporated into the BeanEditForm.

You can also specify a custom error message if you do not like the default one. Try something like this:

firstName-required-message=You cannot have a celebrity without a name!

And of course you combine different validators like we did before.

Summary

Here is what we have learned in this article:

  • Tapestry 5 comes with a powerful framework for user input validation. To use it, we have to do very little, while Tapestry does a lot for us—applies validators, decorates fields in error, and displays error messages.
  • We can provide custom error messages and Regexp patterns using message catalogs. You can also change the styles used to display errors by overriding them in your stylesheet.
  • Tapestry 5 comes with five validators that will cover most needs. You can combine several of them to achieve the desired result.
  • You can provide custom validation, for example cross-form validation, in the validate event handler.
  • To validate the fields of BeanEditForm, the @Validate annotation should be used on either the getter or setter method of the validated property in the edited class.
Tapestry 5: Building Web Applications A step-by-step guide to Java Web development with the developer-friendly Apache Tapestry framework
Published: January 2008
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Alexander Kolesnikov

Alexander Kolesnikov is an author and software developer from Greenock, Scotland. He wrote his first program in FORTRAN back in 1979 for a computer that occupied several rooms. He currently works as a Java Web Developer for CIGNA International. A Soviet military researcher in the past, he recently graduated as a Master of Science with Distinction in Enterprise Systems Development from Glasgow Caledonian University and has also gained a number of professional certifications from Sun Microsystems (SCJP, SCWCD, SCBCD). His first book on software development was "Java Drawing With Apache Batik" (BrainySoftware, 2007).

He is interested in many things, ranging from the most recent web technologies to alternative medicine and wishes wholeheartedly that a day was at least three times longer than it is.

Books From Packt

Drupal 6 Social Networking
Drupal 6 Social Networking

Alfresco Developer Guide
Alfresco Developer Guide

Learning Joomla! 1.5 Extension Development
Learning Joomla! 1.5 Extension Development

Joomla! Web Security
Joomla! Web Security

Java EE 5 Development with NetBeans 6
Java EE 5 Development with NetBeans 6

Moodle Course Conversion: Beginner's Guide
Moodle Course Conversion: Beginner's Guide

Apache OFBiz Development: The Beginner's Tutorial
Apache OFBiz Development: The Beginner's Tutorial

Drupal for Education and E-Learning
Drupal for Education and E-Learning

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software