Search code examples
orbeonxforms

Do I have to use "xxforms:evaluate" in action-loops to use iterator?


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>

Solution

  • 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.