Posted by Kas Thomas AUG 25, 2010
Posted in ajax, crx, crx gems, development, javascript, rest and sling Comment 1
In previous posts, I've shown how to load movie data into CRX and how to render data for individual movies via HTML, SVG, and PDF. What I'd like to do now is show how easy it is to build interactivity into an app using a bit of AJAX combined with Sling's support for RESTful XPath-based search.
It turns out that all we have to do to query the repository for, say, all nodes that have a value of "Hitchcock" under the property named "Director" is put together an XPath expression like
//*[jcr:contains(@Director,'hitchcock')]
and pass it to Sling in a URL that looks like:
http://localhost:7402/content.query.json?queryType=xpath&statement=//*[jcr:contains(@Director,'hitchcock')]
(assuming the repository is on port 7402 of localhost). This request will invoke a Lucene search of all nodes stored under the /content subtree. The results will come back as a JSON-formatted array:
[
{
"name": "notorious",
"jcr:path": "/content/films/notorious",
"jcr:score": 3331
},
{
"name": "under_capricorn",
"jcr:path": "/content/films/under_capricorn",
"jcr:score": 3331
},
. . .
]
This is perfect, because it means we can use the JSON data to populate a dropdown menu (a "select" control in an HTML form) showing the names of films; and we can arrange things so that when the user clicks a "Show Details" button, the form updates to show detail information (title, director, year, genre, actor, actress, etc.) for the film in question. To get the detail information, of course, we can perform a behind-the-scenes AJAX query to the server. I already showed, in a previous post, how to render detail information for a given movie in an HTML page. All we really need to do at this point is put that HTML page into its own iframe, and (right next to it) add search controls to the page.
The following form shows one possible way of handling things.

Basically, we have an HTML form in which there are two action buttons: One is a Search button ("Search Films by") that initiates an XPath-based search of the repository based on a user-chosen criterion of Title, Director, Year, Genre, Actor, or Actress. The other is a Show Details button, underneath a picklist of films. Clicking the Search button populates the picklist with hits. When the user chooses a hit from the list and clicks Show Details, the left side of the page updates with detail information.
The form consists of 200 lines of JavaScript and markup, as follows:
<head>
<script>
var CRX_BASE_URL = "http://localhost:7402";
function addEventListeners( ) {
document.getElementById( "_Query_" ).addEventListener(
"keypress", function( e ) {
if ( 13 == e.keyCode )
handleClick( null );
},
false );
document.getElementById( "_QueryButton_" ).addEventListener(
"click", handleClick, false );
document.getElementById( "_Fetch_" ).addEventListener(
"click", handleFetch, false );
}
function getSearchMode( ) {
return document.getElementById("_Select_").value;
}
function handleFetch( e ) {
var list = document.getElementById("_Hits_");
if (list.value) {
var url = CRX_BASE_URL + list.value + ".html";
var iframe = document.getElementsByTagName("iframe")[0];
// force a reload of the iframe:
iframe.src = url;
}
}
// get user's input and call server
function handleClick( e ) {
var userData =
document.getElementById( "_Query_" ).value;
if ( !userData )
return; // nothing to do
var CRX_QUERY_PATH = "/content.query.json?queryType=xpath&statement=";
var GETheader = {
"Accept": "application/json",
};
var query = createXPathQuery( userData );
var url = CRX_BASE_URL + CRX_QUERY_PATH + query;
myHttpGet( url, GETheader, handleResponse ); // hit server
}
function myHttpGet( url, header, handler ) {
try {
request = new XMLHttpRequest();
request.open("GET", url, true);
for (i in header)
request.setRequestHeader( i, header[i] );
request.onreadystatechange = handler;
request.send("");
}
catch(e ) {
alert("Problem sending request: " + e.toString());
}
}
function handleResponse( ) {
if (request.readyState == 4) {
showResults( request );
}
}
function showResults( request ) {
var json = request.responseText;
var hits = eval ( json );
if ( null == hits ) {
alert( "No hits were found." );
return;
}
display( hits );
}
function display( hits ) {
var div = document.getElementById( "_Hits_" );
if ( null == div )
throw( "Problem getting div for hitlist." );
showHitCount( hits.length );
var markup = "";
for (var i = 0; i < hits.length; i++) {
markup += "<option value=\"" + hits[i][ "jcr:path" ] + "\">";
markup += fixName( hits[i].name );
markup += "</option>";
}
div.innerHTML = markup;
}
function fixName( name ) {
var tmp = name.split("_");
for (var i = 0; i < tmp.length; i++)
tmp[i] = capitalize( tmp[i] );
return tmp.join(" ");
}
function capitalize(a) {
return typeof a[0] == 'undefined'?
"":a[0].toUpperCase() + a.substring(1);
}
function showHitCount( numberOfHits ) {
var div = document.getElementById( "_hitcount_" );
if ( null != div )
div.innerHTML = ("Total hits: " + numberOfHits).italics();
}
// build xpath query url
function createXPathQuery( userString ) {
var xpathTerms = [];
var querySemantics = " and ";
// trim leading & trailing spaces off query
var terms = userString.replace(/^\s+/,"").replace(/\s+$/,"");
// split on whitespace
terms = terms.split(/\s+/);
var _queryBasis = "//*[_#_]/@location";
var mode = getSearchMode( );
for ( var i = 0; i < terms.length; i++ )
xpathTerms.push( "jcr:contains(@" + mode + ",'" + terms[i] + "')" );
var query = _queryBasis.replace( '_#_', xpathTerms.join( querySemantics ) );
return query;
}
</script>
</head>
<body onload="addEventListeners()">
<iframe width="380" height="410" style="border:none" src="http://localhost:7402/content/films/wild_at_heart.html"></iframe>
<span style="font-size:small;position:absolute;right:80px;top:7px;">
<input type="text" id="_Query_" size="25"/>
<input type="button" id="_QueryButton_" value="Search Films by:"/>
<select id="_Select_">
<option value="Title">Title</option>
<option value="Director">Director</option>
<option value="Actor">Actor</option>
<option value="Actress">Actress</option>
<option value="Year">Year</option>
<option value="Subject">Genre</option>
</select>
<br/>
<select id="_Hits_" size="10"></select><div id="_hitcount_"></div>
<br/>
<input type="button" id="_Fetch_" value="Show Details"/>
</span>
<div id="hitlist"></div>
</body>
</html>
This form (movieForm.html), along with the data for 1700 films (and scripts and PDF files discussed in prior posts), is available in the zip file below, which can also be downloaded from Day Package Share. After installing the package, go to http://localhost:7402/apps/films/movieForm.html to see the form in action (assuming your CRX is on port 7402).




