Alfresco Example: Share Dashlets that Retrieve Data from the Repository

If you need to learn how to write an Alfresco Share dashlet, hopefully you’ve already worked through the Hello World dashlets in the Share Extras project on Google Code. The rest of the dashlets in that project are either “mash-up” style dashlets that read data from third-party sites or dashlets that work with data from the Alfresco repository tier, but are of moderate to high complexity. What I think is missing is a dashlet that goes one small step beyond Hello World to show how to read and display a simple set of data from the repository tier. In this post I show you how to do just that.

I created the example I’m going to walk you through in response to a question someone posted in the forum. They asked, “How can I write a dashlet that will show basic metadata from the most recently-modified document in a given folder?”. I think this is something we haven’t explained to people–we show Hello World and we show dashlets from the other end of the spectrum, but nothing to illustrate this basic pattern. So I wrote a quick-and-dirty example. Fellow forum user, RikTaminaars, also got in on the act, making the dashlet configurable, which I’ll show in a subsequent post.

In this post, I’ll create the most basic implementation possible. The result is a simple dashlet that reads a path from a configuration file, asks the repository for the most recently modified document in that folder, then displays some basic metadata. In my next post, I’ll add to it by making the folder path configurable within the user interface.

Developer Setup

First, a bit about my setup. I’m running Alfresco 4.0.d Community, WAR distribution, Lucene for search, Tomcat application server, and MySQL for the database. I’m using Eclipse Indigo, but this implementation requires no compiled code. For organizing the project files I used the Sample Dashlet project structure from Share Extras. I’m using the Share Extras Ant build script to deploy my code from my project folder structure to the Alfresco and Share web applications.

When I’m doing Share customizations I use two Tomcats–one for the repository tier and one for the Share tier. This helps me avoid making assumptions about where my Alfresco and Share web applications are running and it speeds up my development because my Share Tomcat restarts very quickly. To get the Share Extras build script to work with a two Tomcats setup, I’ve added the following to a build.properties file in my home directory:

tomcat.home=/opt/apache/tomcat/apache-tomcat-6.0.32
tomcat.share.home=/opt/apache/tomcat81/apache-tomcat-6.0.32
tomcat.share.url=http://localhost:8081

Now the build script will know to put the deployed code in each of my Tomcat servers.

The Data Web Script/Presentation Web Script Pattern

The Alfresco repository runs in a web application called “alfresco” while the Share user interface runs in a separate web application called “share”. Each of these two web applications has its own web script engine. If you aren’t familiar with web scripts, take a look at this tutorial, then come back, or just accept the high-level notion that web scripts are an MVC framework used to bind URLs to code and that web scripts can run on the repository tier as well as the Share tier.

Share dashlets–the “boxes” of content that display in the Share user interface–are just web scripts that return HTML. In fact, everything you see in Share is ultimately a web script. When a dashlet needs to display data from the Alfresco repository, it makes a call over HTTP to invoke a web script running on the repository tier. The repository tier web script responds, usually with JSON. The dashlet gets what it needs from the JSON, then renders outputs HTML to be rendered as part of the page. Because the repository tier web script responds with raw data, you may see these referred to as “data web scripts”.

In the previous paragraph, I was purposefully vague about where the request to the repository tier is made. That’s because there are two options. The first option, and the one we’ll use in this example, is to make the call from the dashlet’s web script controller. This might be called “the server-side option” because once the dashlet’s view is rendered, it has all of the data it needs–no further calls are needed.

The second option is to make AJAX calls from the client browser. To avoid cross-site scripting limitations, these calls typically go through a proxy that lives in the Share application, but their ultimate destination is the repository tier.

Two tier web scripts

You may be wondering about the details of making these remote connections. It’s all taken care of for you. Regardless of whether you make the remote invocation of the repository web script from the Share tier or through AJAX in the browser, all you need to specify is the URL you want to invoke and the framework handles everything else.

Show Me the Code: Repository Tier

