Apache Wicket: Group a set of CheckBox inputs using an enum

UPDATE: a few days after posting this, I realized that there is a CheckGroup wicket class that will group checkboxes together. I’ll write a new article to show how to integrate an enumerated checkbox group with the CheckGroup class.

When presenting a list of radio buttons to a user in a form, it’s a given that they must be grouped together. Wicket provides a RadioGroup form component to achieve this. However, sometimes we want to group together a set of checkboxes because they are related to each other. I’ve solved this problem by defining a CheckBoxEnumGroup class that groups a set of checkboxes. An enum is used to define the unique identifiers (keys) for each checkbox.

In addition to creating a checkbox group, I wanted to encapsulate the selected checkboxes in a Java Set, instead of each checkbox requiring its own boolean variable in the model attached to the form. For example, if checkboxes 1 and 3 have been checked, then the Set would contain the keys for those two checkboxes and nothing else. This makes my model object cleaner, because I just need one variable for the group, instead of a separate variable for every checkbox. It also makes my code more scalable because I can add, rename, and remove checkboxes simply by changing my enum and the HTML template without touching the model.

Let’s start with an example. We’ll create a checkbox group for a form with three types of squash: pumpkin, zucchini, and butternut. First, we need an enum, as follows.

public enum Squash {
    PUMPKIN,
    ZUCCHINI,
    BUTTERNUT
}

The intention is to wrap the checkboxes within an element that groups them together. This can be achieved in the HTML template by enclosing the checkboxes within a span tag. We’ll name the group ‘squash’ and each checkbox in the group as ‘squash_’ concatenated with the enum value, as follows.

<span wicket:id="squash">

    <input type="checkbox" wicket:id="squash_PUMPKIN"/>Pumpkin
    <input type="checkbox" wicket:id="squash_ZUCCHINI"/> Zucchini
    <input type="checkbox" wicket:id="squash_BUTTERNUT"/> Butternut
</span>

Before I go into details on what code must change and how it’s done, I’ll show you what the end result will look like.

form.add(
    new CheckBoxEnumGroup<Squash>("squash", Squash.class));

Pretty cool. An entire group of checkboxes can be defined using one line of Java code and an enum class, and the selected values are stored in a single Set in the model object.

Now, let’s see how this is done. First, we’ll need to extend Wicket’s CheckBox into a CheckBoxEnum class and override a few methods. The first problem is that CheckBox will look for a variable in the model with the same name as the id of the checkbox component. In the above example, we would need to define three booleans named squash_PUMPKIN, squash_ZUCCHINI, and squash_BUTTERNUT. Instead, we want one single variable defined in the model object:

Set<Squash> squash = new HashSet<Squash>();

To achieve this, we must redefine the behaviour that obtains the currently selected value from the model and that sets the value in the model when it changes.

Let’s examine the changes required to obtain the selected value from the model. First, the checkbox instance must have knowledge of its enum value by defining a new private variable called enumValue and setting it in the constructor. Second, the getModelValue() method must be overridden so that instead of trying to pull the value from the checkbox’s model, it uses the model for the group. T is a generic type for the enum.

public CheckBoxEnum(String id, T enumValue) {
	super(id);
	this.enumValue = enumValue;
}

@Override
protected String getModelValue() {
    final IModel m = getParent().getDefaultModel();
    final Set<T> s = (Set<T>)m.getObject();
    return Boolean.toString(s.contains(enumValue));
}

Now that querying the model is complete, we need to prevent Wicket from trying to set a boolean squash_XX and instead update the Set. This is done by overriding the updateModel() method to perform a no-op and delegate the responsibility to the parent CheckBoxEnumGroup to update the Set.

@Override
public void updateModel() {
	// This is overriden to perform a no-op because the parent now takes care of updating the model via the
	// CheckBoxEnumGroup.convertValue method. If the super method is called, then wicket will expect a property
	// to exist in the model matching the id assigned to this instance.
}

That’s it for the CheckBoxEnum class. The second class is named CheckBoxEnumGroup and will have each CheckBoxEnum as its child. I’ve modelled this class after RadioGroup, as both classes share some common behaviours. Some of the methods are relatively straightforward and identical to RadioGroup, so I won’t discuss them further. However, I’ve defined convertValue(String[]) differently. This is because RadioGroup is looking for zero or one selected value and returns the selected value (or null). My definition returns a Set containing the checked values. I’ve added comments to the code below to describe the logic.

@SuppressWarnings("unchecked")
@Override
protected Set convertValue(String[] input) throws ConversionException
{
	final Set selectedValues = new HashSet();
	// iterate through the checkboxes belonging to this group and add all the checked ones to the selectedValues set
	visitChildren(
		CheckBoxEnum.class,
		new Component.IVisitor<CheckBoxEnum>()
		{
			public Object component(CheckBoxEnum cb)
			{
				final String v = cb.getValue();
				// "on" if checked, null if not checked
				if ("on".equals(v))
					selectedValues.add(cb.getEnumValue());
				return CONTINUE_TRAVERSAL;
			}
		});

	// return the selected values, which will be assigned to this group's model by wicket
	return selectedValues;
}

The final method for discussion adds the checkboxes as children to the group and is called from the constructors in CheckBoxEnumGroup. This iterates through each value in the enumeration and creates a CheckBoxEnum instance as a child. Since we don’t know what the type of T is until runtime, it must be passed as a parameter.

private void addCheckBoxes(final String id, final Class<T> enumType) {
	for (T e: enumType.getEnumConstants()) {
		add(new CheckBoxEnum<T>(id + "_" + e, e));
	}
}

6 comments

  1. AC says:

    I didn’t go through the entire thing because I’m about to head out for the evening, but to further the DRY (Don’t Repeat Yourself) principal, wouldn’t you want to entirely encapsulate the definition of your Enums (including the elements themselves, the ordering of them and the ‘readable’ representation of them in a single place? In your example you repeat the definition of your enum elements in the HTML, while you could instead put the readable representation of your Enums by (for example) overriding toString() or perhaps in a manner that supports I8N. Currently if your Enum definition changes you need to update your HTML.

    • Dan says:

      That’s really good feedback, thanks. Regarding repetition in the HTML template, that can be removed by modifying the addCheckBoxes method to instantiate a ListView and passing enumType.getEnumConstants() to its constructor. It’s a bit more involved than that because the labels for the checkboxes will need to be defined in the ListView.populateItem(ListItem) method. The project I’m working on that brought this problem on requires complete i18n, and what I’ve written above is simplified from the actual code I based this post on. I’ll follow up on this post in the future with an enhancement showing how to internationalize it using a ListView and resource file. The HTML would be reduced to a single span for the checkbox group and an input element for the ListView to repeat.

  2. Victor says:

    Hello, Dan! Could you show me a full code of these two files? I tried to implement its myself, but I’m getting failed )
    vrebesbyrd@gmail.com

    • Dan says:

      Hi Victor, my intention is to make a library with this code and other useful code that might be of value to others. Stay tuned for when I release this.

      • Victor says:

        Ok, I have figured out how this code works. One question – where did you find the “on” word? )

        • Dan says:

          I ran it through the debugger. I do recommend you use the CheckGroup class provided by Wicket though, instead of my CheckBoxEnumGroup.