XLSForm proposal to calculate a value when another value changes

We have already agreed upon and specified a mechanism to calculate a value when another value changes. Both Enketo and ODK Collect support this feature already. See the xforms-value-changed event in the ODK XForms spec for technical spec details if you're interested in such things.

Now, I think it is time to agree upon a syntax in XLSForm, after which it can be implemented in pyxform and then actually be used. I'd like to get this discussion started with a proposal to introduce a new trigger column that works in conjunction with type=calculate (only).

type name label calculation trigger
text a Enter text
calculate b now() ${a}

Strengths of trigger:

  • it is one word
  • it is flexible enough to in the future expand with other (2) events in our XForms specification that do not refer to another node, e.g. the column could in the future also allow the values first-load or new-repeat or something similar (with other question types).
  • it would work well with another action (subject of a future proposal) to record the location (which could be another question type)

Weaknesses of trigger:

  • the future flexibility means the column name is not as descriptive as something like upon_change or when_changed would be
  • we already use the word "trigger" as an alias for acknowledge (though - thankfully - this is not documented in xlsform.org)

I looking forward to hearing what you think!

2 Likes

Given trigger is already a well-defined XForm element term, I'd be reluctant to re-use it in a quasi-similar (wrt both being 'event' related') but quite different context, even though the former, original, XForm usage isnt readily exposed via XLSForm today...

Although I do agree trigger is certainly a good fit here otherwise! But perhaps instead (but less descriptive, alas) a synonym like activate, actuate, initiate, instigate.... (incite? :slight_smile: )

2 Likes

Thanks for this proposal, @martijnr! It would indeed be great to add to XLSForm.

What are you imagining that the resulting XML would look like? Would the calculate become a field with a string bind, no calculate and no corresponding body control element? It seems that it can't have a calculation or that would override the result of any setvalue action, right?

That was also my gut reaction and it's still making me uneasy.

It's not really the case that a field is a trigger for a setvalue, the trigger is the change in that field. If you're comfortable with a field being in the proposed trigger column, how about using the label when? I think that works very nicely for other events

type name label calculation when
text a Enter text
calculate b now() ${a}

I think the two major use cases for the value changed event will be to pull some value from outside the form as you've demonstrated (time, location) and setting dynamic defaults based on values supplied by the user (typically pulled from external datasets).

Do you agree that the latter case is worth supporting? In that case, trigger would need to apply to types other than calculate.

When I started thinking about this, I was imagining it flipped so that the expression would be defined in the row of the value that changes. For example, to set the value of b when a changes, I was thinking of something like:

type name label on_change
text a Enter text ${b}=instance('foo')/root/item[name=${a}]
select_one foo b Select one

(Note that this would probably be more realistic with pulldata given current documentation)

The downsides of this is that it introduces some new syntax with the = and that it's not as clear what the hidden value case like your example should look like. One concept would be to introduce a column for suppressing a body element:

type name label on_change user_facing
text a Enter text ${b}=now()
dateTime b no

This would also address the request for typed calculate fields that comes up periodically. I think that pyxform could validate that a field without a body element is either the target of an action or has a calculation but not both. But this adds to the cross product of possible question properties so certainly adds complexity.

One of the things you mention thinking ahead to is how to surface the setgeopoint action triggered by events other than odk-instance-first-load which I agree is important. Is this what you'd have in mind?

type name label trigger
text a Enter text
auto_geopoint b ${a}

In what I described as an alternative, this might be done by introducing a special keyword that could be used in the default or on_change columns. That wouldn't feel so clean.

I'm still thinking through all this and am not advocating for one solution over the other yet. I hope that sharing my thinking may spark some ideas.

1 Like

WIP: discussion with @ln and @Xiphware:

type name label calculation when
text a Enter text
dateTime b now() ${a}
dateTime c Date of diagnostic now() ${a}
dateTime d Date of something ${a} empties value!
dateTime e ${a} empties value!

Change rules:

  • If there is no calculation a label is required unless it has when.
  • If it has a label, a body element is included in the XForms output.
  • An empty calculation with when, will reset the value to empty.

It looks like pyxform currently allows the absence of a label if the question has a hint, so I want to propose a tiny tweak to our plan:

  • If there is no calculation a label or a hint is required unless it has a value for 'when'.
  • If it has a label or a hint, a body element is included in the XForms output.

This would avoid issues with editing existing forms.

Does that seem alright?

1 Like

Hmm... I'm not sure this behavior is strictly XForm spec conformant. Specifically, from label:

8.2.1 The label Element
This element provides a descriptive label for the containing form control. ...

whereas, for hint (emphasis added):

8.2.3 The hint Element
The author-optional element hint provides a convenient way to attach hint information to a form control. ...

Although not explicit, my interpretation might be that label is not optional, whereas hint is (do you agree?)

Not that this probably directly affects the issue at hand, other than perhaps we might be perpetuating a pre-existing pyxform non-conformance... :wink:

2 Likes

Good point @Xiphware. The W3C site seems inaccessible atm, but I think (maybe wishfully) that the <label> element is indeed required, but that it may be empty.

Pyxform doesn't actually output an empty <label/> though.

1 Like

Thanks for a good discussion, @martijnr and @Xiphware. I'm on board for the extensions described at XLSForm proposal to calculate a value when another value changes with the small tweaks to maintain current hint/label rules.

I think we can move forward with that and separately take on the question of whether we should consider changing the label and hint requirement rules or output. I agree with @Xiphware's reading of the W3C spec that a label element should always exist. It does seem like an empty element should be ok so that might be an easy way to make pyxform compliant without affecting edits to existing form definitions.

@martijnr do you want to pull all this into a pyxform issue that we can do one last sanity check on?

Yes, I will do that shortly, and I've heard somebody is going to prepare a PR for this.

1 Like

I agree. I could forsee situations where you might want a widget without a (text) label

I drafted something here: https://github.com/XLSForm/pyxform/issues/438 with two related issues that we discussed in this thread linked at the top.

1 Like

Thanks, @martijnr! As I wrote on the issue, this seems ready to build to me.

When we had a call about this, I said I'd confirm a couple of things about the JavaRosa/Collect implementation:

  • There can be multiple setvalue actions nested in a form control (test)
  • A setvalue action can set the value of a readonly field (test)

Not exactly related to this spec but brought up by the second point, we also wanted to confirm that calculates on readonly fields with form controls were executed. They are indeed so I believe this behavior is consistent across the ecosystem. Should it be explicitly called out in the spec?

I also had in my notes that I would expand the xforms-value-changed documentation to make clear that actions triggered by the event should be nested within the form control of the value they observe. However, I'm wondering whether our current link out to the W3C XForms spec may be sufficient. It makes it clear that core form controls are the event targets. Would adding an example help avoid confusion? Do you think there also needs to be some additional text describing the event?

1 Like

Yes, I think that would be very good to do this. I just added https://github.com/opendatakit/xforms-spec/issues/267

I think we should elaborate a bit. At least an example showing multiple setvalue/xforms-value-change
elements inside one input, and one of these having its own form control would probably be helpful.

1 Like