XForms/Selecting Codes From File
Motivation
editPeople who design forms frequently have to maintain many code tables. These code tables may change frequently and there may be processes that update a database of centrally maintained code tables. So you want each form to dynamically get the current relevant codes from an XML file or a RESTful web service.
This sample program demonstrates how to read a list of codes with labels directly from a file or code-table web service. In this example the file that contains the codes is just a well-formed XML file in the same directory that the form is located in.
Loading XML instance data from a local file or web service
editThe following code fragment, usually in the HTML head, demonstrates how to read XML data from a local file in the same directory as the form by using the "src" attribute of the instance.
Loading a Single Codes Table into a Single Instance
edit<html>
<xf:model>
<xf:instance src="XMLSchemaTypeCode.xml" id="XMLSchemaTypeCode"/>
</xf:model>
</html>
Loading Codes from a Web Service
editIt is common to have a single web service load all code tables. This service can be passed parameters such as the name of the code table as well as the group the person is a part of. This can narrow down the list of selections for long lists.
<html>
<xf:model>
<xf:instance id="ApprovalCodes" src="/db/mdr/services/all-codes.xq?code=ApprovalCodes&group=editor"/>
</xf:model>
</html>
Loading all Codes into a single Instance
editThe problem with loading individual code tables into individual instances is that you must perform a separate HTTP get for each code table. This is not a problem for forms with one or two small selection lists. But for large forms with many selection lists this can slow down form response time. The solution is to load all codes into a single instance with a single HTTP GET request. Each code table can then be selected from this instance.
<html>
<xf:model>
<xf:instance id="code-tables" src="/db/mdr/services/all-codes.xq?form=DataElementManager&group=admin"/>
</xf:model>
</html>
In this second example all the codes in the entire form are generated by a service of the Metadata Registry (MDR). The data returned by this server is a collection of codes for each select1 control in the form.
This will look for a well-formed XML file in the current directory and load it into the model instance.
Note that in this case the model is given an id. This is necessary to allow multiple code tables to each be read into their own separate model.
Screen Image
editHere is a screen image of the program. As the user selects from the drop-down list, the value of the output is immediately updated.
Note that label on the screen is not the same as the label stored in the model.
Sample Program
edit<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms" >
<head>
<title>Select List From File</title>
/* the default model */
<xf:model>
<xf:instance id="save-data" xmlns="">
<data>
<MyXMLSchemaTypeCode/>
</data>
</xf:instance>
<xf:instance id="code-tables" src="XMLSchemaTypeCode.xml"/>
</xf:model>
</head>
<body>
<p>This selection list was read from a file.</p>
<xf:select1 ref="/MyData/MyXMLSchemaTypeCode">
<xf:label>Select XML Schema data type: </xf:label>
<xf:itemset ref="instance('code-tables')/XMLSchemaTypeCode/item">
<xf:label ref="label"/>
<xf:value ref="value"/>
</xf:itemset>
</xf:select1>
<br/>
<xf:output ref="instance('save-data')/MyXMLSchemaTypeCode">
<xf:label>Value of MyXMLSchemaTypeCode: </xf:label>
</xf:output>
</body>
</html>
Sample XML Data
edit<?xml version="1.0" encoding="UTF-8"?>
<XMLSchemaTypeCode>
<label>XML Schema Type:</label>
<item>
<label>Date and Time</label>
<value>dateTime</value>
</item>
<item>
<label>Time (HH:MM:SS-06:00)</label>
<value>time</value>
</item>
<item>
<label>Date (yyyy-mm-dd)</label>
<value>date</value>
</item>
<item>
<label>Year and Month</label>
<value>gYearMonth</value>
</item>
<item>
<label>Year (nnnn)</label>
<value>gYear</value>
</item>
<item>
<label>Month and Day</label>
<value>gMonthDay</value>
</item>
<item>
<label>Day of Month (1 .. 31)</label>
<value>gDay</value>
</item>
<item>
<label>Month (1 .. 12)</label>
<value>gMonth</value>
</item>
<item>
<label>String</label>
<value>string</value>
</item>
<item>
<label>Boolean (true/false)</label>
<value>boolean</value>
</item>
<item>
<label>Base 64 Binary</label>
<value>base64Binary</value>
</item>
<item>
<label>Decimal (0.00)</label>
<value>decimal</value>
</item>
<item>
<label>Any URI</label>
<value>anyURI</value>
</item>
<item>
<label>Integer (...,-2,-1,0,1,2,...)</label>
<value>integer</value>
</item>
<item>
<label>Non-Positive Integer (...,-2,-1,0)</label>
<value>nonPositiveInteger</value>
</item>
<item>
<label>Negative Integer (...,-2,-1)</label>
<value>negativeInteger</value>
</item>
<item>
<label>Long (-9,223,372T .. 9,223,372T)</label>
<value>long</value>
</item>
<item>
<label>Int (-2,147,483,648 .. 2,147,483,647)</label>
<value>int</value>
</item>
<item>
<label>Short (-32,768 .. 32,767)</label>
<value>short</value>
</item>
<item>
<label>Byte (-128 .. 127)</label>
<value>byte</value>
</item>
<item>
<label>Non-negative Integer (0..N)</label>
<value>nonNegativeInteger</value>
</item>
<item>
<label>Positive Integer (1..N)</label>
<value>positiveInteger</value>
</item>
<item>
<label>Unsigned Long (0 .. 18,446,744T)</label>
<value>unsignedLong</value>
</item>
<item>
<label>Unsigned Int (0 .. 4,294,967,295)</label>
<value>unsignedInt</value>
</item>
<item>
<label>Unsigned Short (0.. 65,535)</label>
<value>unsignedShort</value>
</item>
<item>
<label>Unsigned Byte (0..255)</label>
<value>unsignedByte</value>
</item>
</XMLSchemaTypeCode>
Note that to be well-formed the XML file MUST contain a root data element that must match the node-set argument.
Extracting data from an XML REST web service
editYou can replace the instance src attribute with a path directly to a XML REST web service. For example, if you put all your code tables into a project resources collection the path would look like the following:
<xf:instance src="../resources/code-tables/PersonGenderCode.xml"/>
or if your system codes are stored in a single XML file and you have a wrapper XQuery
<xf:instance src="../resources/code-tables/get-codes-for.xq?element=PersonGenderCode"/>
Selection Lists From A Single Instance
editTo keep your forms fast, it is usually better to do a single HTTP GET operation for all your codes. Forms that have many selection lists will take a long time to load if each list does a separate HTTP GET operation. For large forms it is easy to get a 10x speedup in form load times.
Structure of Code Tables Instance
editThe following is a sample structure of all codes loaded into a single instance:
<xf:instance id="code-tables">
<code-tables>
<code-table>
<code-table-name>MyElementCode</code-table-name>
<items>
<item>
<label>Joe Smith</label>
<value>42</value>
</item>
<item>
<label>Sue Johnson</label>
<value>47</value>
</item>
<items>
</code-table>
<code-table>
<code-table-name>ColorCode</code-table-name>
<items>
<item>
<label>Red</label>
<value>1</value>
</item>
<item>
<label>Orange</label>
<value>2</value>
</item>
<items>
</code-table>
</code-tables>
</xf:instance>
Sample Select1
editOnce your data is loaded into your model, each of your select1 or select controls can get its data directly from the model using the xf:itemset element. Itemset works just like repeat and uses nodeset (not ref) to get all its values. Here is an example of how to get the itemset data from your code-tables instance.
<xf:select1 ref="instance('save-data')/MyElementName">
<xf:label>My Element:</xf:label>
<xf:itemset nodeset="instance('code-tables')/code-table[code-table-name='MyElementCode']/items/item">
<xf:label ref="label"/>
<xf:value ref="value"/>
</xf:itemset>
</xf:select1>
Note that the predicate [code-table-name='MyElementCode'] will only put the items for that code into the selection list.
Auto Generation of Code Tables from Metadata Registry
editBecause XML Schema does not contain information for presentation we use the following workflow as a "best practice".
- Store all label/value pairs in external code table files or get this information from a web service.
- For each form, create a web service that will generate a code-tables element for all the codes used in that form.
- Load the code-tables element into a separate instance in your model with an id of "code-tables".
- Select the code table out of that instance for each select1 or select element by getting a code table with that name.
Discussion
editWhen a group of complex forms that share common codes needs to be placed in their own directory and yet still link to a central directory of codes, you can also use relative code table linking. For example a sibling directory could be called "code-tables" and the src="MyCode.xml" statement could be modified to be src="../code-tables/MyCode.xml".
If you have an XML Schema for the form you can also extract a list of all enumerations for each simpleType. This list of codes can then be passed to a web service that creates a list of all the codes in your registry. This allows the form to automatically be updated when new codes are added to the XML Schema.
If you have a RESTful web service that contains all the codes for a form, you can replace this web service URI for the XML file.