Form Input
This chapter shows how you can process input from users using forms, inputs and other form related components. We show how to add validation to your fields and give your users feedback when they entered wrong input.
Introduction
Many webapplications need some sort of user input. This is an integral part of dynamic web applications. Many web applications don't take localization and internationalization into account. Often you have to write a lot of code for parsing request parameters, validating and converting them into the actual types your POJOs use.
With Wicket things will be different: you won't have to write lots and lots of scaffolding: Wicket takes care of a lot of things: updating your POJOs, converting the user input taking localization into account, validating the input, displaying internationalized messages when the input isn't valid, etc.
Form Components
Most, if not all, Wicket form components will be discussed in the following sections. The goal is not to be complete and show all possible uses of each component, but to give an initial outline of how such a component can be used. For more information, please look in the JavaDoc of the components.
Form
The Form component is a container for your form components. In the markup a form looks like this:
<form wicket:id="myForm"> ... <input type="submit" value="Submit" /> </form>
To implement a form, subclass the Form , add FormComponents (such as CheckBoxes, ListChoices or TextFields) to the form and override the onSubmit method. You can nest multiple buttons if you want to vary submit behaviour. However, it is not necesary to use Wicket's button class, just putting e.g. <input type="submit" value="go"> suffices.
public class MyForm extends Form { public MyForm(String id, IModel model, IFeedback feedback) { // add (form) components } public void onSubmit() { // handle the submission of the form. } public void onError() { // do something special when an error occurs, // instead of displaying messages. } }
Wicket will call onSubmit when the user input is valid. When one or more child components of the form fails to validate, Wicket will call onError. Wicket also supports multiple buttons, which are discussed later.
TextField
A TextField receives user input in possibly its most basic form: a single line of text. The markup for a TextField component must be a <input type="text" ... /> tag, otherwise Wicket will throw an exception.
The markup for a textfield looks as follows:
<form wicket:id="myForm"> <input wicket:id="textField" type="text" /> ... </form>
In the Java code you can use several constructors for the component, depending on the type of model you want to bind to the textfield.
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); add(new TextField("textField", new PropertyModel(model, "name"))); ... }
It is possible to use the true types of the fields in the textfield component. Say that you need an integer value. Instead of performing the dataconversion yourself, you specify for Wicket that you require an integer value by adding an extra parameter to the TextField constructor: the type of the property.
new TextField("integerField", new PropertyModel(model, "myInt"), Integer.class)
More information on this subject is provided in the validation section later on.
PasswordTextField
The PasswordTextField component is used for users to enter their passwords. The field can be used in both login forms and registration forms. In login forms it is usually required that the password is always cleared when the page is rendered. When editing account information, this is not the case, so the password field can be told not to clear the field when rendered. The markup looks like this (the type="password" is required!):
<input wicket:id="pwd" type="password"/> </code} The corresponding Java declaration looks like this:
PasswordTextField pwd = new PasswordTextField("pwd", new PropertyModel(model, "myPwd"));
When you want to use the field in a registration form, or some other form where the password should not be reset on each render, you have to set the reset password flag to false:
pwd.setResetPassword(false);
The default is to clear the field each time it is rendered. h3. TextArea The TextArea component is a multi-line text editing component. The component requires to be attached to a <textarea> tag in the markup: {code:html} <form wicket:id="myForm"> <textarea wicket:id="myTextArea" rows="6" cols="20"></textarea> ... </form>
The corresponding Java code looks like this:
TextArea area = new TextArea("myTextArea", new PropertyModel(model, "text"));
If you have to programmatically alter the rows or cols attributes of the tag, then you can add AttributeModifiers to the TextArea in order to produce the required effect:
area.add(new AttributeModifier("rows", 10));
CheckBox
The CheckBox component displays a checkbox. The checkbox can either be checked or not, corresponding to a true or falseconstant> value for the bound model. The CheckBox requires to be attached to a input field whose type is 'checkbox':
<form wicket:id="myForm"> <input wicket:id="myCheckbox" type="checkbox" /> ... </form>
The corresponding Java code:
CheckBox check = new CheckBox("myCheckbox", new PropertyModel(model, "boolValue"));
It is also possible to have the CheckBox respond directly to changes. Wicket will add a small piece of JavaScript in order to generate the onSelectionChanged(Object newSelection) event.
CheckBox check = new CheckBox("myCheckbox", new PropertyModel(model, "boolValue")) { protected boolean wantOnSelectionChangedNotifications() { return true; } protected void onSelectionChanged(Object newSelection) { Boolean value = (Boolean)newSelection; if(value.boolValue()) { // do something } } };
RadioChoice
The RadioChoice component creates a number of radiobuttons on your form. The RadioChoice requires both a model to bind the value in, and a list of choices to display and select from. The list of choices can come from a database or be filled from your Java code. It requires an input markup tag of type 'radio':
<form wicket:id="myForm"> <input wicket:id="myRadio" type="radio" value="myValue" /> ... </form>
When you have a list filled with the strings 'foo' and 'bar', and you supply the RadioChoice with that list, Wicket will generate each choice for you in the final markup.
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); List choices = new ArrayList(); choices.add("foo"); choices.add("bar"); add(new RadioChoice("myRadio", new PropertyModel(model, "foobar"), choices)); }
It is possible to construct more advanced methods of binding and creating selection choices. See the chapter on database applications for more information.
ListChoice
This component shows a plain listbox filled with the choices to choose from. The component only supports single selection values. When the number of choices is bigger than the number of displayed values, a scrollbar is shown (by the browser). The required markup looks like this:
<form wicket:id="myForm"> <select wicket:id="myListChoice" size="2"> <option>option1</option> <option>option2</option> </select> ... </form>
The size attribute is merely for previewing purposes. Wicket will add the attribute when it is not present. The Java code for creating a ListChoice:
public MyForm(String id, IModel model, IFeedback feedback) { List choices = new ArrayList(); choices.add("foo"); choices.add("bar"); ListChoice lc = new ListChoice("myListChoice", new PropertyModel(model, "foobar"), choices); lc.setMaxRows(2); add(lc); ... }
ListMultipleChoice
This component shows a plain listbox filled with the choices to choose from. The component supports multiple selection values. When the number of choices is bigger than the number of displayed values, a scrollbar is shown (by the browser). The required markup looks like this:
<form wicket:id="myForm"> <select wicket:id="myMultiListChoice" size="2"> <option>option1</option> <option>option2</option> </select> ... </form>
The component needs a java.util.Collection as the model object, in order to supply your object with the multiple choices. The Java code for this component looks like:
public MyForm(String id, IModel model, IFeedback feedback) { List choices = new ArrayList(); choices.add("foo"); choices.add("bar"); MultiListChoice lc = new MultiListChoice("myMultiListChoice", new PropertyModel(model, "foobarList"), choices); add(lc); ... }
DropDownChoice
This component shows a dropdown box filled with the choices to choose from. The selected value during rendering is the value of the model object. When the selection is changed, the selected value is set on the model object. The required markup looks like this:
<form wicket:id="myForm"> <select wicket:id="myDropDownChoice"> <option>option1</option> <option>option2</option> </select> ... </form>
The corresponding Java code looks like:
public MyForm(String id, IModel model, IFeedback feedback) { List choices = new ArrayList(); choices.add("foo"); choices.add("bar"); DropDownChoice lc = new DropDownChoice("myDropDownChoice", new PropertyModel(model, "foobar"), choices); add(lc); ... }
The DropDownChoice also supports listening to onchange events on the clientside, like the CheckBox does. Wicket will add a small piece of JavaScript in order to generate the onSelectionChanged(Object newSelection) event.
DropDownChoice choice = new DropDownChoice("myDropDownChoice", new PropertyModel(model, "foobar"), choices) { protected boolean wantOnSelectionChangedNotifications() { return true; } protected void onSelectionChanged(Object newSelection) { // do something } };
Processing User Input
In the following section you will learn what Wicket does when your user presses the submit button. It is possible to add multiple buttons to a form, so that you can perform different actions when the form is submitted. This is described in the section 'Multiple Buttons'. It is also possible to bypass the Wicket form processing, which is described in 'Advanced Form Processing'.
Submitting a Form
By default, the processing of a form works like this: The submitting button is looked up. A submitting button is a button that is nested in this form (is a child component) and that was clicked by the user. If a submitting button was found, and it has the immediate field true (default is false), its onSubmit method will be called right away, thus no validition is done, and things like updating form component models that would normally be done are skipped. In that respect, nesting a button with the immediate field set to true has the same effect as nesting a normal link. If you want you can call validate() to execute form validation, hasError() to find out whether validate() resulted in validation errors, and updateFormComponentModels() to update the models of nested form components. When no immediate submitting button was found, this form is validated (method validate()). Now, two possible paths exist:
- Form validation failed. All nested form components will be marked valid, and onError() is called to allow clients to provide custom error handling code.
- Form validation succeeded. The nested components will be asked to update their models and persist their data if applicable. After that, method delegateSubmit with optionally the submitting button is called. The default when there is a submitting button is to first call onSubmit on that button, and after that call onSubmit on this form. Clients may override delegateSubmit if they want different behaviour.
Multiple Buttons
As mentioned before, Wicket allows you to use multiple buttons in your form for submitting the form. Say you want to provide a 'OK' and a 'Cancel' button on your form. The markup is as follows:
<form wicket:id="myForm"> ... <input wicket:id="button1" type="submit" value="OK" /> <input wicket:id="button2" type="submit" value="Cancel" /> </form>
Next we need to add the buttons and the code that needs to be performed when the button is pressed to the Java code:
public MyForm(String id, IModel model, IFeedback feedback) { ... add(new Button("button1") { public void onSubmit() { // do the OK bit } }); add(new Button("button2") { public void onSubmit() { // do the cancel bit } }); }
When the user presses the 'OK' button (button1), then the onSubmit method of button1 is called. When the user presses the 'Cancel' button (button2), then the onSubmit method of button2 is called. Note that the onSubmit method is only called when the input is correctly validated. After the selected button has executed its submit method, the form's submit is called. This behaviour can be altered by overriding the delegateSubmit(Button submittingButton) of class Form.
public class MyForm extends Form { ... protected void delegateSubmit(Button submittingButton) { if(submittingButton != null) { // submit the button submittingButton.onSubmit(); } // don't submit the form } }
Advanced Form Processing
TODO: still under considerable developmen
Adding Validation
In this section we will look at adding validation to your components, returning feedback to your users and give an overview of the validations Wicket provides.
Introduction
Each web application needs some sort of validation of user input. Wicket provides you with localized validations that are easy to add to your components and easy to create yourself. Giving feedback to your users is paramount to the succes of your application, so Wicket makes it easy to give excellent feedback to your users in their own language using the standard Java localization and internationalization support.
The basic structure of adding validation to your form components is the following:
- add a feedback component to your page
- register the feedback component with the form
- add the validators to your form components
- add validation messages to a resourcebundle
The Feedback loop
TODO: still under considerable development
Wicket Validations
Adding a validator to your components is not difficult: you need to get an instance
of the validator (usually they are singletons) and add it to your component. The
following code is taken from the form input example from the Wicket examples project.
<form wicket:id="myForm"> <input wicket:id="required" type="text" /> <input wicket:id="integer" type="text" /> <input wicket:id="minmax" type="text" /> <input wicket:id="text" type="text" /> ... etc. <input type="submit" value="submit" /> </form>
In the following sections we will add to each field a different validator.
Required Input
In order to force that input is available for a certain field, Wicket provides a
RequiredValidator
. The following code makes the first field in the example
(wicket:id="required"
) a required field:
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); TextField field = new TextField("required", new PropertyModel(model, "text")); field.setRequired(true)); add(field); }
Adding the following feedback message to the MyPage.properties:
myForm.required.RequiredValidator=${name} is required.
The RequiredValidator only adds the ${name} parameter to the feedback message.
Typesafe Input
To check whether user input is of the correct type, you'll have to add a TypeValidator to
the field. A TypeValidator takes the class of the type which is required: Integer.class,
Long.class etc. Usually you'd use the basic types from java.lang, because Wicket has already
got the converters in place for those types. If you need more power, then you'd have to
take a look at implementing your own IConverter.
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); TextField field = new TextField("integer", new PropertyModel(model, "integerField")); field.add(new TypeValidator(Integer.class)); add(field); }
As a shorthand, Wicket allows you to add the class of the property type to the field
constructor. Wicket will then add a TypeValidator with the given type to the field.
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); TextField field = new TextField("integer", new PropertyModel(model, "integerField"), Integer.class); add(field); }
Consequently, you'll have to add a message to the properties file with the following
name: component.path.TypeValidator
(replace the component.path bit with your path
to the field).
myForm.integer.TypeValidator=${input} is not a valid ${type}.
This component adds ${name}, ${input}, ${type}, ${exception}, ${locale} and ${format} as
parameters to the error message interpolation. Format is only valid if the type conversion involves
a date.
Range Checking
The IntegerValidator supplies you with the possibility to validate whether the user input lies within certain boundaries.
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); TextField field = new TextField("minmax", new PropertyModel(model, "integerField")); field.add(IntegerValidator.range(-100, 100)); add(field); }
The validator has the following parameters available in the error message interpolation: ${name},
${input}, ${min} and ${max}.
myForm.minmax.IntegerValidator=${input} is not between ${min} and ${max}.
For your convenience, the IntegerValidator has some default validators at hand: IntegerValidator.INT ensures that the value is in the valid integer range, IntegerValidator.LONG ensures that the value is in the valid long range, IntegerValidator.POSITIVE_INT ensures that the value is equal or larger than 0 in the valid integer range, and finally IntegerValidator.POSITIVE_LONG does the same, but then for long values.
Length Validations
Ensuring that user input is of a certain length, or minimaly X or maximaly Y long can be done
using the LengthValidator
. The LengthValidator
has three factory methods. min returns
a minimal length validator, max gives you a maximum length validator and range returns a
validator which checks whether the user input is at least min characters long and most max
characters long.
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); TextField field = new TextField("text", new PropertyModel(model, "text")); field.add(LengthValidator.min(100)); add(field); }
The LengthValidator adds ${name}, ${input}, ${min} (when checking for a minimum
length), ${max} (when checking for a maximum length) and ${length} (the actual
length of the user's input) to the error message parameterlist.
myForm.text.LengthValidator=${name} should contain between ${min} and ${max} characters
Pattern Validation
Sometimes it is needed to validate whether a user has filled in text that needs to comply with
some pattern. For instance, the dutch zip code format is four digits followed by two uppercase
letters, seperated by a space: 1234 AB. Wicket allows you to specify a PatternValidator using
the full power of the java.util.regex regular expression package.
The constructor of PatternValidator takes either a regular expression string, or a precompiled
regular expression.
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); TextField email = new TextField("zipcode", new PropertyModel(model, "zipcode")); field.add(new PatternValidator("\d\d\d\d [A-Z][A-Z]")); add(field); }
The PatternValidator just adds ${name} and ${input} to the error message parameters.
myForm.zipcode.PatternValidator=${input} is not a valid zipcode
E-mail Address Validation
To validate e-mail addresses Wicket provides a special PatternValidator
which uses a
standard (albeit rather complex) regular expression for e-mail addresses. Instead of
creating your own e-mail address validator, we advise you to use the EmailAddressPatternValidator
as it is very easy to make a mistake in validating e-mail addresses.
public MyForm(String id, IModel model, IFeedback feedback) { super(id, model, feedback); TextField email = new TextField("email", new PropertyModel(model, "email")); field.add(EmailAddressPatternValidator.getInstance()); add(field); }
The EmailAddressPatternValidator just adds ${name} and ${input} to the error message parameters.
myForm.email.EmailAddressPatternValidator=${input} is not a valid e-mail address