The XForms cookbook

This handbook contains a collection of tips, tricks and patterns to help move your XForms programming forward. You don't need to be a master programmer to adapt them for your own use, but you will need to have read the Introduction to XForms to understand them.

Allowing typed data elements to be valid when empty

To indicate that some element is valid even if it is empty, we can use the XSD Schema attribute xsd:nillable in the schema definition. To illustrate, say we wanted to have some data that must be a decimal number between 0 and 5. But additionally, if the user doesn't provide a value, we don't want this to break validity. In XML Schema we would do the following (note how nillable is an attribute of xsd:element, not of the data type being created):

<xsd:element name="some-elem" type="myType" nillable="true" />

<xsd:simpleType name="myType">
  <xsd:restriction base="xsd:decimal">
    <xsd:minInclusive value="0" />
    <xsd:fractionDigits value="5" />
  </xsd:restriction>
</xsd:simpleType>

Although this might seem quite a bit of work, it is still more logical than the technique that is often used by newcomers to XForms and schemas, which is to create a type that is a union of some data type and the empty string. This technique not only requires us to keep adding new unions whenever we want an element that can be empty, but it is also unlikely to be a reflection of the underlying logic.

To illustrate of why a simple union is often incorrect, consider a ticket booking system. If I book a single--or one-way--ticket, there is no return date for my trip--it's 'empty' or nil. This is very different to saying that my return date is 'a date of zero length', which is what is implied by the approach that creates a new type which allows the empty string.

Using xsi:nil

Once an element has been set up with @xsd:nillable in the schema, then if it is ever empty, it won't break schema validation provided that it has the attribute xsi:nil set to a value of true:

<some-elem xsi:nil="true" />

This is extremely useful from an XForms point of view, since we can manipulate this attribute in our instance data just like any other data. We could, for example, link it to a 'checkbox' to indicate whether the element is nil or not. So, in a form asking a user for their travel dates, we could use xsi:nil to indicate whether the user wants a return ticket, and then hide the return date control if they don't (see Example 1). Conversely, we could also use a calculation to automatically set the element to nil (by settting the attribute to true) if the element is empty (see Example 2).

Examples

Example 1

This example asks the user for two dates for a journey--the outward and return parts of the trip. If the customer only wants a single, or one-way ticket we set the value of the return date to nil.

First we set up the model with instance data:

<xf:model>
  <xf:instance id="my-order">
    <order xmlns="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <travel-date>2005-05-07</travel-date>
      <return-date xsi:nil="false">2005-05-07</return-date>
    </order>
  </xf:instance>

Next we indicate the data type of the two dates, and set the return date to only be displayed to the user if the xsi:nil attribute is true:

  <xf:bind nodeset="travel-date" type="xsd:date" />

  <xf:bind nodeset="return-date" type="xsd:date" relevant="boolean-from-string(@xsi:nil)=true()">
    <xf:bind nodeset="@xsi:nil" type="xsd:boolean" />
  </xf:bind>
</xf:model>

The user interface for this model is quite straightforward. All we need are normal input controls for each of the three pieces of information we need to collect, and XForms does the rest for us. If the user indicates that they want a return trip (i.e., sets the xsi:nil attribute to true via the check-box) then the second date input is shown:

<xf:input ref="travel-date">
  <xf:label>Travel Date:</xf:label>
</xf:input>

<xf:input ref="return-date/@xsi:nil">
  <xf:label>Return trip?</xf:label>
</xf:input>

<xf:input ref="return-date">
  <xf:label>Return Date:</xf:label>
</xf:input>

Example 2
Whilst the previous example allows the user to set the nillable value explicitly, this example works the other way round--if the value of an element is '' (the empty string) then the element is set to nil, ensuring that it will pass schema validation.



xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
xsi:type="xsd:nonNegativeInteger">0


Navigating recordsets

View tutorial.

This tutorial will be moved from www.formsplayer.com.

