Sling JSON Query Servlet + YUI TreeView

Wednesday, February 24th, 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

posted by John Kraus

Exadel Flamingo and Seam

Tuesday, December 15th, 2009

Earlier this year, I spent some time working on an application that provided CRM functionality to a client that provides teleconferencing services. This was a straight forward, data driven application implemented with JBoss Seam. You can read more in this case study.

Fast forward a few months. Sten Anderson and Bill Burlien told me they would be adding some geographic and financial charting and graphing screens to the application using JavaFX. I thought this was a great example of increasing our client’s productivity using an RIA technology. I wished them luck and looked forward to seeing the results. Not so fast, they asked me to modify the existing Seam application to produce binary responses so their JavaFX code could consume it on the client side.

It turns out this is a very easy task thanks to Exadel’s Flamingo product. Basically, flamingo allows you to expose Seam actions to RIA consumers, specifically Flex and JavaFX. And it is as simple as including a dependency and touching a couple of configs. Exadel has documented the steps very well here. However, early on, we ran into deserialization errors when sending collections of domain objects over the wire. We worked around this by executing native queries, in the JPA sense of the phrase, when returning collections. This worked very well, as did returning single results of domain objects. Bill and Sten deserve a lot of credit for being able to plug this data into meaningful and rich representations on the JavaFX side. I, like many others, believe in the growth potential RIA technologies have in the enterprise. The cost benefit will be realized in increased productivity by providing rich and intuitive user interfaces in business applications. It was fun to work on a project that achieved these goals

We demoed some of the early functionality at JBoss World in Chicago. Max Katz from Exadel wrote up a quick post about it. Check out the screenshot below. I am looking forward to more great features from the Flamingo product and seeing how we can leverage new features!

PRAdventPulse

posted by John Kraus

Alfresco Code Camp in Chicago

Thursday, February 12th, 2009

Last week, Shane Johnson and I spoke at the Alfresco code camp in Chicago.  This event was organized by CITYTECH, and it was a great opportunity to share some of our Alfresco 3 experience with the community.  From what I understand, the attendance rivaled that of code camps in New York and Boston.  Hopefully, this is a positive indicator of the growing Alfresco presence in the Chicago/Midwest region.

Shane kicked things off with an excellent presentation on Surf as an application framework, which transitioned nicely into Share exercises.  In the afternoon, I took over.  Fortunately for me, only one person made a quick exit when that happened ;)

I covered Web Studio, Alfresco’s new web content authoring and site construction application.  It was first available in the labs 3c release, and now in the labs3d/final release.  I provided a few hands on exercises that demonstrated the following:

  • Creating a new surf web project
  • Defining templates and regions
  • Working with the surf javascript api
  • Creating custom web studio components

In the spirit of Jeff Pott’s code camp at home blog post, I thought I would post the application the Chicago code campers walked away with. Please note, if you are not familiar with web studio or Alfresco 3 at all, I would recommend starting with these tutorials.  Otherewise, here are the steps to get started with the app.

  1. Install Alfresco labs 3.

  2. Download and extract webapp.

  3. Start alfresco.

  4. Open http://localhost:8080/studio in a browser login as admin/admin

  5. Create a new web project based on the blank website

  6. Mount the avm filesystem view

    1. This is the Alfresco repository that stores web content.. On my linux machine, this is done by executing

        sudo mount -t cifs -o username=admin,password=admin //localhost/avm /media/alfresco/cifs/v

  7. Copy the contents of the extracted ROOT folder to /path/to/avm/mount/point/newwebprojectname—admin/HEAD/DATA/www/avm_webapps/ROOT

  8. Go to http://localhost:8080/studio/service/index and press “Refresh Web Scripts”
  9. Go back to http://localhost:8080/studio or refresh the broswer and you are all set!

You should see something like this:

These steps are a little awkward.  At the code camp, we distributed a vm to minimize these setup tasks.  Perhaps if there is a demand, I can make the entire vm available.  Also, to get the full effect, drop some files (pdfs, docs, etc.) in the Guest Home folder via the alfresco explorer app found at http://localhost:8080/alfresco

So here is the basic structure of the application.  There is a template defined with four regions.  Header, navigation, spaces and headlines.  The header and navigation are just using out of the box components.  The spaces region is using the custom spacechildren webscript, which can be found in the WEB-INF/classes/alfresco/site-webscripts directory.  This webscript uses the CMIS api to list content in the alfresco repository.  Next, the headlines webscript grabs a feed from yahoo finance using data configured on the component (The stock symbol).  Again, the source can be found in WEB-INF/classes/alfresco/site-webscripts.

