Category: Content Management

Enterprise Content Management (ECM), Web Content Management (WCM), Document Management (DM). Whatever you call it this category covers market happenings and lessons learned.

Alfresco demo showing rule based on lat/lon, Mobile App, & CMIS

When I go to conferences and events like OSCON, JavaONE, and Red Hat Summit that are broader than our little ECM corner of the IT world, I run into many people who have yet to discover Alfresco. So I always try to have a demo ready that is a mile wide and an inch deep. Here’s a screencast of the one I used for OSCON this year:

It isn’t technically mind-blowing, but that’s not the point of the demo. The point is to answer the simple question: What can I do with Alfresco?

I like this demo because it shows…

  • Multiple examples of rules. The ability for an end-user to configure rules in the user interface is such a simple concept, but it is a very powerful feature.
  • Metadata extraction. In this case I’m using the out-of-the-box image metadata extraction to grab the lat/long from some image files. The rule then sorts the images into folders that correspond to the geographic region where the photos were taken.
  • Multiple ways of getting content into the repository. I’m showing inbound SMTP and drag-and-drop, but if someone asks about FTP, WebDAV, or CIFS, that’s easy to show too. And showing that those rules fire in any case is key.
  • A simple example of server-side JavaScript, which is great for power users and administrators.
  • A custom data list (which can lead to a discussion of custom content models). I’m using a data list to define the geographic regions the script uses to sort the photos into.
  • Two of my favorite community contributed Add-Ons, the Geotagged Content Dashlet and the JavaScript Console.
  • The mobile app and gives a glimpse of what you can do with a mobile app when it integrates with other cool mobile apps (PDFExpert in this example).
  • A custom application (happens to be a little Python app, less than 200 lines of code, IIRC) using CMIS. If I really want to drive the CMIS point home I’ll show the app hitting Alfresco, then I’ll point the same app at another vendor’s CMIS-compliant repository. Database people yawn, but anyone that’s ever had to code against more than one vendor’s ECM repository love that.

If I’m doing this for a small group this is usually enough to get a conversation started and we can go off into the weeds based on what piqued their interest, whether that’s a lower level of detail on the points above or some other part of the platform that the demo didn’t hit (like search, workflow, versioning, security, web scripts, and on and on).

There are some rough spots and the “photo contest” story could be tightened up, but I think it gets people’s gears turning.

What about you? What’s your favorite way to demo Alfresco to newcomers?

 

Alfresco News Recap: DevCon, Survey, Dashlets, & a Forums Milestone

The news in the Alfresco world is happening faster than my sluggish blogging pace can keep up with, so I am forced to write a “recap” style post to keep you informed. It won’t win a Peabody, but at least you’ll be in the know…

Alfresco DevCon Registration Goes Live

Alfresco DevCon registration has been live for a little over a week. This year, we have a cool site just for DevCon that includes the full agenda, travel info, speaker bios, and a sponsor listing. Early-bird registration ends September 10 for both Berlin and San Jose. We’re on a pretty good pace right now with registrations so I would not wait around to secure your spot.

Alfresco Community Survey

The survey ended a couple of months ago. Honestly, we had a disappointing response rate compared to last year. Still, there was some good feedback provided. I’m responding to many of you to get you to elaborate further on your suggestions or to respond to specific questions. I’m about halfway through my follow-up list. Last year, I published the survey results and I’ll do that again this year before too long.

Alfresco Dashlet Challenge

The Alfresco Dashlet Challenge has just kicked off. This is a developer-focused contest in which people try to see who can create the coolest Add-Ons for Alfresco Share. You could win one of three Android tablets and a free DevCon pass if you can edge out the stiff competition. My blog post on socialcontent.com talks about some of last year’s submissions and includes a link to the full terms and conditions. My fellow American citizens were a big no-show in last year’s Dashlet Challenge, so I’m hoping to see that corrected this year!

Mark Rogers Makes His 4,000th Post

If you’ve spent any time in the Alfresco Forums, odds are you’ve come across Mark. He’s been a dedicated soul, working tirelessly to answer questions on just about every topic imaginable, since 2008. He’s routinely in the top 1 or 2 users in terms of volume in any given month. What’s great about Mark, though, is not just that he’s prolific–he’s also helpful. The guy has racked up 287 points, which is second only to Mike Hatfield. Needless to say, I’m a big fan of Mark’s. Well, last month, Mark made his 4000th post in the Alfresco Forums. 4000 posts! Just to give you some perspective, that’s about 1.5 times higher than the person with the 3rd highest number of posts (Kevin Roast). Of course everyone who spends significant amount of time in the forums on their own time deserves kudos, but when you see Mark at DevCon (or run into him in the forums) please congratulate him on this milestone.