Using repeat with large nodesets

There are a number of situations where we need to use repeat over a large nodeset, but don't want the performance hit of rendering hundreds of items that are not 'in view'.

One solution is to use the number attribute on repeat which tells formsPlayer to only show a specified number of items. This is fine for many situations, but the problem is that when selecting an item in the repeat using setindex, the XForms specification only requires that this item becomes 'visible'--it doesn't say where.

For example, if you have a nodeset of 20 items, and want to allow the user to interact with them in 4 pages of 5, you could do this:

<xf:repeat id="rpt-items" number="5" nodeset="items/item">
  ...
</xf:repeat>

To move to another 'page' you set the index to whatever value is required:

<xf:trigger>
  <xf:label>Next &gt;</xf:label>
  <xf:action ev:event="DOMActivate">
    <xf:setindex repeat="rpt-items" index="index('rpt-items') + 5" />
  </xf:action>
</xf:trigger>

The problem here is that formsPlayer will only do enough to bring the next item into 'frame', but it won't bring an entire new page into view. For example, if the repeat is currently showing rows:

1 2 3 4 5

clicking the 'next' button above will give us the following rows:

2 3 4 5 6

There are other problems with this approach, such as the need to specify the page size in two places, and all in all we need something more powerful, and over which we have more control. A better set-up can be created by using instance data and a bind statement.

Let's first establish the instance data that we want to work with in the repeat:

<xf:model id="mdl-main">
  <xf:instance id="inst-data">
    <instanceData xmlns="">
      <items>
        <item>apple</item>
        <item>pear</item>
        .
        .
        .
        <item>kiwi fruit</item>
      </items>
    </instanceData>
  </xf:instance>

Now we need some more data to allow us to keep track of the position and page size:

  <xf:instance id="inst-control">
    <control xmlns="">
      <position>1</position>
      <page-size>5</page-size>
    </control>
  </xf:instance>

Now we create a bind statement that uses this control data to select a subset of the items in the data instance; the selection begins at position, and contains page-size nodes:

  <xf:bind
   id="bnd-items"
   nodeset="
     instance('inst-data')/items/item[
       position() &gt;= instance('inst-control')/position
       and
       position() &lt; instance('inst-control')/position
        + instance('inst-control')/page-size
     ]
   "
  />

This bind will create a nodeset that only contains one 'page' of nodes, selected from the larger list of nodes in items/item. All we need to do now is to allow the position value to be adjusted, which is easily done with two event handlers:

  <xf:action ev:event="my-prev-page">
    <xf:setvalue ref="instance('inst-control')/position"
      value=". - ../page-size" />
    <xf:rebuild model="mdl-main" />
  </xf:action>

  <xf:action ev:event="my-next-page">
    <xf:setvalue ref="instance('inst-control')/position"
      value=". + ../page-size" />
    <xf:rebuild model="mdl-main" />
  </xf:action>
</xf:model>

(We also need to prevent position from going out of range--this can be done either with a bind/@calculate or a conditional action in the event handler.)

To make use of this configuration we use the nodeset in a repeat:

<xf:repeat id="rpt-items" bind="bnd-items">
  ...
<xf:repeat>

and then provide triggers to move forwards and backwards through the 'pages':

<xf:trigger>
  <xf:label>&lt; Prev</xf:label>
  <xf:action ev:event="DOMActivate">
    <xf:dispatch target="mdl-main" name="my-prev-page" />
  </xf:action>
</xf:trigger>

<xf:trigger>
  <xf:label>Next &gt;</xf:label>
  <xf:action ev:event="DOMActivate">
    <xf:dispatch target="mdl-main" name="my-next-page" />
  </xf:action>
</xf:trigger>

XForms and OpenWFE - Part 1

The following information is taken from a document that was created for an x-port.net client, eVision for their XRAI application, which is an online doctoral fellowship award system.

The information was intended as an introduction to OpenWFE for new developers to the project and gives a high level overview of the main OpenWFE components and how they are used within the XRAI project.

