11 Wicket forms in detail - Reference Documentation
Authors: Andrea Del Bene, Carsten Hufe, Christian Kroemer, Daniel Bartl
Version: 1.0.0.BUILD-SNAPSHOT
Table of Contents
11 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 10.5).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.11.1 Default form processing
In paragraph 9.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:- 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.
- Updating of models: if validation succeeds, the form updates the model of its children components with the converted values obtained in the previous step.
- 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 10.4 we will introduce interface IFormSubmitter).
11.2 Form validation and feedback messages
A basic example of a validation rule is to make a field required. In paragraph 9.3.2 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 :TextField email = new TextField("email"); email.add(new EmailAddressValidator());
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 followingField '<label>' is required.
<label> is the value of the label model set on a FormComponent with method setLabel(IModel <String> model). If such model is not provided, component id will be used as the default value.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 12. 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 12.4.5.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 in paragraph 10.2.5)
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:<ul class="feedbackPanel"> <li class="feedbackPanelERROR"> <span class="feedbackPanelERROR">Field 'Username' is required.</span> </li> </ul>
- 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 in paragraph 10.2.6.
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 bigger 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.
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: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.
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:public class UsernameValidator implements IValidator<String> { List<String> existingUsernames = Arrays.asList("bigJack", "anonymous", "mrSmith"); public void validate(IValidatable<String> validatable) { String chosenUserName = validatable.getValue(); if(existingUsernames.contains(chosenUserName)){ ValidationError error = new ValidationError(this); Random random = new Random(); error.setVariable("suggestedUserName", validatable.getValue() + random.nextInt()); validatable.error(error); } } }
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.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)
<body> <form wicket:id="form"> Username: <input type="text" wicket:id="username"/> <br/> <input type="submit"/> </form> <div style="color:green" wicket:id="succesMessage"> </div> <div style="color:red" wicket:id="feedbackMessage"> </div> </body>
public class HomePage extends WebPage { public HomePage(final PageParameters parameters) { Form form = new Form("form"){ @Override protected void onSubmit() { super.onSubmit(); success("Username is good!"); } }; TextField mail; form.add(mail = new TextField("username", Model.of(""))); mail.add(new UsernameValidator()); add(new FeedbackPanel("feedbackMessage", new ExactErrorLevelFilter(FeedbackMessage.ERROR))); add(new FeedbackPanel("succesMessage", new ExactErrorLevelFilter(FeedbackMessage.SUCCESS))); add(form); } class ExactErrorLevelFilter implements IFeedbackMessageFilter{ private int errorLevel; public ExactErrorLevelFilter(int errorLevel){ this.errorLevel = errorLevel; } public boolean accept(FeedbackMessage message) { return message.getLevel() == errorLevel; } } //UsernameValidator definition //… }
11.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)://this field must receive an integer value TextField integerField = new TextField("number", new Model()).setType(Integer.class));
//retrieve converter for Boolean type 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 RadioChoice1) use their choice render and their collection of possible choices to perform input conversion.
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:public ConverterLocator() { set(Boolean.TYPE, BooleanConverter.INSTANCE); set(Boolean.class, BooleanConverter.INSTANCE); set(Byte.TYPE, ByteConverter.INSTANCE); set(Byte.class, ByteConverter.INSTANCE); set(Character.TYPE, CharacterConverter.INSTANCE); set(Character.class, CharacterConverter.INSTANCE); set(Double.TYPE, DoubleConverter.INSTANCE); set(Double.class, DoubleConverter.INSTANCE); set(Float.TYPE, FloatConverter.INSTANCE); set(Float.class, FloatConverter.INSTANCE); set(Integer.TYPE, IntegerConverter.INSTANCE); set(Integer.class, IntegerConverter.INSTANCE); set(Long.TYPE, LongConverter.INSTANCE); set(Long.class, LongConverter.INSTANCE); set(Short.TYPE, ShortConverter.INSTANCE); set(Short.class, ShortConverter.INSTANCE); set(Date.class, new DateConverter()); set(Calendar.class, new CalendarConverter()); set(java.sql.Date.class, new SqlDateConverter()); set(java.sql.Time.class, new SqlTimeConverter()); set(java.sql.Timestamp.class, new SqlTimestampConverter()); set(BigDecimal.class, new BigDecimalConverter()); }
public class RegExpPatternConverter implements IConverter<Pattern> { @Override public Pattern convertToObject(String value, Locale locale) { return Pattern.compile(value); } @Override public String convertToString(Pattern value, Locale locale) { return value.toString(); } }
@Override protected IConverterLocator newConverterLocator() { ConverterLocator defaultLocator = new ConverterLocator(); defaultLocator.set(Pattern.class, new RegExpPatternConverter()); return defaultLocator; }
public class HomePage extends WebPage { private Pattern regExpPatter; private String stringToSplit; public HomePage(final PageParameters parameters) { TextField mail; TextField stringToSplitTxt; Form form = new Form("form"){ @Override protected void onSubmit() { super.onSubmit(); String messageResult = "Tokens for the given string and pattern:<br/>"; String[] tokens = regExpPatter.split(stringToSplit); for (String token : tokens) { messageResult += "- " + token + "<br/>"; } success(messageResult); } }; form.setDefaultModel(new CompoundPropertyModel(this)); form.add(mail = new TextField("regExpPatter")); form.add(stringToSplitTxt = new TextField("stringToSplit")); add(new FeedbackPanel("feedbackMessage").setEscapeModelStrings(false)); add(form); } }
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.
11.4 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: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 <div> tag containing a text field and a submit button with some JavaScript code to trigger it:<div style="width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden"> <input type="text" autocomplete="off"/> <input type="submit" name="submit2" onclick=" var b=document...."/> </div>
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.
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 <input> or <button> tags. The string model received as input by its constructor is used as button label and it will be the value of the markup attribute value.In the following snippet we have a form with two submit buttons bound to an <input> tag. One of them is set as default button and both have a string model for the label:HTML:<body> <form wicket:id="form"> Username: <input type="text" wicket:id="username"/> <br/> <input type="submit" wicket:id="submit1"/> <input type="submit" wicket:id="submit2"/> </form> </body>
public class HomePage extends WebPage { public HomePage(final PageParameters parameters) { Form form = new Form("form"); form.add(new TextField("username", Model.of(""))); form.add(new Button("submit1", Model.of("First submitter"))); Button secondSubmitter; form.add(secondSubmitter = new Button("submit2", Model.of("Second submitter"))); form.setDefaultButton(secondSubmitter); add(form); } }
<form wicket:id="form" id="form1" method="post" action="?0-1.IFormSubmitListener-form"> <div> … <!-- Code generated by Wicket to handle the default button --> … </div> Username: <input type="text" wicket:id="username" value="" name="username"/> <br/> <input type="submit" wicket:id="submit1" name="submit1" id="submit13" value="First submitter"/> <input type="submit" wicket:id="submit2" name="submit2" id="submit22" value="Second submitter"/> </form>
<html xmlns:wicket="http://wicket.apache.org"> <head> </head> <body> <form wicket:id="form"> Password: <input type="password" wicket:id="password"/> <br/> </form> <button wicket:id="externalSubmitter"> Submit </button> </body> </html>
public class HomePage extends WebPage { public HomePage(final PageParameters parameters) { Form form = new Form("form"); form.add(new PasswordTextField("password", Model.of(""))); //specify the form to submit add(new SubmitLink("externalSubmitter", form)); add(form); } }
Disabling default form processing
With an IFormSubmittingComponent we can choose to skip the default form submission process by setting the appropriate flag to false with the setDefaultFormProcessing method. When the default form processing is disabled only the submitter's onSubmit is called while form's validation and models updating are skipped.This can be useful if we want to implement a “Cancel” button on our form which redirects user to another page without validating his/her input.When we set this flag to false we can decide to manually invoke the form processing by calling the process(IFormSubmittingComponent) method.11.5 Nested forms
As you might already known, HTLM doesn't allow to have nested forms1. However with Wicket we can overcome this limitation by adding one or more form components to a parent form.This can be useful if we want to split a big form into smaller ones in order to reuse them and to better distribute responsibilities among different components. Forms can be nested to an arbitrary level:<form wicket:id="outerForm"> … <form wicket:id="innerForm"> … <form wicket:id="veryInnerForm"> … </form> </form> </form>
11.6 Multi-line text input
HTML provides a multi-line text input control with <textarea> tag. The Wicket counterpart for this kind of control is org.apache.wicket.markup.html.form.TextArea component:HTML:<textarea wicket:id="description" rows="5" cols="40"></textarea>
form.add(new TextArea("description", Model.of("")));
11.7 File upload
Wicket supports file uploading with the FileUploadField component which must be used with the <input> tag whose type attribute must be set to "file". In order to send a file on form submission we must enable multipart mode calling MultiPart(true)on our form.In the next example (project UploadSingleFile) we will see a form which allows users to upload a file into the temporary directory of the server (path /tmp on Unix/Linux systems):HTML:<html> <head> </head> <body> <h1>Upload your file here!</h1> <form wicket:id="form"> <input type="file" wicket:id="fileUploadField"/> <input type="submit" value="Upload"/> </form> <div wicket:id="feedbackPanel"> </div> </body> </html>
public class HomePage extends WebPage { private FileUploadField fileUploadField; public HomePage(final PageParameters parameters) { fileUploadField = new FileUploadField("fileUploadField"); Form form = new Form("form"){ @Override protected void onSubmit() { super.onSubmit(); FileUpload fileUpload = fileUploadField.getFileUpload(); try { File file = new File(System.getProperty("java.io.tmpdir") + "/" + fileUpload.getClientFileName()); fileUpload.writeTo(file); } catch (IOException e) { e.printStackTrace(); } } }; form.setMultiPart(true); //set a limit for uploaded file's size form.setMaxSize(Bytes.kilobytes(100)); form.add(fileUploadField); add(new FeedbackPanel("feedbackPanel")); add(form); } }
The maximum size for uploaded files can also be set at application's level using the setDefaultMaximumUploadSize(Bytes maxSize) method of the IApplicationSettings interface:@Override public void init() { getApplicationSettings().setDefaultMaximumUploadSize(Bytes.kilobytes(100)); }
Upload multiple files
If we need to upload multiple files at once, we can use the MultiFileUploadField component which allows the user to select an arbitrary number of files to send on form submission.An example showing how to use this component can be found in Wicket module wicket-examples in file MultiUploadPage.java. The live example is hosted at http://www.wicket-library.com/wicket-examples-6.0.x/upload/multi .11.8 Creating complex form components with FormComponentPanel
In chapter 3.2.2 we have seen how to use class Panel to create custom components with their own markup and with an arbitrary number of children components.While it's perfectly legal to use Panel also to group form components, the resulting component won't be itself a form component and it won't participate in the form's submission workflow.This could be a strong limitation if the custom component needs to coordinate its children during sub-tasks like input conversion or model updating. That's why in Wicket we have the org.apache. wicket.markup.html.form.FormComponentPanel component which combines the features of a Panel (it has its own markup file) and a FormComponent (it is a subclass of FormComponent).A typical scenario in which we may need to implement a custom FormComponentPanel is when our web application and its users work with different units of measurement for the same data.To illustrate this possible scenario, let's consider a form where a user can insert a temperature that will be recorded after being converted to Kelvin degrees (see the example project CustomForm ComponentPanel).The Kelvin scale is wildly adopted among the scientific community and it is one of the seven base units of the International System of Units , so it makes perfect sense to store temperatures expressed with this unit of measurement.However, in our everyday life we still use other temperature scales like Celsius or Fahrenheit, so it would be nice to have a component which internally works with Kelvin degrees and automatically applies conversion between Kelvin temperature scale and the one adopted by the user.In order to implement such a component, we can make a subclass of FormComponentPanel and leverage the convertInput and onBeforeRender methods: in the implementation of the convertInput method we will convert input value to Kelvin degrees while in the implementation of onBeforeRender method we will take care of converting the Kelvin value to the temperature scale adopted by the user.Our custom component will contain two children components: a text field to let user insert and edit a temperature value and a label to display the letter corresponding to user's temperature scale (F for Fahrenheit and C for Celsius). The resulting markup file is the following:<html> <head> </head> <body> <wicket:panel> Registered temperature: <input size="3" maxlength="3" wicket:id="registeredTemperature"/> <label wicket:id="mesuramentUnit"></label> </wicket:panel> </body> </html>
public class TemperatureDegreeField extends FormComponentPanel<Double> { private TextField<Double> userDegree; public TemperatureDegreeField(String id) { super(id); } public TemperatureDegreeField(String id, IModel<Double> model) { super(id, model); } @Override protected void onInitialize() { super.onInitialize(); AbstractReadOnlyModel<String> labelModel=new AbstractReadOnlyModel<String>(){ @Override public String getObject() { if(getLocale().equals(Locale.US)) return "°F"; return "°C"; } }; add(new Label("mesuramentUnit", labelModel)); add(userDegree=new TextField<Double>("registeredTemperature", new Model<Double>())); userDegree.setType(Double.class); }
// continued example @Override protected void convertInput() { Double userDegreeVal = userDegree.getConvertedInput(); Double kelvinDegree; if(getLocale().equals(Locale.US)){ kelvinDegree = userDegreeVal + 459.67; BigDecimal bdKelvin = new BigDecimal(kelvinDegree); BigDecimal fraction = new BigDecimal(5).divide(new BigDecimal(9)); kelvinDegree = bdKelvin.multiply(fraction).doubleValue(); }else{ kelvinDegree = userDegreeVal + 273.15; } setConvertedInput(kelvinDegree); } @Override protected void onBeforeRender() { super.onBeforeRender(); Double kelvinDegree = (Double) getDefaultModelObject(); Double userDegreeVal = null; if(kelvinDegree == null) return; if(getLocale().equals(Locale.US)){ BigDecimal bdKelvin = new BigDecimal(kelvinDegree); BigDecimal fraction = new BigDecimal(9).divide(new BigDecimal(5)); kelvinDegree = bdKelvin.multiply(fraction).doubleValue(); userDegreeVal = kelvinDegree - 459.67; }else{ userDegreeVal = kelvinDegree - 273.15; } userDegree.setModelObject(userDegreeVal); } }
11.9 Stateless form
In chapter 6 we have seen how Wicket pages can be divided into two categories: stateful and stateless. Pages that are stateless don't need to be stored in the user session and they should be used when we don't need to save any user data in the user session (for example in the public area of a site).Besides saving resources on server-side, stateless pages can be adopted to improve user experience and to avoid security weaknesses. A typical situation where a stateless page can bring these benefits is when we have to implement a login page.For this kind of page we might encounter two potential problems if we chose to use a stateful page. The first problem occurs when the user tries to login without a valid session assigned to him. This could happen if the user leaves the login page opened for a period of time bigger than the session's timeout and then he decides to log in. Under these conditions the user will be redirected to a 'Page expired' error page, which is not exactly a nice thing for user experience.The second problem occurs when a malicious user or a web crawler program attempts to login into our web application, generating a huge number of page versions and consequently increasing the size of the user session.To avoid these kinds of problems we should build a stateless login page which does not depend on a user session. Wicket provides a special version of the Form component called StatelessForm which is stateless by default (i.e its method getStatelessHint() returns true), hence it's an ideal solution when we want to build a stateless page with a form. A possible implementation of our login form is the following (example project StatelessLoginForm):HTML:<html> <head> <meta charset="utf-8" /> </head> <body> <div>Session is <b wicket:id="sessionType"></b></div> <br/> <div>Type 'user' as correct credentials</div> <form wicket:id="form"> <fieldset> Username: <input type="text" wicket:id="username"/> <br/> Password: <input type="password" wicket:id="password"/><br/> <input type="submit"/> </fieldset> </form> <br/> <div wicket:id="feedbackPanel"></div> </body> </html>
public class HomePage extends WebPage { private Label sessionType; private String password; private String username; public HomePage(final PageParameters parameters) { StatelessForm form = new StatelessForm("form"){ @Override protected void onSubmit() { //sign in if username and password are “user” if("user".equals(username) && username.equals(password)) info("Username and password are correct!"); else error("Wrong username or password"); } }; form.add(new PasswordTextField("password")); form.add(new TextField("username")); add(form.setDefaultModel(new CompoundPropertyModel(this))); add(sessionType = new Label("sessionType", Model.of(""))); add(new FeedbackPanel("feedbackPanel")); } @Override protected void onBeforeRender() { super.onBeforeRender(); if(getSession().isTemporary()) sessionType.setDefaultModelObject("temporary"); else sessionType.setDefaultModelObject("permanent"); } }
11.10 Working with radio buttons and checkboxes
In this paragraph we will see which components can be used to handle HTML radio buttons and checkboxes. Both these input elements are usually grouped together to display a list of possible choices:A check box can be used as single component to set a boolean property. For this purpose Wicket provides the org.apache.wicket.markup.html.form.CheckBox component which must be attached to <input type="checkbox".../> tag. In the next example (project SingleCheckBox) we will consider a form similar to the one used in paragraph 9.5 to edit a Person object, but with an additional checkbox to let the user decide if she wants to subscribe to our mailing list or not. The form uses the following bean as backing object:public class RegistrationInfo implements Serializable { private String name; private String surname; private String address; private String email; private boolean subscribeList; /*Getters and setters*/ }
<form wicket:id="form"> <div style="display: table;"> <div style="display: table-row;"> <div style="display: table-cell;">Name: </div> <div style="display: table-cell;"> <input type="text" wicket:id="name"/> </div> </div> <div style="display: table-row;"> <div style="display: table-cell;">Surname: </div> <div style="display: table-cell;"> <input type="text" wicket:id="surname"/> </div> </div> <div style="display: table-row;"> <div style="display: table-cell;">Address: </div> <div style="display: table-cell;"> <input type="text" wicket:id="address"/> </div> </div> <div style="display: table-row;"> <div style="display: table-cell;">Email: </div> <div style="display: table-cell;"> <input type="text" wicket:id="email"/> </div> </div> <div style="display: table-row;"> <div style="display: table-cell;">Subscribe list:</div> <div style="display: table-cell;"> <input type="checkbox" wicket:id="subscribeList"/> </div> </div> </div> <input type="submit" value="Save"/> </form>
public HomePage(final PageParameters parameters) { RegistrationInfo registrtionInfo = new RegistrationInfo(); registrtionInfo.setSubscribeList(true); Form form = new Form("form", new CompoundPropertyModel<RegistrationInfo>(registrtionInfo)); form.add(new TextField("name")); form.add(new TextField("surname")); form.add(new TextField("address")); form.add(new TextField("email")); form.add(new CheckBox("subscribeList")); add(form); }
Working with grouped checkboxes
When we need to display a given number of options with checkboxes, we can use the org.apache. wicket.markup.html.form.CheckBoxMultipleChoice component. For example, If our options are a list of strings, we can display them in this way:HTML:<div wicket:id="checkGroup"> <input type="checkbox"/>It will be replaced by the actual checkboxes… </div>
List<String> fruits = Arrays.asList("apple", "strawberry", "watermelon"); form.add(new CheckBoxMultipleChoice("checkGroup", new ListModel<String>(new ArrayList<String>()), fruits));
<div wicket:id="checkGroup"> <input type="checkbox"/>It will be replaced by actual checkboxes… </div>
Person john = new Person("John", "Smith"); Person bob = new Person("Bob", "Smith"); Person jill = new Person("Jill", "Smith"); List<Person> theSmiths = Arrays.asList(john, bob, jill); ChoiceRenderer render = new ChoiceRenderer("name"); form.add(new CheckBoxMultipleChoice("checkGroup", new ListModel<String>(ArrayList<String>()), theSmiths, render));
How to implement a "Select all" checkbox
A nice feature we can offer to users when we have a group of checkboxes is a “special” checkbox which selects/unselects all the other options of the group:Wicket comes with a couple of utility components that make it easy to implement such a feature. They are CheckboxMultipleChoiceSelector and CheckBoxSelector classes, both inside org. apache.wicket.markup.html.form package. The difference between these two components is that the first works with an instance of CheckBoxMultipleChoice while the second takes in input a list of CheckBox objects:/* CheckboxMultipleChoiceSelector usage: */CheckBoxMultipleChoice checkGroup; //checkGroup initialization… CheckboxMultipleChoiceSelector cbmcs = new CheckboxMultipleChoiceSelector("id", checkGroup);/* CheckBoxSelector usage: */CheckBox checkBox1, checkBox2, checkBox3; //checks initialization… CheckBoxSelector cbmcs = new CheckBoxSelector("id", checkBox1, checkBox2, checkBox3);
Working with grouped radio buttons
For groups of radio buttons we can use the org.apache.wicket.markup.html.form. RadioChoice component which works in much the same way as CheckBoxMultipleChoice:HTML:<div wicket:id="radioGroup"> <input type="radio"/>It will be replaced by actual radio buttons… </div>
List<String> fruits = Arrays.asList("apple", "strawberry", "watermelon"); form.add(new RadioChoice("radioGroup", Model.of(""), fruits));
11.11 Selecting multiple values with ListMultipleChoices and Palette
Checkboxes work well when we have a small amount of options to display, but they quickly become chaotic as the number of options increases. To overcome this limit we can use the <select> tag switching it to multiple-choice mode with attribute multiple="multiple":Now the user can select multiple options by holding down Ctrl key (or Command key for Mac) and selecting them.To work with multiple choice list Wicket provides the org.apache.wicket.markup.html.form. ListMultipleChoice component:HTML:<select wicket:id="fruits">
<option>choice 1</option>
<option>choice 2</option>
</select>
List<String> fruits = Arrays.asList("apple", "strawberry", "watermelon"); form.add(new ListMultipleChoice("fruits", new ListModel<String>(new ArrayList<String>()), fruits));
Component Palette
While multiple choice list solves the problem of handling a big number of multiple choices, it is not much intuitive for end users. That's why desktop GUIs have introduced a more complex component which can be generally referred to as multi select transfer component (it doesn't have an actual official name):This kind of component is composed by two multiple-choice lists, one on the left displaying the available options and the other one on the right displaying the selected options. User can move options from a list to another by double clicking on them or using the buttons placed between the two list.Built-in org.apache.wicket.extensions.markup.html.form.palette.Palette component provides an out-of-the-box implementation of a multi select transfer component. It works in a similar way to ListMultipleChoice:HTML:<div wicket:id="palette"> Select will be replaced by the actual content… <select multiple="multiple"> <option>option1</option> <option>option2</option> <option>option3</option> </div>
Person john = new Person("John", "Smith"); Person bob = new Person("Bob", "Smith"); Person jill = new Person("Jill", "Smith"); Person andrea = new Person("Andrea", "Smith");List<Person> theSmiths = Arrays.asList(john, bob, jill, andrea); ChoiceRenderer render = new ChoiceRenderer("name");form.add(new Palette("palette", Model.of(new ArrayList<String>()), new ListModel<String> (theSmiths), render, 5, true));
<table cellspacing="0" cellpadding="2" class="palette"> <tr> <td class="header headerAvailable"><span wicket:id="availableHeader">[available header]</span></td> <td> </td> <td class="header headerSelected"><span wicket:id="selectedHeader">[selected header]</span> </td> </tr> <tr> <td class="pane choices"> <select wicket:id="choices" class="choicesSelect">[choices]</select> </td> <td class="buttons"> <button type="button" wicket:id="addButton" class="button add"><div/> </button><br/> <button type="button" wicket:id="removeButton" class="button remove"><div/> </button><br/> <button type="button" wicket:id="moveUpButton" class="button up"><div/> </button><br/> <button type="button" wicket:id="moveDownButton" class="button down"><div/> </button><br/> </td> <td class="pane selection"> <select class="selectionSelect" wicket:id="selection">[selection]</select> </td> </tr> </table>