XForms Technique: Selections

Steven Pemberton, CWI Amsterdam

Version: 2020-08-23.

The Select Control

The XForms select control allows you to select zero or more items from a list.

For instance, you want to go shopping, and you can have a list of items you might want to buy, and you select the ones you actually want to buy:

<instance>
   <data xmlns="">
      <shopping/>
   </data>
</instance>
 ...
<select ref="shopping">
   <item><label>Bread</label><value>Bread</value><item>
   <item><label>Butter</label><value>Butter</value><item>
   <item><label>Milk</label><value>Milk</value><item>
   <item><label>Cheese</label><value>Cheese</value><item>
   <item><label>Bananas</label><value>Bananas</value><item>
</select>
<output ref="shopping" label="The result:"/>

The values of the items that you select get gathered together as a string, and stored in the location. Try it:

Source

If the shopping location initially contains values, the matching items in the select are selected initially:

<instance>
   <data xmlns="">
      <shopping>Butter Bananas</shopping>
   </data>
</instance>

Source

The list of items to be selected from doesn't need to be hard-wired into the select control like this, but can come from data. Since the labels and values are the same in this case, we don't need to duplicate them:

<instance id="items">
   <items xmlns="">
      <produce>Bread</produce>
      <produce>Butter</produce>
      <produce>Milk</produce>
      <produce>Cheese</produce>
      <produce>Bananas</produce>
   </items>
</instance>

Alternatively, you can put the data in a file, and load it from there:

<instance id="items" src="items.xml"/>

Then the select control uses this data in an itemset:

<select ref="shopping" appearance="full">
   <itemset ref="instance('items')/produce">
      <label ref="."/>
      <value ref="."/>
   </itemset>
</select>

The result of course looks and acts exactly the same:

Source

Structure

Now, to be perfectly honest with you, the reason that select collects the result in a single string like this is because that is what HTML did and does. But from a data-structuring point of view, instead of a result like

<shopping>Bread Bananas</shopping>

it can be far more useful to have structured data, like

<shopping>
   <produce>Bread</produce>
   <produce>Bananas</produce>
</shopping>

To achieve this we only have to change one thing: we change

<select ref="shopping" appearance="full">
   <itemset ref="instance('items')/produce">
      <label ref="."/>
      <value ref="."/>
   </itemset>
</select>

to

<select ref="shopping" appearance="full">
   <itemset ref="instance('items')/produce">
      <label ref="."/>
      <copy ref="."/>
   </itemset>
</select>

Then when an item is selected, rather than just the value of the element being saved, the whole element is saved.

This means that we also have to change how the result is output, from:

<output ref="shopping"/>

to:

<repeat ref="shopping/produce">
   <output ref="."/>
</repeat>

Source

Going Shopping

The list of things you potentially want to buy can get very long (we've kept it at 5 things here for brevity).

When you actually go shopping, what you really want is the much shorter list of just the things you have to buy, and even better, rather than just a list, a list that you can check off.

So what we do here is replace the output of the selected produce with another select control that gets its itemset from the list of things to buy. Instead of

<repeat ref="shopping/produce">
   <output ref="."/>
</repeat>

we use another select:

<select ref="bought" appearance="full">
    <itemset ref="../shopping/produce">
       <label ref="."/>
       <copy ref="."/>
    </itemset>
 </select>

(and add another location:)

<instance id="shopping">
   <data xmlns="">
      <shopping/>
      <bought/>
   </data>
</instance>

Source

Or to make it even more obvious what is happening: displaying the things you buy as you buy them:

<repeat ref="bought/produce">
   <output ref="."/>
</repeat>

Source

Self-reference

But now a sneaky trick. Instead of the new select control referencing the bought location, how about if we reference the shopping list itself? Can you see what will happen?

  1. Initally all the items you have to buy will be selected
  2. As you check them off, they will disappear from the list
<select ref="shopping" appearance="full">
    <itemset ref="produce">
       <label ref="."/>
       <copy ref="."/>
    </itemset>
 </select>

Source

You may need a moment to understand what is happening here, but I hope you agree that it's pretty cool!

Final Steps

The final step is to make this into a shopping app.

We split the two cases, one selecting what you need to buy and the other for actually going to buy, into two cases of a switch:

<switch>
   <case id="add">
    ...
   </case>
   <case id="buy">
    ...
   </case>
</switch>

each proceeded by a trigger that toggles the other case:

<trigger appearance="minimal">
   <label>▸ buy</label>
   <toggle case="add" ev:event="DOMActivate"/>
</trigger>

In full:

<switch>
   <case id="add">
      <trigger class="line" appearance="minimal">
         <label>▾ buy</label>
         <toggle case="buy" ev:event="DOMActivate"/>
      </trigger>
      <select ref="shopping" appearance="full">
         <itemset ref="instance('items')/produce">
            <label ref="."/>
            <copy ref="."/>
         </itemset>
      </select>
   </case>
   <case id="buy">
      <trigger class="line" appearance="minimal">
         <label>▸ buy</label>
         <toggle case="add" ev:event="DOMActivate"/>
      </trigger>
      <select ref="shopping" appearance="full">
         <itemset ref="../shopping/produce">
            <label ref="."/>
            <copy ref="."/>
         </itemset>
      </select>
   </case>
</switch>

giving the final result:

Source