10 Wicket models and forms - Reference Documentation
Authors: Andrea Del Bene, Carsten Hufe, Christian Kroemer, Daniel Bartl
Version: 1.0.0.BUILD-SNAPSHOT
Table of Contents
10 Wicket models and forms
In Wicket the concept of “model” is probably the most important topic of the entire framework and it is strictly related to the usage of its components. In addition, models are also an important element for internationalization, as we will see in paragraph 12.6. However, despite their fundamental role, in Wicket models are not difficult to understand but the best way to learn how they work is to use them with forms. That's why we haven't talked about models so far, and why this chapter discusses these two topics together.10.1 What is a model?
Model is essentially a facade interface which allows components to access and modify their data without knowing any detail about how they are managed or persisted. Every component has at most one related model, while a model can be shared among different components. In Wicket a model is any implementation of the interface org.apache.wicket.model.IModel:The IModel interface defines just the methods needed to get and set a data object (getObject() and setObject()), decoupling components from concrete details about the persistence strategy adopted for data. In addition, the level of indirection introduced by models allows access data object only when it is really needed (for example during the rendering phase) and not earlier when it may not be ready to be used.Any component can get/set its model as well as its data object using the 4 public shortcut methods listed in the class diagram above. The two methods onModelChanged() and onModelChanging() are triggered by Wicket each time a model is modified: the first one is called after the model has been changed, the second one just before the change occurs. In the examples seen so far we have worked with Label component using its constructor which takes as input two string parameters, the component id and the text to display:add(new Label("helloMessage", "Hello WicketWorld!"));
public Label(final String id, String label) { this(id, new Model<String>(label)); }
In general, Wicket models support a detaching capability that allows us to work also with non-serializable objects as data model. We will see the detaching mechanism later in this chapter.Just like any other Wicket components, Label provides a constructor that takes as input the component id and the model to use with the component. Using this constructor the previous example becomes:
add(new Label("helloMessage", new Model<String>("Hello WicketWorld!")));
The Model class comes with a bunch of factory methods that makes it easier to build new model instances. For example the of(T object) method creates a new instance of Model which wraps any Object instance inside it. So instead of writing new Model<String>("Hello WicketWorld!")we can write Model.of("Hello WicketWorld!")If the data object is a List, a Map or a Set we can use similar methods called ofList, ofMap and ofSet. From now on we will use these factory methods in our examples.It's quite clear that if our Label must display a static text it doesn't make much sense to build a model by hand like we did in the last code example. However is not unusual to have a Label that must display a dynamic value, like the input provided by a user or a value read from a database. Wicket models are designed to solve these kinds of problems.Let's say we need a label to display the current time stamp each time a page is rendered. We can implement a custom model which returns a new Date instance when the getObject() method is called:
IModel timeStampModel = new Model<String>(){ @Override public String getObject() { return new Date().toString(); } };add(new Label("timeStamp", timeStampModel));
By default class Component escapes HTML sensitive characters (like '<', '>' or '&') from the textual representation of its model object. The term 'escape' means that these characters will be replaced with their corresponding HTML entity (for example '<' becomes '< '). This is done for security reasons as a malicious user could attempt to inject markup or JavaScript into our pages. If we want to display the raw content stored inside a model, we can tell the Component class not to escape characters by calling the setEscape ModelStrings(false) method.
10.2 Models and JavaBeans
One of the main goals of Wicket is to use JavaBeans and POJO as data model, overcoming the impedance mismatch between web technologies and OO paradigm. In order to make this task as easy as possible, Wicket offers two special model classes: org.apache.wicket.model.PropertyModel and org.apache.wicket.model.CompoundPropertyModel. We will see how to use them in the next two examples, using the following JavaBean as the data object:public class Person implements Serializable { private String name; private String surname; private String address; private String email; private String passportCode; private Person spouse; private List<Person> children; public Person(String name, String surname) { this.name = name; this.surname = surname; } public String getFullName(){ return name + " " + surname; } /* * Getters and setters for private fields */ }
PropertyModel
Let's say we want to display the name field of a Person instance with a label. We could, of course, use the Model class like we did in the previous example, obtaining something like this:Person person = new Person(); //load person's data...Label label = new Label("name", new Model(person.getName()));
Person person = new Person(); //load person's data...Label label = new Label("name", new PropertyModel(person, "name"));
Label label = new Label("spouseName", new PropertyModel(person, "spouse.name"));
PropertyModel is null-safe, which means we don't have to worry if property expression includes a null value in its path. If such a value is encountered, an empty string will be returned.If property is an array or a List, we can specify an index after its name. For example, to display the name of the first child of a Person we can write the following property expression:
Label label = new Label("firstChildName", new PropertyModel(person, "children.0.name"));
CompoundPropertyModel and model inheritance
Class org.apache.wicket.model.CompoundPropertyModel is a particular kind of model which is usually used in conjunction with another Wicket feature called model inheritance. With this feature, when a component needs to use a model but none has been assigned to it, it will search through the whole container hierarchy for a parent with an inheritable model. Inheritable models are those which implement interface org.apache.wicket.model.IComponentInheritedModel and CompoundPropertyModel is one of them. Once a CompoundPropertyModel has been inherited by a component, it will behave just like a PropertyModel using the id of the component as property expression. As a consequence, to make the most of CompoundPropertyModel we must assign it to one of the containers of a given component, rather than directly to the component itself.For example if we use CompoundPropertyModel with the previous example (display spouse's name), the code would become like this://set CompoundPropertyModel as model for the container of the label setDefaultModel(new CompoundPropertyModel(person));Label label = new Label("spouse.name");add(label);
//Create a person named 'John Smith' Person person = new Person("John", "Smith"); setDefaultModel(new CompoundPropertyModel(person));add(new Label("name")); add(new Label("surname")); add(new Label("address")); add(new Label("email")); add(new Label("spouse.name"));
//Create a person named 'John Smith' Person person = new Person("John", "Smith"); CompoundPropertyModel compoundModel; setDefaultModel(compoundModel = new CompoundPropertyModel(person));add(new Label("xyz", compoundModel.bind("spouse.name")));
Model is referred to as static model because the result of its method getObject is fixed an it is not dynamically evaluated each time the method is called. In contrast, models like PropertyModel and CompoundProperty Model are called dynamic models.
10.3 Wicket forms
Web applications use HTML forms to collect user input and send it to the server. Wicket provides org.apache.wicket.markup.html.form.Form class to handle web forms. This component must be bound to <form> tag. The following snippet shows how to create a very basic Wicket form in a page:Html:<form wicket:id="form"> <input type="submit" value="submit"/> </form>
Form form = new Form("form"){ @Override protected void onSubmit() { System.out.println("Form submitted."); } }; add(form);
Form and models
A form should contain some input fields (like text fields, check boxes, radio buttons, drop-down lists, text areas, etc.) to interact with users. Wicket provides an abstraction for all these kinds of elements with component org.apache.wicket.markup.html.form.FormComponent:The purpose of FormComponent is to store the corresponding user input into its model when the form is submitted. The form is responsible for mapping input values to the corresponding components, avoiding us the burden of manually synchronizing models with input fields and vice versa.Login form
As first example of interaction between the form and its models, we will build a classic login form which asks for username and password (project LoginForm).The topic of security will be discussed later in chapter 18. The following form is for example purposes only and is not suited for a real application. If you need to use a login form you should consider to use component org.apache.wicket.authroles.authentication.panel.SignInPanel shipped with Wicket.This form needs two text fields, one of which must be a password field. We should also use a label to display the result of login process1. For the sake of simplicity, the login logic is all inside onSubmit and is quite trivial.The following is a possible implementation of our form:
public class LoginForm extends Form { private TextField usernameField; private PasswordTextField passwordField; private Label loginStatus; public LoginForm(String id) { super(id); usernameField = new TextField("username", Model.of("")); passwordField = new PasswordTextField("password", Model.of("")); loginStatus = new Label("loginStatus", Model.of("")); add(usernameField); add(passwordField); add(loginStatus); } public final void onSubmit() { String username = (String)usernameField.getDefaultModelObject(); String password = (String)passwordField.getDefaultModelObject(); if(username.equals("test") && password.equals("test")) loginStatus.setDefaultModelObject("Congratulations!"); else loginStatus.setDefaultModelObject("Wrong username or password!"); } }
usernameField = new TextField("username", Model.of("")); passwordField = new PasswordTextField("password", Model.of("")); loginStatus = new Label("loginStatus", Model.of(""));
java.lang.IllegalStateException: Attempt to set model object on null model of component:
<html> <head> <title>Login page</title> </head> <body> <form id="loginForm" method="get" wicket:id="loginForm"> <fieldset> <legend style="color: #F90">Login</legend> <p wicket:id="loginStatus"></p> <span>Username: </span><input wicket:id="username" type="text" id="username" /><br/> <span>Password: </span><input wicket:id="password" type="password" id="password" /> <p> <input type="submit" name="Login" value="Login"/> </p> </fieldset> </form> </body> </html>
public class HomePage extends WebPage { public HomePage(final PageParameters parameters) { super(parameters); add(new LoginForm("loginForm")); } }
public class LoginForm extends Form{ private String username; private String password; private String loginStatus; public LoginForm(String id) { super(id); setDefaultModel(new CompoundPropertyModel(this)); add(new TextField("username")); add(new PasswordTextField("password")); add(new Label("loginStatus")); } public final void onSubmit() { if(username.equals("test") && password.equals("test")) loginStatus = "Congratulations!"; else loginStatus = "Wrong username or password !"; } }
Keep in mind that when CompoundPropertyModel is inherited, it does not consider the ids of traversed containers for the final property expression, but it will always use the id of the visited child. To understand this potential pitfall, let's consider the following initialization code of a page:The value displayed by label "name" will be "John" and not the spouse's name "Jill" as you may expect. In this example the label doesn't own a model, so it must search up its container hierarchy for an inheritable model. However, its container (WebMarkup Container with id 'spouse') doesn't own a model, hence the request for a model is forwarded to the parent container, which in this case is the page. In the end the label inherits CompoundPropertyModel from page but only its own id is used for the property expression. The containers in between are never taken into account for the final property expression.//Create a person named 'John Smith' Person person = new Person("John", "Smith"); //Create a person named 'Jill Smith' Person spouse = new Person("Jill", "Smith"); //Set Jill as John's spouse person.setSpouse(spouse);setDefaultModel(new CompoundPropertyModel(person)); WebMarkupContainer spouse = new WebMarkupContainer("spouse"); Label name; spouse.add(name = new Label("name"));add(spouse);
10.4 Component DropDownChoice
Class org.apache.wicket.markup.html.form.DropDownChoice is the form component needed to display a list of possible options as a drop-down list where users can select one of the proposed options. This component must be used with <select> tag:Html:<form wicket:id="form"> Select a fruit: <select wicket:id="fruits"></select> <div><input type="submit" value="submit"/></div> </form>
List<String> fruits = Arrays.asList("apple", "strawberry", "watermelon"); form.add(new DropDownChoice<String>("fruits", new Model(), fruits));
- a model containing the current selected item. This parameter is not required if we are going to inherit a CompoundPropertyModel for this component.
- a list of options to display which can be supplied as a model or as a regular java.util.List.
<select name="fruits" wicket:id="fruits"> <option value="" selected="selected">Choose One</option> <option value="0">apple</option> <option value="1">strawberry</option> <option value="2">watermelon</option> </select>
List<Person> persons; //Initialize the list of persons here… ChoiceRenderer personRenderer = new ChoiceRenderer("fullName", "passportCode"); form.add(new DropDownChoice<String>("persons", new Model<Person>(), persons, personRenderer));
10.5 Model chaining
Models that implement the interface org.apache.wicket.model.IChainingModel can be used to build a chain of models. These kinds of models are able to recognize whether their model object is itself an implementation of IModel and if so, they will call getObject on the wrapped model and the returned value will be the actual model object. In this way we can combine the action of an arbitrary number of models, making exactly a chain of models. Chaining models allows to combine different data persistence strategies, similarly to what we do with chains of I/O streams. To see model chaining in action we will build a page that implements the List/Detail View pattern, where we have a drop-down list of Person objects and a form to display and edit the data of the current selected Person.The example page will look like this:What we want to do in this example is to chain the model of the DropDownChoice (which contains the selected Person) with the model of the Form. In this way the Form will work with the selected Person as backing object. The DropDownChoice component can be configured to automatically update its model each time we change the selected item on the client side. All we have to do is to override method wantOn SelectionChangedNotifications to make it return true. In practice, when this method returns true, DropDownChoice will submit its value every time JavaScript event onChange occurs, and its model will be consequently updated. To leverage this functionality, DropDownChoice doesn't need to be inside a form.The following is the resulting markup of the example page:… <body> List of persons <select wicket:id="persons"></select> <br/> <br/> <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> <input type="submit" value="Save"/> </form> </body>
Model<Person> listModel = new Model<Person>(); ChoiceRenderer<Person> personRender = new ChoiceRenderer<Person>("fullName"); personsList = new DropDownChoice<Person>("persons", listModel, loadPersons(), personRender){ @Override protected boolean wantOnSelectionChangedNotifications() { return true; }};
public class PersonListDetails extends WebPage { private Form form; private DropDownChoice<Person> personsList; public PersonListDetails(){ Model<Person> listModel = new Model<Person>(); ChoiceRenderer<Person> personRender = new ChoiceRenderer<Person>("fullName"); personsList = new DropDownChoice<Person>("persons", listModel, loadPersons(), personRender){ @Override protected boolean wantOnSelectionChangedNotifications() { return true; } }; add(personsList); form = new Form("form", new CompoundPropertyModel<Person>(listModel)); form.add(new TextField("name")); form.add(new TextField("surname")); form.add(new TextField("address")); form.add(new TextField("email")); add(form); } //loadPersons() //… }
10.6 Detachable models
In chapter 6 we have seen how Wicket uses serialization to store page instances. When an object is serialized, all its referenced objects are recursively serialized. For a page this means that all its children components, their related models as well as the model objects inside them will be serialized. For model objects this could be a serious issue for (at least) two main reasons:- The model object could be a very large instance, hence serialization would become very expensive in terms of time and memory.
- We simply may not be able to use a serializable object as model object. In paragraphs 1.4 and 9.2 we stated that Wicket allows us to use a POJO as backing object, but POJOs are ordinary objects with no prespecified interface, annotation or superclass, hence they are not required to implement the standard Serializable interface.
The following model is provided for example purposes only and is not intended to be used in production environment. Important aspects such as transaction management are not taken into account and you should rework the code before considering to use it.
public class JpaLoadableModel<T> extends LoadableDetachableModel<T> { private EntityManagerFactory entityManagerFactory; private Class<T> entityClass; private Serializable identifier; private List<Object> constructorParams; public JpaLoadableModel(EntityManagerFactory entityManagerFactory, T entity) { super(); PersistenceUnitUtil util = entityManagerFactory.getPersistenceUnitUtil(); this.entityManagerFactory = entityManagerFactory; this.entityClass = (Class<T>) entity.getClass(); this.identifier = (Serializable) util.getIdentifier(entity); setObject(entity); } @Override protected T load() { T entity = null; if(identifier != null) { EntityManager entityManager = entityManagerFactory.createEntityManager(); entity = entityManager.find(entityClass, identifier); } return entity; } @Override protected void onDetach() { super.onDetach(); T entity = getObject(); PersistenceUnitUtil persistenceUtil = entityManagerFactory.getPersistenceUnitUtil(); if(entity == null) return; identifier = (Serializable) persistenceUtil.getIdentifier(entity); } }
Since the model of this example holds a reference to the EntityManager Factory, the implementation in use must be serializable.
10.7 Using more than one model in a component
Sometimes our custom components may need to use more than a single model to work properly. In such a case we must manually detach the additional models used by our components. In order to do this we can overwrite the Component's onDetach method that is called at the end of the current request. The following is the generic code of a component that uses two models:/** * * fooModel is used as main model while beeModel must be manually detached * */ public class ComponetTwoModels extends Component{ private IModel<Bee> beeModel; public ComponetTwoModels(String id, IModel<Foo> fooModel, IModel<Bee> beeModel) { super(id, fooModel); this.beeModel = beeModel; } @Override public void onDetach() { if(beeModel != null) beeModel.detach(); super.onDetach(); } }
10.8 Use models!
Like many people new to Wicket, you may need a little time to fully understand the power and the advantages of using models. Taking your first steps with Wicket you may be tempted to pass row objects to your components instead of using models:/** * * NOT TO DO: passing row objects to components instead of using models! * */ public class CustomComponent extends Component{ private FooBean fooBean; public CustomComponent(String id, FooBean fooBean) { super(id); this.fooBean = fooBean; } //...some other ugly code :)… }