Share Document Previews in Alfresco 4

One of the improvements introduced in Share’s Document Library as part of Alfresco 4 was the refactoring of the web-preview component, which is responsible for rendering the in-line document views in the Document Details page.
These preview capabilities are one of the core strengths of Share, and now these changes allow developers and administrators to tweak the capability as they need.

Flash Document Previewer

Web preview component in Alfresco 4 for a PowerPoint Presentation

The Basics

Let’s start with a quick overview.
The web-preview component is similar to most other Share components. The page component itself is rendered by a web script, which is responsible for loading some basic information about the document being requested, and outputting this into the markup that then forms part of the overall page.
The output does not render HTML directly, but rather instantiates a client-side component Alfresco.WebPreview, passing over the document metadata as an object literal.
Previously, that was it. The logic to set up the Flash previewer was contained within Alfresco.WebPreview, and that was what you got for all documents and images. Anything else that couldn’t be transformed by the repository to a SWF file would not be previewed.

What’s Changed?

Alfresco 4 changes that. Now, the rendering of content items is separated from Alfresco.WebPreview and instead performs the work via a series of plug-ins. The component still takes care of loading the document metadata, setting up the basic screen layout and working out which plug-ins can be used to render the content on-screen. This has several benefits –

  • System administrators can decide which plug-ins should be used under which circumstances, via configuration.
  • Plug-ins are chained together in the configuration. If one fails, then the other available ones are tried in turn, until one succeeds.
  • The implementation of the plug-ins by developers does not need to touch any core files. They are tied into the component via configuration alone.

What’s more, Share comes with a number of different plug-ins already present. It’s likely that you will still see the Flash document previewer for most of the content you look at, but you should notice that by default images are now rendered as real images within the page, and will be automatically resized if they are too big. That’s because images are now handled by a different plug-in, but you can still use the Flash previewer if you reconfigure the component.

Image Viewer

Web preview component in Alfresco 4 for a JPEG image


Other previewers are provided for displaying audio and video content using Strobe Media Player or FlashFox, plus direct playback of supported formats using the HTML5 <video> and <audio> tags. Take a look in the directory components/preview within the Share webapp and you will see them there. It’s worth looking at the source code if you’re thinking of implementing your own custom previewers, as many of them are surprisingly simple.

Configuring Plug-ins

The configuration that dictates which plug-ins are used under what circumstances is contained in the web script configuration file web-preview.get.config.xml. You can find this in the Share webapp in the directory WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/preview and view it there, but if you’re planning on making any changes you should first copy it into tomcat/shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/preview (you may need to create the directories below web-extension).
You should see that the config is expressed as a series of conditions, each of which contains a list of plug-ins to attempt to use. For each condition that matches, each of the plug-ins within it is then added to the list to try to use.
Here’s an example for MPEG-4 video files

<condition mimeType="video/mp4" thumbnail="imgpreview">
    <plugin poster="imgpreview" posterFileSuffix=".png">StrobeMediaPlayback</plugin>
    <plugin poster="imgpreview" posterFileSuffix=".png">FlashFox</plugin>
    <plugin poster="imgpreview" posterFileSuffix=".png">Video</plugin>
 </condition>

The <condition> element’s attributes form sub-conditions, which are AND-ed together to give the overall result. In this case, the condition will match when the content being viewed has the MIME type video/mp4 and the thumbnail with the name imgpreview can be generated (note, it may not yet have been generated) via the thumbnail service. Other conditions will only test one of these sub-conditions, but here we test both.
Each of the <plugin> elements then provides the name of the plug-in in the element body and any configuration attributes required as element attributes. In this case, each of the plug-ins is being configured to use the imgpreview thumbnail as the static ‘poster’ image to display in the player before the play button is pressed, and to assume this image is in PNG format. Note, these attributes will be different for each plug-in since they are mapped to the options object on the plug-in implementation.
As an administrator, within this file you can add, remove and re-order plug-ins or conditions as you need, change the conditions or change the configuration that is passed to the plug-ins at instantiation time.

Implementing Your Own

It’s easy to create your own plug-ins to render content in interesting and different ways. A plug-in is implemented as a single JavaScript class, which must meet the following simple criteria

  • The name of  its constructor must be the same as the value used in the <plugin> configuration element to tie it into the component, and must be defined as a child property of Alfresco.WebPreview.prototype.Plugin. By convention CamelCase is used for the name, e.g. WebPreviewer, FlashFox, Image.
  • The object prototype must define an object literal named options and two functions report() and display(). See the in-line JSDoc of the out-of-the-box plug-ins for full details of the method signatures and return types required.

You will also need to ensure that the client-side file where you define the plug-in object is included in the <head> section of the Document Details page, you can do this as an administrator by overriding the web-preview.get.head.ftl template, or (better) as a developer by extending the .head.ftl template using a customization module applied to the web-preview.get web script.

New Viewers on Share Extras

Keep an eye out for the upcoming release of the (to-be-renamed) Media Previews add-on in Share Extras. Peter has been instrumental in helping to build up some new plug-ins for viewing different types of content that we hope can be used to remove the Flash dependency that still remains in Share for viewing document-based content. They should also provide some further examples of how you can define your own, custom, plug-ins.