While I’m on the topic of forums, we did pretty good on cutting down on unanswered posts in February, March, and April. Those months had some of the lowest number of unanswered topics as a percentage of topics created. But now we’re creeping back up to our old numbers. If you get a chance, maybe you could spend an extra 30 minutes in the forums this week. If everyone did an extra post a week (which is about 1/30th of Mark’s pace!) it would really help out.

Alfresco Virtual Meetup Recording and Related Links

If you missed watching the live streaming of the Alfresco Virtual Meetup live, well you missed a little nugget of Alfresco history. But you can re-live it in all of its glory by watching the recording.

Here are some links related to the discussion:

  • Learn about Alpaca: http://code.cloudcms.com/alpaca/1.0.1-SNAPSHOT/
  • Chris Paul’s Repo: http://github.com/cmpaul/alfraca
  • Dave’s Chrome Extension: http://code.google.com/p/alfresco-activities-browser-extensions/
  • And AlfJS is available at GitHub here: https://github.com/Alfresco/AlfJS

Let me know what you think of the format. Maybe we should start doing Tech Talk Live using Google Hangouts instead of WebEx?

 

Alfresco virtual meetup via Google Hangouts on July 2

On Monday, July 2 at 13:00 US/Central 19:00 London time we will have the first-ever Alfresco virtual meetup using Google Hangouts on Air. Planned panelists include Luis Sala, David Draper, Chris Paul, and myself. We’ll be talking about some cool side projects these guys have going, including a low-level JavaScript client for Alfresco called AlfJS, a Share Activities Browser Plug-in, and a look at Alpaca, a client-side forms and templating engine.

The meetup will be broadcast live on the Alfresco Google+ Page and recorded for later viewing on YouTube. Join us!

New features make Alfresco in the Cloud worth a look

It occurred to me today that I’ve never blogged about the great work that David Gildeh and the Engineering team have been doing on Alfresco Cloud, which is a huge oversight on my part. The team launched several new features today, so that’s given me the nudge I needed. And if you haven’t tried it yet, maybe it will be the nudge you needed too.

I’ve been using Alfresco in the Cloud for some time now, both as a tester of the early offering and to do real work. For example, we’ve got an external firm working on the design of a new web site just for DevCon. It was a no-brainer to simply spin up a new Alfresco site to share documents with the external team via Alfresco in the Cloud. Much easier than it would be to get them access to our internal, on-premise Alfresco server. I think we’ll also create an Alfresco site for DevCon speakers and sponsors, to make it easier to share things like presentation templates, presentations, speaker bios, speaker headshots, sponsor collateral, etc.

In case you haven’t tried it, Alfresco in the Cloud is a multi-tenant SAAS offering of Alfresco Enterprise. Well, it isn’t exactly Alfresco Enterprise–there are a few differences. At the moment it is only about file sharing. You can add comments and ratings to a document and you can follow other users, but there aren’t other types of collaborative features that are available in the on-premise version. Still, if you’ve used Alfresco on-premise you’ll immediately recognize and be familiar with Alfresco in the Cloud.

A Few New Features Worth Mentioning

Today Alfresco released several new features that should motivate you to try it out. The first is the ability to publicly share any document. You just click the “Share” link and Alfresco generates a shortened URL. When your colleagues click the link you send them, they’ll go straight to the document preview with no login and no download required. That’s pretty cool. (One thing I’d like to see, though, would be a link to the full context so that if I have the rights I could then edit the metadata and so on, but that seems obvious, so I’m sure it is already on the list) (UPDATE: It’s in there, as David Caruana points out, see comments).

The next new feature is the addition of folders rules and actions. This is huge because (1) it is so useful and (2) it is a unique feature compared to what you’d see typically in consumer-grade cloud file sharing. In case you aren’t familiar, folder rules and actions make it possible to automate repetitive tasks as documents are added, updated, or deleted to/from a folder. In the Cloud, the actions are currently restricted to “Move”, “Copy”, and “Transform”. So, for example, suppose you want to transform all GIFs to PNGs as they arrive (similar for DOC to PDF and so on). A rule lets you do that. Or maybe you want to put everything that has “requirements” somewhere in the file name into a folder called “Requirements”. Rules are great for stuff like that.

