Tuesday, February 12, 2008

More powerful XForms actions thanks to iterations

Rotor
XForms 1.1's small number of actions are surprisingly powerful. By combining them, you can do almost any operation you can think of within an XForms page. Yeah, XForms is Turing complete.

There are however a few use cases that remain difficult to achieve. For example, you can easily repeat an action while a condition is satisfied with the while attribute. However, you cannot yet directly iterate over a sequence of node and perform a group of actions for each iteration. As the XForms Working Group is working on fixing this, we have implemented in Orbeon Forms the exforms:iterate attribute (also available as xxforms:iterate attribute) on XForms action elements. This attribute was first proposed as part of the eXforms extensions.

So how does it work? Consider the following three XForms instances:
<xf:instance id="main-instance">
    <instance/>
</xf:instance>

<xf:instance id="source-instance">
    <instance>
        <title>Don Quixote de la Mancha</title>
        <author>Miguel de Cervantes Saavedra</author>
        <title>Jacques le fataliste et son maître</title>
        <author>Denis Diderot</author>
        <title>Childhood's End</title>
        <author>Arthur C. Clarke</author>
    </instance>
</xf:instance>

<xf:instance id="template-instance">
    <book>
        <title/>
        <author/>
    </book>
</xf:instance>
The goal is to take data from source-instance, and copy it over to main-instance but formatted differently. Here is a way to do it:
<xf:action xxf:iterate="instance('source-instance')/title">
    <xf:insert context="instance('main-instance')"
      nodeset="book" origin="instance('template-instance')"/>
    <xf:setvalue ref="instance('main-instance')/book[last()]/title"
      value="context()"/>
    <xf:setvalue ref="instance('main-instance')/book[last()]/author"
      value="context()/following-sibling::author"/>
</xf:action>
The outermost action iterates over the <title> elements of source-instance. For each of those, a new <book> element, copied from the template stored in template-instance, is inserted into main-instance. Then values from the <title> and <author> elements are copied over to the new structure. The XForms 1.1 context() function, also recently implemented in Orbeon Forms, provides access to each of the iterated nodes. The resulting main-instance is as follows:
<instance>
    <book>
        <title>Don Quixote de la Mancha</title>
        <author>Miguel de Cervantes Saavedra</author>
    </book>
    <book>
        <title>Jacques le fataliste et son maître</title>
        <author>Denis Diderot</author>
    </book>
    <book>
        <title>Childhood's End</title>
        <author>Arthur C. Clarke</author>
    </book>
</instance>
It looks easy this way, doesn't it?