Let’s walk through the code. I’ll start with the repository tier. Recall that the requirements are to find the most recently modified document in a given folder and return its metadata. There may be out-of-the-box web scripts that could be leveraged to return the most recently modified document, but this is actually very simple to do and I don’t want to have to worry about those out-of-the-box web scripts changing in some subsequent release, so I’m implementing this myself.

Using the Share Extras project layout, repository tier web scripts reside under config/alfresco/templates/webscripts. The best practice is to place your web script files in a package structure beneath that, so I am using “com/someco/components/dashlets” for my package structure. Yours would be different.

The first thing to think about is what the URL should look like. I want it to be unique and I need it to accept an argument. The argument is the folder path to be searched. For this example, my URL will look like this:

/someco/get-latest-doc?filterPathView={filterPathView}

If you’ve worked with web scripts, you know that a “path” pattern could be followed instead of a “query string” pattern but it doesn’t really matter for this example. The descriptor, get-latest-doc.get.desc.xml, declares the URL and other details about authentication and transaction requirements.

The controller performs the search and populates the model with data. This is a GET, so the controller is named get-latest-doc.get.js. Look at the source for the complete listing–I’ll just hit the high points.

First, I grab a handle to the folder represented by the path argument:

var folder = companyhome.childByNamePath(args.filterPathView);

Then I use the childFileFolders() method to get the most recently modified file and stick that in the model:

var results = folder.childFileFolders(true, false, 'cm:folder', 0, 1, 0, 'cm:modified', false, null);
model.latestDoc = files[0];

Obviously, I could have used a search here, but this method is quite handy. It must be new (thanks, Rik!).

The view then takes the data in the model and returns some of the metadata as JSON:

<#macro dateFormat date>${date?string("dd MMM yyyy HH:mm:ss 'GMT'Z '('zzz')'")}</#macro>
<#escape x as jsonUtils.encodeJSONString(x)>
{
"nodeRef": "${latestDoc.nodeRef}",
"name": "${latestDoc.properties.name}",
"title": "${latestDoc.properties.title!}",
"description": "${latestDoc.properties.description!}",
"created": "<@dateFormat latestDoc.properties.created />",
"modified": "<@dateFormat latestDoc.properties.modified />"
}
</#escape>

Note the exclamation points at the end of the title and description. That will keep the template from blowing up if those values are not set.

That’s all there is to the repository tier. If you want to add additional metadata, custom properties, or data from elsewhere in the repo, it is easy to do that.

At this point you should be able to deploy (run ant hotcopy-tomcat-jar, for example) and test the repository tier web script by pointing your browser to: http://localhost:8080/alfresco/s/someco/get-latest-doc?filterPathView=/Some/Folder/Path

Show Me the Code: Share Tier

Okay, the back-end is ready to return the data I need. Now for the front-end. The Share Extras project structure puts front-end web scripts in config/alfresco/site-webscripts so following a similar pattern as I did for the repository-tier web scripts, I put my share-tier web scripts into the site-webscripts folder under com/someco/components/dashlets.

The descriptor, get-latest-doc.get.desc.xml, declares a URL for the dashlet and specifies the “dashlet” family. This will make the dashlet appear in the “add dashlets” menu for both the global dashboard and the site dashboard.

I want to make the folder path configurable, so I created get-latest-doc.get.config.xml and used it to specify a title for the dashlet and the default folder path. I’ll read in this configuration from the controller.

As I mentioned, we’re doing everything server-side in this example, so the controller needs to call the web script I deployed to the repository tier to retrieve the JSON. Then it can put that object into the model and let the view format it as it sees fit. The controller lives in get-latest-doc.get.js and starts out by reading the configuration XML:

Then it uses the built-in “remote” object to invoke the repository tier web script and puts the result into the model:

Note that the URL starts with my web script’s URL–it includes nothing about where to find the Alfresco repository. That’s because the “end point” is declared in Share’s configuration (to be precise, it is actually Surf configuration, the framework on which Share is built) and the remote object uses the “alfresco” endpoint by default. I don’t see it used very often, but you can actually define your own end points and invoke them using the remote object.

