Custom Document Library Action

cancel
Showing results for 
Search instead for 
Did you mean: 

Custom Document Library Action

resplin
Intermediate
0 0 8,028

Obsolete Pages{{Obsolete}}

The official documentation is at: http://docs.alfresco.com



3.03.13.2ExamplesDeveloper GuideAlfresco Share






IMPORTANT
The procedures outlined below are for Share v3.x only and should not be used for v4.0 and newer.

Document Library Actions


The Document Library in Alfresco Share comes shipped with a number of default actions, automatically filtered by file type and user permissions.
DocLib_default_actions.png

The Document Library ('DocLib') formats the list of actions so that the overflow appears under the 'More' link.


Anatomy of an Action


There are various parts that make up a typical action:


  • HTML mark-up (pre-V3.2) or Configuration (V3.2 onwards) containing type and permissions metadata, client-side JavaScript function and i18n label name
  • i18n .properties file for translation look-up
  • CSS style to provide an icon
  • Client-side .js logic to construct the data webscript call
  • Optional web-tier mark-up and logic to present any dialog for further user input
  • Data webscript to perform the actial action

Obviously, not all actions require all parts and some actions may need more (e.g. interaction with third party services). For the purposes of this article, we'll be concentrating on a simple, repository-based action that requires no further user input.


Available Actions


HTML Mark-up (pre-V3.2)


(see documentlist.get.html.ftl)
This is the mark-up for the 'Move to...' action.




   ...
  
<a rel='delete' href='#' class='action-link' title='${msg('actions.document.move-to')}'>
      ${msg('actions.document.move-to')}</a>

   ...


  • Note: It's important to not add linebreaks inside the HTML for an action, as it effects the DOM parsing.

It's parent container DIV indicates that this action belongs to an action set for documents. The actual action set assigned to a particular asset is determined by the repository data webscript action-sets.lib.js and is beyond the scope of this article.

The action DIV's class attribute refers to the client-side JavaScript function which will be invoked when the user selects the action. This class also gives the link it's icon via a CSS rule.

Note the rel attribute on the A tag which indicates that the user must have delete permissions for this action.

The final point is the i18n message labels which are applied to the label and the link's title, used in 'Simple View' mode.


Configuration (V3.2 onwards)


(see documentlist.get.config.xml)
In Alfresco V3.2 onwards, the available actions are now specified via configuration instead of HTML mark-up.
The equivalent XML for the 'Move to...' action is now:



<actionSet id='document'>
   ...
   <action id='onActionMoveTo' type='action-link' permission='delete' label='actions.document.move-to' />
   ...
</actionSet>

The configuration entries are processed at run-time to render HTML mark-up as before, but are now much more intuitive.


I18N properties


(see documentlist.get.properties)
Convention is to label the action in an appropriate way depending on it's action set>



actions.document.move-to=Move to...

CSS Style


(see documentlist.css)
The only style required for the action's icon as a background image:



.doclist .onActionMoveTo a
{
   background-image: url(images/move-to-16.png);
}

Client-side JavaScript


(see documentlist.js and doclib-actions.js)
The 'Move to' action uses a dialog to obtain the required destination where the user wants to move the document. The details of the function aren't important here, just the function signature and how the function obtains the actual file being actioned.



/**
* Move single document or folder.
*
* @method onActionMoveTo
* @param row {object} DataTable row representing file to be actioned
*/
onActionMoveTo: function DL_onActionMoveTo(row)
{
   var file = this.widgets.dataTable.getRecord(row).getData();

   ...
},

The data webscript can be called by utilizing the genericAction() function provided by the Alfresco.module.DocLibActions module, via the DocumentList's modules.actions class variable. We'll be using this method in the example below...


Data Webscript


(see move-to.post.json.js)
To ease development of data webscripts, the Share DocLib provides a small framework to help build actions. The data webscript should follow this basic pattern:



<import resource='classpath:/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/action.lib.js'>

/**
* Move multiple files action
* @method POST
*/

/**
* Entrypoint required by action.lib.js
*
* @method runAction
* @param p_params {object} Object literal containing files array
* @return {object|null} object representation of action results
*/
function runAction(p_params)
{
   var results = [];
   var files = p_params.files;

   ...

   return results;
}

/* Bootstrap action script */
main();

The output template is trivial:
(see move-to.post.json.ftl)




Creating a New Action


