12. Wicket forms in detail

In the previous chapter we have only scratched the surface of Wicket forms. The Form component was not only designed to collect user input but also to extend the semantic of the classic HTML forms with new features.

One of such features is the ability to work with nested forms (they will be discussed in paragraph 12.6).

In this chapter we will continue to explore Wicket forms learning how to master them and how to build effective and user-proof forms for our web applications.

12.1. Default form processing

In paragraph 11.3 we have seen a very basic usage of the Form component and we didn’t pay much attention to what happens behind the scenes of form submission. In Wicket when we submit a form we trigger the following steps on server side:

  1. Form validation: user input is checked to see if it satisfies the validation rules set on the form. If validation fails, step number 2 is skipped and the form should display a feedback message to explain to user what went wrong. During this step input values (which are simple strings sent with a web request) are converted into Java objects. In the next paragraphs we will explore the infrastructures provided by Wicket for the three sub-tasks involved with form validation, which are: conversion of user input into objects, validation of user input, and visualization of feedback messages.

  2. Updating of models: if validation succeeds, the form updates the model of its children components with the converted values obtained in the previous step.

  3. Invoking callback methods onSubmit() or onError(): if we didn’t have any validation error, method onSubmit() is called, otherwise onError() will be called. The default implementation of both these methods is left empty and we can override them to perform custom actions.

Please note that the model of form components is updated only if no validation error occurred (i.e. step two is performed only if validation succeeds).

Without going into too much detail, we can say that the first two steps of form processing correspond to the invocation of one or more Form’s internal methods (which are declared protected and final). Some examples of these methods are validate(), which is invoked during validation step, and updateFormComponentModels(), which is used at the step that updates the form field models.

The whole form processing is started invoking public method process(IFormSubmitter) (Later in paragraph 12.5 we will introduce interface IFormSubmitter).

12.2. Form validation and feedback messages

A basic example of a validation rule is to make a field required. In paragraph 11.3 we have already seen how this can be done calling setRequired(true) on a field. However, to set a validation rule on a FormComponent we must add the corresponding validator to it.

A validator is an implementation of the org.apache.wicket.validation.IValidator interface and the FormComponent has a version of method add which takes as input a reference of this interface.

For example if we want to use a text field to insert an email address, we could use the built-in validator EmailAddressValidator to ensure that the inserted input will respect the email format local-part@domain :

  1. TextField email = new TextField("email");
  2. email.add(EmailAddressValidator.getInstance());

Wicket comes with a set of built-in validators that should suit most of our needs. We will see them later in this chapter.

12.2.1. Feedback messages and localization

Wicket generates a feedback message for each field that doesn’t satisfy one of its validation rules. For example the message generated when a required field is left empty is the following

Field ‘

The entire infrastructure of feedback messages is built on top of the Java internationalization (I18N) support and it uses resource bundles to store messages.

The topics of internationalization will be covered in chapter 15. For now we will give just few notions needed to understand the examples from this chapter.

By default resource bundles are stored into properties files but we can easily configure other sources as described later in paragraph 15.2.

Default feedback messages (like the one above for required fields) are stored in the file Application. properties placed inside Wicket the org.apache.wicket package. Opening this file we can find the key and the localized value of the message:

Required=Field ‘${label}’ is required.

We can note the key (Required in our case) and the label parameter written in the expression language (${label}). Scrolling down this file we can also find the message used by the Email AddressValidator:

EmailAddressValidator=The value of ‘${label}’ is not a valid email address.

By default FormComponent provides 3 parameters for feedback message: input (the value that failed validation), label and name (this later is the id of the component).

Remember that component model is updated with the user input only if validation succeeds! As a consequence, we can’t retrieve the wrong value inserted for a field from its model. Instead, we should use getValue() method of FormComponent class. (This method will be introduced in the example used later in this chapter)

12.2.2. Displaying feedback messages and filtering them