The last part of this example is the HTML markup for the dashlet itself. The first half of get-latest-doc.get.html.ftl is boilerplate that has to do with the dashlet resizer. For this example, the length of the list is static–it will only ever have one entry. So the resizer is kind of overkill. The bottom half is where the markup for the document’s metadata lives:

If you’ve already worked with web scripts, you know that get-latest-doc.get.head.ftl is used to place resources into “<head>”. In this case, that is a CSS file used to style our dashlet and client-side JavaScript used by the resizer. The strings used in the dashlet are localized through the get-latest-doc.get.properties file.

Test It Out

You should be able to test this out yourself by creating a folder anywhere in the repository, adding one or more files to the folder, specifying that folder path in the get-latest-doc.get.config.xml file, and then using “ant deploy hotcopy-tomcat-jar” to deploy the customizations to the alfresco and share web applications. You’ll need to restart Tomcat to pick up the new JAR (both Tomcats if you are running two). Then, login and click “Customize Dashboard” to add the “Get Latest Document” dashlet to your dashboard. It should look something like this:

Screenshot: Get Latest Document Dashlet

Summary

This is a simple example, but it is powerful because it is the same pattern used throughout Share: A dashlet needs data from the repository tier, so its web script controller uses the “remote” object to call a web script on the repository tier. The repository tier web script returns JSON which the Share tier web script then formats into a nice looking dashlet.

In my next post, I’ll show how to make the folder path configurable, so that you can change it at run-time with a folder picker in the Share user interface instead of editing the config XML.

