You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 20 Next »

This New User's Guide is a work in progress on a manual for Wicket. Using a wiki is a nice form for collaborative work on documentation.

Table of contents

Setup

To build a wicket web application you need to understand the WAR-file layout and what the WEB-INF/lib is all about, otherwise you should go read the Introduction on Java web applications.

Quick start

The following JAR-files are the bare minimum needed in WEB-INF/lib to develop a wicket application:

  • wicket-1.3.0.jar
  • slf4j-api-1.4.2.jar
  • slf4j-jdk14-1.4.2.jar

The use of Servlet filters in the web.xml configuration is explained here.

That's it, now go kick the tires!

Maven

If you have maven installed there is an archetype that can get you up to speed quickly. See the quickstart

Eclipse, NetBeans, IntelliJ

There are descriptions at the Wicket Quickstart on how to set up your favourite development environment.

Wizardry

qwicket can create a ready made project for you.

{composition-setup}{composition-setup}

My First Application

The Wicket application we are going to develop is the ubiquitous Hello, World! application. Simply put, we will create a web page using Wicket technology for printing the Hello, World! string on a web page.
In the second release, some interactivity will be added to show how to do simple form related component development.

The goal is to generate the following markup:

html <html> <body> Hello, world! </body> </html>

The HTML

Even though the requested result is so simple, there are many ways to achieve it. In this example we will use a rendering component called 'Label' to create the text 'Hello, World!'. So basically we have two things to consider:

  1. the HTML page
  2. the label component

The markup for the HTML page is the same as earlier:

html <html> <body> Hello, world! </body> </html>

It is of course possible to add header information, apply style sheets, put javascript in there, etc. For the sake of the example, this is omitted.

Using this HTML-code is cheating, since we don't generate the required string programatically, but have hard coded it in the HTML page.
In order to have Wicket render the text through a component, we need to tell Wicket which piece of the web page is a component. In this case we can replace the text with a label component definition.

html <html> <body> <span wicket:id="message">Message goes here!</span> </body> </html>

We have used the span element to markup the component and the wicket:id attribute to identify the component.

What we have accomplished until now is not enough to show the message in a web page. We have only told Wicket where to render the label component in the web page. In the next section we will show how to tell Wicket to render the message.

The Java Code

In Wicket creating a web page consists of two tasks: creating the markup in which the components are defined and other information is shown, and creating Java classes to couple the web page to the required functionality. In the case of this example, we need to tell Wicket it needs to render the Label component. We also need to tell the label component to render the text 'Hello, World!'. In this section we will perform these steps one at a time.

First we need to create a web page. Wicket has a WebPage class suited for this task. All webpages need to be subclasses of WebPage. Another requirement is that the actual HTML file and the class name are equal: Index.html and Index, IndexPage.html and IndexPage or HelloWorld.html and HelloWorld. They also need to be in the same place on the classpath. The best way to develop is to put them in the same directory. This might seem strange in the beginning, especially when you are accustomed to separate html files and java files. However, since all pages are actually just components, it makes perfect sense in terms of reusability.

import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.basic.Label; public class HelloWorld extends WebPage { public HelloWorld() { add(new Label("message", "Hello World!")); } }

Note that the base package names changed or rather moved from Wicket version 1.3. This means that if you are using these versions you would need to change your imports (and other references to these for packages/classes) to reflect these new package locations. The basic idea is just to prefix all your wicket packages with "org.apache". For example, the imports:

import wicket.markup.html.WebPage; import wicket.markup.html.basic.Label;

would change to:

import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.markup.html.basic.Label;:

When you add components, you need to give them an identifier in the form of a string. In the above example, our component's identifier is 'message'. The id should be unique within it's own scope. In other words, no siblings are allowed with the same id.

When the markup is rendered, the id's of tags are matched against the component ids. Thus

<span wicket:id="message">Message goes here</span>

will look for a component with the id 'message' in the current scope.

Besides having an id, components also have a model. A model contains and represents a state of component. Usually, a model wraps an arbitrary object and provides data from this object to the component, which displays them. In this case, the model wrapes the string "Hello World!" and this string is used for the replacement of the body of the span tag. Generally, you have to create an instance of model and associate it with the component (models are classes that implement the IModel interface). However, many components have convenience constructors that construct a proper model for you. With labels, you can just provide a string, which is then wrapped into a simple model for the label to use.

Generally, you use models for the 'flexible' part of components, while it is best practice to keep your components immutable.

One more thing we need to get our example running is an Application object. You only need one for an application, and it can serve as a place to do the configuration for the application.

Our application could look like this:

package mypackage; import org.apache.wicket.protocol.http.WebApplication; public class HelloWorldApplication extends WebApplication { public Class getHomePage() { return HelloWorld.class; } }

Sidenote: To see changes to template files immediately, include the following snippet in your web.xml or to use a system property -Dwicket.configuration

xml <context-param> <param-name>configuration</param-name> <param-value>development</param-value> </context-param>

web.xml configuration

To load our application and have it available, we need to add the following lines to web.xml:

xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <context-param> <param-name>configuration</param-name> <param-value>development</param-value> </context-param> <filter> <filter-name>WebApplication</filter-name> <filter-class> org.apache.wicket.protocol.http.WicketFilter </filter-class> <init-param> <param-name>applicationClassName</param-name> <param-value>mypackage.HelloWorldApplication</param-value> </init-param> </filter> <filter-mapping> <filter-name>WebApplication</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>

