Recomputing calculated field in repeat group, after previous instance changed

I have a repeat group in my form where I need to prevent users from entering the same value twice.

Below is my current solution. It works reasonably well. However, it fails if people go backwards in the form and edit a previous repeat group entry.

type name label calculate constraint
begin_repeat person Household member
calculate calc_previous_names join(’|’, /data/person/person_name[position(.) < position(current()/…)])
text person_name Name not(contains(concat(’|’, ${calc_previous_names}, ‘|’), concat(’|’, ., ‘|’)))

In brief: I have a calc_previous_names field that calculates a |-separated list of all previously entered names. The person_name field then has a constraint to check that the name doesn’t appear in calc_previous_names.

This all work well, except that the calc_previous_names field is not updated when the enumerator goes back through the form and changes one of the previously entered names. For example, the following could happen.

  1. Enumerator enters “John”
  2. Enumerator enters “Mary”
  3. Enumerator enters “John” and sees the validation failure
  4. Enumerator goes back to the first entry, changes the name to “John the first”
  5. Enumerator goes forward to the third entry. Enumerator still can’t submit “John”. It’s still rejected because the calc_previous_names has not been updated.

Is there a way to update calc_previous_names not just when the repeat group instance is created, but whenever anything changes in a previous repeat group?

Or… is there a better way to achieve my desired functionality?

I’ve attached a complete xlsform that I’m using to test this: Kobo Experiments.xlsx (7.7 KB)

@Sjlver, you could do it as outlined in the post discussed previously:

Thanks, @Kal_Lam. Your answer brought me onto a good track.

After a day of experimentation, I now have a solution that works robustly and handles all the problems related to moving back and forth in the form. It also shows pretty nice error messages. I’d like to share the file here as an example for anyone who encounters similar problems in the future: Kobo Experiments.xlsx (9.0 KB)

A few explanations:

The form uses a required note field to display error messages when duplicates are detected. The note is displayed conditionally using its relevant field. This is different from the solution in the answer above, that use a constraint to prevent duplicates. It works better, because all global values are updated when the relevance is evaluated.

There are also other note fields that are not marked as required; these can serve as warnings. Warnings help the enumerator find places where they can correct something. Here’s how it looks in Enketo:

Later in the form, there is a section on nets (bednets protecting people from mosquitoes). This uses the new “select one from repeat” feature. Each net covers a certain number of people, and validation rules prevent the same person from being covered twice. For example:

As a bonus, the error message tells the enumerator the number of the net where the person has been used before. There’s some clever awful string manipulation to extract that position.

After each net, a summary shows which people have already been covered and which ones are outstanding. Unfortunately, this only works in Collect, not in Enketo:

The core ideas that make this possible:

  • Use a global variable all_selected that joins together all the selected elements.
  • Check if a value appears twice in this variable, by checking whether substring-before(all_selected, value) contains value,
  • For the summary, generate lists of not yet selected elements using something like join(', ', /data/person/person_name[not(contains(${all_selected}, .))])

Hope this is helpful!