To display feedback messages we must use component org.apache.wicket.markup.html.panel.FeedbackPanel. This component automatically reads all the feedback messages generated during form validation and displays them with an unordered list:

  1. <ul class="feedbackPanel">
  2. <li class="feedbackPanelERROR">
  3. <span class="feedbackPanelERROR">Field 'Username' is required.</span>
  4. </li>
  5. </ul>

CSS classes feedbackPanel and feedbackPanelERROR can be used in order to customize the style of the message list:

feedback panel style

The component can be freely placed inside the page and we can set the maximum amount of displayed messages with the setMaxMessages() method.

Error messages can be filtered using three built-in filters:

  • ComponentFeedbackMessageFilter: shows only messages coming from a specific component.

  • ContainerFeedbackMessageFilter: shows only messages coming from a specific container or from any of its children components.

  • ErrorLevelFeedbackMessageFilter: shows only messages with a level of severity equals or greater than a given lower bound. Class FeedbackMessage defines a set of static constants to express different levels of severity: DEBUG, ERROR, WARNING, INFO, SUCCESS, etc…​. Levels of severity for feedback messages are discussed later in this chapter.

These filters are intended to be used when there are more than one feedback panel (or more than one form) in the same page. We can pass a filter to a feedback panel via its constructor or using the setFilter method. Custom filters can be created implementing the IFeedbackMessageFilter interface. An example of custom filter is illustrated later in this paragraph.

12.2.3. Built-in validators

Wicket already provides a number of built-in validators ready to be used. The following table is a short reference where validators are listed along with a brief description of what they do. The default feedback message used by each of them is reported as well:

EmailAddressValidator

Checks if input respects the format local-part@domain.

Message:

The value of ‘${label}’ is not a valid email address.

UrlValidator

