16 Wicket advanced topics - Reference Documentation
Authors: Andrea Del Bene, Carsten Hufe, Christian Kroemer, Daniel Bartl
Version: 1.0.0.BUILD-SNAPSHOT
Table of Contents
16 Wicket advanced topics
In this chapter we will learn some advanced topics which have not been covered yet in the previous chapters but which are nonetheless essential to make the most of Wicket and to build sophisticated web applications.16.1 Enriching components with behaviors
With class org.apache.wicket.behavior.Behavior Wicket provides a very flexible mechanism to share common features across different components and to enrich existing components with further functionalities. As the class name suggests, Behavior adds a generic behavior to a component modifying its markup and/or contributing to the header section of the page (Behavior implements the interface IHeaderContributor).One or more behaviors can be added to a component with Component's method add(Behavior...), while to remove a behavior we must use method remove(Behavior).Here is a partial list of methods defined inside class Behavior along with a brief description of what they do:- beforeRender(Component component): called when a component is about to be rendered.
- afterRender(Component component): called after a component has been rendered.
- onComponentTag(Component component, ComponentTag tag): called when component tag is being rendered.
- getStatelessHint(Component component): returns if a behavior is stateless or not.
- bind(Component component): called after a behavior has been added to a component.
- unbind(Component component): called when a behavior has been removed from a component.
- detach(Component component): overriding this method a behavior can detach its state before being serialized.
- isEnabled(Component component): tells if the current behavior is enabled for a given component. When a behavior is disabled it will be simply ignored and not executed.
- isTemporary(Component component): tells component if the current behavior is temporary. A temporary behavior is discarded at the end of the current request (i.e it's executed only once).
- onConfigure(Component component): called right after the owner component has been configured.
- onRemove(Component component): called when the owner component has been removed from its container.
- renderHead(Component component, IHeaderResponse response): overriding this method behaviors can render resources to the header section of the page.
public class RedAsteriskBehavior extends Behavior { @Override public void beforeRender(Component component) { Response response = component.getResponse(); StringBuffer asterisktHtml = new StringBuffer(200); if(componet instanceof FormComponent && ((FormComponent)component).isRequired()){ asteriskHtml.append(" <b style="color:red;font-size:medium">*</b>"); } response.write(asteriskHtml); } }
16.2 Generating callback URLs with IRequestListener
With Wicket it's quite easy to build a callback URL that executes a specific method on server side. This method must be defined in a functional interface1 that inherits from built-in org.apache.wicket. IRequestListener and it must be a void method with no parameters in input:public interface IMyListener extends IRequestListener { /** * Called when the relative callback URL is requested. */ void myCallbackMethod(); }
public interface IMyListener extends IRequestListener { /**RequestListenerInterface instance*/ public static final RequestListenerInterface INTERFACE = new RequestListenerInterface(IMyListener.class); /** * Called when the relative callback URL is requested. */ void myCallbackMethod(); }
@Override public void onRequest() { Request request = RequestCycle.get().getRequest(); IRequestParameters requestParameters = request.getRequestParameters(); StringValue choiceId = requestParameters.getParameterValue("choiceId"); //boundComponent is the component that the behavior it is bound to. boundComponent.setDefaultModelObject(convertChoiceIdToChoice(choiceId.toString())); }
@Override public void onComponentTag(Component component, ComponentTag tag) { super.onComponentTag(component, tag); CharSequence callBackURL = getCallbackUrl(); String separatorChar = (callBackURL.toString().indexOf('?') > -1 ? "&" : "?"); String finalScript = "var isSelect = $(this).is('select');n" + "var component;n" + "if(isSelect)n" + " component = $(this);n" + "else n" + " component = $(this).find('input:radio:checked');n" + "window.location.href='" + callBackURL + separatorChar + "choiceId=' + " + "component.val()"; tag.put("onchange", finalScript); }
public CharSequence getCallbackUrl(){ if (boundComponent == null){ throw new IllegalArgumentException( "Behavior must be bound to a component to create the URL"); } final RequestListenerInterface rli; rli = IBehaviorListener.INTERFACE; return boundComponent.urlFor(this, rli, new PageParameters()); }
Implementing interface IBehaviorListener makes a behavior stateful because its callback URL is specific for a given instance of component.
Wicket events infrastructure
Starting from version 1.5 Wicket offers an event-based infrastructure for inter-component communication. The infrastructure is based on two simple interfaces (both in package org. apache.wicket.event) : IEventSource and IEventSink.The first interface must be implemented by those entities that want to broadcast en event while the second interface must be implemented by those entities that want to receive a broadcast event.The following entities already implement both these two interfaces (i.e. they can be either sender or receiver): Component, Session, RequestCycle and Application. IEventSource exposes a single method named send which takes in input three parameters:- sink: an implementation of IEventSink that will be the receiver of the event.
- broadcast: a Broadcast enum which defines the broadcast method used to dispatch the event to the sink and to other entities such as sink children, sink containers, session object, application object and the current request cycle. It has four possible values:
Value | Description |
---|---|
BREADTH | The event is sent first to the specified sink and then to all its children components following a breadth-first order. |
DEPTH | The event is sent to the specified sink only after it has been dispatched to all its children components following a depth-first order. |
BUBBLE | The event is sent first to the specified sink and then to its parent containers. |
EXACT | The event is sent only to the specified sink. |
- payload: a generic object representing the data sent with the event.
@Override public void onEvent(IEvent event) { //if the type of payload is MyPayloadClass perform some actions if(event.getPayload() instanceof MyPayloadClass) { //execute some business code. }else{ //other business code } }
16.3 Initializers
Some components or resources may need to be configured before being used in our applications. While so far we used Application's init method to initialize these kinds of entities, Wicket offers a more flexible and modular way to configure our classes.During application's bootstrap Wicket searches for any properties file named wicket.properties placed in one of the classpath roots visible to the application. When one of these files is found, the initializer defined inside it will be executed. An initializer is an implementation of interface org.apache.wicket.IInitializer and is defined inside wicket.properties with a line like this:initializer=org.wicketTutorial.MyInitializer
public class MyInitializer implements IInitializer{ public void init(Application application) { //initialization code } public void destroy(Application application) { //code to execute when application is terminated } }
public class MainInitializer implements IInitializer{ public void init(Application application) { new AnotherInitializer().init(application); new YetAnotherInitializer().init(application); //… } //destroy… }
16.4 Using JMX with Wicket
JMX (Java Management Extensions) is the standard technology adopted in Java for managing and monitoring running applications or Java Virtual Machines. Wicket offers support for JMX through module wicket-jmx. In this paragraph we will see how we can connect to a Wicket application using JMX. In our example we will use JConsole as JMX client. This program is bundled with Java SE since version 5 and we can run it typing jconsole in our OS shell.Once JConsole has started it will ask us to establish a new connection to a Java process, choosing between a local process or a remote one. In the following picture we have selected the process corresponding to the local instance of Jetty server we used to run one of our example projects:After we have established a JMX connection, JConsole will show us the following set of tabs:JMX exposes application-specific informations using special objects called MBeans (Manageable Beans), hence if we want to control our application we must open the corresponding tab. The MBeans containing the application's informations is named org.apache.wicket.app.<filter/servlet name>.In our example we have used wicket.test as filter name for our application:As we can see in the picture above, every MBean exposes a node containing its attributes and another node showing the possible operations that can be performed on the object. In the case of a Wicket application the available operations are clearMarkupCache and clearLocalizerCache:With these two operations we can force Wicket to clear the internal caches used to load components markup and resource bundles. This can be particularly useful if we have our application running in DEPLOYMENT mode and we want to publish minor fixes for markup or bundle files (like spelling or typo corrections) without restarting the entire application. Without cleaning these two caches Wicket would continue to use cached values ignoring any change made to markup or bundle files.Some of the exposed properties are editable, hence we can tune their values while the application is running. For example if we look at the properties of ApplicationSettings we can set the maximum size allowed for an upload modifying the attribute DefaultMaximumUploadSize:16.5 Generating HTML markup from code
So far, as markup source for our pages/panels we have used a static markup file, no matter if it was inherited or directly associated to the component. Now we want to investigate a more complex use case where we want to dynamical generate the markup directly inside component code.To become a markup producer, a component must simply implement interface org.apache. wicket.markup.IMarkupResourceStreamProvider. The only method defined in this interface is getMarkupResourceStream(MarkupContainer, Class<?>) which returns an utility interface called IResourceStream representing the actual markup.In the following example we have a custom panel without a related markup file that generates a simple <div> tag as markup:public class AutoMarkupGenPanel extends Panel implements IMarkupResourceStreamProvider { public AutoMarkupGenPanel(String id, IModel<?> model) { super(id, model); } @Override public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) { String markup = "<div>Panel markup</div>"; StringResourceStream resourceStream = new StringResourceStream(markup); return resourceStream; } }
Avoiding markup caching
As we have seen in the previous paragraph, Wicket uses an internal cache for components markup. This can be a problem if our component dynamical generates its markup when it is rendered because once the markup has been cached, Wicket will always use the cached version for the specific component. To overwrite this default caching policy, a component can implement interface IMarkupCacheKeyProvider.This interface defines method getCacheKey(MarkupContainer, Class<?>) which returns a string value representing the key used by Wicket to retrieve the markup of the component from the cache. If this value is null the markup will not be cached, allowing the component to display the last generated markup each time it is rendered:public class NoCacheMarkupPanel extends Panel implements IMarkupCacheKeyProvider { public NoCacheMarkupPanel(String id, IModel<?> model) { super(id, model); } /** * Generate a dynamic HTML markup that changes every time * the component is rendered */ @Override public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) { String markup = "<div>Panel with current nanotime: " + System.nanoTime() + "</div>"; StringResourceStream resourceStream = new StringResourceStream(markup); return resourceStream; } /** * Avoid markup caching for this component */ @Override public String getCacheKey(MarkupContainer arg0, Class<?> arg1) { return null; } }