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:
Label for the first text box</t:label>:
<input type="text" t_id="userName" t_type="TextField" t:label="User Name" t_validate="required"/>
The second label</t:label>:
<input type="text" t_id="password" t_label="Password" t:type="PasswordField" t_validate="required"/>
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:
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:
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:
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:
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.
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:
<td><input type="text" t_type="textfield" t_id="age"/></td>
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:
Now we can test all the available validators on one page. Let’s specify the validation rules first:
- Both User Name and Password are required. Also, they should not be shorter than three characters and not longer than eight characters.
- 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).
- 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:
<input type="text" t_type="textfield" t_id="userName" t:validate="required,minlength=3,maxlength=8"/>
<input type="text" t_type="passwordfield" t_id="password" t:validate="required,minlength=3,maxlength=8"/>
<input type="text" t_type="textfield" t_id="age" t:validate="required,min=5,max=120"/>
<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:
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:
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:
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:
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:
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.