And you are now ready to run your first Wicket app!

NOTE: Remember to change the wicket pages names to reflect the org.apache base package for Wicket version 1.3 and up. See the 'The Java Code' section above.

Deploying the application

One easy way of testing the application is by using Wicket QuickStart. Note that in some releases, some jar files from the jetty distribution were omitted: you need to add lib/org.mortbay.servlet.jar and ext/jasper-*.jar to your buildpath manually.

If you wish to build the package manually, you would need the following directory structure.

WEB-INF/ classes/ HelloWorldApplication.java HelloWorld.java HelloWorld.html lib/ wicket-1.3.0.jar slf4j-simple-1.4.3.jar. slf4j-api-1.4.3.jar web.xml

Then to build the application, make sure wicket-1.3.0.jar is in your classpath, and build the .java files. To run you need to ensure all .jar files in the "lib" directory are in your classpath. For most web servers and servlet containers (Tomcat for one) will automatically load all these files in the lib directory into your classpath.

It is recommended that you use the correct tools/methods to build your applications. The above can be used as an indication as to what is needed to be able to build/run a Wicket application. This information can be used to setup your project in your favorite environment (Eclipse, Netbeans, etc.) and server (Tomcat, etc.).

An interactive HelloWorld

The next thing we want to see is some interaction. Let us make the first example a little bit more dynamic.

We are going to extend our example to display an interactive message. We want the user to input what message should be displayed, and use that input to render our label with.

The HTML

<html>
  <body>
    <span wicket:id="message">Message goes here</span>
    <form wicket:id="messageInputForm">
    	<input type="text" wicket:id="messageInput"/>
    	<input type="submit" value="update"/>
    </form>
  </body>
</html>

In the above markup you can see three Wicket components referenced: 'message', 'messageInputForm', and 'messageInput'. So, besides a label, we now have a form and input field.

The Java code

package mypackage;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;

public class HelloWorld extends WebPage
{
  public HelloWorld()
  {
    IModel messageModel = new Model("Hello World!");
    add(new Label("message", messageModel));
    add(new MessageForm("messageInputForm", messageModel));
  }

  private final class MessageForm extends Form
  {
    public MessageForm(String id, IModel model)
    {
      super(id);
      add(new TextField("messageInput", model));
    }

    protected void onSubmit()
    {
      // nothing to do here as the model is automatically updated
    }
  }
}

The first thing you should notice is the nesting of components. The MessageForm is a sibling of the label, so it has to have a different id. The text field is a child of the MessageForm and thus has a different scoping. As the text field is set as a child of the form, it needs to be a nested tag in the form as well.

The next thing to notice is the use of a Model object. As said earlier, every Wicket component has a model. What a component uses its model for, depends on the component. A label uses its model to render the tag body, but a ListView expects a java.util.List as the model object, and loops through its elements. A model can be anything, as long as it implements org.apache.wicket.model.IModel.

To process user input, you can use a Form. Forms update the models of their nested FormComponent-s automatically. It does this by calling setObject(...) on the object with the input for that specific component. Hence, when a user pushes the submit button, the model of the text field is updated with the value that was in the HTML input text field. As, in this example, the label and the text field share the same model, when the model is updated via the text field, the label will render the new model object value as well.

More on models

Let us take a closer look at models.

The HelloWorld example program demonstrates the simplest model type in Wicket:

public class HelloWorld extends WicketExamplePage
{
  public HelloWorld()
  {
    add(new Label("message", "Hello World!"));
  }
}

The first parameter to the Label component added in the constructor is the Wicket id, which associates the Label with a tag in the HelloWorld.html markup file:

<html>
    <head>
	<title>Wicket Examples - helloworld</title>
	<link rel="stylesheet" type="text/css" href="style.css"/>
    </head>
    <body>
	<span wicket:id="mainNavigation">main nav will be here</span>
	<span wicket:id="message">Message goes here</span>
    </body>
</html>

The second parameter to Label is the model for the Label, which provides content which replaces any text inside the <span> tag that the Label is attached to. The model being passed to the Label constructor above is apparently a String. But if we look inside the Label constructor, we see that this constructor is merely there for convenience - it wraps its String argument in a Model object like this:

public Label(final String id, String label)
{
  this(id, new Model(label));
}

If we look up the inheritance hierarchy for Label, we see that Label extends WebComponent, which in turn extends Component. Each of these superclasses has only two constructors. One Component constructor takes a String id:

public Component(final String id)
{
  setId(id);
}

And the other takes a String id and an IModel:

public Component(final String id, final IModel model)
{
  setId(id);
  setModel(model);
}

IModel is the interface that must be implemented by all Wicket model objects. It looks like this:

public interface IModel extends IDetachable
{
  public Object getObject();
  public void setObject(Object object);
}

And the base interface IDetachable looks like this:

public interface IDetachable extends Serializable
{
  public void detach();
}

These details are not especially interesting to a beginning model user, so we will come back to this later. What we need to know right now is that Model is an IModel and therefore it can be passed to the Component(String, IModel) constructor we saw a moment ago.

If we take a peek inside Model, we'll see that the Model object is simply a wrapper that holds a Serializable object:

private Serializable object;

Model's object value is Serializable in order to support deployment of web applications in clustered environments. Note also that since IModel extends IDetachable, which extends Serializable, all IModel wrapper classes are themselves Serializable. So all Wicket models are therefore serializable, which means that they can be replicated between servers in a cluster. Don't worry if you don't fully understand this. We will talk more about clustering later.

