Pre-populating select with default option

The question of pre-populating a select_one (or select_multi) with a desired option has come up a few times in the past - eg Set a default choice for single-select, Randomly Selecting One Option by default - Not working, and most recently First choice selected by default (select_one).

Although, like any question, you can always manually add a value in the 'default' column of a select question, this is a bit ungainly and requires you to manually copy one of your option values (important: not the label!) over, and if you ever change these options in your form you have to remember to also go back and manually edit the default value. A better solution would be to be able to prepopulate the select with, say, the first option - whatever it may be.

Always up for a challenge (and undeterred by @Grzesiek2010's assertion: "The only option is to use default 2 and put there your first option in each question but that's it" :wink: )...

Pre-populating select_one / select_multiple With Default Option

Here is a trick you can use to programmatically pre-populate a select_one or select_multiple with the first (or in fact any!) option, without having to manually copy the desired option value into the default.

Actually, its 2 tricks:

  1. The first is to force pyxform to turn your select option list into a secondary instance. This can be accomplished by adding a redundant choice_filter into your select. Normally a choice_filter is used for cascading selects, but it also has the useful side-effect of 'instance-izing' your option list into a separate instance XML in your XForm, which you can then lookup things using regular XPath expressions [this trick originally courtesy @LN :slight_smile: ]. I use a simple choice_filter that simply returns everything:

    choice_filter: name != ''

  2. Now that you have your select options accessible in this secondary instance, you can use a once() calculation to populate your select with a value, in this case your desired option value, which you lookup with a somewhat cyptic XPath command:

    once(instance('options')/root/item[position()=1]/name)

Deciphering:

  • instance('options')/root references the XML secondary instance that the choice_filter created containing your option list.
  • /item[position()=1] selects the first option item. Or if you want to pre-populate with the 3rd option this would be item[position()=3]
  • /name selects the option value (as opposed to the option label) which is needed to correctly populate the initial select result.

The once() function effectively only fires when the current select result is null/empty, so it will set the initial select value to your chosen option, but then wont keep overwriting (with the default) if you subsequently select other things, or unselect the default.

This technique works for both select_one or select_multi. The following form implements both. Have a play around, eg in XLSForm Online, and let me know what you think.

selectdefault.xlsx (9.8 KB)
selectdefault.xml (2.5 KB)

4 Likes

@Xiphware tried to pre prepopulate more than one options in multiple select questions but failed.

This technique should still work, provided you populate the select_multi with an appropriate space-separated option string, eg

once(concat(instance('options')/root/item[position()=1]/name, ' ', instance('options')/root/item[position()=3]/name))

[give it a go and report back! :slight_smile: ]

2 Likes

tested and it works.:slightly_smiling_face:

Nice writeup!

There is a danger that when unselecting all options, the default will re-calculate though (this is because once() is not named correctly and should actually be called something like if-empty(). E.g. when a draft record is saved after unselecting all options, and is then re-loaded for further editing, the defaults will come back. Test here: https://enketo.ona.io/x/#C0M2SE2a

2 Likes

Yup. This is a problem when using once() for programmatically populating defaults (from other data) irregardless of the question type: if you can subsequently null the value - be it a string, or selectmulti, or whatever - then the once() will likely fire again at some point and reinsert the default!

Unfortunately, until dynamic defaults become widespread, is probably about the best we can do right now. :slightly_frowning_face:

1 Like

or perhaps 'coalesce()'... :wink: