Projects‎ > ‎

XForms - Repeat Events

Rationale

In XForms 1.1, there is the concept of repeat objects which represent runtime repeat iterations. XForms1.1 states:

"A repeat object is an implicitly generated group element that contains the set of run-time objects generated to represent the repeat template content for a single repeat item of the repeat collection. These run-time objects are form controls, XForms actions and other host language elements that correspond to elements in the repeat template."

Less formally, XForms 1.1 also mentions the repeat container control, which manages the repeat collection.

This distinction has been represented in Orbeon Forms since a long time, as it is a natural one. Also, recently (2011-12), as part of refactoring of the event handler code, the static representation of controls also includes a representation of both the repeat container and the repeat "object" (AKA implicit group or repeat iteration), as that distinction facilitates the analysis of event handlers.

So much is clear. However, one question is not fully answered, regarding some events that are conceptually are designed to target the repeat container itself: xxforms-nodeset-changed and xxforms-index-changed. 

The purpose of this page is to clarify this.

Use case

The main use case that needed to be supported around 2010-01 looked like this:

<xforms:repeat ref="value" id="my-repeat">
    <xforms:action ev:event="xxforms-nodeset-changed" ev:target="#observer" ..."/>
</xforms:repeat>

This caused a problem:
  • xxforms-nodeset-changed is conceptually associated with the repeat container, not a repeat iteration
  • but the event handler is associated with an iteration
The solution chosen was to say that:
  • xxforms-nodeset-changed is still associated with the container
  • but events dispatched to the container are instead dispatched to the "current" (indexed) repeat iteration
This allows the scenario above to work.

In order to determine whether anything has changed with the repeat, you can write this:

<xforms:action ev:event="xforms-enabled xforms-disabled xxforms-index-changed xxforms-nodeset-changed" ev:target="#observer">

This works because:
  • xxforms-nodeset-changed is not dispatched when going from no iteration to some iteration, or the other way around
  • xforms-enabled/disabled are dispatched to the container but not to individual iterations
This is ok but introduces a special case, namely:
  • that the container can kind of receive events, but handlers are not really attached to that container
It would be good to get rid of this special case, namely:
  • event are either dispatched to the container, or to iterations
  • in either case, event handler attachment does not follow special rules
  • also, iterations should receive xforms-enabled/disabled, if they are in fact behaving as implicit groups

Option A: observing container

With this option, the repeat is transformed to:

<xforms:repeat ref="value" id="my-repeat">
   <xforms:group ref=".">
       <xforms:action ev:event="my-event"/>
   </xforms:group>
   ...
</xforms:repeat>

So what's wrong with dispatching xxforms-nodeset-changed to the container? Not much in itself, but there are side issues.

One issue is how to register a handler. It is convenient to register a handler simply by nesting it within the repeat, as shown above.

But we must maintain the rule that handlers within repeats are attached to iterations, not to the repeat container itself. It really makes sense, not only because XForms 1.1 says everything is repeated, but also consider this:

<xforms:repeat ref="value" id="my-repeat">
    <xforms:variable name="foo" value="."/>
    <xforms:setvalue ev:event="my-event" ref="$foo"/>
</xforms:repeat>

This shows that the XPath context of nested handlers must be within an iteration, otherwise this would be inconsistent with variables and controls.

The handler should by default observe the iteration because it is more consistent. If you want to observe the container itself, you could write:

<xforms:repeat ref="value" id="my-repeat">
    <xforms:variable name="foo" value="."/>
    <xforms:setvalue ev:event="my-event" ev:observer="my-repeat" ref="$foo"/>
</xforms:repeat>

This is fine, except that the id of the repeat must be known. It is often convenient to not require the id. Some solution could be found for this as well, for example:

<xforms:repeat ref="value" id="my-repeat">
    <xforms:variable name="foo" value="."/>
    <xforms:setvalue ev:event="my-event" ev:observer="#grandparent" ref="$foo"/>
</xforms:repeat>

Note that when the repeat is not relevant (which is also the case when the repeat has no iteration), the nested handler should not run.

However just before the repeat becomes non-relevant, the handler should run (for events like xforms-disabled, and possibly for xxforms-nodeset-changed).

An action to listen to every repeat change would look like this:

<xforms:action ev:event="xforms-enabled xforms-disabled xxforms-index-changed xxforms-nodeset-changed" ev:observer="#grandparent" ev:target="#observer">

Now there is an important drawback with this solution, which is that the action handler is repeated, therefore the action will run for every iteration.

It could be possible to fix this with a condition, for example:

<xforms:action ev:event="xforms-enabled xforms-disabled xxforms-index-changed xxforms-nodeset-changed" ev:observer="#grandparent" ev:target="#observer" if="position() = xxforms:index()">

This still means however more events and more XPath to execute.

Another way around this would be to place the handler before the repeat and introduce #following to anonymously listen to the following control:

<xforms:action ev:event="xforms-enabled xforms-disabled xxforms-index-changed xxforms-nodeset-changed" ev:observer="#following" ev:target="#observer"/>
<xforms:repeat ref="value" id="my-repeat"/>

This satisfies the following:
  • repeat id is not needed
  • action handler is not repeated
  • you don't get XPath context issues upon getting to no iteration
Cost:
  • implement the new #following pseudo-id
  • change existing code which uses xxforms-nodeset-changed from within iterations
  • update doc & compatibility notes
Further, if xxforms-nodeset-changed is changed to also dispatch when going from no iteration to some iteration and back, then xforms-enabled xforms-disabled are not needed. 

Option B: non-observing container

With this option, the repeat is transformed to:

<xforms:repeat ref="value">
   <xforms:group id="my-repeat" ref=".">
       <xforms:action ev:event="my-event"/>
   </xforms:group>
   ...
</xforms:repeat>

Another option is to make xxforms-index-changed and xxforms-nodeset-changed dispatch always directly to an iteration (the current indexed iteration), never to the repeat container.

This allows keeping handlers within the iteration, but makes sure they run only once, as there is always only one currently indexed iteration.

The repeat container could still receive other events, like xforms-enabled/disabled, but it might be better if it does not.

Iterations can also receive xforms-enabled/disabled. In order to make sure that the handler runs only once, xxforms-nodeset-changed is changed to also dispatch when going from no iteration to some iteration and back.

Cost:
  • xxforms-nodeset-changed and xxforms-index-changed are not conceptually associated with an iteration, but with the container, so it is a bit awkward
  • update doc
The solution can still be considered, especially since uses of xxforms-nodeset-changed are rare.

Implementation

While as of 2012-01-04, we agreed with John's interpretation, we have, as of 2012-05-18, committed a slightly different solution. See #229: Fix handling of handlers within repeat.

Here are the main aspects of the solution:
  • we keep the repeat container/repeat iteration split
  • we still associates the repeat id to the repeat container
  • events are still dispatched to the repeat container (xforms-enabled, xxforms-nodeset-changed, etc.)
  • we no longer need the hack of redirecting some events from repeat to iteration
  • we now statically make nested handlers without explicit observers observe not the repeat iteration, but the repeat container itself

Comments