Page Edited :
WICKET :
Working with Wicket models
Working with Wicket models has been edited by Loren Rosen (Jan 22, 2007). Change summary: mostly rewritten Table of contents What are Wicket Models?In Wicket, as in other view frameworks, a model provides the data a component should show and/or edit. Each Wicket component has some associated data, and a model instance which sits in-between the component and the data. All the model does is provide a way to get and set a value for a component - nothing more. The nature of this value depends on the component. For a Label it must be something which can be converted to a String which will be displayed when the label is rendered. For a ListView it must be a java.util.List containing the values to be displayed in the ListView. The intermediary model allows Wicket to do several things that would not be possible if the component accessed the data directly. The data might be retrieved only when needed, or might be read from a resource file, to give possibilities. Each framework implements the model concept differently. Swing, for instance, has a number of component-specific model interfaces, whereas Struts requires that the model be a Java Bean and there is no explicit model interface. The use of a single model interface (as compared to having multiple interfaces, or having no model interface at all) has a number of advantages:
Simple ModelsThe HelloWorld example program demonstrates the simplest model type in Wicket: public class HelloWorld extends WicketExamplePage { public HelloWorld() { add(new Label("message", "Hello World!")); } } The constructor for this page constructs a Label component. The first parameter to the Label component's constructor is the Wicket id, which associates the Label with a tag in the HelloWorld.html markup file: <?xml version="1.0" encoding="UTF-8"?> <html xmlns:wicket='http://wicket.sourceforge.net/wicket'> <head> <title>Wicket Examples - helloworld</title> <link rel="stylesheet" type="text/css" href="" class="code-quote">"style.css"/> </head> <body> <span wicket:id="mainNavigation"/> <span wicket:id="message">Message goes here</span> </body> </html> The second parameter to the Label component's constructor is the model data for the Label, providing content that replaces any text inside the <span> tag to which the Label is associated. The model data passed to the Label constructor above is apparently a String. Internally Label creates a Model for the String. Model is a simple default implementation of IModel.
Compound Property ModelsCompound models allow containers to share models with their children. This saves memory, but more importantly, it makes replication of models much cheaper in a clustered environment. The basic idea is that the contained components usually want model data that can be easily derived at runtime from the model of their container. So, give the contained components no explicit model, and when a model is needed, Wicket will search up the containment hierarchy for a compound model. The compound model can retrieve model data for any of its contained components. CompoundPropertyModel is the most commonly used compound model. An instance of this class uses the name of the contained component as a property _expression_ to retrieve data from its own model data. To use a CompoundPropertyModel, simply set one as the model for a container, such as a Form or a Page. Create the contained components with no model of their own. Insure that the component identifier names match the appropriate property names. Here's a simple example using a CompoundPropertyModel. Suppose we have a Person class, with two properties: Name and Age. We want a simple form for the user to edit a Person. Form personForm = new Form("aPersonForm", new CompoundPropertyModel(person)); personForm.add(new RequiredTextField("name")); personForm.add(new RequiredTextField("age", Integer.class)); (A complete working example would require a save button and so forth but the use of a compound model doesn't change those.) The component name can in fact be a more complicated property _expression_. Suppose for example that the Person class also has an address property, of class Address, and that class in turn has a city property. To define this field in the form we can do this: personForm.add(new RequiredTextField("address.city")); The corresponding input field in the html must have a wicket id of "address.city". This works, but it does expose the internal structure of the model data in the html. CompoundPropertyModel has a subclass, BoundCompoundPropertyModel, that can be used to rectify this. BoundCompoundPropertyModel adds three new methods. They associate a child component of the model with a specific property _expression_ and/or type conversion. public Component bind(final Component component, final String propertyExpression) public Component bind(final Component component, final Class type) public Component bind(final Component component, final String propertyExpression, final Class type) With this association in place the child component can have whatever name we like, rather than having the match the property _expression_. To use BoundCompoundPropertyModel for the city field discussed above we might do something like this: BoundCompoundPropertyModel personModel = new BoundCompoundPropertyModel(person); Form personForm = new Form("aPersonForm", personModel); TextField cityField = new RequiredTextField("city"); personForm.add(cityField); personModel.bind(cityField, "address.city"); Note that the bind methods return the child component, thus the above can be more compactly written: BoundCompoundPropertyModel personModel = new BoundCompoundPersonModel(person); Form personForm = new Form("aPersonForm", personModel); personForm.add(personModel.bind(new RequiredTextField("city"), "address.city")); Resource ModelsLocalization of resources in Wicket is supported by the Localizer class. This provides a very convenient way to retrieve and format application-wide or component-specific resources according to the Locale that the user's Session is running under. You can retrieve a String using Localizer and use the result as a model object, but it is usually more convenient to use one of Wicket's resource model classes. ResourceModel is the simplest of these. It simply takes a resource key and uses it to determine the model object (by looking for the resource in a property file). Thus for a example a label can be created this way: add(new Label("greetings", new ResourceModel("label.greetings"))); (Note however that you can also accomplish the same thing using the wicket:message tag in the html.) ResourceModel has another constructor which takes an additional string to be used as a default if the resource is not found. The model objects created via ResourceModel vary according to the locale and the contents of the resource bundle, but they don't otherwise depend on the program state. For example, the greeting message produced above doesn't contain the user's name. More dynamic models can be constructed using StringResourceModel. StringResourceModels have a resource key, a Component, an optional model and an optional array of parameters that can be passed to the Java MessageFormat facility. The constructors look like this: public StringResourceModel(final String resourceKey, final Component component, final IModel model) public StringResourceModel(final String resourceKey, final Component component, final IModel model, final Object[] parameters) The resourceKey is used to find the resource in a property file. The property file used depends on the component. A very simple example of a StringResourceModel might be a Label constructed like this: add(new Label("greetings", new StringResourceModel("label.greetings", this, null))); where the resource bundle for the page contains an entry like this: label.greetings=Welcome! This label is essentially the same as that constructed with ResourceModel above. However, with StringResourceModel we can add the name from a User object to the greeting. We pass the user object as the model parameter to the constructor. Wicket will do find the localized string in the resource bundle, find any substrings within it of the form ${propertyexpression}, and replace these substrings with their values as property expressions. The model is used to evaluate the property expressions. For example, suppose we wanted to add the name from a User object to the greeting above. We'd say: User user; ... add(new Label("greetings", new StringResourceModel("label.greetings", this, user))); and have a resource like: label.greetings=Welcome, ${user.name}! where User has a getName() method exposing its "name" property. Note that StringResourceModel is dynamic: the property _expression_ is re-evaluated every time getObject() is called. In contrast, if the application code called Localizer itself and used the resulting string as model data, the result would be a static (non-changing) model. Detachable ModelsWicket maintains information about recently created pages in the servlet Session. The models for the pages' components are a large portion of this data. There are several motivations for reducing the amount of memory consumed by this data. It increases the number of users that can be served with the same amount of memory. Also, we may wish to replicate session data in a cluster so we can move sessions if a web server fails, and in this case there is serialization overhead we want to minimize. It's quite common that model data contains some very small sub-part, such as a database identifier, from which the rest of the object can be reconstituted. So one way to reduce Session memory usage is to discard the data when display of the page is complete, except for the small special piece needed to recover the rest. Wicket supports this via detachable models. The easiest way to create a detachable model is to extend LoadableDetachableModel. This class has an abstract method protected abstract java.lang.Object load(); For example: class LoadablePersonModel extends LoadableDetachableModel { Long id; LoadablePersonModel(Long id) { this.id = id; } @Override protected Object load() { return DataManager.getPersonFromDatabase(id); } } When getObject() is called for the first time on a given instance of this class, it will call load() to retrieve the data. At this point the data is said to be attached. Subsequent calls to getObject() will return the attached data. At some point (after the page has been rendered) this data will be removed (the internal references to it will be set to null). The data is now detached. If getObject() is subsequently called (for example, if the page is rendered again), the data will be attached again, and the cycle starts anew. Chaining modelsSuppose you want a model that is both a loadable model and a compound model, or some other combination of the model types described above. One way to achieve this is to chain models, like this: CompoundPropertyModel personModel = new CompoundPropertyModel(new LoadablePersonModel(personId)); (here LoadablePersonModel is a subclass of LoadableDetachableModel, as described earlier) The model classes in the core Wicket distribution support chaining where it make sense. More about the IModel interfaceWe are now in a position to understand the complete IModel interface: public interface IModel extends IDetachable { public Object getNestedModel(); public Object getObject(final Component component); public void setObject(final Component component, final Object object); } IModel extends IDetachable, which means that all models must provide a method public void detach(). Some model classes (Model for one) provide an empty, no-op implementation. In some cases model instances form a natural hierarchy. For example, several CompoundPropertyModels share the same model object. The getNestedModel() method will return this common shared object. In cases where there is no such object it returns null. Compound models are also the motivation for the component parameters to getObject and setObject. Several components may share the same CompoundPropertyModel object. If getObject (or setObject) is called it must return something different depending on the component (e.g., evaluate the appropriate property _expression_). Thus these two methods must be passed a component as a parameter. |
Unsubscribe or edit your notifications preferences