For people who have upgraded from the free forever 10 GB account to a Cloud subscription, you can take advantage of another nice feature that was recently-added: WebDAV support. This means you can work with files that live in the cloud directly from Windows Explorer, Mac Finder, Office, or other tools that support WebDAV.

The final new feature is the addition of 256-bit AES encryption. It doesn’t provide much in the way of a sexy demo, but it is certainly a critical requirement to Enterprise users looking to store sensitive content in the repository.

The Beauty of Alfresco: One Platform, One API

What I think is really cool, though, is that the software we’re running in the Cloud is the same as what you can run on-premise (or on your own cloud infrastructure or on your developer laptop). So there is no mystery in how it works–it is all open source, after all. And it means that the lessons we learn about running Alfresco for thousands of users with tons of data make their way into Community Edition and Enterprise Edition.

But it gets better. Sometime later this year, as a developer, you’ll be able to write your own custom apps that persist content to Alfresco in the Cloud. That’s when it is going to get real interesting to me. Because at that point, you’ll be able to write apps that use Alfresco as a back-end, and it won’t matter whether you’re persisting against Cloud or on-premise (or both)–it is the same API (CMIS-based, I might add). And it won’t matter whether your users need to get to their content from a mobile device or from their browser (or both). Your app can run everywhere your users are and still use the same API to work with content.

I’m chomping at the bit to get my API key so I can play with this. I know several of you will be to.

Try It, You’ll Like It

Until then, you’ll just have to be patient and just enjoy Alfresco in the Cloud as an end-user. It is free to get started (sign-up). Partners get a free upgraded account, if I’m not mistaken, so if you are a partner and you don’t know about that already, ask your partner rep.

Great idea: Alfresco Training goes a la carte

Just wanted to pass along this little gem in case you missed it: Alfresco Training now offers its eLearning courses a la carte. They’ve taken their popular topics, upgraded them to Alfresco 4, and made them available as online courses under the “Alfresco Elements” moniker.

This seems like a smart move for both Alfresco and potential students. It doesn’t really cost Alfresco anything to offer modules individually and students get to take exactly what they need at a reasonable price.

Some courses are available free of charge. Most are $99. More information is available here.

Alfresco Example: Share Dashlet Part Two–Configuration

In my last post I showed a simple example that illustrates the “repository tier data web script/share tier presentation web script” pattern that is used any time you need to create a dashlet in Alfresco Share that reads data from the Alfresco repository. The example I used is called “Get Latest Document” and it does just that–given a folder path, it reads some basic metadata about the most recently modified document and displays it in a dashlet.

That example read the folder path from an XML configuration file that is included as part of the Share tier web script. But what if you wanted to make it easier to configure the dashlet by allowing a Share user to open up a configuration dialog that includes a folder picker? Luckily, Alfresco Share already includes all of the bits and pieces you need to make this possible–it’s just up to you to wire them together. I’ll show you how to do that in this post.

The code that implements this example lives in a Google Code project. I’ve tagged the prior post’s source code–the “XML configuration” code–as “1.0“. The code that goes along with this post is tagged as “2.0“. So if you are following along make sure you are using the appropriate code set.

I’m using Alfresco 4.0.d Community, WAR-only distribution, Lucene for search, two Tomcats, and MySQL. My project is organized following the Share Extras Sample Dashlet project folder structure.

Here’s the Recipe

The goal is to take the 1.0 version of the Get Latest Document dashlet and make it configurable through the UI instead of an XML configuration file. Recall from the prior post that we have a repository tier web script that returns JSON for the most recently modified document in a specified folder path. To make that folder path configurable, nothing at all needs to change on the repository tier. All of the changes are going to take place on the Share tier.

Here’s what has to be created or changed to make this work:

  1. Modify the dashlet markup to include a configuration link
  2. Create a custom client-side JavaScript component to handle the configuration dialog and the folder picker
  3. Write a new Share-tier web script that persists the configuration data

Not too bad, right? Well let’s get after it then.

Modify the dashlet markup to handle configuration

In a minute I’m going to show you the code for the client-side JavaScript component that listens for configuration clicks. For now, I’ll just point out where that client-side object is instantiated within the dashlet’s markup. It is near the top of get-latest-doc.get.html.ftl:

