XForms spec proposal: add event fired when new repeat instance is created

spec-proposal
odk-xforms
#1

The odk-instance-first-load event combined with the setvalue action make it possible to define dynamic defaults that will be evaluated once when a new form instance is created. However, this mechanism doesn't allow for dynamic defaults set when new repeat instances are added because those instances don't exist when the form instance is created. To support this case in a consistent manner, @martijnr proposed introducing an event that is fired when a new repeat instance is created.

The W3C XForms spec does describe an xforms-insert event. That event is fired in response to an insert action which is generalized (not just for repeats). The event has various properties and the spec says that "An XForms Model Processor [...] should support the notification events xforms-insert and xforms-delete" (not must). I don't yet have a great understanding of how the properties of the event are used but my sense is that this is introducing a lot more complexity than we need at this time. As @martijnr pointed out, JavaRosa/CommCare defines a custom jr-insert event and this is probably why.

I propose that our spec take a similar approach and add a narrow event that just fires when a new repeat instance is added. We could name it jr-insert since that already is in JavaRosa but I'd like to suggest something more descriptive like odk-new-repeat, odk-repeat-add or something like that. If ever we wanted to support the full insert action and corresponding xforms-insert event, the door would still be open for that in the future.

For example:

...
  <model>
    <instance>
      <data>
        <my_age />
        <friends jr:template="">
          <first_name />
          <last_name />
          <age />
        </friends>
      </data>
    </instance>
    ...
    <setvalue event="odk-new-repeat" ref="/data/friends/age" value="/data/my_age + 2" />
    ...
  </model>
<h:body>
  ...
  <repeat nodeset="/data/friends">
    ...
  </repeat>
</h:body>
...

When a new friends repeat is added, the default value will be set to 2 more than whatever the user set as their age. jr-insert-example.xml.zip (2.2 KB) is a form with the jr-insert event name that works in Collect today.

The new repeat would be used to qualify the references used in ref and value so nested repeats arbitrarily deep should be supported.

For XLSForm spec proposal: add syntax to make it easy to use a value from the last saved instance, any expression used in the default column in a row that is nested in a repeat would result in a setvalue expression with the event proposed here added to the XForm model.

One odd case I'm not sure about is when repeat instances are part of the original form definition (e.g. when jr:template isn't specified. The repeat insertion event would not be fired for those so I'm not sure what if anything the XLSForm spec should say about that.

I know that's a lot to digest. Questions to consider:

  • Are we ok inventing a new, narrow event just for repeat creation?
  • Is having a generic insert event that fires on any repeat insertion and using the action ref prefix to filter when the action should occur an acceptable simplification? See explanation.
  • If so, what should the event be named?
  • If so, are the semantics described above what you would expect?
  • Either way, what should XLSForm do when repeat instances are part of the original form definition?
1 Like
XLSForm spec proposal: add syntax to make it easy to use a value from the last saved instance
ODK TSC-1 Call - 2019-05-15
#2

One more thing I realized I should have called out explicitly. The proposal above (based on the jr-insert implementation) includes a big simplification on the properties of the xforms-insert event: actions are registered on a generic insert event rather than on insert of a specific repeat type and it's the absolute prefix of the action's ref that determines which actions run.

For example, let's say there were an enemies repeat in the form above. The generic insertion event would fire when an instance of enemies was added and this would trigger the setvalue action. But since the action's ref starts with /data/friends and not /data/enemies, the action would have no effect.

This means that ref always needs to be an absolute path which in turn means inserting a repeat can only change values in that new repeat instance. You couldn't, for example, set up a counter of repeat instances that gets updated on insertion (the count function can be used for that particular requirement).

#3

Not to derail anything, but... is there any reason we couldnt simply use the existing once() for this purpose? Its actually probably exactly the assumed behavior of once() [hence the name...], despite the fact that in reality what it is doing is the equivalent of coalesce(., ${whatever})

... and instead fix javaRosa DAG check to permit legitimate XPAth expression like coalesce(., ${whatever}) (which I rather wanted to propose anyway...)

