Steven Pemberton, CWI, Amsterdam
2017-06-30
A request came in for a form that had a short introduction, gave the user a choice of three options, and then revealed a small number of questions, based on the choice.
Something like this (try it):
In the most obvious and direct approach, the body could look like this:
Please help us discover problems and solutions that would improve our processes. <select1 ref="choice" appearance="full"> <label>How can you help?</label> <item><label>You know a problem that needs to be fixed</label><value>problem</value></item> <item><label>You know a 'solution' that doesn't work</label><value>failure</value></item> <item><label>You have a prediction about a future possible failure</label><value>prediction</value></item> </select1> <switch ref="choice"> <case name=""/> <!-- Until a choice is made, nothing is displayed --> <case name="problem"> <group ref="problem"> <label>You know a problem that needs to be fixed</label> <textarea ref="problem"><label>What problem do you see?</label></textarea> <textarea ref="solution"><label>Can you propose a solution?</label></textarea> </group> </case> <case name="failure"> <group ref="failure"> <label>You know a 'solution' that doesn't work</label> <textarea ref="problem"><label>What 'solution' will fail or cause trouble?</label></textarea> <textarea ref="solution"><label>Can you propose a fix?</label></textarea> </group> </case> <case name="prediction"> <group ref="prediction"> <label>You have a prediction about a future possible failure</label> <textarea ref="problem"><label>What is your scenario?</label></textarea> <select1 ref="likely"> <label>How likely is this scenario?</label> <item><label>High</label><value>1</value></item> <item><label>Medium</label><value>2</value></item> <item><label>Low</label><value>3</value></item> </select1> <textarea ref="who"><label>Who should address these issues?</label></textarea> <textarea ref="solution"><label>Can you propose a solution?</label></textarea> </group> </case> </switch> <submit><label>Submit</label></submit>
This uses the following instance for the data:
<instance id="data"> <data xmlns=""> <choice/> <problem> <problem/> <solution/> </problem> <failure> <problem/> <solution/> </failure> <prediction> <problem/> <likely/> <who/> <solution/> </prediction> </data> </instance>
One of the early decisions made was that since this form would be used in a multi-lingual environment, it would be made so that the user could select which language to use.
This involved creating an instance that contained all the labels and other texts:
<instance id="m"> <messages xmlns="" lang="en"> <intro>Please help us discover problems and solutions that would improve our processes.</intro> ... </messages> </instance>
and in the body:
<output ref="instance('m')/intro"/>
For the select1
, the messages:
<choice> <label>How can you help?</label> <item value="problem">You know a problem that needs to be fixed</item> <item value="failure">You know a 'solution' that doesn't work</item> <item value="prediction">You have a prediction about a future possible failure</item> </choice>
with the body now reading:
<select1 ref="choice"> <label ref="instance('m')/choice/label"/> <itemset ref="instance('m')/choice/item"><label ref="."/><value ref="@value"/></itemset> </select1>
And for the groups within the cases, the messages:
<problem> <label>You know a problem that needs to be fixed</label> <problem>What problem do you see?</problem> <solution>Can you propose a solution?</solution> </problem>
And in the body:
<case name="problem"> <group ref="problem"> <label ref="instance('m')/problem/label"/> <textarea ref="problem"><label ref="instance('m')/problem/problem"/></textarea> <textarea ref="solution"><label ref="instance('m')/problem/solution"/></textarea> </group> </case>
At was at this point the similarities between the different sets of answers was noticed, and how they could be generalised. The data instance was changed so that instead of:
<instance id="data"> <data xmlns=""> <choice/> <problem> <problem/> <solution/> </problem> <failure> <problem/> <solution/> </failure> <prediction> <problem/> <likely/> <who/> <solution/> </prediction> </data> </instance>
the following was used:
<instance id="data"> <data xmlns=""> <choice/> <answer choice="problem"> <problem/> <solution/> </answer> <answer choice="failure"> <problem/> <solution/> </answer> <answer choice="prediction"> <problem/> <likely/> <who/> <solution/> </answer> </data> </instance>
This then allowed replacing the whole switch
with a single
group
. The group
selects only the answer whose
choice
attribute matches that of the value actually chosen:
<group ref="answer[@choice=../choice]"> <label ref="instance('m')/answer/item[@choice=context()/@choice]/label"/> <textarea ref="problem"> <label ref="instance('m')/answer/item[@choice=context()/../@choice]/problem"/> </textarea> <select1 ref="likely"> <label ref="instance('m')/answer/item[@choice=context()/../@choice]/likely/label"/> <itemset ref="instance('m')/answer/item[@choice=context()/../@choice]/likely/item"> <label ref="."/><value ref="@value"/> </itemset> </select1> <textarea ref="who"> <label ref="instance('m')/answer/item[@choice=context()/../@choice]/who"/> </textarea> <textarea ref="solution"> <label ref="instance('m')/answer/item[@choice=context()/../@choice]/solution"/> </textarea> </group>
Note that:
answer
has a
choice
with a value that has been selected (since nothing has
yet been selected), nothing will be displayed for the answers.likely
or who
element, the controls for those
elements won't be displayed, since they are not bound to any element.The message for the group's label is selected using
item[@choice=context()/@choice]
. This selects the
item
element, whose choice
attribute has the same
value as the context element's choice
attribute. In this case the
context element is the answer
element that has been selected.
For the first textarea
element, the context item is now
problem
, which is a child of answer
, so you have to
go up one level to get to answer
, in order to get its
choice
attribute:
item[@choice=context()/../@choice]
.
The change to the instance data required an associated change to the structure of the messages instance:
<answer> <label>How can you help?</label> <item choice="problem"> <label>You know a problem that needs to be fixed</label> <problem>What problem do you see?</problem> <solution>Can you propose a solution?</solution> </item> <item choice="failure"> <label>You know a 'solution' that doesn't work</label> <problem>What 'solutions' will fail or cause trouble?</problem> <solution>Can you propose a solution?</solution> </item> <item choice="prediction"> <label>You have a prediction about a future possible failure</label> <problem>What is your scenario?</problem> <likely> <label>How likely is this scenario?</label> <item value="1">High</item> <item value="2">Medium</item> <item value="3">Low</item> </likely> <who>Who should address these issues?</who> <solution>Can you propose a solution?</solution> </item> </answer>
Since it is pointless to submit the data for answers that have not been
chosen, a bind was added to the data instance that said that the only answer
that was relevant was the one that matched the value of choice
:
<bind ref="answer" relevant="@choice=../choice"/>
Also the submit
button was not displayed until a set of answers
had been chosen. To do this, an admin
instance was created that
contained an element that indicated whether the submit button should be
displayed:
<instance id="admin"> <admin xmlns=""> <submit/> </admin> </instance> <bind ref="instance('admin')/submit" relevant="instance('data')/choice != ''"/>
changing the submit element to:
<submit ref="instance('admin')/submit"><label ref="instance('m')/submit"/></submit>
Finally, a message of thanks was added, once the data had been submitted, and the user was allowed to enter another set of answers.
<group ref="instance('admin')/thanks"> <label ref="instance('m')/thanks"/> <trigger> <label ref="instance('m')/another"/> <action ev:event="DOMActivate"> <reset/> </action> </trigger> </group>
When the "another" button gets pressed, the form is just reset to its initial state. To support the "thanks" message, the admin instance was added to:
<instance id="admin"> <admin xmlns=""> <submit/> <submitted/> <thanks/> </admin> </instance> <bind ref="instance('admin')/submit" relevant="instance('data')/choice != '' and ../submitted!='yes'"/> <bind ref="instance('admin')/thanks" relevant="../submitted='yes'"/>
and the submit button changed to:
<submit ref="instance('admin')/submit"> <label ref="instance('m')/submit"/> <action ev:event="DOMActivate"> <setvalue ref="instance('admin')/submitted" value="'yes'"/> </action> </submit>
The data instance was put in an external file, and loaded from there:
<instance id="data" src="data.xml"/>
and likewise for the messages:
<instance id="m" src="messages.xml"/>
Giving the following result:
<model xmlns="http://www.w3.org/2002/xforms"> <instance id="data" src="data.xml"/> <bind ref="answer" relevant="@choice=../choice"/> <submission resource="add-data" method="post"/> <instance id="m" src="messages.xml"/> <instance id="admin"> <admin xmlns=""> <submit/> <submitted/> <thanks/> </admin> </instance> <bind ref="instance('admin')/submit" relevant="instance('data')/choice != '' and ../submitted!='yes'"/> <bind ref="instance('admin')/thanks" relevant="../submitted='yes'"/> </model> <group xmlns="http://www.w3.org/2002/xforms"> <label ref="instance('m')/label"/> <output ref="instance('m')/intro"/> <select1 ref="choice" appearance="full"> <label ref="instance('m')/answer/label"/> <itemset ref="instance('m')/answer/item"><label ref="label"/><value ref="@choice"/></itemset> </select1> <group ref="answer[@choice=../choice]"> <label><output ref="instance('m')/answer/item[@choice=context()/@choice]/label"/></label> <textarea ref="problem"> <label ref="instance('m')/answer/item[@choice=context()/../@choice]/problem"/> </textarea> <select1 ref="likely"> <label ref="instance('m')/answer/item[@choice=context()/../@choice]/likely/label"/> <itemset ref="instance('m')/answer/item[@choice=context()/../@choice]/likely/item"> <label ref="."/><value ref="@value"/> </itemset> </select1> <textarea ref="who"> <label ref="instance('m')/answer/item[@choice=context()/../@choice]/who"/> </textarea> <textarea ref="solution"> <label ref="instance('m')/answer/item[@choice=context()/../@choice]/solution"/> </textarea> </group> <submit ref="instance('admin')/submit"> <label ref="instance('m')/submit"/> <action ev:event="DOMActivate"> <setvalue ref="instance('admin')/submitted" value="'yes'"/> </action> </submit> <group ref="instance('admin')/thanks"> <label ref="instance('m')/thanks"/> <trigger> <label ref="instance('m')/another"/> <action ev:event="DOMActivate"> <reset/> </action> </trigger> </group> </group>
We now have a form that is driven from two data files: the template for the data, and the messages.
Suppose we wanted to add a fourth option to the form; all we have to do is add it to the data template, with a suitable new choice value:
<answer choice="solution"><problem/><who/><solution/></answer>
and matching messages in the message file:
<item choice="solution"> <label>You know an existing solution that can be adopted</label> <problem>What solution do you know of?</problem> <who>Who should we approach?</who> <solution>How could we best adopt the solution?</solution> </item>
and the form now works without change for the new entry.
This makes life much easier for the content providers: they can make textual changes to the form without having to ask the programmers to do it, they can add new cases themselves fairly easily. In fact, you could even make a form to simplify the process!
Although the form has been prepared to be multi-lingual, it isn't so yet.
The first thing that must be done, is a select1
added at the
top of the page, to allow setting of language:
<select1 ref="lang"> <label>🗨</label> <item><label>English</label><value>en</value/></item> <item><label>Nederlands</label><value>nl</value/></item> ... </select1>
This adds a new value to the data instance, called lang
, which
should be initialised in the data.xml
file to whatever the default
language should be.
Of course, this is not how we are going to do the select1
, but
instead access the values from an instance:
<select1 ref="lang"> <label>🗨</label> <itemset ref="instance('languages')/item"><label ref="."/><value ref="@code"/></itemset> </select1>
where the languages
instance is loaded from a file:
<instance id="languages" src="languages.xml"/>
and looks like this:
<languages> <item code="en">English</item> <item code="nl">Nederlands</item> ... </languages>
What we want, is that each time a different language is selected, that the
messages instance is replaced with values from a different file. So we add to
the select1
:
<select1 ref="lang"> <label>🗨</label> <itemset ref="instance('languages')/item"><label ref="."/><value ref="@code"/></itemset> <action ev:event="xforms-value-changed"> <send submission="change-language"/> </action> </select1>
and add an extra submission
element:
<submission id="change-language" method="get" replace="instance" instance="m"> <resource value="concat('lang-', lang, '.xml')"/> </submission>
which would, if Nederlands is chosen, replace the
messages
instance with the contents of the file
lang-nl.xml
.
This means that to add a new language, let's say for French, you just
translate the lang-en.xml
file and store the result in
lang-fr.xml
, and add the single line to the
languages.xml
file:
<item code="fr">Français</item>
Our form is nearly complete. However, adding the language selection has
changed one thing: if the user clicks the "Add another" button, the form is
returned to its initial state using the <reset/>
action.
However, this also reverts to the default language, which isn't very polite.
So, we have to change the thanks section from
<group ref="instance('admin')/thanks"> <label ref="instance('m')/thanks"/> <trigger> <label ref="instance('m')/another"/> <action ev:event="DOMActivate"> <reset/> </action> </trigger> </group>
to
<group ref="instance('admin')/thanks"> <label ref="instance('m')/thanks"/> <trigger> <label ref="instance('m')/another"/> <action ev:event="DOMActivate"> <send submission="restart"/> <setvalue ref="instance('admin')/submitted"/> </action> </trigger> </group>
where the reset
submission just reloads the original data
file:
<submission id="restart" method="get" resource="data.xml" replace="instance" instance="data"/>
Now we really are done. Here's the result: