If you don’t use Wicket Fragments often, they are great for creating small components that are especially useful inside of lists. The thing that prevents them from being truly reusable in your application is that you have to duplicate the fragment markup in each component.
We can get around this limitation by creating a self-contained fragment that takes care of specifying its own markup. Then we can reuse them throughout our application without duplication.
HtmlFragment
The HtmlFragment class declares an abstract method getHtml() that implementations must provide.
public abstract class HtmlFragment extends Fragment implements IMarkupResourceStreamProvider, IMarkupCacheKeyProvider {
private static final long serialVersionUID = 1L;
public HtmlFragment(String id) {
super(id, "", null);
}
protected abstract String getHtml();
@Override
protected IMarkupSourcingStrategy newMarkupSourcingStrategy() {
return new PanelMarkupSourcingStrategy(false);
}
@Override
public String getCacheKey(MarkupContainer container, Class<?> containerClass) {
return containerClass.isAnonymousClass() ? containerClass.getSuperclass().getSimpleName() : containerClass.getSimpleName();
}
@Override
public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) {
String str = "<wicket:panel>" + getHtml() + "</wicket:panel>";
return new StringResourceStream(str);
}
}
It’s important to remember that if your specific fragment implementation has dynamic/conditional markup creation, you need to override the getCacheKey() method to handle that accordingly.
Let’s look at an example
Suppose you want a reusable fragment that displays the first and last names of a user. You could create a fragment extending HtmlFragment like so:
public class UserNameFragment extends HtmlFragment {
private static final long serialVersionUID = 1L;
public UserNameFragment(String id, IModel<User> userModel) {
super(id);
add(new Label("fname", new PropertyModel<>(userModel, "firstName")));
add(new Label("lname", new PropertyModel<>(userModel, "lastName")));
}
@Override
protected String getHtml() {
return "<p>first name: <span wicket:id=\"fname\"></span></p>" +
"<p>last name: <span wicket:id=\"lname\"></span></p>";
}
}
In this case, our HTML is static, so we don’t need to modify the creation of the cache key.
Using it
Using UserNameFragment inside of a list of users is straightforward:
<span wicket:id="userList">
<span wicket:id="nameFrag"></span>
</span>
public HtmlFragmentTestPage() {
super();
addOrReplace(new ListView<User>("userList", LoadableDetachableModel.of(this::getUsers)) {
@Override
protected void populateItem(ListItem<User> item) {
item.addOrReplace(new UserNameFragment("nameFrag", item.getModel()));
}
});
}
private List<User> getUsers() {
User u1 = new User(1, "John", "James", true);
User u2 = new User(2, "Mike", "Jones", true);
return List.of(u1, u2);
}
See the full working example here!