Working with POJOs

While using a String like we did in the above example ('Hello World') is convenient, you will usually want to work with domain objects directly. To illustrate how Wicket supports working with domain object, we use the following Plain Old Java Object (POJO) for our examples:

import java.util.Date;

public class Person
{
  private String name;
  private Date birthday;

  public Person()
  {
  }
  public String getName()
  {
    return name;
  }
  public void setName(String name)
  {
    this.name = name;
  }
  public Date getBirthday()
  {
    return birthday;
  }
  public void setBirthday(Date birthday)
  {
    this.birthday = birthday;
  }
}

Static models

Like we have seen above, the simplest way of providing e.g. a Label a model, is just to provide a string, and let the Label implicitly construct one for you.

add(new Label("name", person.getName()));

We can create the model explicitly like:

Model model = new Model(person.getName());
add(new Label("name", model));

This has the same effect as the first example, but as we now have the reference to the model, we can use it e.g. to share it with other components like we did in our little interactive example earlier.

Using models like this works fine as long as there are no changes in the underlying data that should be displayed. You could call these models static or push models, as you have to synchronize any data changes to the model itself.

Dynamic models

While static models are the easiest to use, it is often a good idea to let your models get their current objects dynamically from some source so that you can be certain that they display the most recent value. You could call these models dynamic or pull models.

Property models

The PropertyModel class allows you to create a model that accesses a particular property of its nested model object at runtime. This property is accessed using an expression language which has a syntax similar to OGNL (see http://www.ognl.org) (until version 1.1, Wicket actually did use the OGNL library, however it has been replaced by custom implementation). The simplest PropertyModel constructor is:

public PropertyModel(final Object modelObject, final String expression)

which takes a model object and a property expression. When the property model is asked for its value by the framework, it will use this property expression to access the model object's property.

Looking at our example POJO, the property expression "name" can be used to access the "name" property of any Person object via the getName() getter method. Also, when updating a model, using property expression "name" has the effect of calling setName(String). Hence construct a property model to use our POJO, give it a property expression "name" and set it on a TextField, not only will that TextField display the current value of the "name" property, it will also update the "name" property with any user input provided.

More complex property expressions are possible as well. For example, you can access sub-properties via reflection using a dotted path notation, which means the property expression "person.name" is equivalent to calling getPerson().getName() on the given model object. Arrays can be accessed as in "persons[4].name". As you can see, PropertyModels are quite a powerful way to give Wicket components access to model objects.

More information on the syntax

More information on the allowed property expressions can be found either in the wicket.util.lang.PropertyResolver JavaDoc or on the Property Expression Language wiki page

Compound property models

If you have looked at some examples of Wicket already, you may have wondered why components, which generally must have a model, can be constructed with only an id, omitting any model. There are three reasons for this.

The first, most obvious reason is that not all components actually need a model. Panels and pages, for example, do not use models, though you might find it handy to store a model in these components.

The second reason to construct a Component with no model is that it is sometimes impossible to have a model ready for the call to super(String, IModel) in the Component's constructor. In this case, the Component can be constructed without a model, and the model can be later set with a call to setModel().

The third reason to construct a component with no model is that some components are actually bound to their models at runtime. When an attempt is made to retrieve the model for a Component which does not yet have one, instead of failing and returning null, the getModel() method in Component calls the method initModel(), which gives the Component an opportunity to lazy-initialize, returning a model on-the-fly.

This mechanism opens up all kinds of possibilities for model initialization. One particularly convenient and efficient lazy model initialization strategy is employed by the CompoundPropertyModel and BoundCompoundPropertyModel classes to allow containers to share their models with children. By sharing model objects like this, fewer objects are created, which saves memory, but more importantly, it makes replication of models much cheaper in a clustered environment.

To use a CompoundPropertyModel, you simply set one as the model for any container. However, a very typical place to install a CompoundPropertyModel is on a Form or Page. This is natural, because the compound model itself is usually the model that is being displayed by the Page or edited by the Form.

To see a CompoundPropertyModel in action, take a look at the FormInput example . In the InputForm nested class in FormInput.java, the Form is constructed like this:

public InputForm(String name)
{
  super(name, new CompoundPropertyModel(new FormInputModel()));
  ...
}

If we look at the next few lines of the Form's constructor, we see statements like:

add(new RequiredTextField("stringProperty"));
add(new RequiredTextField("integerProperty", Integer.class));
add(new RequiredTextField("doubleProperty", Double.class));

these statements are adding FormComponents to the Form. But they are not explicitly attached to any model. Instead, they will inherit the CompoundPropertyModel for the form at runtime. When the framework needs the model for a RequiredTextField, it will call getModelObjectAsString().
This method retrieves the text field's model by calling getModel(), which is implemented like this:

public IModel getModel()
{
  if (model == null)
  {
    this.model = initModel();
  }
  return model;
}

Since the RequiredTextField has no model, the initModel() method will be called, resulting in a search up the containment hierarchy for a CompoundPropertyModel that can be inherited by the model-less child:

protected IModel initModel()
{
  for (Component current = getParent(); current != null; current = current.getParent())
  {
    final IModel model = current.getModel();
    if (model instanceof CompoundPropertyModel)
    {
      return model;
    }
  }
  return null;
}

The text field will discover the CompoundPropertyModel for the Form and set that as its own model. So getModel() on the text field ultimately returns the model of the Form.

Next, the nested model object is retrieved from the model by passing in the Component (in this case the RequiredTextField) as a parameter to IModel.getObject(Component). This is the magic that allows the CompoundPropertyModel to retrieve the right value from the Form's model for the Component that is asking for it:

final Object modelObject = model.getObject(this);

The retrieval is done by using the name of the Component as the property expression to access the model. In the case of this component

add(new RequiredTextField("stringProperty"));

the getStringProperty() method would be called on the Form's model.

The model's object value is available in this way to the component for processing. In the case of the stringProperty text field component, it will convert the value to a String. In the case of other RequiredTextField's in the FormInput form, a conversion may occur based on the third parameter to the Component. For example, this component:

add(new RequiredTextField("integerProperty", Integer.class));

will convert the contents of the text field to and from an Integer value.

BoundCompoundPropertyModel has been removed in Wicket 6!

In the event that you need more flexibility or property expressions power in your compound models, you can use the BoundCompoundPropertyModel. This class provides three methods you can call on the container's model object to bind a given child Component to 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)

To see how this might be used, suppose that the stringProperty value needed to be bound to the property expression "person[0].stringProperty". In the FormInput constructor, we'd say something like this:

super(name);
final BoundCompoundPropertyModel formModel = new BoundCompoundPropertyModel(new FormInputModel());
setModel(formModel);
add(formModel.bind(new RequiredTextField("stringProperty"), "person[0].stringProperty"));

The Form is constructed without a model. We then create the Form's model as a BoundCompoundPropertyModel, set it as the Form's model and finally use it to bind the RequiredTextField to the desired property expression.

String Resource Models

Localization of resources in Wicket is supported by the Localizer class. This provides a very convenient way to retrieve and format application and component specific resources according to the Locale that the user's Session is running under. In many cases, it will be sufficient to simply retrieve a localized String in this way, but if a formatted, localized string is desired as a model, a StringResourceModel may be used.

StringResourceModel-s have a resource key (for looking up the string resource in a property file), 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)

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!