OpenWFE

For the workflow engine component of the Xrai system we have chosen to use OpenWFE, an open source multi-platform project, written in Java. OpenWFE can run as a complete standalone application or with some of its components running in a servlet container, which is the configuration in the Xrai environment, using Apache Tomcat.

Components

There are five main components that make up OpenWFE, the Engine, Worklist, APRE, Droflo and Webclient. For the Xrai environment the Droflo and Webclient components are deployed in Tomcat as .war files and, in the x-port Windows based development environment, the Engine, Worklist and APRE are configured to run as services using the Java Service Wrapper. The eVision development environment is running Linux.

Definitions of what each of the components do in the workflow engine are available in the OpenWFE docs and the general architecture section of OpenWFE is available
here.

Workflows

In the Xrai environment we use Droflo via the browser to design the workflow that users will follow and the Webclient webapp to test the developed flow. Note that it is not necessary to use the Droflo application to create the workflow, you can directly create an XML file by hand if you prefer.

Please take a look at the workflow definition syntax.

Once we are happy with the flow definition we then develop an XForm that interacts with the flow via the OpenWFE REST interface.

To make a developed workflow available to an XForm, we make it available to be accessed via HTTP. To do this we use another Tomcat webapp, 'workdefs', that serves up the workflow definition files.

Users (participants)

To interact with a workflow we have to have users. Users are stored in an XML file by default in OpenWFE but can be configured to be stored in a relational database instead, which is the case in the Xrai setup (we use MySQL). This enables us to create users dynamically using a web service called from our XForm.

In the Xrai system we've created roles that users are assigned to, applicant and referee. When a user is created they are either assigned the applicant or referee role which affects the information the created user is allowed to see and actions they are able to perform in/on the workflow (constraints are defined in the workflow definition itself). There are many configurations of users/roles and stores that can be used in OpenWFE. Read more about them in the online documentation.

Stores

The OpenWFE has one or more stores that contain workitems for users. Stores can configured a number of ways, from a store per user to a single store for all users depending on the strategies configured for them. In the Xrai system we only have one store for all users so that when a new user is created we don't have to create a store also. Again, check the online documentation.

XForm/OpenWFE Interaction

Once the workflow has been defined and is available via HTTP we are then able to write the XForm to use the workflow over the web. The OpenWFE engine has available a REST interface for this reason. Have a look at the documentation for the REST interface.

In the XForm we use a series of GET and POST XForm submissions, combined with xforms-submit-done/xforms-submit-error events to call the REST functions that process our workflow.

Links

XForms and OpenWFE - Part 2

So far (in part 1) we have looked at the general system architecture and also two major components that make up the Xrai environment. The idea of this next article is to describe in some more detail how the XForm interacts with those components.

The worklist

The form is designed to work in a way such that a user will navigate to the XForm in their browser and, after creating (registering) a username/password, will fill in their details. After correctly submitting their details the user is met with their worklist. The worklist is made up of 3 sections describing which tasks they are allowed to launch, currently launched tasks and also a task history. All of the information that the user sees in the worklist is the result of queries to the open workflow engine.

The first section of the form, 'Launch a New Process', is retrieved from the workflow engine by a GET HTTP request, to the REST interface, that asks the workflow engine for a list of items that this user has been granted permission to launch.

An example URL for this would be:

http://hostname:5080/worklist/Store.sshrc?session=1157983906796&action=listLaunchables&time=2006%2D09%2D11T14%

The HTTP response from the workflow engine being (for an applicant user):

<?xml version="1.0" encoding="ISO-8859-1"?>
<launchables>
	<launchable engine-id="mainEngine" url="http://localhost:8080/workdefs/sshrc-apply-for-doctoral-fellowship__..."/>
	<launchable engine-id="mainEngine" url="http://xrai.x-port.net:8080/workdefs/launchitems/sshrc-apply-for-... />