It just seems like we may be introducing a degree of redundancy here between a (new) odk-instance-first-load event and once() XPath function; at least on the surface to a user, semantically they seem to be intended to serve the same/similar purpose (although again, I recognize the actual implementation behaves quite differently)

Which is to say, I'd probably prefer if once() did just that - ie fire once (presumably on form load) and never thereafter - and then enable the existing coalesce(., ${whatever}) to actually do what once() is currently doing today [which I think other clients like Enketo permit]. Or deprecate once() in favor of this new instance-first-load mechanism? Or ... Thoughts anyone?

#4

To be explicit, you are suggesting an alternative to both this and XLSForm spec proposal: add syntax to make it easy to use a value from the last saved instance which is to change the semantics of once() from running when the context node's value is empty to once on instance first load and to use that to define dynamic defaults, did I get that right?

I see in the last message in the last saved instance XLSForm syntax thread that the spec was approved during the TSC call. Did y'all discuss your alternative suggestion by any chance?

I agree that once() is not a very good name for what it does. I suspect it was added as a quick hack for functions that rely on state outside the form such as random() and now() and that the ramifications were not thought through at the time. I put it in the same "not-my-favorite-but-users-rely-on-it" bucket as jr:choice-name and indexed-repeat.

I see what you are getting at and it does have a certain simplicity. That said, I'm even less comfortable with this new version of once() which knows when it's being run. I think it's also unfortunate to have both a calculation and a user interface control for the same instance node.

XForms is fundamentally event-driven and provides a nice mechanism for triggering behavior based on events so using events and actions in this case certainly seems more XForms-y. I also can't think of a function-driven way to support things like the xforms-value-changed event so it seems we'll want to continue making use of the event/action paradigm.

I would not be opposed to putting once() as it exists today on a deprecation path.

1 Like
#5

Yeah, bit last minute... sorry. :disappointed_relieved: It only just occurred to me when re-reading stuff before the meeting. once() has been nagging at me for a while, and I think it just 'clicked'...

I wanted to run the idea by you before discussing anything further. I dont see an issue with the current proposal, indeed it seems more general purpose (which is a plus). But it does rather leave an open question in my mind - and potentially users - when do I use this new feature vs when do I use once(), what's the difference between the two, etc.

I think proceeding with instance-first-load makes sense, as being the new recommended (and more clearly defined) mechanism to populate elements with a default value on form load, deprecate once() as the hacky old way of trying to accomplish something mostly equivalent via XPath, and do something with javaRosa DAG checking to enable the legitimate use of coalesce(.,${foo}) when needed [which currently you have to use once() to 'fake out' validation]. The last issue of which I was going to raise via a new roadmap entry for TSC consideration...

Agreed, which is why once() always felt wrong. XPath functions are just functions, which ideally should have no side-effects, and certainly no memory (as is implied by 'once'). And whatever actual functionality once() is currently accomplishing is already handled by coalesce().

2 Likes
#6

Your proposed plan of action sounds good.

I'd like to wait for a plan on how repeats will be handled before moving forward on implementation, though. I'll be very interested in hearing your thoughts on the narrow odk-new-repeat (or other name) that I've described above.

I aggressively agree. :smile: :beers:

#7

Can you wait till the 11th hour, again? :grin:

split this topic #8

2 posts were merged into an existing topic: Spec proposal: add first-load event to replace xforms-ready

#9

Thanks a lot for this proposal!

Wrt the event name odk-new-repeat sounds good to me.

I do not think the presence of an explicit template makes a difference. If a template is not there the implicit template is the emptied copy of a (the first) repeat instance. That brings up an interesting revelation, in addition to the one discussed above wrt once(). I think this new event would allow us to deprecate jr:template (since it's only raison d'être is defaults)!

No, I don't think that filtering from the ref path is the way to target the setvalue action for this event. The ref's only purpose should probably be to point to the node in the model to set the value of.

I'm wondering we should provide context in a similar manner as we describe in the example in our spec for value change events. So in your example, the setvalue directive could perhaps move to the <body> like this:

...
  <model>
    <instance>
      <data>
        <my_age />
        <friends jr:template="">
          <first_name />
          <last_name />
          <age />
        </friends>
      </data>
    </instance>
    ...
  </model>