By adding a model, we can use the property expressions to interpolate properties from the model into the localized string in the resource bundle. 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.

The Component class has convenience method which you may find useful:

add(new Label("greetings", getString("label.greetings", new Model(user))));

Custom models

If we do not want to use the property expressions/introspection, but instead want to to pull a fresh value from the model on each request in a more strongly typed manner, we could provide our own implementation of IModel:

final Person person = new Person();
person.setName("Fritzl");
Model model = new Model()
{
  public Object getObject()
  {
    return person.getName();
  }
};
add(new Label("name", model));

Note that in the above example, we extended from Model instead of implementing IModel directly, and we did not bother writing setObject() (as for the label it is not needed).

By implementing your own models, you can support any kind of model behaviour you can think of, though it is usually a more verbose way of doing things.

Detachable Models

All model classes in Wicket have the ability to be detachable. A detachable model in Wicket is a model which can get rid of a large portion of its state to reduce the amount of memory it takes up and to make it cheaper to serialize when replicating it in a clustered environment. When an object is in the detached state, it contains only some very minimal non-transient state such as an object id that can be used to reconstitute the object from a persistent data store. When a detached object is attached, some logic in the object uses this minimal state to reconstruct the full state of the object. This typically involves restoring fields from persistent storage using a database persistence technology such as JDO or Hibernate.

If you look again at the IModel, you will notice that this interface extends IDetachable interface, which has single method void detach(). The simplest IModel implementation, Model implements this method as a no-op. However, there are other models, which provide true detachable functionality.

To make implementation of detachable models easy, LoadableDetachableModel provides some basic inheritable logic for attaching and detaching models. The basis for this logic is a transient boolean field which is true when the object is in the attached state and false when it is in the detached state:

private transient boolean attached = false;

Which state a detachable object is in is available via:

public boolean isAttached()
{
  return attached;
}

This boolean variable is used to determine whether to load the transient object from the persistence technology. Furthermore, it is used in the detach() method (which implements the IDetachable interface contract).

public void detach()
{
  if (attached)
  {
    attached = false;
    transientModelObject = null;
    onDetach();
  }
}

protected void onAttach()
{
}

protected void onDetach()
{
}

The transient object is loaded in the getObject() method, if necessary.

public Object getObject()
{
    if (!attached)
    {
      attached = true;
      transientModelObject = load();

      onAttach();
    }
    return transientModelObject;
}

LoadableDetachableModel

Put in another words, LoadableDetachableModel holds a temporary, transient model object, that is set on attaching by calling abstract method load(), and that will be reset/set to null on detaching.

In the following example, we load objects (persons) on attachement.

LoadableDetachableModel personListModel = new LoadableDetachableModel()
{
  protected Object load()
  {
     return getPersonDao().findPersons();
  }
};

During the handling of the request cycle, the list of persons we found by calling findPersons() is returned when getObject() is called on the personListModel. When detached, the model representation is cleaned up.

Another example would be storing a single object which can be reconstituted by its primary key.

public class PersonModel extends LoadableDetachableModel
{
  private Integer primaryKey;
  
  public PersonModel(Integer primaryKey)
  {
    this.primaryKey = primaryKey;
  }

  protected Object load()
  {
     return getPersonDao().findPersonByPk(primaryKey);
  }
}

Now we have an example of a functioning detachable model that shrinks and then restores its state when replicated in a cluster.

Read-only models

AbstractReadOnlyModel is another simple implementation of IModel. It has empty logic for detachment and disallows setting an model object, thus becoming read-only model.