In this example, we'll create a new action taken from the JavaScript API Cookbook, which will copy a document to a backup folder.


  • Note: These steps assume you are familiar with building a deploying the Share web application from SVN. If you do not wish to do this, it is also possible to edit the deployed web application directly (the webapps/share folder in Tomcat), although make sure you remove the share.war after it's initial deployment otherwise your changes will be overwritten.
  • Future versions of Share will allow new actions to be deployed without directly editing the source code.

Web Tier modifications


For the source (svn) case, the root folder for these web-tier webscripts is root/projects/slingshot/config/alfresco/site-webscripts/org/alfresco/components/documentlibrary and for the deployed option is webapps/share/WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/documentlibrary. Client-side assets can be found at root/projects/slingshot/source/web/components/documentlibrary and webapps/share/components/documentlibrary respectively.


Action HTML Mark-up (pre-V3.2)


First of all, we'll create the mark-up for the action by editing the documentlist.get.html.ftl presentation template. Our new action will apply to documents only, so add the following DIV tag to the 'document' action set:
As before, create this action with no linebreaks in the HTML.



<a rel='' href='#' class='action-link' title='${msg('actions.document.backup')}'>
   ${msg('actions.document.backup')}</a>


Action Configuration (V3.2 onwards)


We'll configure the new action by editing the documentlist.get.config.xml file. Our new action will apply to documents only, so add the following XML element to the 'document' action set:



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

Remaining Web Tier Modifications


Add the following i18n labels to documentlist.get.properties



actions.document.backup=Backup
message.backup.success='{0}' successfully backed up.
message.backup.failure=Couldn't backup '{0}

  • Note: You may have to restart the web application in order for new i18n strings to be picked up.

Add a suitable image to the images subfolder (call it 'backup-16.png'):
backup-16.png
(Icon credit famfamfam.com Silk Icons)

Add a new CSS file called backup-action.css in the client-side folder, next to documentlist.css to add a new CSS rule for the new action:



.doclist .onActionBackup a
{
   background-image: url(images/backup-16.png);
}

Add a blank JavaScript file called backup-action.js, also in the client-side folder where you just created the new CSS file. We'll be populating this file a bit later.

To ensure both these files are served to the web browser, you need to add two lines (and a suitable comment is recommended) to the documentlist.get.head.ftl presentation template.



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

If you are deploying to version 3.3 or greater and are compressing your client-side JavaScript assets (as you should be), then the @link and @script macros are provided in Freemarker to automatically include compressed versions of your files where necessary. In this case the following lines should be used instead.




At this point, you can deploy your changes and load up the Document Library to check that your new action appears correctly.

DocLib_backup_action.png

Clicking the action won't do anything yet, as the action handler code is smart enough not to try and call the onActionBackup function that we haven't defined yet.


Repository Webscript


At this point, it makes sense to create the repository webscript which will actually be making the backup copy. This is so we can determine exactly what parameters the webscript might need, so we can call it correctly from the client.

We'll add our repository actions to the Web Scripts Extensions area of the Data Dictionary in the Repository (not the file system). In order to provide the correct relative path for the include files, create the following space hierarchy under Web Scripts Extensions:



/org
   /alfresco
      /slingshot
         /documentlibrary
            /action

Create the following three files, which will form our backup repository action:


backup.post.desc.xml



<webscript>
  <shortname>backup</shortname>
  <description>Document List Action - Backup document(s)</description>
  <url>/slingshot/doclib/action/backup/site/{site}/{container}</url>
  <format default='json'>argument</format>
  <authentication>user</authentication>
  <transaction>required</transaction>
</webscript>

backup.post.json.js



<import resource='classpath:/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/action/action.lib.js'>

/**
* Backup multiple files action
* @method POST
*/

/**
* Entrypoint required by action.lib.js
*
* @method runAction
* @param p_params {object} Object literal containing files array
* @return {object|null} object representation of action results
*/
function runAction(p_params)
{
   var results = [];
   var files = p_params.files;
   var file, fileNode, result, nodeRef;

   // Find destination node
   var destNode = p_params.rootNode.childByNamePath('/Backup');
   if (destNode == null)
   {
      destNode = p_params.rootNode.createFolder('Backup');
   }

   // Must have destNode by this point
   if (destNode == null)
   {
      status.setCode(status.STATUS_NOT_FOUND, 'Could not find or create /Backup folder.');
      return;
   }

   // Must have array of files
   if (!files || files.length == 0)
   {
      status.setCode(status.STATUS_BAD_REQUEST, 'No files.');
      return;
   }

   for (file in files)
   {
      nodeRef = files[file];
      result =
      {
         nodeRef: nodeRef,
         action: 'backupFile',
         success: false
      }

      try
      {
         fileNode = search.findNode(nodeRef);
         if (fileNode === null)
         {
            result.id = file;
            result.nodeRef = nodeRef;
            result.success = false;
         }
         else
         {
            result.id = fileNode.name;
            result.type = fileNode.isContainer ? 'folder' : 'document';
            // copy the node to the backup folder
            result.nodeRef = fileNode.copy(destNode);
            result.success = (result.nodeRef !== null);
         }
      }
      catch (e)
      {
         result.id = file;
         result.nodeRef = nodeRef;
         result.success = false;
      }

      results.push(result);
   }

   return results;
}