<h:body>
  ...
  <repeat nodeset="/data/friends">
      <setvalue event="odk-new-repeat" ref="/data/friends/age" value="/data/my_age + 2" />
      <input ref="/data/friends/age">
           <label>Update age</label>
      </input>
      ...
  </repeat>
</h:body>
...

I added the <input> as well in case the question has a form control. The <setvalue> could be probably be inside the <input> as well, or maybe not?

If done like this, we should specify in the spec that the odk-new-repeat event fires on all nodes inside a newly created repeat instance.

#10

Thanks as always for your thoughtful comments, @martijnr. :heart_eyes_cat:

Agreed!

I think this would be good to clarify in the ODK XForms spec. My understanding from the W3C spec is that it's the repeat form control in the body that defines the template. I believe that's equivalent to what you're saying.

My question isn't so much about templates, it's about repeat instances already in the body. Maybe easier to see if you think about the case where the form definition defines more than one repeat instance such as in example B in the default value spec. Probably the repeat creation event would get fired for all of those at the time of primary instance load? If so, that would mean that to define some existing repeat instances and also provide a template for new ones, the form would include a setvalue action with an expression that sets the value only if it's not blank.

Or maybe the event would never be fired for those? Then I think that to have dynamic defaults on all instances of the repeat including the one(s) defined in the instance you would need a setvalue action that responds both to form instance and repeat instance creation.

:sob:Ok, fine.

Upon more reflection, here's how I believe the W3C XForms xforms-insert event works with setvalue:

  • the event is dispatched to the instance (see this table)
  • any actions that want to listen for it are directly in the model (as siblings of instance, just like in my example above; that means they are triggered when the event bubbles up)
  • an action that responds to xforms-insert can't be nested in a form control because the event is not dispatched to control elements (or descendants)
  • the event provides an evaluation context for the setvalue action's ref (the newly created repeat instance)
  • if the ref is a relative expression that can be contextualized given the provided context, then the value expression is evaluated using the contextualized ref as the context node. If the ref can't be contextualized given the event context, nothing happens. This is one way for the action to only run when instances of a particular repeat are added.
  • if the ref has an absolute path, I believe the action will run any time any repeat instance is inserted.
  • the action can also set the observer attribute to only respond to inserts of instances of a particular repeat

I think it's the evaluation context piece that's tricky. Here's what W3C XForms says for the evaluation context used to evaluate the XPath expression in setvalue's value: "The evaluation context for this XPath expression is the result from the Single Node Binding". My understanding from the section on Single Node Binding is that this means the contextualized ref is used as the context for value.

And this about a change from XForms 1.0 to 1.1:

"the setvalue action has been improved due to the addition of the context() function. Now it is possible to express the value attribute in terms of the same context node used to evaluate the single node binding. This improves the ability to use setvalue inside of a repeat to set values of instance nodes that are outside of the repeat nodeset based on values that are within the repeat nodeset."

Here's how I understand your counter-proposal; please correct me if I got any of it wrong:

  • actions that are triggered by the odk-new-repeat event must be nested in a repeat form control
  • when a new instance of a repeat is added, the odk-new-repeat event is dispatched to the repeat, triggering actions that are directly nested in that repeat's form control (that is, if an action is defined in the form control for /data/repeat1/repeat2/, it will be triggered when an instance of repeat2 is created, but not when an instance of repeat1 is created)
  • values of any attributes defined on an action triggered by odk-new-repeat are evaluated using the contextualized ref node as context

The nesting makes it clear which new repeats should be responded to. It also provides a clear evaluation context node for the ref (the new instance of the repeat it is immediately nested in). If the evaluation context node for value is indeed the contextualized ref, then if ref is a node outside the repeat, value can't be a node in the repeat. I think that's a fine limitation.

I don't think that would make sense -- the nesting identifies the repeat whose odk-new-repeat event should be responded to, right? I don't see what additional information nesting it in a form control provides.

I'm tentatively onboard with your suggestion. Let's see whether I misunderstood anything or if the additional references I pointed to might provide more ideas.