From Web Scripts to Portlets with Alfresco 3.4

Since Alfresco 3.2r the ProxyPortlet support in Alfresco Share has allowed developers to easily embed specific bits of Share functionality into a portal such as Liferay, using only a small amount of XML configuration to wire in existing web scripts.
This support has been substantially improved in version 3.4 of Alfresco, in order to allow the entire Share Document Library page to be embedded within a portal. Unfortunately the changes mean that the steps in Luis’s original tutorial no longer work in the latest version.
As one of the features we demonstrated today at our Madrid event was the Doclib portlet, I managed to get five minutes to get the original web script-backed method working too.
Since the CMIS repository browsing web scripts used in Luis’s example are no longer shipped with Share, I used my own Hello World dashlet from share-extras as a starting point instead. The web script is basic, but demonstrates displaying a simple greeting to the user including their user name.
Hello World Portlet
The following steps should work using a recent version of Alfresco 3.4 and Liferay 5.2.3 running as the portal, provided that the two components are first set up as per the Installing and Configuring instructions for the Doclib Portlet.
Once you’ve set everything up, the first thing to do is to add the web script files to the instance of Share that you have already deployed to Liferay. Since you should already have created some directories in Liferay’s tomcat-x.x.x/shared/classes directory to define your share-config-custom.xml, the easiest thing is to create a new directory named site-webscripts within the existing tomcat-x.x.x/shared/classes/alfresco/web-extension and place the following files in it.
org/alfresco/test/hello-world.get.desc.xml

<webscript>
   <shortname>Hello World</shortname>
   <description>Displays Hello World text to the user</description>
   <authentication>none</authentication>
   <url>/test/hello-world</url>
</webscript>

org/alfresco/test/hello-world.get.html.ftl

<html>
   <head>
      <title>${msg("header")}</title>
   </head>
   <body>
      ${msg("label.hello", user.id)}
   </body>
</html>

org/alfresco/test/hello-world.get.properties

header=Hello World!
label.hello=Hello {0}

