Selections

Allowing users to select from a set of options is a common requirement in user interfaces.

To support selections, several aspects have to be taken into account:

List Models

Calyxo Forms introduces the concept of list models. A list model defines an ordered set of options, each option consisting of

  • a key – a unique, non-empty string, which is taken as HTTP parameter value
  • a value – a unique, non-null object, which is taken as form property value
  • labels – localized strings to be displayed for this option

List models implement the de.odysseus.calyxo.forms.view.ListModel interface, which defines methods to map keys to values and vice versa, to iterate over option values in some particular order, to access labels, and so on.

Implementations

Though the ListModel interface may be implemented from scratch, the de.odysseus.calyxo.forms.view.DefaultListModel class is provided to serve as a base class for custom list model implementations. The default list model provides the following additional method to add an option for the specified key and value:

addOption(String key, Object value)

The DefaultListModel doesn't doesn't know how to lookup labels, so it simply uses list model keys as labels. Therefore, subclasses are probably left with the implemention of the following method:

String getLabel(Object value, Locale locale)

The de.odysseus.calyxo.forms.view.I18nListModel subclass implements that method by consulting an i18n support instance, taking its bundle property as a resource bundle name and using option keys as resource keys.

Configuration

A simple i18n-aware list model using the module's i18n support instance and bundle name "choices" could be created and saved to module scope like this:

ListModel model = new I18nListModel(moduleContext, "choices");
model.addOption("one", new Integer(1));
model.addOption("two", new Integer(2));
model.addOption("three", new Integer(3));
moduleContext.setAttribute("model123", model);

Alternatively, the same could be expressed in a configuration file:

<functions prefix="type" class="de.odysseus.calyxo.base.misc.TypeFunctions"/>

<set var="model123" scope="module">
  <object class="de.odysseus.calyxo.forms.view.I18nListModel">
    <base:constructor>
      <base:arg value="${moduleContext}"/>
      <base:arg value="choices"/>
    </base:constructor>
    <method name="addOption">
      <arg value="one"/>
      <arg value="${type:toInteger(1)}"/>
    </method>
    <method name="addOption">
      <arg value="two"/>
      <arg value="${type:toInteger(2)}"/>
    </method>
    <method name="addOption">
      <arg value="three"/>
      <arg value="${type:toInteger(3)}"/>
    </method>
  </object>
</set>

Note, that - if inside a Calyxo Forms configuration file - the above elements would have to be used with namespace prefix base.

Validation

Validating against a list model is done by mapping option keys to option values. The de.odysseus.calyxo.forms.selection.ListModelConverter is provided to do this job. The converter succeeds if the input string is a valid list model key. Additionally, a null or empty input is accepted and mapped to value null.

A single selection could be validated like this:

<input name="choice">
  <field property="choice">
    <convert name="list">
      <property name="attribute" value="model123"/>
      <property name="scope" value="module"/>
      <message>
        <arg name="field" value="choice"/>
      </message>
    </convert>
  </field>
</input>

A multiple selection has to be mapped to an array:

<input name="choices" array="true">
  <field property="choices">
    <convert name="list">
      <property name="attribute" value="model123"/>
      <property name="scope" value="module"/>
      <message>
        <arg name="field" value="choices"/>
      </message>
    </convert>
  </field>
</input>

JSP Tags

The Calyxo Forms tag library contains several tags, that support list models:

Radio/Checkbox Groups

The <group> tag may contain <checkboxitem> or <radioitem> tags to render checkbox- or radio groups. Nested group item elements inherit the <group>'s name, class and style attributes.

Consider a <group name="foo">...</group> tag. The <group> tag itself doesn't rendered to HTML, however

  • nested <checkboxitem value="..."> tags are rendered to <input type="checkbox" name="foo" value="..."> HTML tags, resulting in a multiple (array) parameter foo.
  • nested <radioitem value="..."> tags are rendered to <input type="radio" name="foo" value="..."> HTML tags, resulting in a single parameter foo.

The forms.list accessor can be used to iterate over list model values to render checkbox and radio groups:

<c:set var="model123" value="${calyxo.base.module.attribute['model123']}"/>
<c:set var="list" value="${calyxo.forms.list[model123]}"/>

<!-- render single selection as radio group -->
<forms:group name="choice" listModel="${model123}">
  <c:forEach items="${list.values['label']}" var="value">
    <forms:radioitem value="${list.key[value]}"/>
    <jsp:text> </jsp:text>${list.label[value]}<br/>
  </c:forEach>
</forms:group>

<!-- render multiple selection as checkbox group -->
<forms:group name="choices" listModel="${model123}">
  <c:forEach items="${list.values['label']}" var="value">
    <forms:checkboxitem value="${list.key[value]}"/>
    <jsp:text> </jsp:text>${list.label[value]}<br/>
  </c:forEach>
</forms:group>

When specifying a list model to the group using the listModel attribute (as above), then the value attributes of nested <checkboxitem> and <radioitem> tags are verified to be valid list model keys.

Selection Lists

The <select> tag may contain a <listoptions> tag to render all options from a list model at once. The list model has to be specified via the listModel attribute.

<c:set var="model123" value="${calyxo.base.module.attribute['model123']}"/>

<!-- render single selection -->
<forms:select name="choice" listModel="${model123}">
  <forms:listoptions sort="label"/>
</forms:select>

<!-- render multiple selection (use array input) -->
<forms:select name="choices" listModel="${model123}" size="3">
  <forms:listoptions sort="label"/>
</forms:select>

The <listoption> tag can be used to render a single list model option. The value attribute is used to specify a list option's key (not its value!). Thus, the previous example could be rewritten as

<c:set var="model123" value="${calyxo.base.module.attribute['model123']}"/>
<c:set var="list" value="${calyxo.forms.list[model123]}"/>

<!-- render single selection -->
<forms:select name="choice" listModel="${model123}">
  <c:forEach items="${list.values['label']}" var="value">
    <forms:listoption value="${list.key[value]}"/>
  </c:forEach>
</forms:select>

<!-- render multiple selection (use array input) -->
<forms:select name="choices" listModel="${model123}" size="3">
  <c:forEach items="${list.values['label']}" var="value">
    <forms:listoption value="${list.key[value]}"/>
  </c:forEach>
</forms:select>

However, to render all list model options, using the <listoptions> tag is recommended for simplicity and performance reasons.

By omitting the value attribute, the <listoption> tag can be used to render a default option:

<c:set var="model123" value="${calyxo.base.module.attribute['model123']}"/>

<forms:select name="choice" listModel="${model123}">
  <forms:listoption>Choose...</forms:listoption>
  <forms:listoptions sort="label"/>
</forms:select>

The default option will be selected in the list, if the group model selection is empty.

In the previous example, the Choose... option will be shown in the list, even if another option is selected. To avoid this, we need to check, if the group model has an empty selection:

<c:set var="model123" value="${calyxo.base.module.attribute['model123']}"/>

<forms:select name="choice" listModel="${model123}" varGroupModel="group">
  <c:if test="${group.selectedCount == 0}">
    <forms:listoption>Choose...</forms:listoption>
  </c:if>
  <forms:listoptions sort="label"/>
</forms:select>