Checks if input is a valid URL. We can specify in the constructor which protocols are allowed (http://, https://, and ftp://).

Message:

The value of ‘${label}’ is not a valid URL.

DateValidator

Validator class that can be extended or used as a factory class to get date validators to check if a date is greater than a lower bound (method minimum(Date min)), smaller than a upper bound (method maximum(Date max)) or inside a range (method range(Date min, Date max)).

Messages:

The value of ‘${label}’ is less than the minimum of ${minimum}.

The value of ‘${label}’ is larger than the maximum of ${maximum}.

The value of ‘${label}’ is not between ${minimum} and ${maximum}.

RangeValidator

Validator class that can be extended or used as a factory class to get validators to check if a value is bigger than a given lower bound (method minimum(T min)), smaller than a upper bound (method maximum(T max)) or inside a range (method range(T min,T max)).

The type of the value is a generic subtype of java.lang.Comparable and must implement Serializable interface.

Messages:

The value of ‘${label}’ must be at least ${minimum}.

The value of ‘${label}’ must be at most ${maximum}.

The value of ‘${label}’ must be between ${minimum} and ${maximum}.

StringValidator

Validator class that can be extended or used as a factory class to get validators to check if the length of a string value is bigger then a given lower bound (method minimumLength (int min)), smaller then a given upper bound (method maximumLength (int max)) or within a given range (method lengthBetween(int min, int max)).

To accept only string values consisting of exactly n characters, we must use method exactLength(int length).

Messages:

The value of ‘${label}’ is shorter than the minimum of ${minimum} characters.

The value of ‘${label}’ is longer than the maximum of ${maximum} characters.

The value of ‘${label}’ is not between ${minimum} and ${maximum} characters long.

The value of ‘${label}’ is not exactly ${exact} characters long.

CreditCardValidator

Checks if input is a valid credit card number. This validator supports some of the most popular credit cards (like “American Express”, “MasterCard” , “Visa” or “Diners Club”).

Message:

The credit card number is invalid.

EqualPasswordInputValidator

This validator checks if two password fields have the same value.

Message:

${label0} and ${label1} must be equal.

12.2.4. Overriding standard feedback messages with custom bundles

If we don’t like the default validation feedback messages, we can override them providing custom properties files. In these files we can write our custom messages using the same keys of the messages we want to override. For example if we wanted to override the default message for invalid email addresses, our properties file would contain a line like this:

EmailAddressValidator=Man, your email address is not good!

As we will see in the next chapter, Wicket searches for custom properties files in various positions inside the application’s class path, but for now we will consider just the properties file placed next to our application class. The name of this file must be equal to the name of our application class:

custom properties file

The example project OverrideMailMessage overrides email validator’s message with a new one which also reports the value that failed validation:

EmailAddressValidator=The value ‘${input}’ inserted for field ‘${label}’ is not a valid email address.

validation error message

12.2.5. Creating custom validators

If our web application requires a complex validation logic and built-in validators are not enough, we can implement our own custom validators. For example (project UsernameCustomValidator) suppose we are working on the registration page of our site where users can create their profile choosing their username. Our registration form should validate the new username checking if it was already chosen by another user. In a situation like this we may need to implement a custom validator that queries a specific data source to check if a username is already in use.

For the sake of simplicity, the validator of our example will check the given username against a fixed list of three existing usernames.

A custom validator must simply implement interface IValidator:

  1. public class UsernameValidator implements IValidator<String> {
  2. List<String> existingUsernames = Arrays.asList("bigJack", "anonymous", "mrSmith");
  3. public void validate(IValidatable<String> validatable) {
  4. String chosenUserName = validatable.getValue();
  5. if(existingUsernames.contains(chosenUserName)){
  6. ValidationError error = new ValidationError(this);
  7. Random random = new Random();
  8. error.setVariable("suggestedUserName",
  9. validatable.getValue() + random.nextInt());
  10. validatable.error(error);
  11. }
  12. }
  13. }

The only method defined inside IValidator is validate(IValidatable validatable) and is invoked during validation’s step. Interface IValidatable represents the component being validated and it can be used to retrieve the component model (getModel()) or the value to validate (getValue()).

The custom validation logic is all inside IValidator’s method validate. When validation fails a validator must use IValidatable’s method error(IValidationError error) to generate the appropriate feedback message. In the code above we used the ValidationError class as convenience implementation of the IValidationError interface which represents the validation error that must be displayed to the user. This class provides a constructor that uses the class name of the validator in input as key for the resource to use as feedback message (i.e. ‘UsernameValidator’ in the example). If we want to specify more then one key to use to locate the error message, we can use method addKey(String key) of ValidationError class.

In our example when validation fails, we suggest a possible username concatenating the given input with a pseudo-random integer. This value is passed to the feedback message with a variable named suggestedUserName. The message is inside application’s properties file:

UsernameValidator=The username ‘${input}’ is already in use. Try with ‘${suggestedUserName}’

To provide further variables to our feedback message we can use method setVariable(String name, Object value) of class ValidationError as we did in our example.

The code of the home page of the project will be examined in the next paragraph after we have introduced the topic of flash messages.

12.2.6. Using flash messages

So far we have considered just the error messages generated during validation step. However Wicket’s Component class provides a set of methods to explicitly generate feedback messages called flash messages. These methods are:

  • debug(Serializable message)

  • info(Serializable message)

  • success(Serializable message)

  • warn(Serializable message)

  • error(Serializable message)

  • fatal(Serializable message)

Each of these methods corresponds to a level of severity for the message. The list above is sorted by increasing level of severity.

In the example seen in the previous paragraph we have a form which uses success method to notify user when the inserted username is valid. Inside this form there are two FeedbackPanel components: one to display the error message produced by custom validator and the other one to display the success message. The code of the example page is the following:

HTML:

  1. <body>
  2. <form wicket:id="form">
  3. Username: <input type="text" wicket:id="username"/>
  4. <br/>
  5. <input type="submit"/>
  6. </form>
  7. <div style="color:green" wicket:id="succesMessage">
  8. </div>
  9. <div style="color:red" wicket:id="feedbackMessage">
  10. </div>
  11. </body>

Java code:

  1. public class HomePage extends WebPage {
  2. public HomePage(final PageParameters parameters) {
  3. Form<Void> form = new Form<Void>("form"){
  4. @Override
  5. protected void onSubmit() {
  6. super.onSubmit();
  7. success("Username is good!");
  8. }
  9. };
  10. TextField mail;
  11. form.add(mail = new TextField("username", Model.of("")));
  12. mail.add(new UsernameValidator());
  13. add(new FeedbackPanel("feedbackMessage",
  14. new ExactErrorLevelFilter(FeedbackMessage.ERROR)));
  15. add(new FeedbackPanel("succesMessage",
  16. new ExactErrorLevelFilter(FeedbackMessage.SUCCESS)));
  17. add(form);
  18. }
  19. class ExactErrorLevelFilter implements IFeedbackMessageFilter{
  20. private int errorLevel;
  21. public ExactErrorLevelFilter(int errorLevel){
  22. this.errorLevel = errorLevel;
  23. }
  24. public boolean accept(FeedbackMessage message) {
  25. return message.getLevel() == errorLevel;
  26. }
  27. }
  28. //UsernameValidator definition
  29. //...
  30. }

The two feedback panels must be filtered in order to display just the messages with a given level of severity (ERROR for validator message and SUCCESS for form’s flash message). Unfortunately the built-in message filter ErrorLevelFeedbackMessageFilter is not suitable for this task because its filter condition does not check for an exact error level (the given level is used as lower bound value). As a consequence, we had to build a custom filter (inner class ExactErrorLevelFilter) to accept only the desired severity level (see method accept of interface IFeedbackMessageFilter).

Since version 6.13.0 Wicket provides the additional filter class org.apache.wicket.feedback.ExactLevelFeedbackMessageFilter to accept only feedback messages of a certain error level.

12.3. Input value conversion

Working with Wicket we will rarely need to worry about conversion between input values (which are strings because the underlying HTTP protocol) and Java types because in most cases the default conversion mechanism will be smart enough to infer the type of the model object and perform the proper conversion. However, sometimes we may need to work under the hood of this mechanism to make it properly work or to perform custom conversions. That’s why this paragraph will illustrate how to control input value conversion.

The component that is responsible for converting input is the FormComponent itself with its convertInput() method. In order to convert its input a FormComponent must know the type of its model object. This parameter can be explicitly set with method setType(Class<?> type):

  1. //this field must receive an integer value
  2. TextField integerField = new TextField("number", new Model()).setType(Integer.class));