Thats about all there is to it.  One thing to note, I ran into this issue, but unless you are using open jdk, you should be all set.

posted by John Kraus

Alfresco Labs Sample Dashlets

Tuesday, October 14th, 2008

Alfresco 3 And Surf

Like many others in the Enterprise Content Management community, I am looking forward to the upcoming release of Alfresco 3.  One of the key components of this release is the Alfresco Surf platform.  If you are unfamiliar with Surf, Shane Johnson wrote an excellent post which provides an overview and analysis of the platform.

In September, Alfresco made the Labs 3b release available for download. This allows us to examine Surf and the Share collaboration application built on Surf.  I recently spent some time building surf components that can be plugged into standalone applications as well as share.  Small dashboard widgets called dashlets can be created for share via Alfresco webscripts.

I thought it might be helpful to others to write a post about how I went about creating these dashlets.  Additionally, others may find a use for these components inside their own share instance, or surf powered application.

A Flashy Dashlet

In almost every web content management project I have been involved in, there has been a requirement to build a web component which renders a flash asset from various repositories,  so I thought this may be a useful component to try with surf and the Alfresco repository.  So here is what I did.

First, I setup the labs3b release of Alfresco locally.  This is pretty much all the software required to build this dashlet.  So let’s talk about the files needed to create the component.  If you are already familiar with webscipts, this will be very straightforward.  All of these files reside in /path/to/alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts From there I just put them in an examples subdirectory.

First, there is the component description, flash.get.desc.xml.  We make the usual webscript definition here.  One thing to note, is the element needed to define this component as a dashlet,

<family>user-dashlet</family>

Next, we have flash.get.head.ftl  This is free marker code that allows us to push component specific meta-data to a page’s head element.  For this example, I just add the following to this file.

<script type=”text/javascript” src=”http://swfobject.googlecode.com/svn/trunk/swfobject/swfobject.js”</script>

I’m a big fan of the swfobject library for the obvious flexibility, cross browser support and graceful degradation reasons.

Finally, we have the free marker template that will generate the html snippet.  flash.get.html.ftl.  Here, I just create a div to hold the swf content, and then a call to swfobject.  That looks something like this.

<script type=”text/javascript”>
swfobject.embedSWF(”http://localhost:8080/alfresco/d/d/workspace/SpacesStore/35fdea34-9e44-4614-b918-a2490830e5da/sample.swf”, “swfcontent”, “415″, “300″, “9.0.0″);
</script>

That url points to an asset in the Alfresco repository, which I added via the alfresco web client.  In a real wcm setting, there would be in context editing that would allow a business user or designer to specify the location of the asset, rather than hardcoding it.  If that were the case, I would use flash.get.js to retrieve that value from the content store.

After a refresh of share’s webscripts, we are ready to add the flashlet to our dashboard.

A More Useful and Interesting Example

The example above was simple and conceptually useful.  However, it is probably more useful in an actual site, rather than as a dashlet in share.  Of course, I easily adapted the webscript out of the share dashlet context and into a generic component used in a surf enabled app.  However, I still wanted to create an example that would accomplish two things:

  1. Demonstrate the platform’s ability to render data from a call to an existing service
  2. Provide functionality that would be useful to a real organization’s share instance.

Suppose your organization has a YouTube channel which delivers product update videos, or perhap’s training videos.  For example, GMShowroom or alfresco101.  Wouldn’t it be useful to have a dashlet that embeds the channel’s latest video right onto your share dashboard?  Here’s how.

For the most part, everything works the same as the example above.  I created the dashlet webscript definition and added the swfobject code to my webscript’s head.ftl file.  I created youtube-channel.get.js, which uses google’s YouTube api to retrieve the appropriate data.

That code looks like this.

var connector = remote.connect(”http”);
var feed = connector.call(”http://gdata.youtube.com/feeds/users/alfresco101/uploads?max-results=1&orderby=published”);

// parse the feed and set namespace
feed = new String(feed);
var i = feed.indexOf(”<feed”);
if(i > -1)
{
feed = feed.substring(i);
}

var xml = new XML(feed); var entry = xml.*::entry;

var videoUrl = entry.*::id.toString();
model.videoid = videoUrl.substring(videoUrl.lastIndexOf(’/'),videoUrl.length)
model.title = entry.*::title.toString();

Surf will make the model object available to us in the free marker template, youtube-channel.get.html.ftl. So all that is left to do is make the swfobject call with the appropriate url.  Check out the results.

In an upcoming post, I plan to talk about how I created my own Surf application, and used these components within that app.

posted by John Kraus

CityTech Home