public final void setObject(final Object object)
{
  throw new UnsupportedOperationException("Model " + getClass() + " does not support setObject(Object)");
}

The StringResourceModel we talked about above is another fine example of loadable detachable (and therefore read-only) model, since resource strings are read-only in nature. Actually, the StringResourceModel is a subclass of LoadableDetachableModel.

Property models

The first example of property model is AbstractPropertyModel. It provides implementation details for the PropertyModel, CompoundPropertyModel and BoundCompoundPropertyModel classes we discussed above by implementing getObject() and setObject() using a property expression and conversion type retrieved from a subclass via abstract method propertyExpression(). The simple PropertyModel class just returns the property expression and type it was constructed with:

protected String propertyExpression()
{
  return expression;
}

The conversion type is determined in the method getObjectClass() provided by AbstractPropertyModel.

Another type of property model, CompoundPropertyModel, returns the name of the component as its property expression:

protected String propertyExpression(final Component component)
{
  return component.getId();
}

Finally, BoundCompoundPropertyModel stores a set of bindings internally and returns the property expression for a component based on this information:

protected String propertyExpression(final Component component)
{
  final Binding binding = getBinding(component);
  if (binding != null)
  {
    return binding.propertyExpression;
  }
  else if (component != null)
  {
    return component.getId();
  }
  return null;
}

Special purpose models

Some components need or use specialized models for their workings. Or, as is the case with components that inherit from AbstractChoice, they need more than one model. Currently, these specialized models are ListItemModel and FileUpload. You can find information on them in the JavaDocs of the components that use them, or you can take a look at the examples.

Custom components

You already have seen some Wicket components in action. You also learned that you can influence what the components actually do by providing them with proper models. Now it is time to look at how to customize Wicket components.

Extending

Usually you do not want to start from scratch when you customize a component. Most components can be extended, and have methods that are explicitly meant for customization.

Some components do not even work without this, like wicket.markup.html.link.Link, which has abstract method onClick(), or wicket.markup.html.list.ListView that has abstract method populateItem().

Component wicket.markup.html.form.DropDownChoice is a non-abstract component, but by extending it, we can alter its behaviour. For example, look at:

add(new DropDownChoice("venueSelect",
                        new PropertyModel(this, "currentVenue"), getVenueDao().findVenues())
{
  protected boolean wantOnSelectionChangedNotifications()
  {
    return true;
  }
});

By overloading method wantOnSelectionChangedNotifications(), and have it return true (instead of false which is the default), an onchange javascript event handler will be generated, so that on each new selection of the dropdown, a roundtrip will be made to the server, updating the model of the dropdown component. And if you used your models smart enough, this can have the effect that other components have an updated rendering as well.

And if we want to customize the component even further, we could do:

add(new DropDownChoice("venueSelect",
                       new PropertyModel(this, "currentVenue"), getVenueDao().findVenues())
{
  protected boolean wantOnSelectionChangedNotifications()
  {
    return true;
  }

  protected void onSelectionChanged(Object newSelection)
  {
    // do something here...
  }
});

and do something nice in the onSelectionChanged() event handler.

By making use of models that use e.g. a shared property, we can do all kinds of neat things. Here is an example:

add(new DropDownChoice("venueSelect",
                       new PropertyModel(this, "currentVenue"), getVenueDao().findVenues())
{
  protected boolean wantOnSelectionChangedNotifications()
  {
    return true;
  }
});
WebMarkupContainer container = new WebMarkupContainer("rolesListContainer")
{
  public boolean isVisible()
  {
    return getCurrentVenue() != null;
  }
};
add(container);

Note that we fed our dropdown with a property model that works on this (e.g. a Page) using property 'currentVenue'.  Now, with the second component we override method isVisible, and use that same property 'currentVenue' for our evaluation.
If any venue is selected, we'll display the container and anything that is nested on it. If no venue is selected, we do not display anything. This is a very handy trick to switch parts of your page on and off, and as the dropdown will generate a roundtrip on each selection, our page will feel very dynamic.

TODO... write more

AttributeModifiers

TODO

Panels and Borders

See also Panels and borders to see the difference between Panels and Borders.

Both Panels and Borders have their own markup file, but the markup is used in different ways:

The Border component inserts markup before and after its child components.

<html>
    <body>
     <div wicket:id="myBorder">
      <!-- Border markup -->
      <span wicket:id="myContent">content</span>
      <!-- Border markup -->
     </div>
    </body>
   </html>

The Panel component inserts markup into the body of the tag.

<html>
    <body>
     <div wicket:id="myPanel">
      <!-- Panel markup -->
     </div>
    </body>
   </html>

See Panel for more information.

Doing it (almost) from scratch

TODO

Using Forms

TODO

Start with an example using the pojo from 4

Subclass wicket.markup.html.WebPage and in the constructor initialize and add the required Form implementation.

public class EditCustomerPage extends WebPage {
    public EditCustomerPage() {
        add(new CustomerForm("customerForm", new Customer()));
    }
}

Subclass wicket.markup.html.form.Form and in the constructor add the necessary form elements.
These will be bound to the individual html form elements through the wicket:id attributes.

public class CustomerForm extends Form {
    public CustomerForm(String name, Customer customer) {
        super(id, new CompoundPropertyModel(customer));
        add(new TextField("name"));
    }
}

