I learned something interesting about survey research recently. Given a survey question with a choice list answer, such as a list of icecream flavors or political parties, apparently there is a non-trivial segment of the population who read only the first few possible choices before rendering an answer. This segment can introduce significant bias into survey results unless the list of choices is randomized for each survey.
My recommended approach is to use web application code to put the choice list in the desired order and then push the list into the form before sending the form to an end-user for interaction. However, we increasingly see that people want the server side web application code to be as simple as "send form to user, and when it comes back, extract the data". As a result, front-end form logic is increasingly used in lieu of writing middle tier Java code.
So, here's a sample XForms single choice selection control that, due to the appearance attribute, would be presented as a group of radio buttons:
<xforms:select1 appearance="full" ref="/survey/icecream">
<xforms:label>Pick your favorite flavor of icecream:</xforms:label>
<xforms:itemset nodeset="instance('Flavors')/choice">
<xforms:label ref="."/>
<xforms:value ref="@value"/>
</xforms:itemset>
</xforms:select1>
The set of choice items come from XForms instance data at the location given by the XPath location in the nodeset attribute of the itemset element. The label element then indicates what is shown to the user, and the value element indicates what internal data value corresponds to that display label.
<xforms:instance id="Flavors" xmlns="">
<choices>
<choice value="BS">Butterscotch</choice>
<choice value="C">Chocolate</choice>
<choice value="CC">Cotton candy</choice>
<choice value="S">Strawberry</choice>
<choice value="V">Vanilla</choice>
</choices>
</xforms:instance>
Now, since we don't want to bias the results toward butterscotch and chocolate flavors, we want to randomize this list each time the question is presented to an end-user. The best solution would be if XForms had additional appearance keywords as custom namespaced extensions, something like this:
<xforms:select1 appearance="full xfdl:randomized" ref="/survey/icecream">
In current XForms, the appearance attribute only allows one keyword, and only the words minimal, compact and full are defined. The XForms working group agreed today to change the appearance attribute to a space-separated list to allow extension features like this ("xfdl:sorted" would be nice too, though we also recommend sorting a list before putting it in the form).
Supposing you do want to randomize a list of choices within an XForm, let's start by focusing on just creating a permutation when a form starts. Start with a list of any number of data values, such as 5 values, like this:
<xforms:instance id="Permutation" xmlns="">
<permutation count="" position="" random="" size="" swapvalue="">
<data>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
<value>5</value>
</data>
</permutation>
</xforms:instance>
Now, on form load, we'll create a permutation of them, like this:
<xforms:action id="GenPermutation" ev:event="xforms-model-construct-done">
<xforms:setvalue ref="instance('Permutation')/@random" value="random(true())"/>
<xforms:setvalue ref="instance('Permutation')/@size"
value="count(instance('Permutation')/data/value)"/>
<xforms:setvalue ref="instance('Permutation')/@count">1</xforms:setvalue>
<xforms:action while="instance('Permutation')/@count < instance('Permutation')/@size">
<xforms:setvalue ref="instance('Permutation')/@random"
value="floor(random(false()) * (../@size - ../@count + 1))"/>
<xforms:setvalue ref="instance('Permutation')/@position" value="../@random + ../@count"/>
<xforms:action if="instance('Permutation')/@count != instance('Permutation')/@position">
<xforms:setvalue ref="instance('Permutation')/@swapvalue"
value="../data/value[number(../../@position)]"/>
<xforms:setvalue ref="instance('Permutation')/data/value[number(../../@position)]"
value="../value[number(../../@count)]"/>
<xforms:setvalue ref="instance('Permutation')/data/value[number(../../@count)]"
value="../../@swapvalue"/>
</xforms:action>
<xforms:setvalue ref="instance('Permutation')/@count" value=". + 1"/>
</xforms:action>
</xforms:action>
The first line hooks the start of form processing, after the data model has been initialized. Next, we seed the random number generator and a couple of variables used in the main loop. The loop iterates through positions 1 to n-1 of an n-length permutation. For each position k, the value is swapped with a randomly determined position between k and n, inclusive.
Because XPath automatically concatenates the text nodes of a given XML tree or subtree, you can look at the whole permutation using a single XForms output, like this:
<xforms:output value="instance('Permutation')/data">
<xforms:label>Permutation: </xforms:label>
</xforms:output>
More importantly, you can use the permutation as a lens on the set of items shown by a selection control, such as an XForms select1, like this:
<xforms:select1 appearance="full" ref="/survey/icecream">
<xforms:label>Pick Favorite (random lens)</xforms:label>
<xforms:itemset nodeset="instance('Flavors')/choice">
<xforms:label ref="../choice[number(instance('Permutation')/data/value[1+count(current()/preceding-sibling::choice)])]"/>
<xforms:value ref="../choice[number(instance('Permutation')/data/value[1+count(current()/preceding-sibling::choice)])]/@value"/>
</xforms:itemset>
</xforms:select1>
Compared to the first code sample above, the only differences are the ref attribute expressions in the label and value elements, but they both do something interesting with the nodes produced by the itemset element. Typically, the itemset is used as a "for each" construct, and the label and value ref expressions drill *into* each node to obtain the label for an item and the data value corresponding to choosing that item. In this example, the itemset is really only used to obtain the correct number of items to generate and to set an initial context node within the set of items. The label and value ref expressions then pop up to the parent of that context node, and then drill down into a different choice element based on the order given in the permutation.
Finally, note that you only have to generate the one permutation of a particular size, which can be used as a lens on all selection controls that have the same number of elements as the permutation. If you have a survey in which different questions have different numbers of answers, then you'll need to generate a permutation for each different number of answers. However, once you have all the permutations you need, you can dynamically pick the permutation to use in the label and value ref expressions based on using the last() function to obtain the size of the itemset's node set.