XForms over REST with Marklogic

Researching for an up and coming Marklogic project, I’ve been looking at the REST interface. I’d set one last month up on the laptop so that I could test whether we could push data in from our Apache Camel engine. That worked nicely and the thought occurred to me that I might be able to put an XForms interface directly over the top of the REST endpoints – a ‘one-tier’ application so to speak.

UntitledFor my demo, I took the idea of a herd book rather than the canonical name/address game.
For those who don’t keep animals, a herd book is simply a database of animals you have and what happens to them in terms of breeding, medicines etc. The very simplest ‘toy’ one  you might need for alpacas might be as shown here.

Setup: The Same Origin issue.

To set up for my experiment, all I did was alter my working REST interface in Marklogic admin, so that the ‘modules-database’ was on the file-system. That gave me somewhere tUntitledo put my XForm and the xsltforms library to drive it. I could of course have created a new Application Server on a different port, but that would have put the REST endpoint in a different domain and hence triggered same-origin security.

In a real application you’d use a proxy or CORS via headers or some such, but for this it was simply easiest to run it all together (this came to bite me later in searching though).

Creating documents via POST

First stop, now I had somewhere for my form to live was to get it to store a document. In REST, the endpoint for this lives at /v1/documents where you can PUT a document into the system at an address URI. However, that requires you to crete URIs and often you don’t really care what a new document is actually called, you just need a unique name. Marklogic has a nifty solution to this; you can POST a document instead and get your URI generated for you in a directory you provide (you can read about it here). In essence, you POST the data and you get a header back saying what it got called. Translated into XForms that gives this sort of <submission/>

 <xf:submission id="newanimal"
 method="post" replace="none" separator="&amp;" ref="instance('animal')">
 <xf:action ev:event="xforms-submit-done">
     <xf:setvalue ref="instance('control')/uri"
 value="substring-after(event('response-headers'),'=')" />
         <xf:output value="event('response-reason-phrase')" />

Here I POST my form data, in ‘animal’ to REST and give it the target directory. I then use the ‘xforms-submit-done’ event to read the return header out via the event mechanism and put it in a ‘control’ instance, so I know the URI for editing. Success!

Updating documents via PUT

The simplest way to update a document is often just to write over it with a PUT to the same URI. That’s fine for documents if they’re small enough but it’s worth saying that the Marklogic REST interface also supports Partial Updates to both the document and the meta-data which might be better in some cases. Getting back to this form though, now I have a URI, if I update a document  I can simply replace the data with the following <submission/>:

<xf:submission id="upcase"
 method="put" replace="none" separator="&amp;" ref="instance('animal')">
<xf:resource value="concat('v1/documents?uri=',instance('control')/uri)"/>
<xf:action ev:event="xforms-submit-done">
        <xf:output value="'Updated'" />

This is straightforward apart from the fact that I have to use an in-line <xf:resource/> element to construct the url with the URI I stored earlier. {Note to self. AVT?]

Searching documents

The last step in this little demo is getting lists of animals, searching etc. This bit took the most time due to a problem with how search works. In Marklogic, the modules-database gets used to store all the search options. Now this is great; you can pre-set a load of search templates, give them a name and put that in the search url in ‘options’. However, if you’ve hijacked the modules database for your code, well, you can’t. This is a pain because it doesn’t in my case allow you to override the snippet settings to get particular fields back. Now I thought that was that, until I found out that ML7 had the ability to send <options/> with a Structured Query. (thanks to @adamfowleruk).

Turns out all I needed for search was to POST a Structured Query format <instance/> using the accompanying <submission/> like so:

<xf:instance id="squery">
<search xmlns="http://marklogic.com/appservices/search">
 <transform-results apply="metadata-snippet">
 <element name='name' ns=''/>
 <element name='dob' ns=''/>
 <element name='colour' ns=''/>

<xf:submission id="list all" action="v1/search?directory=/my/herdbook"
omit-xml-declaration="yes" />

That gets me all the alpacas with their name,colour and dob so I can display them in a list. Note in this example the <query/> section is empty as I want every alpaca. In a real app, of course I could add something like a range query to get a particular animal linked to a search box. A bit of judicious code in some triggers and I can paginate using header data from the reply as well.


Lastly, is a <action/> in the  list so that each row in the results triggers a fetch of that document so I can edit it if I want. Given the URI is in the search results, I simply need to copy it over to become my ‘current’ URI and send the instance using GET to /v1/documents. At the moment I send category=content (also the default) as I want the document, but I could expand that to category=meta easily to start working with the meta-data instead.

More to do?

UntitledThat’s pretty much it so far. I haven’t time currently to work this up into a full example at the moment, but it seems a very handy way to quickly put an XForms interface over REST, even if there are compromises, such as making the search a little harder.
Of course I’ve used Marklogic for this, but in theory it’s an approach that could work works with any REST system as long as it will take POST data in XML.
As to whether this is a viable way to write an application? I’d say probably yes for a prototype or something simple, especially as you get the REST endpoints thrown in for free.