If no type has been provided, FormComponent will try to ask its model for this information. The PropertyModel and CompoundPropertyModel models can use reflection to get the type of object model. By default, if FormComponent can not obtain the type of its model object in any way, it will consider it as a simple String.

Once FormComponent has determined the type of model object, it can look up for a converter, which is the entity in charge of converting input to Java object and vice versa. Converters are instances of org.apache.wicket.util.convert.IConverter interface and are registered by our application class on start up.

To get a converter for a specific type we must call method getConverter(Class type) on the interface IConverterLocator returned by Application’s method getConverterLocator():

  1. //retrieve converter for Boolean type
  2. Application.get().getConverterLocator().getConverter(Boolean.class);
Components which are subclasses of AbstractSingleSelectChoice don’t follow the schema illustrated above to convert user input.

These kinds of components (like DropDownChoice and RadioChoice) use their choice render and their collection of possible choices to perform input conversion.

12.3.1. Creating custom application-scoped converters

The default converter locator used by Wicket is org.apache.wicket.ConverterLocator. This class provides converters for the most common Java types. Here we can see the converters registered inside its constructor:

  1. public ConverterLocator()
  2. {
  3. set(Boolean.TYPE, BooleanConverter.INSTANCE);
  4. set(Boolean.class, BooleanConverter.INSTANCE);
  5. set(Byte.TYPE, ByteConverter.INSTANCE);
  6. set(Byte.class, ByteConverter.INSTANCE);
  7. set(Character.TYPE, CharacterConverter.INSTANCE);
  8. set(Character.class, CharacterConverter.INSTANCE);
  9. set(Double.TYPE, DoubleConverter.INSTANCE);
  10. set(Double.class, DoubleConverter.INSTANCE);
  11. set(Float.TYPE, FloatConverter.INSTANCE);
  12. set(Float.class, FloatConverter.INSTANCE);
  13. set(Integer.TYPE, IntegerConverter.INSTANCE);
  14. set(Integer.class, IntegerConverter.INSTANCE);
  15. set(Long.TYPE, LongConverter.INSTANCE);
  16. set(Long.class, LongConverter.INSTANCE);
  17. set(Short.TYPE, ShortConverter.INSTANCE);
  18. set(Short.class, ShortConverter.INSTANCE);
  19. set(Date.class, new DateConverter());
  20. set(Calendar.class, new CalendarConverter());
  21. set(java.sql.Date.class, new SqlDateConverter());
  22. set(java.sql.Time.class, new SqlTimeConverter());
  23. set(java.sql.Timestamp.class, new SqlTimestampConverter());
  24. set(BigDecimal.class, new BigDecimalConverter());
  25. }

