There are a number of situations where we need to use repeat over a large nodeset, but don't want the performance hit of rendering hundreds of items that are not 'in view'.
One solution is to use the number attribute on repeat which tells formsPlayer to only show a specified number of items. This is fine for many situations, but the problem is that when selecting an item in the repeat using setindex, the XForms specification only requires that this item becomes 'visible'--it doesn't say where.
For example, if you have a nodeset of 20 items, and want to allow the user to interact with them in 4 pages of 5, you could do this:
<xf:repeat id="rpt-items" number="5" nodeset="items/item"> ... </xf:repeat>
To move to another 'page' you set the index to whatever value is required:
<xf:trigger>
<xf:label>Next ></xf:label>
<xf:action ev:event="DOMActivate">
<xf:setindex repeat="rpt-items" index="index('rpt-items') + 5" />
</xf:action>
</xf:trigger>
The problem here is that formsPlayer will only do enough to bring the next item into 'frame', but it won't bring an entire new page into view. For example, if the repeat is currently showing rows:
1 2 3 4 5
clicking the 'next' button above will give us the following rows:
2 3 4 5 6
There are other problems with this approach, such as the need to specify the page size in two places, and all in all we need something more powerful, and over which we have more control. A better set-up can be created by using instance data and a bind statement.
Let's first establish the instance data that we want to work with in the repeat:
<xf:model id="mdl-main">
<xf:instance id="inst-data">
<instanceData xmlns="">
<items>
<item>apple</item>
<item>pear</item>
.
.
.
<item>kiwi fruit</item>
</items>
</instanceData>
</xf:instance>
Now we need some more data to allow us to keep track of the position and page size:
<xf:instance id="inst-control">
<control xmlns="">
<position>1</position>
<page-size>5</page-size>
</control>
</xf:instance>
Now we create a bind statement that uses this control data to select a subset of the items in the data instance; the selection begins at position, and contains page-size nodes:
<xf:bind
id="bnd-items"
nodeset="
instance('inst-data')/items/item[
position() >= instance('inst-control')/position
and
position() < instance('inst-control')/position
+ instance('inst-control')/page-size
]
"
/>
This bind will create a nodeset that only contains one 'page' of nodes, selected from the larger list of nodes in items/item. All we need to do now is to allow the position value to be adjusted, which is easily done with two event handlers:
<xf:action ev:event="my-prev-page">
<xf:setvalue ref="instance('inst-control')/position"
value=". - ../page-size" />
<xf:rebuild model="mdl-main" />
</xf:action>
<xf:action ev:event="my-next-page">
<xf:setvalue ref="instance('inst-control')/position"
value=". + ../page-size" />
<xf:rebuild model="mdl-main" />
</xf:action>
</xf:model>
(We also need to prevent position from going out of range--this can be done either with a bind/@calculate or a conditional action in the event handler.)
To make use of this configuration we use the nodeset in a repeat:
<xf:repeat id="rpt-items" bind="bnd-items"> ... <xf:repeat>
and then provide triggers to move forwards and backwards through the 'pages':
<xf:trigger>
<xf:label>< Prev</xf:label>
<xf:action ev:event="DOMActivate">
<xf:dispatch target="mdl-main" name="my-prev-page" />
</xf:action>
</xf:trigger>
<xf:trigger>
<xf:label>Next ></xf:label>
<xf:action ev:event="DOMActivate">
<xf:dispatch target="mdl-main" name="my-next-page" />
</xf:action>
</xf:trigger>