The html form itself must contain a wicket:id attribute that matches the Form added to the WebPage. Each form element must also have a corresponding wicket:id to each FormComponent added to the Form. In this example the form is bound to the "customerForm" Form instance and the text input is bound to the "name" TextField. There is also a single submit button that is not bound to any component. Wicket automatically provides the submission handling functionality through the Form base class implementation. Button components only need to be added to the Form if multiple buttons are required.

<form wicket:id="customerForm">
    name: <input type="text" wicket:id="name"/><br>
    <input type="submit"/>
</form>

Updating the model

What happens when the form is submitted? Describe how the automatic updating of pojo's when property models are used. Describe how to use buttons and a button-less form.

Validation

NO LONGER VALID AS OF RC1, FORM parent method doesn't take a FeedbackPanel

Describe how validation works and how to implement custom validation. Also give a short explanation of how to display messages.

Adding RequiredValidator to a form element will allow only non-null and non-empty values to pass validation successfully.

public CustomerForm(String name, Customer customer, IFeedback feedback) {
    super(id, new CompoundPropertyModel(customer), feedback);
    add(new TextField("name").add(RequiredValidator.getInstance()));
}

For convenience the class RequiredTextField is provided. This TextField automatically includes a RequiredValidator.

public CustomerForm(String name, Customer customer, IFeedback feedback) {
    super(id, new CompoundPropertyModel(customer), feedback);
    add(new RequiredTextField("name"));
}

To provide validation feedback a FeedbackPanel can be instantiated. This should be added to the page (for display) and associated with the form by passing it into the form constructor.

public EditCustomerPage() {
    FeedbackPanel feedback = new FeedbackPanel("feedback");
    add(feedback);
    add(new CustomerForm("customerForm", new Customer(), feedback));
}

The FeedbackPanel can then be added to the html with a wicket:id span.

<span wicket:id="feedback">feedback will be here</span>

Finally, create a .properties file in the same directory as your html template and class file using the same naming conventions. In this case it would be called EditCustomerPage.properties. Use this file to define the error messages for each input in the form.

formId.inputId.ValidatorClassName=Your custom message.

Conversion

Describe how conversion works (when using property models) and how to implement custom conversion.

Form components

Give a list with a short description of the form components that are available in core.

Life cycle

TODO Describe the life cycle of Wicket apps, sessions and requests.
See Request processing overview. It's not exactly description of life cycles but is related to that.

Custom Sessions

The wicket way of storing objects in your session is by extending the wicket session. For example if you want to store some kind of user object in your session, you would create a getter and setter for it in
your custom wicket session:

Add the property to your custom Session object.

public class MySession extends WebSession {
   public MySession(Request request) {
      super(request);
   }
   private String myAttribute;
   // ... getters and setters
}

and override either of the following methods on your Application class:

@Override
public Session newSession(Request request, Response response) {
   return new MySession(request);
}

retreive the Session by

MySession session = (MySession )WebSession.get();

Custom RequestCycles

Describe how to use custom request cycles.

Listeners

TODO Describe the IRequestListener, and how the ILinkListener and IOnChangeListener interfaces were implemented. This chapter will give users a better, in-depth understanding of Wicket's workings.

See Request targets. It's not about listeners, but has small diagram of listeners classes hierarchy and sequence diagram of how RequestListenerInterface is used.

Localization and Skinning of Applications

Wicket provides a wide range of features that make it possible to easily create web applications for different locales and with different skins (styles). First, lets take a look at the basic localization and skinning features of Wicket. After that we will move on to how you can extend this functionality to take full control over localization.

Locale and Style

Wicket will use the locale of the incoming request along with a Style identifier to load resources. These resources include

  • Markup files
  • localization resource bundles (properties files)
  • form input validators (also properties files)

Locale is set in the Wicket WebRequest object from the http request header locale in the User Agent field. Optionally, a user's locale can be set in their Session object by calling setLocale.

Style enables skinning of the application and is set by calling setStyle on the Wicket Session object. Session.get() returns the Session object associated with the current request.

Additionally, classes derived from Component provide a getVariation method which the developer can override on a per component basis to further discriminate resources to load.

Markup Files

Describe the loading of markup files by locale and style.

The ResourceStreamLoader determines what markup file to load for a component based on the variant, style, and locale values set for that component.

The Pub example in wicket-examples demonstrates how html markup is chosen based on locale. There is a default Home.html, Home_de_DE.html for users with German locale set, Home_zh_CN.html for Mandarin, etc.

These pages can be skinned by setting the Style attribute in the user's session. You could have three different styles for home: stone, wood, and brick. If the style you set does not have a corresponding markup file, then the localized markup is returned. If there is no markup for the locale, the default markup is returned. You can skin the Home page for a German user by providing Home_stone_de_DE.html, Home_wood_de_DE.html, and Home_brick_de_DE.html.

There is also a variation attribute that is additive to the style attribute. If no style attribute has been set, then variation takes the place of style in the resource name. If style is set, variation + "_" is prepended to the style name, which is then returned by getStyle().

If you have a panel inside Home called kitchen, you can override getVariation in the kitchen panel to return "granite" or "corriander". For a German locale, you could provide the following markups for the granite variation: Kitchen_granite_de_DE.html, Kitchen_granite_stone_de_DE.html, Kitchen_granite_wood_de_DE.html, Kitchen_granite_brick_de_DE.html. The possibilities are exponential.

