I had a problem with updating instance structure which contains repeating nodes. I wanted to use <action while=""/>
construction but there was a problem using defined iterator inside this loop. Eventually it always used one value (first one) even though it was incremented. I resolved this problem by using xxforms:evaluate
function thus I have:
xxforms:evaluate(concat('instance(''main'')/item[',xxforms:bind('idx'),']'))
instead of simpler
instance('main')/item[xxforms:bind('idx')]
Is this the only way to iterate across the list of nodes inside an action?
Example:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xxforms="http://orbeon.org/oxf/xml/xforms">
<head>
<title>Test</title>
<xf:model id="model">
<xf:instance id="main" xmlns="">
<main>
<item>
<name />
</item>
<item>
<name />
</item>
<item>
<name />
</item>
</main>
</xf:instance>
<xf:instance id="temp" xmlns="">
<main>
<idx></idx>
<value>inserted node</value>
</main>
</xf:instance>
<xf:bind id="idx" nodeset="instance('temp')/idx" type="xsd:integer" />
</xf:model>
</head>
<body>
<xf:trigger>
<xf:label>Not working as expected</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue bind="idx" value="1" />
<xf:action while="number(xxforms:bind('idx')) le count(instance('main')/item)">
<xf:insert context="instance('main')/item[xxforms:bind('idx')]" nodeset="name" position="after" origin="instance('temp')/value" if="not(exists(value))" />
<xf:setvalue bind="idx" value=". + 1" />
</xf:action>
</xf:action>
</xf:trigger>
<xf:trigger>
<xf:label>Working as expected but too complicated</xf:label>
<xf:action ev:event="DOMActivate">
<xf:setvalue bind="idx" value="1" />
<xf:action while="number(xxforms:bind('idx')) le count(instance('main')/item)">
<xf:insert context="xxforms:evaluate(concat('instance(''main'')/item[',xxforms:bind('idx'),']'))" nodeset="name" position="after" origin="instance('temp')/value" if="not(exists(value))" />
<xf:setvalue bind="idx" value=". + 1" />
</xf:action>
</xf:action>
</xf:trigger>
<widget:xforms-instance-inspector id="orbeon-xforms-inspector" xmlns:widget="http://orbeon.org/oxf/xml/widget" />
</body>
</html>
So I get as a result (first trigger):
<main>
<item>
<name/>
<value>inserted node</value>
</item>
<item>
<name/>
</item>
<item>
<name/>
</item>
</main>
but expected (second trigger):
<main>
<item>
<name/>
<value>inserted node</value>
</item>
<item>
<name/>
<value>inserted node</value>
</item>
<item>
<name/>
<value>inserted node</value>
</item>
</main>
Here is a version that works:
<xf:action ev:event="DOMActivate">
<xf:setvalue bind="idx" value="1"/>
<xf:action while="xs:integer(xxforms:bind('idx')) le count(instance('main')/item)">
<xf:insert context="instance('main')/item[xs:integer(xxforms:bind('idx'))]" nodeset="name" position="after"
origin="instance('temp')/value" if="not(exists(value))"/>
<xf:setvalue bind="idx" value=". + 1"/>
</xf:action>
</xf:action>
The issue is that xxforms:bind('idx')
returns an untyped value, even through you specified xsd:integer
. XForms currently doesn't specify that type annotations on binds must cause a typed value to be provided (see these notes on type annotation). This means that in this case, the predicate value is not a number (XPath has both boolean and numeric predicates, and this is often a source of confusion). In order to make it a numeric predicate, converting to a number is needed.
Here I use xs:integer
as number
is kind of an XPath 1 legacy function, and it returns an xs:double
while the count()
function returns an xs:integer
).
There is much simpler solution with xxforms:iterate
:
<xf:action ev:event="DOMActivate" xxforms:iterate="item">
<xf:insert if="not(exists(value))"
context="."
nodeset="name"
origin="instance('temp')/value"/>
</xf:action>
iterate
is currently an extension, but XForms 2 will add it.