|
|
Want to know more about Packt's Article Network? Interested in contributing your article ideas? Please visit our FAQ for more information. See More BROWSE
All Titles WordPress Web Services SOA BPEL Web Graphics & Video Web Development RAW Portugues, Espanol, Italiano, French PHP/MySQL Oracle Open Source Networking & Telephony Moodle Microsoft & .NET Linux Servers jQuery Joomla! JBoss Java e-Learning e-Commerce Dynamics Drupal CRM Cookbook Content Management Beginner Guides Architecture and Analysis AJAX Future Titles Recently Published Titles |
User Input Validation in Tapestry 5
Adding Validation to ComponentsThe 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> 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: <t:form t:id="loginForm"> 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: DIV.t-error LI 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:
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. ValidatorsThe following validators come with the current distribution of Tapestry 5:
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> 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"> Now we can test all the available validators on one page. Let's specify the validation rules first:
Here are the changes to the Registration page template that will implement the specified validation rules: <td> 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:
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. Tapestry 5: Building Web Applications
Providing Custom Error MessagesWe 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. 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. Handling Validation-Related Form EventsPreviously, 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() 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. 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 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 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() Another conclusion is that if we want to perform any additional validation, this should be done in the onValidate method. Cross-Form ValidationWe 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 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") 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 Finally, we can write the contents for the onValidate method: void onValidate() 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:
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 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:
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 ValidationLet'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")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. SummaryHere is what we have learned in this article:
Tapestry 5: Building Web Applications
About the AuthorAlexander 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 |
|
| ||||||||