The javadoc for the Wicket Session class describes the algorithm for resolving markup resources to load:

 1. [sourcePath]/name[style][locale].[extension]
 2. [sourcePath]/name[locale].[extension]
 3. [sourcePath]/name[style].[extension]
 4. [sourcePath]/name.[extension]
 5. [classPath]/name[style][locale].[extension]
 6. [classPath]/name[locale].[extension]
 7. [classPath]/name[style].[extension]
 8. [classPath]/name.[extension]

String Resources

Wicket can also manage message localization with ResourceBundle properties files.

The Localizer is a utility class that encapsulates all of the localization related functionality in Wicket so it can be accessed by all areas of the framework in a consistent way. All classes derived from Component have a getLocalizer() method which retrieves the shared Localizer from the Application. The Localizer maintains a static cache of messages already retrieved by a key generated from the class, locale, style and messageKey for the message. Message bundles are looked up using classes derived from AbstractStringResourceLoader. The Localizer fetches a list of AbstractStringResourceLoader from the Application object. By default, the Application provides a ComponentStringResourceLoader and a ClassStringResourceLoader.

In the default setup, the ComponentStringResourceLoader is first asked to locate the message bundle. If it can't find one (using the algorithm described below), the application's ClassStringResourceLoader is consulted next; this attempts to find a single resource bundle that has the same name and location as the application

The algorithm employed by ComponentStringResourceLoader to resolve message requests is described in the javadoc for the ComponentStringResourceLoader:

The component based string resource loader attempts to find the resource from a bundle that corresponds to the supplied component object or one of its parent containers. Generally the component will be an instance of Page, but it may also be an instance of any reusable component that is packaged along with its own resource files. If the component is not an instance of Page then it must be a component that has already been added to a page.

The search order for resources is built around the containers that hold the component (if it is not a page). Consider a Page that contains a Panel that contains a Label. If we pass the Label as the component then resource loading will first look for the resource against the page, then against the panel and finally against the label.

An exception will be thrown if you are using Localizer on a component that has not already been added to a page. Unless you are sure your component has already been attached to the Page hierarchy, use StringResourceModel, which will defer message bundle lookup until after the component has been attached to a page (during the render process).

The naming convention for resource lookup is the similar to the convention to markup files. The resource loader is style and locale aware. For example, you have a page Home with a panel Kitchen that has a panel Sink, and Sink has labels LeftFawcett (key fawcett.left) and RightFawcett (key fawcett.right) that need to be localized. Your locale is German and your style is "standard". The ComponentStringResourceLoader will first look for fawcett.left and fawcett.right in Home_standard_de_DE.properties, then the same extension for Kitchen, and then Sink. If the key is not found, it will continue following the same steps detailed above for the markup files, but for each step trying it for Home, then Kitchen, then Sink

Using getLocalizer()

You can always get localizer inside a component by calling getLocalizer(). The localizer provides various getString methods, two of the most important ones are:

  1. getString(key, component)
  2. getString(key, component, model)

You can use first one if your property does not contain any strings which should be substituted. The second one is used in case if your property contains values which should be replaces, such as

weather.message=Weather station reports that the temperature is ${currentTemperature} ${units}

In this case you can either provide an object which contains getters for currentTemperature and units as model or you can provide a MapModel which maps currentTemprature and units to desired replacements. For example:

Map<String,String> map = new HashMap<String, String>();
map.put("currentTemperature", "12");
map.put("units", "C");
String string = getLocalizer().getString("test", this,new MapModel<String, String>(map));
// string = "Weather station reports that the temperature is 12 C"

Using StringResourceModel

The Localizer contains powerful string substitution methods for manipulating message values. Examples are provided in the javadoc for StringResourceModel, such this:

/**
 * In this example the found resource string contains a property expression that is
 * substituted via the model.
 */