If we want to add more converters to our application, we can override Application’s method newConverterLocator which is used by application class to build its converter locator.

To illustrate how to implement custom converters and use them in our application, we will build a form with two text field: one to input a regular expression pattern and another one to input a string value that will be split with the given pattern.

The first text field will have an instance of class java.util.regex.Pattern as model object. The final page will look like this (the code of this example is from the CustomConverter project):

regex form

The conversion between Pattern and String is quite straightforward. The code of our custom converter is the following:

  1. public class RegExpPatternConverter implements IConverter<Pattern> {
  2. @Override
  3. public Pattern convertToObject(String value, Locale locale) {
  4. return Pattern.compile(value);
  5. }
  6. @Override
  7. public String convertToString(Pattern value, Locale locale) {
  8. return value.toString();
  9. }
  10. }

Methods declared by interface IConverter take as input a Locale parameter in order to deal with locale-sensitive data and conversions. We will learn more about locales and internationalization in Chapter 15.

Once we have implemented our custom converter, we must override method newConverterLocator() inside our application class and tell it to add our new converter to the default set:

  1. @Override
  2. protected IConverterLocator newConverterLocator() {
  3. ConverterLocator defaultLocator = new ConverterLocator();
  4. defaultLocator.set(Pattern.class, new RegExpPatternConverter());
  5. return defaultLocator;
  6. }

Finally, in the home page of the project we build the form which displays (with a flash message) the tokens obtained splitting the string with the given pattern:

  1. public class HomePage extends WebPage {
  2. private Pattern regExpPattern;
  3. private String stringToSplit;
  4. public HomePage(final PageParameters parameters) {
  5. TextField regExpPatternTxt;
  6. TextField stringToSplitTxt;
  7. Form<Void> form = new Form<Void>("form"){
  8. @Override
  9. protected void onSubmit() {
  10. super.onSubmit();
  11. String messageResult = "Tokens for the given string and pattern:<br/>";
  12. String[] tokens = regExpPattern.split(stringToSplit);
  13. for (String token : tokens) {
  14. messageResult += "- " + token + "<br/>";
  15. }
  16. success(messageResult);
  17. }
  18. };
  19. form.setDefaultModel(new CompoundPropertyModel(this));
  20. form.add(regExpPatternTxt = new TextField("regExpPattern"));
  21. form.add(stringToSplitTxt = new TextField("stringToSplit"));
  22. add(new FeedbackPanel("feedbackMessage").setEscapeModelStrings(false));
  23. add(form);
  24. }
  25. }
If the user input can not be converted to the target type, FormComponent will generate the default error message “The value of ‘${label}’ is not a valid ${type}.”. The bundle key for this message is IConverter.

12.4. Validation with JSR 303

Standard JSR 303 defines a set of annotations and APIs to validate our domain objects at field-level. Wicket has introduced an experimental support for this standard since version 6.4.0 and with version 6.14.0 it has became an official Wicket module (named wicket-bean-validation). In this paragraph we will see the basic steps needed to use JSR 303 validation in our Wicket application. Code snippets are from example project JSR303validation.