var getLatestDoc = new SomeCo.dashlet.GetLatestDoc("${el}").setOptions(
{
  "componentId": "${instance.object.id}",
  "siteId": "${page.url.templateArgs.site!""}",
  "title": "${title}",
  "filterPath": "${filterPath}",
  "filterPathView": "${filterPathView}"
}).setMessages(${messages});

Notice the “SomeCo” object. When creating new client-side objects, you definitely want to steer clear of the “Alfresco” object if you can so that your stuff doesn’t collide with Alfresco’s stuff.

Next, I need an “edit” icon in the dashlet’s title bar. I want it to appear only if the user is a site manager. So I’m going to add a little blurb to the get-latest-doc.get.html.ftl view that does this:

var editDashletEvent = new YAHOO.util.CustomEvent("onDashletConfigure");
editDashletEvent.subscribe(getLatestDoc.onConfigGetLatestDocClick,
                           getLatestDoc, true);
new Alfresco.widget.DashletTitleBarActions("${args.htmlid?html}").
  setOptions(
{
  actions:
  [
    <#if userIsSiteManager>
    {
      cssClass: "edit",
      eventOnClick: editDashletEvent,
      tooltip: "${msg("dashlet.edit.tooltip")?js_string}"
    },
    </#if>

The first part of this code hooks a YUI “onDashletConfigure” event to the “onConfigGetLatestDocClick” handler in the custom GetLatestDoc client-side JavaScript object. The second part is a FreeMarker conditional that is checking the value of “userIsSiteManager”. The controller will set that value. I’ll show you that shortly. The rest of the view is unchanged from the 1.0 version.

The dashlet needs access to the custom client-side JavaScript component as well as some of Alfresco’s out-of-the-box components that I will leverage to open a dialog and to present a folder picker. I’ve added these to get-latest-doc.get.head.ftl:

<!-- getLatestDoc -->
<@link rel="stylesheet" type="text/css"
  href="${page.url.context}/res/extension/components/dashlets/get-latest-doc.css" />
<@script type="text/javascript"
  src="${page.url.context}/res/extension/components/dashlets/get-latest-doc.js"></@script>
<!-- Simple Dialog -->
<@script type="text/javascript"
  src="${page.url.context}/modules/simple-dialog.js"></@script>
<!-- Global Folder Select -->
<@link rel="stylesheet" type="text/css"
  href="${page.url.context}/modules/documentlibrary/global-folder.css" />
<@script type="text/javascript"
  src="${page.url.context}/modules/documentlibrary/global-folder.js"></@script>

Now take a look at the dashlet’s controller, get-latest-doc.get.js. The biggest change here is to add a call to the repository to find out if the current user is a site manager:

var userIsSiteManager = false,
json = remote.call("/api/sites/" + page.url.templateArgs.site +
  "/memberships/" + encodeURIComponent(user.name));
if (json.status == 200)
{
  var obj = eval('(' + json + ')');
  if (obj)
  {
    userIsSiteManager = (obj.role == "SiteManager");
  }
}
model.userIsSiteManager = userIsSiteManager;

The rest of the controller is pretty much the same as 1.0, with the minor addition of checking “args” for values that the configuration service is going to pass in. If they don’t get passed in, the values will be read from the config XML as they were in 1.0.

That’s it for the changes to the dashlet. If you wanted to you could deploy at this point, but clicking the edit button would result in JavaScript errors.

Create a custom client-side JavaScript component

Client-side JavaScript for this example lives in source/web/extension/components in a file called dashlets/get-latest-doc.js. The “source/web” part of the path is dictated by the Share Extras sample project folder layout. I used “extension” to keep my client-side static assets separate from Alfresco’s. You might choose something more unique.

I’m not going to go line-by-line–look at the source for the full detail. The first thing worth noting is at the very beginning of the file: It’s a declaration of the “SomeCo” object. The client-side object I define in this file will live in that namespace. If I had other custom client-side objects I’d declare them as part of that namespace as well. I’ve seen a lot of examples that place their custom client-side objects in the “Alfresco” namespace, which is a bad habit. There’s nothing magical about that Alfresco namespace, so why not make it obvious what’s part of the product and what’s a customization?

if (typeof SomeCo == "undefined" || !SomeCo)
{
  var SomeCo = {};
  SomeCo.dashlet = {};
}

Next comes the declaration of the constructor for this new object:

SomeCo.dashlet.GetLatestDoc = function GetLatestDoc_constructor(htmlId)
{
  SomeCo.dashlet.GetLatestDoc.superclass.constructor.call(this,
    "SomeCo.dashlet.GetLatestDoc", htmlId);
  /**
   * Register this component
   */
  Alfresco.util.ComponentManager.register(this);
  /**
   * Load YUI Components
   */
  Alfresco.util.YUILoaderHelper.require(["button", "container",
    "datasource", "datatable", "paginator", "json", "history",
    "tabview"], this.onComponentsLoaded, this);
  return this;
};

After calling the superclass’ constructor, I ask the Alfresco ComponentManager to register the class. Then, I use Alfresco’s YUILoaderHelper to declare the components on which my component depends.

After that, the actual definition of the object begins. My GetLatestDoc object is going to extend Alfresco’s Base object, so I use YAHOO.extend to make that happen:

YAHOO.extend(SomeCo.dashlet.GetLatestDoc, Alfresco.component.Base,
{

What follows are the properties and methods of the GetLatestDoc object. The two big things it takes care of are (1) Responding to the “Configure” click and (2) Displaying the folder picker dialog.

The first method is the onConfigGetLatestDocClick. You’ll remember that from the updates to the view–I hooked up the “configure” link to this method. Here is the declaration followed by specifying the actionUrl. The actionUrl is where the configure form will be posted. In this case it is a web script I’ll walk you through in the next section.

onConfigGetLatestDocClick: function getLatestDoc_onConfigGetLatestDocClick(e)
{
  var actionUrl = Alfresco.constants.URL_SERVICECONTEXT +
    "modules/someco/get-latest-doc/config/" +
    encodeURIComponent(this.options.componentId);

The onConfigGetLatestDocClick method does two things: (a) It defines the dialog that gets displayed when someone configures the dashlet and (b) it defines field validation for the fields on the configure dialog. Here is the dialog definition part:

That templateUrl is where the SimpleDialog module should find the form to use. It looks just like the actionUrl and it is. The actionUrl will be a POST while the templateUrl will be a GET.

The getLatestDoc_onConfig_callback function will be invoked when the configuration form is successfully posted to the config web script. That response object contains the response from that web script, which, in this case, we are returning as JSON.

Here is the part of the method that defines field validation:

This function makes the title mandatory. Note the final two assignments. These are hooking up buttons on the config dialog to events in this client-side component. So, when someone clicks “select path” the folder picker will be launched and when someone clicks “clear path” the selected path will be reset.

Then, the final part of the method simply displays the dialog:

this.configDialog.setOptions(
{
  actionUrl: actionUrl,
  siteId: this.options.siteId,
  containerId: this.options.containerId
}).show();
},

So when this method is invoked, Alfresco’s out-of-the-box SimpleDialog component is used to display a pop-up dialog. The dialog will contain the form that we return in the config’s GET web script, and when the user saves the values, the values will be POSTed to the config web script.

As part of the configuration, the user needs to specify a folder path. Alfresco already ships with a folder picker–there is no need to code one from scratch.  The onSelectFilterPath method sets that up:

This method uses the out-of-the-box DoclibGlobalFolder to present a tabbed dialog of folders for the user to navigate and pick. When someone selects a folder, it throws a “folderSelected” event which this code listens for. When it hears it, it grabs the selected folder and nodeRef to save for later.

I’m not sure why Rik, my partner in crime for this little example, chose to append the selected path to the end of the nodeRef with a pipe. It could easily be stored in its own property.

Now, at this point, a logical question in your mind might be, “SimpleDialog and DoclibGlobalFolder look generally useful. How do I find out more about those and other goodies that might be available to my client-side JavaScript in Share?”. The answer is JSDoc. The Share Extras project has generated the JSDoc for all client-side JavaScript in Alfresco. The index for Alfresco 4.0.d lives here, the doc for DoclibGlobalFolder lives here, and the doc for SimpleDialog lives here.

With this client-side JavaScript in place, the “configure” link can now be clicked, but the SimpleDialog will be looking for a config web script that doesn’t exist yet. That’s the last step.

Write a Share-tier web script to handle config

In the previous section you saw that the SimpleDialog needs two web scripts: One returns a form that is rendered in the dialog. The other is the web script that dialog will POST to. These web scripts are Share tier web scripts, so they live in config/alfresco/site-webscripts. The com/someco package structure is used to keep code separate from other add-ons, and, by convention, web scripts that aren’t surf components go under “modules”. Under that I’m using “getLatestDoc” to group web scripts related to that and “config” below that to identify the purpose of these web scripts.

First, the GET web script. It’s kind of boring. There is no controller at all. The web script consists only of a FreeMarker view, a descriptor, and some properties files to localize the labels on the form. If you look at the view, config-get-latest-doc.get.html.ftl, you’ll see what I mean. It doesn’t even need any client-side JavaScript–the buttons were hooked up to methods on the GetLatestDoc component in the prior step.

Next, the POST web script. The SimpleDialog component will be sending JSON representing the form data to this web script. Because it is sending JSON, the web script controller is named config-get-latest-doc.post.json.js. That extra little “.json” bit gives me access to the form data in a “json” root-scoped object so I don’t have to fool around with eval.

The logic itself is pretty simple:

var c = sitedata.getComponent(url.templateArgs.componentId);
var saveValue = function(name, value)
{
  c.properties[name] = value;
  model[name] = value;
}
saveValue("title", String(json.get("title")));
saveValue("filterPath", String(json.get("filterPath")));
saveValue("filterPathView", String(json.get("filterPath")).split("|")[1]);
c.save();

What’s going on here? First, the controller grabs a handle to a component using a componentId. The component ID was passed in as an argument by the client-side JavaScript component that told the SimpleDialog which action URL to use. The client-side JavaScript component got the component ID when the dashlet’s view (get-latest-doc.get.html.ftl) instantiated it. So this component is actually the web script that renders the Get Latest Document dashlet.

The saveValue function is just a little helper that sets the properties on the component and the model in one call.

So, given the “json” object that is being passed in from the configuration dialog, all that needs to be done is to read those form field values out of the JSON and stick them onto the component properties and the model.

Now, when the dashlet’s web script is invoked, the framework will pass those properties to it via the args array. If the dashlet’s controller sees the values in the args array, it uses those, otherwise, it uses what it finds in the XML configuration.

Deploy and Test

You can deploy the example by running “ant hotcopy-tomcat-jar”. If you already deployed 1.0 of the code and you are running two Tomcats, you won’t have to restart your repository tier, but you will have to restart your Share tier. Then, go into a site and add the dashlet to the site dashboard. If you click the pencil icon on the dashlet’s title bar, you should see the configuration form pop up:

If you then click “Select Path” the folder browser should be displayed:

On clicking “OK” the new configuration values will be persisted, but you’ll have to refresh the page to see them take effect. Of course you could modify the example further to move the rendering of the metadata to the client-side such that when the configuration data is saved it immediately refreshes the dashlet content, but I’ll save that for another time.

Take the 2012 Alfresco Community Survey and Win $250

It’s that time again, folks. Time to see how we did over the past year and what corrections we need to make in the year ahead to keep the Alfresco community on track.

Whether you are an Enterprise subscriber or someone who works with Community Edition, I need as many people in the ecosystem as possible to take the survey.

Why bother? Because we care what you have to say. Seriously. Many of the things we’ve done in the community over the past year have been a direct result of this survey. You told us, we listened, we executed. Now it is time to find out how effective that execution was and to see what we need to do for the coming year.

So please do take 15 minutes to tell us how we’re doing. And if the “help us help you” reason isn’t enough, two lucky winners will walk away with $250 Amazon Gift Certificates.

You have until June 15 to take the survey.

Thanks ahead of time for your feedback and good luck!

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.

New Activiti Release includes Designer Plug-in Enhancements

Did you know there’s a new release of Activiti out? Okay, it has been out since March but I’m just now getting around to saying something about it. I haven’t given the entire thing a thorough evaluation, but if you are using the Activiti Designer Eclipse plug-in, you’ll be happy to know that you don’t have to fool with two files for each process definition any longer (three files, if you count the PNG). Instead of a .activiti file and a .bpmn20.xml file, there is just a single .bpmn file.

The new Designer also supports modeling sub-processes. And, it is easier to work with boundary events than it was before.

If you have existing Activiti process definitions, you can open your old .bpmn20.xml files and save them as .bpmn files. Then you can delete your .activiti and .png files.

The version of Activiti embedded in Alfresco 4.0 (Community and Enterprise) is 5.7. I’m assured by the development team that processes edited with version 5.9 of the Designer will continue to work with 5.7. However, there may be some items on the palette that are not supported in older Activiti versions.

One thing to note about this release of the Eclipse plug-in: It no longer runs with Eclipse Helios. You’ll have to upgrade to Indigo.