As I’m using schema based validation for my XForms (using Orbeon) I thought it would be a nice idea to generate the select options from the xsd (as well as saving some work!).
As an advanced feature we can also select the value for a second select based on the value of the first.
This is a collection of ideas from different places on the Orbeon wiki and mailing list together with my own thoughts to bring it together.
First the set up:
Load the schema into an instance in this case it’s called study-info-resources – note that this needs to be in a directory where it is not protected by any security – not because of this code but so it can be used for validation.
Next we set up a resources file – this is going to be used for the labels – as well as nice labels we also get multi language support. This resources file will be used to define the relationship between the first and second selects.
<xforms:instance id="study-info-resources" src="../../../../../../insecure/schema/study.xsd"/> <!-- In your XForms model, load resources.xml in an instance. Make sure to make that instance read-only and cacheable: this way the instance will only be stored once in memory (it will be shared by all the users) and using a more efficient (because read-only) representation in memory. xxforms:readonly="true" xxforms:cache="true" --> <xforms:instance id="all-resources" src="/apps/common/resources.xml"/> <!-- NOTE: Here we point to a local file with the oxf: protocol. This is usually yields more performance than using http:, because oxf: will reach a local file on disk. However, in the online version, we use the http:, because we want to load the resource from an online server! Define an instance used to store the current language, e.g. en or fr. --> <xforms:instance id="language"><language>en</language></xforms:instance> <xforms:instance id="iterator"><key></key></xforms:instance> <!-- Define a variable ($resources), which points to <resources> for the current language. You will use this variable as a shortcut in your view to point to specific resources. --> <xxforms:variable name="resources" select="instance('all-resources')/resource[@xml:lang = instance('language')]"/>
Next write the schema elements
<xs:simpleType name="tradeName"> <xs:restriction base="xs:string"> <xs:enumeration value="" /> <xs:enumeration value="fred" /> <xs:enumeration value="bert" /> <xs:enumeration value="Other" /> </xs:restriction> </xs:simpleType> <xs:simpleType name="manufacturer"> <xs:restriction base="xs:string"> <xs:enumeration value="" /> <xs:enumeration value="acme" /> <xs:enumeration value="ernie" /> <xs:enumeration value="Other" /> </xs:restriction> </xs:simpleType>
Now we set up the resources to get our labels
<?xml version="1.0" encoding="UTF-8" ?> <resources> <resource xml:lang="en"> <tradeName>Trade Name</tradeName> <manufacturer>Manufacturer</manufacturer> <tradeNames> <tradeName ref="" /> <tradeName ref="fred">Frederick</tradeName> <tradeName ref="bert">Albert</tradeName> <tradeName ref="Other">Unlisted</tradeName> </tradeNames> <manufacturers> <manufacturer ref="" tradeName=""></manufacturer> <manufacturer ref="fred" tradeName="fred">acme</manufacturer> <manufacturer ref="bert" tradeName="bert">ernie</manufacturer> <manufacturer ref="Other" tradeName="Other">Other</manufacturer> </manufacturers> </resource> </resources>
Now to write our control – as you can see the itemset is populated by looking at the enumeration values from the schema. Looking up the labels from the resources is slightly trickier (of course you can just use the value from the enumeration as the label and not do this look up)
The xforms-value-changed action is only used for the next part so you can usually leave it out.
<xforms:select1 ref="tradeName" appearance="minimal"> <xforms:label class="fixed-width" model="mod-study-info" ref="$resources/tradeName"/> <xforms:itemset model="mod-study-info" nodeset="instance('study-info-resources')//xs:simpleType[@name='tradeName']/xs:restriction/xs:enumeration"> <xforms:label ref="for $currentItemName in @value return $resources//tradeName[@ref = $currentItemName]"/> <xforms:value ref="@value"/> </xforms:itemset> <xforms:action ev:event="xforms-value-changed"> <xforms:insert ref="instance('binding-control')//rebuild" value="something"/> <!-- the rebuild is what should make it work however it's actually the previous statement.... <xforms:rebuild model="mod-study-dashboard" /> --> </xforms:action> </xforms:select1>
For the final part of this example we are going to create a second select control which is populated based on the value of the first.
Here you can see that if we know the trade name (it’s not ‘Other’) then we can populate the manufacturer based on the mappings held in the resources file. If we don’t know then trade name then this functions just like a normal select box – the only thing necessary to set this up is to create a binding.
The trick to making this work as expected (in Orbeon 3.8) is to use the action specified above – it seems that the insert triggers a model rebuild (if the xforms:rebuild is called then it appears to calculate the binding based on the value of the select prior to the change rather than after the change)
<xforms:bind nodeset="manufacturer[not(../tradeName = 'Other')]" calculate=" for $currentItemName in ../tradeName return xxforms:instance('all-resources')//manufacturer[@tradeName = $currentItemName]/@ref " />
<xforms:select1 ref="manufacturer" appearance="minimal"> <xforms:label class="fixed-width" model="mod-study-info" ref="$resources/manufacturer"/> <xforms:itemset model="mod-study-info" nodeset="instance('study-info-resources')//xs:simpleType[@name='manufacturer']/xs:restriction/xs:enumeration"> <xforms:label ref="for $currentItemName in @value return $resources//manufacturer[@ref = $currentItemName]"/> <xforms:value ref="@value"/> </xforms:itemset> </xforms:select1>
The binding control instance is just a bit bucket used to trigger the rebuild
<xforms:instance id="binding-control"> <bc xmlns=""> <rebuild/> </bc> </xforms:instance>