public MyPage extends WebPage
{
   public MyPage(final PageParameters parameters)
   {
       WeatherStation ws = new WeatherStation();
       add(new Label("weatherMessage",
                     new StringResourceModel("weather.message",
                                             this, new Model(ws)));
   }
}

Where the resource bundle contains the entry

weather.message=Weather station reports that the temperature is ${currentTemperature} ${units}

For Localizing components, you mostly will want to rely on StringResourceModel. As mentioned before, you cannot call getLocalizer on a component before it has been attached to the framework (added to a hierarchy that has a Page). StringResourceModel uses Localizer inside it, but does not get rendered until after the component heirarchy has been established, so no exceptions. The StringResourceModel offers all the features of Localizer, plus the application of substitution parameters by java.text.MessageFormat. Again, there is an example demonstrating this in the StringResourceModel javadocs:

public MyPage extends WebPage
{
   public MyPage(final PageParameters parameters)
   {
       WeatherStation ws = new WeatherStation();
       Model model = new Model(ws);
       add(new Label("weatherMessage",
                 new StringResourceModel(
                     "weather.detail", this, model,
                     new Object[]
                     {
                         new Date(),
                         new PropertyModel(model, "currentStatus"),
                         new PropertyModel(model, "currentTemperature"),
                         new PropertyModel(model, "units")
                     }));
   }
}

And where the resource bundle entry is:

weather.detail=The report for {0,date}, shows the temparature as {2,number,###.##} {3} \
                       and the weather to be {1}

FormValidators derived from AbstractValidator or StringValidator also exploit Wicket's localization capabilities. Inside, they delegate localization to the Localizer. There must be a resource bundle properties file in the Localizer search path that has a messageKey equivalent to form-name.component-name.validator-class. This allows the validators to be Style and locale aware. Substitution is enabled, but parameterized MessageFormat is not.

Other Resources

Describe the loading of other resources by locale and style.

String Resource Loaders

Describe how to write new string resource loaders.

Scalability and Session Management

Write about the following:

  • How session attributes are managed
  • How to control what end up in the session
  • Future directions

Unit Test

Unit testing with Wicket is done by mocking the application. This means that you do not have a webserver running. This section will be about how to get started using unit testing with Wicket's testing framework.

Simple example

public class AgencyPageTest extends TestCase {

    public void testBasicRender() {
         WicketTester tester=new WicketTester();
         tester.startPage(myPage.class);
         tester.assertRenderedPage(myPage.class);
	}
}

Above example makes sure that your page can render. This in itself is a very powerful test & and should be the minimum of tests on all your pages (in this author's opinion).

Basic Test Case for some labels and elements ...

/**
 * 
 * <p>
 * Note : This is a TestNG Test case, a JUNIT 4.x implementation should be similiar to do.
 * For more on testNG refer to <a href="http://testng.org/doc/">TestNG</a>.
 * Also, note that i have kept the underneath stuff to minimal logic, but more 
 * to show what the framework testing is like ...
 * </p>
 * 
 * Code for Actor Panel below is :<br/>
 * 
 * <code><br/>
 * public ActorPanel(String id) {<br/>
 *		super(id);<br/>
 *
 * // add actor name<br/>
 *	add(new Label("actorId", "" + getActorData().getId()));<br/>
 *	add(new Label("actorName", getActorData().getActorName()));<br/>
 *	add(new Label("actorDepartment", getActorData().getDepartment()));<br/>
 *	add(new DropDownChoice("department", Arrays.<String>asList("HR","IS","FINANCE/ACCOUNTING")));<br/>
 *}<br/>
 * </code>
 * 
 * In HomePage class you do a simple add(new ActorPanel());<br/>
 * 
 * 
 * @author Vyas, Anirudh
 * 
 */
public class HomePageTest {
	
	public WicketTester wicketTester = null;
	
	/**
	 * Basic Setup of our test 
	 */
	@BeforeClass
	public void testSetup(){
		wicketTester  = new WicketTester();  // <-- A utility inside Wicket Framework for testing ...
		wicketTester.startPage(HomePage.class); // <--- You dont have to start a web server to test your stuff because of this ... =)
		
	}
	
	/**
	 * Basic Home Page Test case for our application...
	 */
	@Test
	public void testHomePage(){

		
		// $Id - Test Case 1.0 : ... is Home Page rendered?
		wicketTester.assertRenderedPage(HomePage.class); 
		
		// $Id - Test Case 2.0 : Elements test cases ... ( Starts the Panel ... prepares a DummyPanel first for testing ).
		wicketTester.startPanel(ActorPanel.class);
		System.out.println(wicketTester.getApplication().getHomePage());

		//$Id - Assertion ( Element - Label - actorDepartment ) --> Not Null
		Assert.assertNotNull(wicketTester.getTagByWicketId("actorDepartment").getValue());
		
		//$Id - Assertion ( Element - Label - actorDepartment ) --> Same as "HR" 
		Assert.assertEquals(wicketTester.getTagByWicketId("actorDepartment").getValue(), "HR");

		//$Id - Assertion ( Element - Label - actorName ) --> Not Null
		Assert.assertNotNull(wicketTester.getTagByWicketId("actorName").getValue());
		
		// $Id - Assertion( Element - Label - actorName ) --> Same as "|| OM Gam Ganapataye Namaha ||"
		Assert.assertEquals(wicketTester.getTagByWicketId("actorName").getValue(), "|| OM Gam Ganapataye Namaha ||");

		//$Id - Assertion ( Element - DropdownChoice (select) - department ) --> Not Null 
		Assert.assertNotNull(wicketTester.getTagByWicketId("department").getValue());
		
                //$Id - Assertion ( Element - DropdownChoice (select) - department ) --> Not Null 
	        Assert.assertNotNull(wicketTester.getTagByWicketId("department").getValue());
		DropDownChoice dChoice = (DropDownChoice) wicketTester.getComponentFromLastRenderedPage("panel:department"); // thanks Igor =)

		//$Id - Assertion ( Element - DropdownChoice (select) - department ) --> 3 values 
		Assert.assertEquals(dChoice.getChoices().size(),3); 
		

	}

}

Note that in above test, should you have panel without a default constructor, then you'd have to do the following :

TestPanelSource testPanelSource = new TestPanelSource(){
			
			private static final long serialVersionUID = 1L;

			public Panel getTestPanel(String panelId){

				// prepare the SUT (which is Panel in this case)
				return new AssociateInformationPanel(panelId);
				
			}
		};
		
		tester.startPanel(testPanelSource);

                // $Id - Assertion( Element - label - associateName) --> Assert label Values
		tester.assertLabel("panel:labelValueAssociateName", "LastName,FirstName");

Similiar to above, if you have a page without a default constructor; you would do the same; this time creating an anonymous ITestPageSource implementation.

In above test, as you can make out, first a WicketTester class is instantiated, This would create a DummyWebApplication instance, which will have new SessionStore, Response etc. and a DummyHomePage in it. A call to startPage method on wicket Tester initiates the cycle. First Mock request and response are setup,then a new RequestCycle is created followed by regular request processing (for details see Lifecycle section of documentation).

A note on paths : As in above assertLabel( ) we have to precede the actual label name or component name with container,in this case a panel.

  • No labels