I used three different methods for implementing XForms with eXist (I am currently only using #3).
- Orbeon Presentation Server
- An XQuery passed through a cocoon pipeline which transformed the XML results using an XSL stylesheet. The edited documents were saved to eXist using REST put.
- An XQuery that first authenticates the user and then outputs an xform using the retrieved document as the XForms instance and using REST put for submission.
I looked at Orbeon about 6 months ago, it seems like a great product, especially in light of the poor browser support for XForms. If I needed to make a commercially viable XForms application I would probably spend some more time playing with Orbeon. However, I felt that Orbeon added yet another layer of complication to my application and it was not necessary in our situation because our XForms are being used in a controlled environment (and I don’t have more time). I can be assured that everyone using the editing interface will be using Firefox, not a great solution, but so far so good.
I generally prefer XSL to XQuery, so my XQueries have, in the past been pretty simple, returning very raw results to an XSL stylesheet. My first attempt at XForms used XQuery to call the XML document to be edited (or to create a new document). Then I had a cocoon pipeline to specify the XSL stylesheet for transformation and the serialization option.
<map:match pattern="getDC.xq">
<map:generate src="getDC.xq" type="xquery"/>
<map:transform src="stylesheets/dcForms.xsl"/>
<map:serialize type="xhtml11"/>
</map:match>
In the main sitemap.xmap file (included with exist), xhtml11 is the serializer that will output mime-type=”application/xhtml+xml”.
The XSL stylesheet then selects the node I’m interested in and uses xsl:copy to populate the XForms instance. The submission element uses put to replace the existing document with the edited document.
<xf:model>
<xf:instance id="metadata">
<xsl:copy-of select="/child::*"/>
</xf:instance>
<xf:submission id="submit" method="put" replace="all">
<xsl:attribute name="action">
<xsl:value-of select="concat
('/exist/servlet/db/mets/collections/',
$filename,'.mets.xml')"/>
</xsl:attribute>
</xf:submission>
</xf:model>
One of the features that I found the most useful in XForms is the ability to repeat elements as needed. Here is an example from a very simple form that we used to edit Dublin Core records that uses XForms repeat. This field populates the dc:subject element and adds a type attribute selected from a drop down menu. New subject elements are added or deleted when the users click the add or delete buttons created by xf:trigger. I use xf:setvalue to create each new element as a blank element, otherwise the element will simply copy the data from the first instance of the element.
<xf:repeat id="repeat.subject" nodeset="//dc:subject">
<xf:input ref=".">
<xf:label>Subject Headings:</xf:label>
</xf:input>
<xf:select1 ref="@type">
<xf:label>Type: </xf:label>
<xf:item>
<xf:label>topic</xf:label>
<xf:value>topic</xf:value>
</xf:item>
<xf:item>
<xf:label>name</xf:label>
<xf:value>name</xf:value>
</xf:item>
</xf:select1>
<xf:trigger class="delete" appearance="minimal">
<xf:label>Remove</xf:label>
<xf:action ev:event="DOMActivate">
<xf:delete nodeset="instance('metadata')//dc:subject"
at="index('repeat.subject')"/>
</xf:action>
</xf:trigger>
<xf:trigger class="add" appearance="minimal">
<xf:label>Add a subject field</xf:label>
<xf:action ev:event="DOMActivate">
<xf:insert nodeset="instance('metadata')//dc:subject"
at="index('repeat.subject')" position="after"/>
<xf:setvalue ref="instance('metadata')//dc:subject[last()]" value=""/>
</xf:action>
</xf:trigger>
</xf:repeat>
This method works fine, but I as stated in an earlier post I wanted to route all my metadata processing XQueries through a password authenticating XQuery. This query calls a series of metadata administrative tasks, including the XForms.
I added the following namespaces for XForms to my xquery:
declare namespace xf="http://www.w3.org/2002/xforms";
declare namespace ev="http://www.w3.org/2001/xml-events";
And the exist:serialize option:
declare option exist:serialize "method=xhtml media-type=application/xhtml+xml";
The XForm is contained in a function called by an authenticating function. I made very few changes to the XForm code I had been using in my XSL stylesheets to get it working when called by the XQuery.
Here is a round-up of some of the resources I have found to be most useful in building these forms:
- eXist mailing list – search the archives for XForms
- eXist – XQuery examples
- Cocoon website (pipelines)
- XForms tutorial – Adrian de Jonge’s blog
- XForms – Tutorials and Cookbook – Wikibooks
- XForms for HTML Authors – W3C
- O’Reilly XForms Essentials by Micah Dubinko