</launchables>

The contents of the HTTP response are then used in the XForm as instance data that an XForms repeat control binds to and is then displayed to the user.

The second section of the worklist, 'Open Current Process', is retreived by exactly the same process, except this time the request to the REST interface will be something like:

http://hostname:5080/worklist/Store.sshrc?session=1157983906796&action=getHeaders&limit=30&time=2006%2D09%

The 'action=getHeaders' part of the request asks OpenWFE to return some XML that is a list of currently in progress work items for a user. An example of the response would be:

<?xml version="1.0" encoding="ISO-8859-1"?>
<headers>
	<header last-modified="2006-08-18 14:41:11+0100" locked="false">
		<flow-expression-id owfe-version="1.7.0b" engine-id="mainEngine" initial-engine-id="mainEngine" workflow-...
			<attributes>
				<smap>
					<entry>
						<key>
							<string>__wfi_id__</string>
						</key>
						<value>
							<string>1155805432546</string>
						</value>
					</entry>
					<entry>
						<key>
							<string>__wfd_name__</string>
						</key>
						<value>
							<string>SSHRC Apply for Doctoral Fellowship Flow</string>
						</value>
					</entry>
					<entry>
						<key>
							<string>__wfd_revision__</string>
						</key>
						<value>
							<string>1.1</string>
						</value>
					</entry>
					<entry>
						<key>
							<string>__wfd_url__</string>
						</key>
						<value>
							<string>http://localhost:8080/workdefs/sshrc-apply-for-doctoral-fellowship__1.0.xml</string>
						</value>
					</entry>
					<entry>
						<key>
							<string>__dispatch_time__</string>
						</key>
						<value>
							<string>2006-08-18 14:41:11+0100</string>
						</value>
					</entry>
					...
				</smap>
			</atributes>
		</flow-expression-id>
	</header>
</headers>

Again this is used in the XForm instance data, with a repeat control for display to the user.

The two requests to the workflow engine are periodically run using a timed Javascript function so that the listed launchable items and current items are updated (and the underlying instance data). This means that if a user has a work item proceeded to them it will show up almost instantly in their lists.

Current work item

When a user clicks on one of their 'in progress' work items another HTTP request is sent to the REST interface. This asks OpenWFE for all the details of a particular work item, it's state, and any other values that are associated with it.

The POSTed request is something like:

http://hostname:5080/worklist/Store.sshrc?session=1157983906796&action=getWorkItem&time=2006-09-11T14:36:38+

The body of the POST being something like:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<flow-expression-id
	owfe-version="1.7.0b"
	engine-id="mainEngine"
	initial-engine-id="mainEngine"
	workflow-definition-url="http://localhost:8080/workdefs/sshrc-apply-for-doctoral-fellowship__1.0.xml"
	workflow-definition-name="SSHRC Apply for Doctoral Fellowship Flow"
	workflow-definition-revision="1.1"
	workflow-instance-id="1155746373218"
	expression-name="participant"
	expression-id="0.0.42.0">
</flow-expression-id>

The response that comes from OpenWFE is a chunk of XML that is used as XForm instance data. The contents of the response include status of documents in the folder, URL's of where to obtain extra instance data, values that are used to implement business rules and also historical information of the work item itself. Full details can be found in the OpenWFE documentation.

An example of the returned XML is:

<?xml version="1.0" encoding="ISO-8859-1"?>
<workitem last-modified="2006-08-18 15:19:48+0100" participant-name="xrai-user-alex10" dispatch-time="2006...>
	<attributes>
		<smap>
			<entry>
				<key>
					<string>applicant_name</string>
				</key>
				<value>
					<string>alex10</string>
				</value>
			</entry>
			<entry>
				<key>
					<string>applicant_email</string>
				</key>
				<value>
					<string>Not used yet</string>
				</value>
			</entry>
			<entry>
				<key>
					<string>__subject__</string>
				</key>
				<value>
					<string>Application for Doctoral Fellowship</string>
				</value>
			</entry>
			<entry>
				<key>
					<string>status_document_eligibility</string>
				</key>
				<value>
					<string>complete</string>
				</value>
			</entry>
			<entry>
				<key>
					status_document_cv</string>
				</key>
				<value>
					<string>started</string>
				</value>
			</entry>
			...
		</smap>
	</attrbiutes>