15 comments

  1. Rolando says:

    Hi! Thanks for this great sample of coding an Alfresco Dashlet. Any chance you could extend it to “Get the Latest N Documents” instead of the last one and publish the bin files. Sorry I do not have Java developer skills.

    thanks a lot.

    grettings from PerĂº…

  2. Nidhi Pancholi says:

    Hi Jeff, I tried the above code in my alfresco 3.4.c but getting the following error :

    An error has occured in the Share component: /share/service/components/dashlets/politie/get-latest-doc.
    It responded with a status of 500 – Internal Error.
    Error Code Information: 500 – An error inside the HTTP server which prevented it from fulfilling the request.
    Error Message: 09260003 Failed to execute script ‘classpath*:alfresco/site-webscripts/org/alfresco/components/dashlets/get-latest-doc.get.js’: 09260002 TypeError: Cannot parse XML: Scanner State 24 not Recognized (file:/C:/Alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/dashlets/get-latest-doc.get.js#63(eval)#1)
    Server: Alfresco Spring WebScripts – v1.0.0 (Release Candidate 2 739) schema 1,000
    Time: Oct 26, 2012 3:57:05 PM
    Click here to view full technical information on the error.
    Exception: org.mozilla.javascript.EcmaError – TypeError: Cannot parse XML: Scanner State 24 not Recognized (file:/C:/Alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/dashlets/get-latest-doc.get.js#63(eval)#1)
    org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3350)
    org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3340)
    org.mozilla.javascript.ScriptRuntime.typeError(ScriptRuntime.java:3356)
    org.mozilla.javascript.xmlimpl.XMLLibImpl.parse(XMLLibImpl.java:411)
    org.mozilla.javascript.xmlimpl.XMLLibImpl.ecmaToXml(XMLLibImpl.java:433)
    org.mozilla.javascript.xmlimpl.XMLObjectImpl.ecmaToXml(XMLObjectImpl.java:779)
    org.mozilla.javascript.xmlimpl.XML.jsConstructor(XML.java:250)
    org.mozilla.javascript.xmlimpl.XMLObjectImpl.execIdCall(XMLObjectImpl.java:570)
    org.mozilla.javascript.IdFunctionObject.call(IdFunctionObject.java:127)
    org.mozilla.javascript.BaseFunction.construct(BaseFunction.java:313)
    org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:3281)
    org.mozilla.javascript.Interpreter.interpret(Interpreter.java:2394)
    org.mozilla.javascript.InterpretedFunction.call(InterpretedFunction.java:162)
    org.mozilla.javascript.ScriptRuntime.evalSpecial(ScriptRuntime.java:2280)
    org.mozilla.javascript.ScriptRuntime.callSpecial(ScriptRuntime.java:2143)
    org.mozilla.javascript.optimizer.OptRuntime.callSpecial(OptRuntime.java:165)
    org.mozilla.javascript.gen.c14._c1(file:/C:/Alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/dashlets/get-latest-doc.get.js:63)
    org.mozilla.javascript.gen.c14.call(file:/C:/Alfresco/tomcat/webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/dashlets/get-latest-doc.get.js)
    org.mozilla.javascript.optimizer.OptRuntime.callName0(OptRuntime.java:108)
    org.mozilla.javas

  3. Ivana says:

    Hi Jeff,
    Could you please tell me if this code works in Alfresco Explorer? I have some errors while trying to deploy this in Alfresco Explorer. For example: “Error during processing of the template ‘Expression args is undefined…”

    Thanks,
    Ivana

  4. Lan says:

    Nice post. I have been reading up a bit on dashlet development, however I notice there aren’t anything that deal with with ‘how to store data into the repository’ something to illustrate the simple process of storing data and the components that need to be created.

  5. jpotts says:

    Matthew,

    I don’t know that this has been tested on 4.2.c. If I get some time in the next few days I’ll take a look. Or if someone else can test it against 4.2.c and resolve any issues, I’ll be happy to make you a committer on the project.

    Jeff

  6. Jamil says:

    Follow the below steps.

    1. Drop “get-latest-doc-dashlet.jar” into alfresco_hom/tomcat/shared/lib(Note: lib file was not created default. I created this folder)
    2.Stared Alfresco and created test folder in repository
    3. clicked on “customize dashboard”
    4. clicked on “Add Dashlets”. But unable to see “get-latest-doc” there.

    Kindly assist.

    Alfresco: alfresco-enterprise-4.1.4-installer-win-x64
    OS : Windows 7

  7. Hello Jeff,
    when i configure this in my alfresco i got error in dashboard a
    .
    500 Description: An error inside the HTTP server which prevented it from fulfilling the request.

    Message: 08180000 Failed to process template org/alfresco/share/header/share-header.get.html.ftl

    Exception: java.lang.NoSuchMethodError – com.yahoo.platform.yui.compressor.JavaScriptCompressor.(Ljava/io/Reader;Lcom/yahoo/platform/yui/javascript/ErrorReporter;)V

    org.springframework.extensions.surf.DependencyAggregator.compressJavaScript(DependencyAggregator.java:1026)
    org.springframework.extensions.surf.DependencyAggregator.getCompressedFile(DependencyAggregator.java:949)
    org.springframework.extensions.surf.DojoDependencyHandler.getDependencies(DojoDependencyHandler.java:243)
    org.springframework.extensions.directives.ProcessJsonModelDirective.createExtensibilityDirectiveData(ProcessJsonModelDirective.java:296)
    org.springframework.extensions.surf.extensibility.impl.AbstractExtensibilityDirective.execute(AbstractExtensibilityDirective.java:133)
    org.springframework.extensions.directives.JavaScriptDependencyDirective.execute(JavaScriptDependencyDirective.java:68)
    freemarker.core.Environment.visit(Environment.java:341)
    freemarker.core.UnifiedCall.accept(UnifiedCall.java:136)
    freemarker.core.Environment.visit(Environment.java:265)
    freemarker.core.MixedContent.accept(MixedContent.java:93)
    freemarker.core.Environment.visit(Environment.java:265)

  8. Jeff Potts says:

    This post and the sample code work with Alfresco 4.0. If you are using a newer version of Alfresco you may have to make adjustments.

Comments are closed.