One of the minor annoyances new Wicket developers run into is the seemingly inflexible static HTML markup files. Typically for each Java Panel class you would have a corresponding HTML file defining the markup. The markup might be something like this:
<wicket:panel>
<h1>Hello World</h1>
<input type="text" wicket:id="textField1" />
<select wicket:id="dropDown1"></select>
<select wicket:id="dropDown2"></select>
</wicket:panel>
The problem
The above markup is relatively static. It might be just fine for most cases, but imagine your Panel class has lots of conditional logic, or is highly reused throughout your code. Based on certain conditions, it can radically change it’s markup. In that case it would be difficult to generate the exact HTML you want.
Your Panel class must also always create the 3 Wicket components textField1, dropDown1, dropDown2 or else face the dreaded ‘Unable to find component with id ‘xxx‘ exception.
The solution – IMarkupResourceStreamProvider
Fortunately Wicket provides us with a simple and elegant solution. By modifying our Panel class to implement the IMarkupResourceStreamProvider interface, we can remove the HTML markup file and generate it from within the class exactly as we want it! Let’s look at a simple example to demonstrate the possibilities:
public class DynamicMarkupPanel extends Panel implements IMarkupResourceStreamProvider {
@Override
public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) {
if(isSimple()) {
return new StringResourceStream(SIMPLE_HTML);
}
String str = StringUtils.replaceEach(COMPLEX_HTML,
new String[]{"{LCLASS}", "{RCLASS}", "{INNERHTML}", "{LBL}"},
new String[]{lClass, rClass, innerHtml, lbl});
return new StringResourceStream(str);
}
}
Now instead of our DynamicMarkupPanel class looking to find the markup in DynamicMarkupPanel.html, it instead will call getMarkupResourceStream to generate it. In this method you have full flexibility to create the exact HTML content you wish!
The final piece of the puzzle – IMarkupCacheKeyProvider
There is one more thing we need to do in order for this to work efficiently and take advantage of Wicket’s internal markup cache. We don’t want to generate this markup each time we render the page. Because we are using dynamic markup, we need to tell Wicket how to create a cache key.
The cache key will tell Wicket under what conditions it should re-use a cached copy of the markup or create new markup. The way you create the cache key will vary for each Panel depending on it’s requirements. Let’s take a look at a simple example:
public class DynamicMarkupPanel extends Panel implements IMarkupResourceStreamProvider, IMarkupCacheKeyProvider {
@Override
public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) {
//code from above
}
@Override
public String getCacheKey(MarkupContainer container, Class<?> containerClass) {
final String classname = containerClass.isAnonymousClass() ? containerClass.getSuperclass().getSimpleName() : containerClass.getSimpleName();
return classname + '_' + lClass + '_' + rClass + '_' +
(isSimple() ? 1 : 0) + '_';
}
}
We modify DynamicMarkupPanel to implement the IMarkupCacheKeyProvider interface as well. The basic idea is to make sure that the cache key is constructed in a way that includes all the dynamic conditions used by getMarkupResourceStream(). You will notice if you are not doing it correctly because the HTML rendered for your Panel will not change when you expect it to, so double check the cache key!
Conclusion
This technique is very powerful in Wicket and can enable you to create highly reusable and performant components. It’s also a great way to cut down on Wicket page size as you may have Panels that sometimes need to create 10 Components, and sometimes only 1.
However it should be used sparingly when it really makes sense to. Usually you should try to keep your HTML and java code separate.
Have fun! 馃檪