With those files added, you’ve successfully defined the web script that we’ll wire into Share in the next section.
Now although the web script itself will be automatically picked up by Share at load-time, some additional config is also needed in web.xml for the ProxyPortlet to work in version 3.4.
The following lines, which define a custom servlet and servlet mapping which will be invoked by the ProxyPortlet, should be placed in the web.xml file belonging to the Share instance which has been deployed in Liferay. You should find the path to this will be something like <LIFERAY_HOME>/tomcat-6.0.18/webapps/share/WEB-INF web.xml (if you have not already started Liferay you will need to do so to force it to deploy share.war and create this structure).

   <servlet>
      <servlet-name>HelloWorld</servlet-name>
      <servlet-class>com.liferay.portal.kernel.servlet.PortletServlet</servlet-class>
      <init-param>
         <param-name>portlet-class</param-name>
         <param-value>org.alfresco.web.portlet.ProxyPortlet</param-value>
      </init-param>
      <load-on-startup>0</load-on-startup>
   </servlet>
   <servlet-mapping>
      <servlet-name>HelloWorld</servlet-name>
      <url-pattern>/HelloWorld/*</url-pattern>
   </servlet-mapping>

You can place these two definitions anywhere within the top-level <web-app> element, but for consistency I always try to add them next to the existing <servlet> and <servlet-mapping> definitions.
Now you’ve done all you need to do to configure the scripts, we can move onto configuring the matching portlet definition which will be picked up by Liferay.
In the same WEB-INF directory where you modified web.xml you should find a file named portlet.xml, to which we add our new definition.

   <portlet>
      <description>Hello World</description>
      <portlet-name>HelloWorld</portlet-name>
      <portlet-class>org.alfresco.web.portlet.ProxyPortlet</portlet-class>
      <init-param>
         <name>viewScriptUrl</name>
         <value>/page/test/hello-world</value>
      </init-param>
      <supports>
         <mime-type>text/html</mime-type>
         <portlet-mode>VIEW</portlet-mode>
      </supports>
      <portlet-info>
         <title>Hello World</title>
         <short-title>Hello World</short-title>
      </portlet-info>
      <security-role-ref>
         <role-name>administrator</role-name>
      </security-role-ref>
      <security-role-ref>
         <role-name>guest</role-name>
      </security-role-ref>
      <security-role-ref>
         <role-name>power-user</role-name>
      </security-role-ref>
      <security-role-ref>
         <role-name>user</role-name>
      </security-role-ref>
   </portlet>

Add this right after the existing <portlet> definitions (which if you look at further it should be obvious define the three Doclib portlets) and save your changes.
Getting these details right is crucial if you’re deploying your own web scripts, so a couple of notes on this are probably useful.

  1. The contents of the <portlet-name> element must match the name of the servlet mapping you have defined in web.xml
  2. The viewScriptUrl parameter must match the URL of your web script, with a prefix of /page added to the beginning (note that in 3.2r the web app context path was also required in the URL, but this now causes an error if supplied)

Lastly you should add the portlet to the two Liferay-specific configuration files in WEB-INF to ensure that authentication is handled correctly and also that the portal appears in the correct category in Liferay’s Applications menu.
In liferay-portlet.xml add the following definition after the existing <portlet> elements

   <portlet>
      <portlet-name>HelloWorld</portlet-name>
      <user-principal-strategy>screenName</user-principal-strategy>
   </portlet>

In liferay-display.xml, add the following within the existing <category> element – it should be obvious that this is adding your portlet to the ‘Alfresco’ category.

<portlet id="HelloWorld"></portlet>

You’ll need to restart Liferay to get it to pick up the new portlet, and for Share in turn to load the additional web script. Once it’s finished loading you should be able to follow the configuration steps in the Doclib Portlet guide to walk you through adding it to a page.
The example is basic, but shows how you can add a web script as a portlet, with a small amount of personalisation based on the identify of the user.
It’s possible to add more complex web scripts, for example to load data from the Alfresco repository or other back-end data sources, but as Luis points out you should be careful how you render any hyperlinks within your scripts, to ensure that they are portal-safe.

To make sure that your URLs are correctly generated, please use the “scripturl()” function in your Freemarker templates to wrap them:
<a href=”${scripturl(url.serviceContext + “/sample/cmis/repo”, false)}”>CMIS Repository</a>

You can download the web script files used in this example in JAR format, which you can extract using any unzip program into the directories specified above (or even easier – simply drop the JAR file itself into Liferay’s tomcat-x.x.x/shared/lib folder).

Share Custom Actions in a JAR

Update, 4th May 2011: The Backup action is now part of Share Extras and documentation and downloads are now available on the Custom Backup Action page.
Since the ability to package Share extensions was added in the HEAD codeline some weeks ago, both Kev and myself have demonstrated how you can package up dashlets in a JAR file for deployment into Share.
The example we both used was my Site Tags dashlet that we showed at the Alfresco Meetups last year, but at the Madrid meetup we also showed an example of how Share’s Document Library can be extended with a custom ‘backup’ action.
Custom backup action
Document Library actions have no web-tier webscripts, but hook into the client-side actions framework using a bit of custom JavaScript. Each action has a small 16×16 icon and a bit of CSS code to apply this image, one or more text labels (which should of course be externalised for i18n goodness) and a bit of config to hook them into the app. Lastly, since most actions by their nature do something, it’s likely that they will make a RESTful call back to the repository to perform their work, which may require a custom webscript there.
That’s quite a few files, but fortunately we can use the JAR extension mechanism to package everything up nicely.
Just like the Site Tags dashlet, I set this up in Eclipse using a standard project layout and my share extensions build script (with a couple of minor changes) to build the JAR file.
To make it easy to copy this structure, I’ve uploaded a ZIP of the project directory, containing the following files

  • build.xml – the extensions build script
  • config/alfresco/messages/slingshot-custom-backup-action.properties – contains the strings used for the action label and confirmation/failure messages
  • config/alfresco/templates/webscripts – contains the repository-tier webscript used to create a back-up copy of the file
  • config/org/springframework/extensions/surf/slingshot-custom-backup-action-context.xml – Spring config used to initialise the i18n messages
  • config/spring-surf-config-custom.xml – not used at present, but could define additional Surf endpoints for calling third-party RESTful services
  • web – contains all client-side resources used by the action

It should be relatively easy to copy this structure to define your own custom action, following the custom action wiki document to understand what each file does. This will help you to build other actions that call back to the repository via a web script, but actions aren’t limited to calling Alfresco services only. For some examples, take a look at my own list of Share action ideas.
Once you have your structure you can build the JAR file using Ant, e.g.

ant -Djar.name=share-backup-action.jar

The JAR file can then be dropped into Tomcat’s shared/lib directory, and all that remains is to configure the document actions web scripts to pull in the action definition. This is the slightly fiddly bit.
Firstly, copy the web script configuration file WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/documentlibrary/documentlist.get.config.xml from the Share webapp into the directory alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary in Tomcat’s shared/classes to override it. You should see a section <actionSet id="document"> which defines all the actions shown for a normal document in the document list view.
To add the backup action to this list, add the following line just before the </actionset> element for that block.

<action type="action-link" id="onActionBackup" permission="" label="actions.document.backup" />

If you also want the action to show up in the document details view, you need to copy the file WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/document-details/document-actions.get.config.xml into alfresco/web-extension/site-webscripts/org/alfresco/components/document-details in shared/classes in the same way.
Lastly, we need to ensure that the client-side JS and CSS assets get pulled into the UI as unfortunately the config files do not allow us to specify these dependencies.
To do this, we must override the file WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/documentlibrary/actions-common.get.head.ftl. Again, copy this into the corresponding directory in shared/classes/alfresco/web-extension and add the following lines at the bottom of the file.

<@link rel="stylesheet" type="text/css" href="${page.url.context}/res/components/documentlibrary/backup-action.css" />
<@script type="text/javascript" src="${page.url.context}/res/components/documentlibrary/backup-action.js"></@script>

That’s it. Now you can restart Tomcat and you should see the ‘Backup’ action – complete with UI labels and icon – in the Document Library.
Downloads