Wednesday, February 11, 2009

A Gmail-like UI to Select Multiple Items

It is common to see, in web applications, Gmail-like UIs, where you show a list of items (emails in the case of Gmail), and where you'd like users to be able to perform actions on multiple items at a time. For this, users need to be able to select items. This can be done by adding a checkbox at the beginning of each line. It is easy enough to list your items in XForms with an <xforms:repeat>, but developers often wonder how to add that checkbox at the beginning of every line. Let's consider a practical case:
  • You have an instance with a list of countries. Each country has a code, which can be used as a key to identify the country.
  • You'd like the selected countries to be stored in a separate instance, as a space separated list of country codes.
  • You don't want to change the instance that contains the list of countries in any way (no adding of an attribute or element "selected" for each country).
  • You want to generate a UI like the one shown along this text.
The trick here is to have:
  • One <xforms:select appearance="full"> control per line.
  • All the <xforms:select appearance="full"> have exactly one item, each with a different value (the country code) and they are all bound to the same node.
Show me the code! The space separated list of country codes will go in the root element of the selected instance:
<xforms:instance id="selected">
    <selected />
</xforms>
And finally the <xforms:repeat> which iterates over the countries and generates the <xforms:select appearance="full"> looks like:
<xforms:repeat nodeset="instance('countries')/country">
    <xxforms:variable name="country" select="."/>
    <xhtml:tr>
        <xhtml:td>
            <xforms:select appearance="full" ref="instance('selected')">
                <xforms:item>
                    <xforms:label/>
                    <xforms:value value="$country/us-code"/>
                </xforms:item>
            </xforms:select>
        </xhtml:td>
        <xhtml:td>
            <xforms:output value="name"/>
        </xhtml:td>
        <xhtml:td>
            <xforms:output value="us-code"/>
        </xhtml:td>
    </xhtml:tr>
</xforms:repeat>
This works because the XForms select is non-destructive: when you uncheck a a checkbox from a specific XForms select, the engine will only remove the value corresponding to the checkbox you unchecked, and will leave other values in the space separated list unchanged. Job done!

7 comments:

  1. And when you want to do something with the selected items, you can use XPath 2.0 constructs:







    --Hank

    ReplyDelete
  2. &lg;xforms:variable name="all" select="for $code in tokenize(instance('selected'),'\s') return $code"/>

    forgot to escape the last post...

    ReplyDelete
  3. And in this case the "for $code in ... return $code" isn't necessary, as "tokenize(instance('selected'),'\s')" is enough, right?

    ReplyDelete
  4. Indeed, yes, tokenize alone is enough to convert the space separated string into a sequence (I was copying from code that interwove two sequences...)

    ReplyDelete
  5. And I'm all wrong! only evaluates nodes, NOT sequences. (Just to explain my error, I copied a piece of code that iterated sequence and nodes, but selected the node, i.e., for $s in tokenize(...), for $e in //xpath return $e , will work because it returns a node, but I thought the repeat was evaluating the sequence).

    ReplyDelete
  6. Hank, I don't quite get your previous message. Are you saying that one of the expression above, which you thought to be working, is in fact not working?

    ReplyDelete
  7. You can make a repeat sequence of the selected items by this:

    <xforms:repeat nodeset="for $s in tokenize(instance('selected'),'\s')
    return xxforms:element('text',$s)">
    ......

    repeat only works with nodes.

    Thanks all for your patience, hope it was worth it...

    ReplyDelete