In the example application we have a form to insert the data for a new Person bean and its relative Address. The code for class Person is the following

  1. public class Person implements Serializable{
  2. @NotNull
  3. private String name;
  4. //regular expression to validate an email address
  5. @Pattern(regexp = "^[_A-Za-z0-9-]+(.[_A-Za-z0-9-]+)*[A-Za-z0-9-]+(.[A-Za-z0-9-]+)*((.[A-Za-z]{2,}){1}$)")
  6. private String email;
  7. @Range(min = 18, max = 150)
  8. private int age;
  9. @Past @NotNull
  10. private Date birthDay;
  11. @NotNull
  12. private Address address;
  13. }

You can note the JSR 303 annotations used in the code above to declare validation constraints on class fields. Class Address has the following code:

  1. public class Address implements Serializable {
  2. @NotNull
  3. private String city;
  4. @NotNull
  5. private String street;
  6. @Pattern(regexp = "\\d+", message = "{address.invalidZipCode}")
  7. private String zipCode;
  8. }

You might have noted that in class Address we have used annotation Pattern using also attribute message which contains the key of the bundle to use for validation message. Our custom bundle is contained inside HomePage.properties:

  1. address.invalidZipCode=The inserted zip code is not valid.

To tell Wicket to use JSR 303, we must register bean validator on Application’s startup:

  1. public class WicketApplication extends WebApplication {
  2. @Override
  3. public void init(){
  4. super.init();
  5. new BeanValidationConfiguration().configure(this);
  6. }
  7. }

The last step to harness JSR 303 annotations is to add validator org.apache.wicket.bean.validation.PropertyValidator to our corresponding form components:

  1. public HomePage(final PageParameters parameters) {
  2. super(parameters);
  3. setDefaultModel(new CompoundPropertyModel<Person>(new Person()));
  4. Form<Void> form = new Form<Void>("form");
  5. form.add(new TextField("name").add(new PropertyValidator()));
  6. form.add(new TextField("email").add(new PropertyValidator()));
  7. form.add(new TextField("age").add(new PropertyValidator()));
  8. //...
  9. }

Now we can run our application an see that JSR 303 annotations are fully effective:

jsr303 form validation

12.5. Submit form with an IFormSubmittingComponent

Besides submitting forms with a standard HTML submit button, Wicket allows us to use special components which implement interface IFormSubmittingComponent. This entity is a subinterface of IFormSubmitter:

class diag IFormSubmittingComponent

At the beginning of this chapter we have seen that form processing is started by process method which takes as input an instance of IFormSubmitter. This parameter corresponds to the IFormSubmittingComponent clicked by a user to submit the form and it is null if we have used a standard HTML submit button (like we have done so far).

A submitting component is added to a form just like any other child component using method add(Component…​).

A form can have any number of submitting components and we can specify which one among them is the default one by calling the Form’s method setDefaultButton(IFormSubmittingComponent component). The default submitter is the one that will be used when user presses ‘Enter’ key in a field of the form. In order to make the default button work, Wicket will add to our form a hidden

tag containing a text field and a submit button with some JavaScript code to trigger it:

  1. <div style="width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden">
  2. <input type="text" autocomplete="off"/>
  3. <input type="submit" name="submit2" onclick=" var b=document...."/>
  4. </div>

Just like Wicket forms, interface IFormSubmitter defines methods onSubmit and onError. These two methods have the priority over the namesake methods of the form, meaning that when a form is submitted with an IFormSubmitter, the onSubmit of the submitter is called before the one of the form. Similarly, if validation errors occurs during the first step of form processing, submitter’s method onError is called before the form’s one.

Starting with Wicket version 6.0 interface IFormSubmitter defines a further callback method called onAfterSubmit(). This method is called after form’s method onSubmit() has been executed.

12.5.1. Components Button and SubmitLink

Component org.apache.wicket.markup.html.form.Button is a basic implementation of a form submitter. It can be used with either the or