Like the real Napster in the movie The Italian Job (the remake), XQuery might have a bit of a chip on its shoulder about the X in AJAX.
Sure, it stands for XML since the idea is that return an XML fragment to the browser to update content in a div, fill in form fields or even create drop down menu options on the fly.
But how do you create that XML? Using static XML files works, but the whole idea is to dynamically respond to user actions and give them information without reloading the whole page.
And what's the best way to dynamically create XML? XQuery of course!
To prove it, lets do a simple example to create a drop-down form field for Shakespeare's characters (using the Shakespeare XML we loaded in the tutorial). This field will auto-complete using AJAX and XQuery and give the user the characters found in the XML that begin with the letters entered into the field.
To get a head start, we'll use the popular AJAX tool Scriptaculous that takes care of all of the hard javascript stuff and lets us just work on creating the backend to deliver the content.
We'll also make use of MarkLogic Server's app server built-ins and its ability to run an HTTP server to make a complete application that presents the HTML, including Scriptaculous. To do this, we'll start with the /modules directory we created and accessed with WebDav in the tutorial (where the CQ application was placed).
Assuming you have that set up, here are the steps to get the client side set up:
- create a js/ directory and install all of the .js scriptaculous libraries that came with the distribution (located here)
- Make a lookup.css in /modules with the sample styles from this page
- create lookup.xqy under the /modules directory with the following HTML in it:
xdmp:set-response-content-type("text/html; charset=utf-8")
(: sets the mime type :)
,
<html>
<head>
<title>Shakespeare Lookup</title>
(: reference the stylesheet :)<link rel="stylesheet" type="text/css" href="lookup.css" media="screen"/>
(: get the scriptaculous scripts loaded - the " " is to prevent them being optimized into an
empty XML node like <script... /> which some browsers don't like :)<script src="js/prototype.js">{" "}</script>
<script src="js/scriptaculous.js">{" "}</script></head>
<body>
<h1>Shakespeare Character Lookup</h1><div>
<form>
(: create the placeholder for the autocomplete field :)
<input type="text" id="autocomplete" name="autocomplete_parameter"/>
<input type="submit" value="select character"/>
</form>
<div id="autocomplete_choices" class="autocomplete"></div>(: run the scriptaculous autocomplete :)
<script type="text/javascript">
new Ajax.Autocompleter("autocomplete", "autocomplete_choices",
"request.xqy", {{}});
</script>
</div>
</body>
</html>
If you are like me and learned HTML and javascript way way back, it may take you a moment to realize that the <input> element named 'autocomplete' is NOT what actually shows up in the browser. The script Ajax.Autocompleter replaces that element with the fully decked out, onClick enabled <input> element that does all of the auto-lookuping including making the calls to your request.xqy. You can create all this yourself . . . but Scriptaculous does it for you, so go ahead and enjoy it!
This should all result in a simple web page with a text field on it that you can get to at http://localhost:8002/lookup.xqy (or wherever your MarkLogic Server is installed). But it won't do anything until we create the backend XQuery.
In the Ajax.autocompleter code, we gave request.xqy as our source for the lookups. We need to create this in the /modules directory and it's contents can be something as simple as this:
(: get the value of the field - sent to us as a POST from the Scriptaculous autocompleter :)
let $query-base := substring-after(xdmp:get-request-body(),"autocomplete_parameter=")
(: add an '*' to it to create a wildcard search :)
let $query := fn:concat($query-base, "*")
return
<ul>{(: use the MarkLogic built-in cts:element-value-match()* to search all of the values in the PERSONA element in the loaded XML plays :)
for $item in cts:element-value-match(xs:QName("PERSONA"),$query)
return(: return the <li> elements scriptaculous expects for its list :)
<li>{fn:string($item)}</li>
}
</ul>
Yup - thats all there is to it: 9 lines of code to query some XML and return XML.
When all this is hooked up and running, you should have a mini-application that looks something like this:
It's now up to you how you will use the power of XQuery to create dynamic content elements for AJAX. Will you populate complex taxonomies and even bring back the content from leaf nodes? Will you create search interfaces that give you the answers in the form?
How about an amazing interface for searching XML content? Check out markmail.org, which is all XQuery and AJAX, for some inspiration.
So XQuery really must be the real X in AJAX, right?
Well it turns out there is a bit of room under the X in AJAX these days. JSON is a popular alternative to XML (and XQuery's got that covered - check out this library Jason Hunter wrote to generate JSON from XQuery) and there are lots of ways to generate both XML and JSON.
But for those of us in the know, XQuery is the only way to go. And with the growing number of XQuery powered content applications, no one can shut down the real X!
Matt
*NOTE: cts:element-value-match() is a MarkLogic built-in that requires an Element Range Index be configured for the element in question. This is pretty straight forward: select your database under the Databases tab in the MarkLogic admin interface (also covered in the tutorial). Under Element Range Index, select Add. For the scalar type select string, namespace can be blank and enter PERSONA for localname. This will create an index for ordering that can also be used to perform the character lookup.
*ALSO NOTE: there are plenty of other ways to get a list of PERSONA values based on user's input such as:
//PERSONA[cts:contains(., "p*")] (: still uses MarkLogic search built-ins :)
//PERSONA[fn:starts-with(fn:lower-case(string(.)), "p")] (: standard XQuery :)
But like scriptaculous, MarkLogic's search built-ins do the work for you (and also do it much more efficiently) so let's just enjoy using them too!