Wicket Models Explained
A few weeks back, Jonathan Locke has written an article concerning the different model types in Wicket. I want to elaborate more on this subject, and shed some light on the basics of one of the most powerful models: the CompoundPropertyModel.
CompoundPropertyModel
When I first started working with these models, I thought: WOW!. Now the wow-moment has passed, I also encountered some limitations to this model type, just like an user on the user-mailinglist, First of all: what is the CompoundPropertyModel?
Wicket uses models to allow you to bind data objects to your web components. Wicket provides several models, some of which are simple, some of which enable the use of OGNL expressions and some allow you to share your model between your web components.
The CompoundPropertyModel allows you to share the model between components, by binding the components at render time to the model. In a typical, non-shared model page, you'd see code like this:
public class InputExamplePage extends WebPage { public class Person { String name; Date birthdate; /* getters and setters */ } public InputExamplePage() { Person person = new Person(); Form form = new Form("form", new Model(person), null); form.add(new TextField("name", new PropertyModel(person, "name")); form.add(new TextField("birthdate", new PropertyModel(person, "birthdate")); add(form); } }
As you can see the model provided to the Form component on this page is a simple Model. This model only holds the data object and doesn't do anything else. Adding the TextFields you see the use of PropertyModels. These allow you to bind components to a property of a class. Note that you can't use the getter directly because that results in a read only TextField. Compare this to the following code, but now using a shared model:
public class InputExamplePage extends WebPage { public class Person { String name; Date birthdate; /* getters and setters */ } public InputExamplePage() { Person person = new Person(); Form form = new Form("form", new CompoundPropertyModel(person), null); form.add(new TextField("name"))); form.add(new TextField("birthdate")); add(form); } }
A lot shorter, isn't it? The only requirement is that the ID's of the components match property names. If this isn't the case, or if you wish to use property navigation (like person.address.street), you may consider using the BoundCompoundPropertyModel. The latter also allows you to navigate over properties, as can be seen in the last example:
public class InputExamplePage extends WebPage { public class Address { String street; /* getters and setters */ } public class Person { String name; Address address; /* getters and setters */ } public InputExamplePage() { BoundCompoundPropertyModel model = new BoundCompoundPropertyModel(new Person()) Form form = new Form("form", model, null); form.add(new TextField("name")); form.add(model.bind(new TextField("street"), "address.street")); add(form); } }
There are some limitations to this model, which aren't apparent when you first start out using them.
This doesn't work
public class InputExamplePage extends WebPage { public class Person { String name; Person partner; /* getters and setters */ } public InputExamplePage() { Person partner = new Person(); partner.setName("Partner"); Person demo = new Person(); demo.setName("Demo"); demo.setPartner(partner); CompoundPropertyModel model = new CompoundPropertyModel(demo); Form demoForm = new Form("demo", model, null) {}; demoForm.add(new TextField("name")); add(demoForm); Form partnerForm = new Form("partner") {}; partnerForm.add(new TextField("name")); demoForm.add(partnerForm); } }
What kind of output do you expect? One text field with the value 'Demo', and one field with the value 'Partner'? Wrong!. You are correct in your assumption that partnerForm.getModelObject() will return the partner object, but the text field of the partner form will also be bound to the demo person. So you will see two fields with the same value: "Demo". This is because the model object of the partner form is shared between itself and all of its children. Mind you: this is not a bug, but behaviour as designed. Perhaps with the advent of Wicket 1.1 we will provide you with a NavigationCompoundPropertyModel enabling the navigation your datamodel by using your components.