/* Bootstrap action script */
main();

backup.post.json.ftl




Copy the three files to the space you created in the Data Dictionary. Then browse to the Web Scripts Home, (http://<server>/alfresco/service/index) and click Refresh Web Scripts.


  • Check the webscripts have registered correctly by browsing to: http://<server>/alfresco/service/script/org/alfresco/slingshot/documentlibrary/action/backup.post

Action JavaScript


The final piece is to wire-up the action link to some client-side JavaScript which will call our new webscript, passing in the file we want to create a backup of.

Open-up the backup-action.js client-side JavaScript file we created earlier and copy the following code into it:



/**
* DocumentList 'Backup' action
*
* @namespace Alfresco
* @class Alfresco.DocumentList
*/
(function()
{
   /**
    * Backup single document.
    *
    * @method onActionBackup
    * @param row {object} DataTable row representing file to be actioned
    */
   Alfresco.doclib.Actions.prototype.onActionBackup = function DL_onActionBackup(row)
   {
      var file = this.widgets.dataTable.getRecord(row).getData();

      this.modules.actions.genericAction(
      {
         success:
         {
            message: this._msg('message.backup.success', file.displayName)
         },
         failure:
         {
            message: this._msg('message.backup.failure', file.displayName)
         },
         webscript:
         {
            name: 'backup/site/{site}/{container}',
            method: Alfresco.util.Ajax.POST
         },
         params:
         {
            site: this.options.siteId,
            container: this.options.containerId
         },
         config:
         {
            requestContentType: Alfresco.util.Ajax.JSON,
            dataObj:
            {
               nodeRefs: [file.nodeRef]
            }
         }
      });
   };
})();

Note for v3.2 onwards: The actions implementation has moved to a different namespace and it's no longer necessary to call getRecord(). Rather use 'file' attribute directly in the argument, not 'row'.



   Alfresco.doclib.Actions.prototype.onActionBackup = function DL_onActionBackup(file)
   {
      this.modules.actions.genericAction(
      ...

Note if the above is not being called when the action is clicked on (seen with v3.3 onwards for instance) then it may need changing to:


  
         Alfresco.DocumentList.prototype.onActionBackup = function DL_onActionBackup(file)

The _msg function has also been renamed to simply 'msg', i.e.



         success:
         {
            message: this.msg('message.backup.success', file.displayName)
         },
         failure:
         {
            message: this.msg('message.backup.failure', file.displayName)
         },

  • If you're the impatient sort, go ahead and deploy the changes and the next action should be 100% operational!
  • One step we haven't added is to tell the Tree component that a new 'Backup' folder may have been created, so it won't know to refresh. For now, just refresh the page and you'll see the new folder.
  • We're also not covering the case where a duplicate document name is already present in the Backup folder. We could modify the code to check for this and keep appending a counter (for example) until a unique name was found.

Note how we're sending the file to be backed up to the data webscript: we pass it as an array of nodeRefs. This allows the data webscript to support backing up multiple files, which we could add by customizing the DocLib's toolbar to provide the 'Backup' action for multi-file selections.


Compressed Action JavaScript


From version 3.3 onwards it is recommended that compressed variants of all client-side JavaScript files are included with the original files. Compression substantially reduces the file sizes of these assets, with a resultant increase in the performance of the application.

The compressed .js files should be placed in the same directory structure as the regular files, but with a -min.js suffix. YUI Compressor is recommended to perform the compression - it provides an executable JAR file which can be used to generate compressed variants as follows (for this example)

java -jar yuicompressor-2.4.2.jar backup-action.js > backup-action-min.js

If you have used the @script macro in your modifications to documentlist.get.head.ftl above then you must include a compressed version in this way, otherwise your action will not work.