Repeat, insert and delete
Basics
A very common requirement of user interfaces consists in repeating visual elements, such as rows in a table or entries in a list. Those repeated sections usually (but not always) have an homogeneous aspect: they all have the same or a very similar structure. For example, multiple table rows will differ only in the particular content they display in their cells. An example of this is an invoice made of lines with each a description, unit price, and quantity.
XForms provides a very powerful mechanism to implement such repeated structures: the <xf:repeat>
action. You use <xf:repeat>
around XHTML elements or XForms controls. For example, to repeat a table row, you write:
This is not enough to be functional code: you need to indicate to the <xf:repeat>
element how many repetitions must be performed. This is done not by supplying a simple count value, but by binding the the element to a node-set with the ref
attribute. Consider the following XForms instance:
Assuming you want to produce one table row per employee, add the following ref
attribute:
This produces automatically three xh:tr
rows. Note that we explicitly use the XForms instance()
function, but you may not have to do so if that instance is already in scope. Then you display in each row the content of the first-name
element for each employee:
This works because for each iteration, the context node for the ref
attribute changes: during the first iteration, the context node is the first employee
element of the XForms instance; during the second iteration, the second employee
element, and so on.
Repeat index
Each <xf:repeat>
element has an associated index, representing the notion of a currently selected repeat iteration. This information can be used in CSS for example to highlight the currently selected iteration. The current index for a given repeat can be obtained with the XPath index()
function.
Each nested repeat keeps its own separate index value.
The index()
function should not be confused wit the position()
function.
NOTE: XForms 1.1 does not explicitly limit in what type of XPath expressions index()
can be used. However, in Orbeon Forms, it is strongly advised at the moment to only use index()
within actions, and to avoid using it in control bindings and binds, as doing so may yield unpredictable results.
Deleting iterations with the delete action
<xf:repeat>
may be used purely for display purposes, but it can also be used for interactively editing repeated data. This includes allowing the user to delete and insert iterations. Two XForms actions are used for this purpose: <xf:delete>
and <xf:insert>
.
<xf:delete>
is provided with a ref
attribute pointing to the collection of nodes to delete. It also has an optional at
attribute, which contains an XPath expression returning the index of the element to delete. See how <xf:delete>
is used in these scenarios:
NOTE: Prior to XForms 1.1, the at
attribute was mandatory. Since XForms 1.1, it is optional, and if omitted the ref
attribute specifies all the nodes to remove.
Inserting iterations with the insert action
See also the Insert a new item into a repeat how-to guide.
<xf:insert>
has a ref
attribute pointing to the collection into which the insertion must take place. If no origin
attribute is specified, <xf:insert>
then considers the last element of that collection (and all its content if any) as a template for the new element to insert: it duplicates it and inserts it into the collection at a position you specify. In this case, the last element of a collection acts as a template for insertions:
The at
attribute contains an XPath expression returning the index of the element before or after which the insertion must be performed. The position
element contains either after
or before
, and specifies whether the insertion is performed before or after the element specified by the at
attribute.
As an extension to XForms 1.1, Orbeon Forms supports inserting an element into a document node:
This is particularly useful in conjunction with the xxf:create-document()
function.
Using a trigger to execute actions
Insertions and deletions are typically performed when the user of the application presses a button, with the effect of adding a new repeated element before or after the currently selected element, or of deleting the currently selected element. You use an xf:trigger
control and the XPath index()
function for that purpose:
or:
Note that we use xf:action
as a container for <xf:insert>
and <xf:delete>
. Since there is only one action to execute, xf:action
is not necessary, but it may increase the legibility of the code. It is also possible to write:
or:
Notice in that case how ev:event="DOMActivate"
has been moved from the enclosing xf:action
to the <xf:insert>
and <xf:delete>
elements.
Nested repeats
It is often desirable to nest repeat sections. Consider the following XForms instance representing a company containing departments, each containing a number of employees:
This document clearly contains two nested sections subject to repetition:
Departments: a node-set containing all the
department
elements can be referred to with the following XPath expression:instance('departments')/department
.Employees: a node-set containing all the
employee
elements can be referred to with the following XPath expression:instance('departments')/department/employees/employee
. However, if the context node of the XPath expression points to a particulardepartment
element, then the following relative XPath expression refers to all theemployee
elements under thatdepartment
element:employees/employee
.
Following the example above, here is how departments and employees can be represented in nested tables with xf:
In the code above, the second <xf:repeat>
's ref
expression is interpreted relatively to the department
element of the parent <xf:repeat>
for each iteration of the parent's repetition. During the first iteration of the parent, the "Research and Development" department is in scope, and employees/employee
refers to the two employees of that department, John and Mary. During the second iteration of the parent, the "Support" department is in scope, and employees/employee
refers to the three employees of that department, Anne, Mark and Sophie.
Iterating over plain values
XForms 1.1 only specifies iterating over instance data nodes (elements or attributes). Orbeon Forms supports iterating over values, for example:
In this case, the context item within the repeat is a number, not a node.
NOTE: This ability is also part of the XForms 2 specification.
Evaluation of initial values upon insert
Orbeon Forms supports the xxf:default
extension attribute on xf:bind
to specify dynamic initial values.
By default (no pun intended), xxf:default
does not apply to the newly inserted nodes. But by setting the xxf:defaults
attribute (note the plural "defaults") on xf:insert
to true
, this behavior can be changed, and any xxf:default
pointing to a newly-inserted node is re-evaluated during the next recalculation.
Consider the following example where the bind points to any <value>
element child of the root element, and sets a dynamic initial value:
The data looks like this after initialization:
Running the following insert adds a new <value>
element after the first one:
Because the action specifies xxf:defaults="true"
, the first value
element is unchanged, but the new <value>
element gets its dynamic initial value set: