If you ever tried to use the <h:selectManyCheckbox> component, you had a bad time figuring out how to split the checkbox on multiple rows - there’s no simple way to do that.
Let’s talk about a feature of the <f:selectItems> tag: it can receive both SelectItem and SelectItemGroup. This means you can create a group of SelectItem easily. Example: if you have a Role list grouped by categories, the List<SelectItem> can have a list of SelectItemGroup and these can have a list of SelectItem with the roles.
Pretty nice, but the items are rendered with the group orientation: this means JSF will render an horizontal table for the groups with nested horizontal tables for the items. Still ugly!
You can solve this using Tomahawk, but I think it’s too much trouble only to get this simple requirement. I prefer using a pure-JSF solution like a new Renderer. Mojarra’s renderer makes this job very easy:
public class MyRenderer
extends SelectManyCheckboxListRenderer {
// keeps track of current rendering
private boolean inner;
// overriding with synchronized
@Override
public synchronized void encodeEnd(
FacesContext context,
UIComponent component) throws IOException {
super.encodeEnd(context, component);
}
@Override
protected void renderBeginText(
UIComponent component, int border,
boolean alignVertical, FacesContext context,
boolean outerTable) throws IOException {
// are we at the inner table?
this.inner = !outerTable;
super.renderBeginText(component, border,
alignVertical ^ inner, context, outerTable);
}
@Override
protected void renderEndText(
UIComponent component, boolean alignVertical,
FacesContext context) throws IOException {
super.renderEndText(component,
alignVertical ^ inner, context);
// reset after inner table is rendered
this.inner = false;
}
@Override
protected void renderOption(
FacesContext context, UIComponent component,
Converter converter, SelectItem curItem,
Object currentSelections,
Object[] submittedValues,
boolean alignVertical, int itemNumber,
OptionComponentInfo optionInfo)
throws IOException {
super.renderOption(context, component, converter,
curItem, currentSelections, submittedValues,
alignVertical ^ inner, itemNumber, optionInfo);
}
}
The trick is pass “alignVertical” parameter XOR inner state. This will change the alignment for the inner table. If you use “pageDirection” as layout, the inner table will use “lineDirection” and vice-versa. Using the “roles” example, the groups will be aligned horizontally and their roles, vertically.
Now, if you don’t want to use groups, you can still benefit - create pseudo-groups for each N elements. This will give you a matrix-like checkbox list.