</workitem>

Further calls to the OpenWFE REST interface are used in the form to carry out further actions on a work item such as proceeding the work item to the next stage, updating values for 'variables' in the flow and locking the work item. The recommended reading is the OpenWFE REST interface online documentation.

Saving/loading Xrai documents to eXist

When a user partially completes a document and has decided to save their progress, we save the document instance data in the the eXist database (POSTed to the eXist REST interface). At the same time as saving to the database we also update and save the workitem instance data (via the REST interface again) with the URL that can be used to retrieve the document instance data via the eXist REST interface. As you can see in the previous section's XML there is an element 'entry/key/string' with the value 'url_document_eligibility'. To go with the 'entry/key/string' element there is also 'entry/value/string' which contains a URL to where the partially complete instance data for the Eligibility Assesment document can be found (this is test data and there is no actual data located at that URL).

When the user loads the work item the XForm checks for any document URLs in the work item and, when it finds one, sends a submission to eXist that retrieves the instance data to replace the default data. The result is that the user then sees the form controls populated with the data they previously entered.

Again, it's advisable to read up on the OpenWFE and eXist REST interfaces. See links below.

Summary

Although this article describes the usage of eXist and OpenWFE in the SSHRC XForm it does not explain how the submissions to the REST interfaces are triggered. Further reading about XForms events and also the if/while/iterate extended XForms functionality is required. See the 'Links' section below.

Links

XForms patterns

This section looks at common patterns that occur when programming with XForms. By documenting patterns for reuse we can speed up the development process and reduce errors.

Initialising a document on first use of a form

Use case

A model has some instance data which is loaded from a particular URL, and which the user can edit during the course of using the form. However, when the form is first used, an attempt to load this data will fail since the document won't yet exist. In this situation we would like to use some inline instance data to initialise the document.

Description

Two submissions are created in the model, one that performs a get and the other a put, and both using the same document URL for their @action value. The put submission can be used at any time to save the instance data, such as after the data has changed, or periodically to ensure no data is lost.

When the model is first loaded (i.e., on notification of xforms-ready), the get submission is attempted. If it is successful then it means that there was already existing data available, and a load event is dispatched to the model.

If the get submission fails it means that this is the first time that the form has been run, and so the put submission is used. Since this submission simply saves the contents of the instance then it will have the effect of saving the initialising data.

Example Useage

<xf:model id="mdl-config">
  <xf:instance id="inst-config">
    <instanceData xmlns="">
      <favourites />
      <settings />
    </instanceData>
  </xf:instance>

  <xf:submission
   id="sub-get-inst-config"
   method="get"
   ref="instance('inst-config')/ANodeThatDoesNotExist"
   action="my-data.xml"
   replace="instance" instance="inst-config"
  >
    <xf:action ev:event="xforms-submit-error">
      <xf:send submission="sub-put-inst-config" />
    </xf:action>
  </xf:submission>

  <xf:submission
   id="sub-put-inst-config"
   method="put"
   ref="instance('inst-config')"
   action="my-data.xml"
   replace="none"
  />

  <xf:action ev:event="xforms-ready">
    <xf:send submission="sub-get-inst-config" />
  </xf:action>
</xf:model>

Template

The proposed template to make use of this pattern is to use a combination of @src and inline instance data:

<xf:model id="mdl-config">
  <xf:instance id="inst-config" src="my-data.xml">
    <instanceData xmlns="">
      <favourites />
      <settings />
    </instanceData>
  </xf:instance>
</xf:model>