Sling JSON Query Servlet + YUI TreeView

Wednesday, February 24, 2010

I have been working with version 5 of Day Software’s CQ WCM platform since it was released. One of the great things about developing on version 5.x is the rich set of frameworks and API’s at your disposal (Sling, JCR, etc.).

Requirements & Design

Recently, a client handed me requirements to create a CQ component that would allow web users to browse documents and folders in specific locations in the repository. Specifically, the client wanted to be able to expand folders and click on links to documents in an interface similar to windows explorer (The filesystem browser they were familiar with). Basically, we needed to put repository content in a tree, and of course, this is a web based interface, and content authors should be able to place this component on any page. I knew there were likely to be existing services or API’s within CQ to help complete this task. Basically, I had a feeling the right way would be quick and easy and the wrong way would consume lots of hours.

While researching an appropriate design, I came across the JSON Query Servlet, which is exposed in CQ thanks to Sling. There is some real power and flexibility here, since you can easily expose repository content to any client that can consume JSON. Also, you can easily customize your result sets using familiar JCR query syntax.

With the content challenge solved, I turned to YUI’s treeview to provide the tree browsing user interface. One of the things that made YUI’s tree widget a good fit, is that you can set a dynamic load property. This means when a user expands a node, the browser will make an ajax call to fetch the children and render the child nodes appropriately, which would will reduce the initial page load time if folders contain large amounts of documents. Additionally, you can add your own css to make parent nodes appear as folders, and leaf nodes appear as styled links, etc.

The Code

All that was left to do at this point is tie everything together. Below is some code of what this might look like in a CQ component’s JSP. This is a simplified version I whipped up to remove the branding and styles from our client.


<%@include file="/libs/wcm/global.jsp"%>
<link type="text/css" rel="stylesheet" href="http://yui.yahooapis.com/2.8.0r4/build/treeview/assets/skins/sam/treeview.css">
<script src="http://yui.yahooapis.com/2.8.0r4/build/yahoo-dom-event/yahoo-dom-event.js" ></script>
<script src="http://yui.yahooapis.com/2.8.0r4/build/animation/animation-min.js" type="text/javascript"></script>
<script src="http://yui.yahooapis.com/2.8.0r4/build/treeview/treeview-min.js" ></script>
<script src="http://yui.yahooapis.com/2.8.0r4/build/yahoo/yahoo-min.js"></script>
<script src="http://yui.yahooapis.com/2.8.0r4/build/event/event-min.js"></script>
<script src="http://yui.yahooapis.com/2.8.0r4/build/connection/connection_core-min.js"></script>
<script src="http://yui.yahooapis.com/2.8.0r4/build/connection/connection-min.js"></script>
<script src="http://yui.yahooapis.com/2.8.0r4/build/json/json-min.js" ></script>
<script type="text/javascript">

function buildTree() {
var tree = new YAHOO.widget.TreeView(”tree”);
tree.setDynamicLoad(loadNodeData);
var root = tree.getRoot();
tree.render();
}

function loadNodeData(node, fnLoadComplete) {
var nodePath = encodeURI(node.data);
var sUrl = “/content.query.json?queryType=xpath&statement=/jcr:root” + nodePath + “/* order by @jcr:name”;
var callback = {
success: function(oResponse) {
var jsonObject = YAHOO.lang.JSON.parse(oResponse.responseText);

for (var i = 0; i < jsonObject.length; i++) {
var tempNode = new YAHOO.widget.TextNode(jsonObject[i].name, node, false);
var path = jsonObject[i]['jcr:path'];
tempNode.data = path;
var type = jsonObject[i]['jcr:primaryType'];
if ( type == ‘dam:Asset’ || type == ‘nt:file’) {
tempNode.href = path;
tempNode.isLeaf = true;
tempNode.target = “_blank”;
}
}
oResponse.argument.fnLoadComplete();
},

failure: function(oResponse) {
oResponse.argument.fnLoadComplete();
},

argument: {
“node”: node,
“fnLoadComplete”: fnLoadComplete
},

timeout: 10000
};

YAHOO.util.Connect.asyncRequest(’GET’, sUrl, callback);
}
YAHOO.util.Event.onDOMReady(buildTree);
</script>

<div class=”yui-skin-sam” id=”tree”>
<ul>
<li class=”expanded” yuiConfig=’{”data”:”<%=properties.get(”rootfolder”,”/content/dam/geometrixx”)%>”}’><%=properties.get(”rootfolderlabel”,”Content Browser”)%></li>
</ul>
</div>

There are a couple interesting things in the code I should point out.


var sUrl = "/content.query.json?queryType=xpath&statement=/jcr:root" + nodePath + "/* order by @jcr:name";

This is the line where we assemble the URL which we will GET to assemble the children of the node that was clicked. As you can see, we can refine the JCR query here to limit the results by node type, order by date, etc.


<li class="expanded" yuiConfig='{"data":"<%=properties.get("rootfolder","/content/dam/geometrixx")%>"}'><%=properties.get("rootfolderlabel","Content Browser")%></li>

This is code which can interact with a CQ authoring dialog to set the starting point in the repository for the root node. I have kept things simple here, and just pointed to Geometrixx’s folder in the DAM. Below is a screenshot of a stylized version of the component.  Overall, I was very pleased with how easy it was to leverage existing CQ tools and YUI to create this custom component.

Screenshot

Top