'Trigger' a calculation at the 'end' of a form

1. What is the issue? Please be detailed.

I have a calculation that I want to trigger to happen at the very end of a form. My thought was that I could use the metadata field ${end} as a trigger. However, that didn't work.

2. What have you tried to fix the issue?
My next best workaround is to use the last 'required' question in the form to be the trigger for the calculation. But this seems risky as that question could change, or the data collector could fill in answers out of order.

From the documentation, it looks like the metadata fields can't be used for triggers in this way, so I'm looking for a more stable way to trigger the calculation at the end of the form to get around this limitation.

Any ideas?

You may be interested in this thread, since its very closely related: Spec proposal: end-geopoint to automatically capture location upon form completion

My thought was that I could use the metadata field ${end} as a trigger. However, that didn't work.

Correct. XLSForm 'triggers' are in actuality xform-value-changed events, whose action is to perform a setvalue operation; see https://getodk.github.io/xforms-spec/#events for the gory details... :slight_smile: This doesn't work for metadata - like ${end} [which in actuality is an XForm timeEnd] because metadata aren't represented by UI controls, so they dont generate an xform-value-changed event when their value happens to be saved.

So, as described previously, what is really required is adding a new event type to the ODK spec - eg odk-instance-finalize - which could then be used to trigger a setvalue (or setgeopoint) action accordingly, eg when the form is finally finalized and no longer editable.

Until then, the best you can do is perhaps have a required question right at the end of your form, eg an XLSForm acknowldege question (https://xlsform.org/en/#question-types) against which you put a trigger to save some calculation result. But, as you've already pointed out, this still doesnt prevent the user from answering this but then going back and changing previous answers, so its far from an ideal solution.

AFAICT there's no foolproof way yet - without an actual odk-instance-finalize (or equivalent) new event type - to accomplish this.

Note, calculations are being re-calculated when you finalize your form; from https://docs.getodk.org/form-logic/#when-expressions-are-evaluated :

When expressions are evaluated

Every expression is constantly re-evaluated as an enumerator progresses through a form. This is an important mental model to have and can explain sometimes unexpected behavior. More specifically, expressions are re-evaluated when:

  • a form is opened
  • the value of any question in the form changes
  • a repeat group is added or deleted
  • a form is saved or finalized

Although, again, when the form is finalized may not necessarily match what you mean by the 'end' of a form [sic]

2 Likes

Thanks for this! I provided a link to this thread onto your spec proposal, for some additional context, and gave it a vote. Yes, it's a great idea to have an odk-instance-finalize event type - that's exactly what I was looking for.

For the calculation at the end of my form, I've got a workflow that depends on editing in Enketo, and needs to allow changes to a certain value when the form is open.

  • So the final calculation needs to capture a value at the very end of the first time the form is submitted, but never again.
  • I need to base the calculation on the current value of the variable being 'blank', and then the 'end form' trigger.
  • If I go back to edit in the future, it will never update again.

Not sure if that makes sense. This is why the usual 'this calculation will keep updating, including at the time of submission' doesn't work for me this time around :roll_eyes:

Can you say more about what that calculation is based on? Do you need to capture something from outside the form like time or are you using values in the form to compute it? If you're using values in the form, you may be able to use once(), trigger or a combination of the two to get the desired behavior. It sounds like you want to allow changes all the way up to finalization in Collect but guarantee no changes even if the source values change from a server edit, is that right? With a more concrete example we may be able to come up with an approach.

FWIW, if you do have to resort to this for the time being, you can somewhat mitigate the above limitation by basically making the rest of your form read-only, contingent on answering this last "Finalize now?" acknowledgement question. eg

Finalize.xlsx (18.1 KB)

You cant complete this form till you hit the acknowledge question (because its required), and as soon as you do all the other questions in the form become read-only so the user cant change them [unless they un-acknowledge, which means they now cant submit the form anymore, until they then re-acknowledge, which re-locks the form, yadda, yadda]

Note, this only works for the Android Collect app and its derivatives, but unfortunately not Enketo because Enketo doesnt support dynamic readonly yet.

2 Likes