Skip navigation
All Places > Alfresco Content Services (ECM) > Blog > 2012 > May

In the first post of this series I showed how to extend the 'documentlist.get.html.ftl' and 'documentlist.get.js' files to instantiate a custom JavaScript widget that extends the default 'Alfresco.DocumentList'. This is a fragment from the “webview.get.html.ftl” file:

<@markup id='widgets'> <@inlineScript group=”dashlets”> var editDashletEvent = new YAHOO.util.CustomEvent('onDashletConfigure'); </@> <@createWidgets group=”dashlets”/> <@inlineScript group=”dashlets”> editDashletEvent.subscribe(webView.onConfigWebViewClick, webView, true); </@> </@> 

Note that another new FreeMarker directive is being used: <@inlineScript> - this directive is used to demarcate sections of JavaScript to be included on the rendered HTML page but allows the specified script to be moved around the page or into aggregated generated resource files (more on this in a future blog).


Setting Object References

This extra code is required in this WebScript to create a custom event that is triggered when a user clicks on a button in the title bar. The <@createWidgets> directive is able to pass a reference the “editDashletEvent” object by way of a special data structure that can be used when creating the instantiation metadata in the JavaScript controller.


The problem is that when creating the model it is impossible to distinguish between a String that is genuinely a String and a String that is a reference to a JavaScript variable defined in the FreeMarker template because the controller has no awareness of that variable (the JavaScript controller is processed before the FreeMarker template).


To work around this problem the 'webview.get.js' controller sets a reference by including the following object to the widgets 'options' metadata object:

eventOnClick: { _alfValue : 'editDashletEvent', _alfType: 'REFERENCE'},


When the <@createWidgets> directive encounters a JSON object with the attributes “_alfValue” and “_alfType” and ONLY those attributes it converts that object into a variable reference instead of a String (effectively it omits the quotes).


PLEASE NOTE: There were many different ways in which we could have solved this problem but based on the perceived frequency of it being required we took this approach, as opposed to (for example) forcing developers to specify all Strings with escaped quotes.


Generated JavaScript

Ultimately the final source for the generated page will contain the following:

<script>//<![CDATA[ var editDashletEvent = new YAHOO.util.CustomEvent('onDashletConfigure'); //]]></script> <script type='text/javascript'>//<![CDATA[ var webView=new Alfresco.dashlet.WebView('page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default').setOptions({'webviewTitle':'','webviewURI':'/share/page/webview-default','isDefault':true,'webviewHeight':'','componentId':''}).setMessages({'label.noWebPage': 'No web page to display.', '': '<p>This dashlet shows the website of your choice. Click the edit icon on the dashlet to change the web address.</p><p>Clicking the dashlet title opens the website in a separate window.</p>', 'label.header': 'Web View'}); new Alfresco.widget.DashletResizer('page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default', ''); new Alfresco.widget.DashletTitleBarActions('page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default').setOptions({'actions':[{'eventOnClick':editDashletEvent,'cssClass':'edit','tooltip':'dashlet.edit.tooltip'},{'bubbleOnClick':{'message':''},'cssClass':'help','tooltip':''}]}); //]]></script> <script type='text/javascript'>//<![CDATA[ editDashletEvent.subscribe(webView.onConfigWebViewClick, webView, true); //]]></script> 


Buried in amongst the generated instantiation script for the 'Alfresco.widget.DashletTitleBarActions' widget is the following call to the '.setOptions()'  function:

.setOptions({ 'actions': [{ 'eventOnClick' : editDashletEvent, 'cssClass' : 'edit', 'tooltip' : 'dashlet.edit.tooltip' }, { 'bubbleOnClick': { 'message' : '' }, 'cssClass' : 'help', 'tooltip' : '' } ] });


The key thing to notice is that the 'eventOnClick' attribute is being set as an object reference and NOT a String.



This post should have hopefully explained how to address the issue of referencing JavaScript objects declared in the FreeMarker template in the widget instantiation metadata (although we expect it to be something of an edge case) and the reason for the 'pre' and 'post' <@markup> directives in the new boiler-plate template. In the next post I'll describe how the <@link>, <@script>, <@inlineScript> and <@createWidgets> directives work in the different runtime modes now available in Surf .


In the last post I provided a simple example of how to customize the instantiation of a client-side JavaScript widget in Alfresco Share without really explaining what has been done to make this possible. In this post I will try to explain the changes that we've made to the Surf libraries and that we're currently in the process of making to the Share WebScripts that will make it possible to customize any part of Share using similar techniques.


Current Implementation

If you look at any of the original implementations of the Share WebScripts that instantiate client-side widgets then you will notice that they follow a common pattern:

    1. The widget is instantiated (sometimes it is assigned to a variable)
    2. The '${args.htmlid}' property is almost always passed as a single instantiation argument.
    3. A '.setOptions(..)' function call is chained to the instantiation call. The argument to this call is a single JavaScript object containing all the options to apply to the widget.
    4. A '.setMessages(..)' function call is chained to the result of the '.setOptions(..)' call

The main variables in this process are:

    1. The fully-qualified name of the widget instantiated
    2. The name of the variable that the widget is ultimately assigned to
    3. The options applied to the widget

Not all WebScripts are coded this way:

    1. Not all assign the widget to a variable
    2. Not all widgets are instantiated with a String as the sole argument
    3. Not all widgets have additional options applied to them
    4. Not all widgets have messages applied to them

We took these variables and constructed a template JavaScript object that encapsulated all the metadata that represented the instantiation of a single widget and created a custom FreeMarker directive ('<@createWidgets/>' that could process this object structure and output the JavaScript code that would perform the instantiation.


There are many WebScripts – most commonly those that create dashlets – that instantiate more than one widget. Therefore we knew that our custom directive would need to be able to process multiple metadata objects so we decided that the controller should always add the metadata objects to a list in the FreeMarker model.


So for example, if the following objects were constructed and set in the model:

widgets: [
    name: 'Alfresco.Widget1',
    assignTo: 'w1',
    initArgs: [ 'x', 'y' ],
    useMessages: true,
    useOptions: true,
    options: {
      option1: 'one',
      option2: two
    name: “Alfresco.Widget2”
    name: “Alfresco.Widget3”,
    useOptions: false,
    useMessages: false


Would result in the following JavaScript output (Note: I've intentionally left ${messages} and ${args.htmlId} as FreeMarker properties):

<script type=”text/javascript”>
  var w1 = new Alfresco.Widget1(“w”, “y”).setMessages(${messages}).setOptions({
    option1: “one”,
    option2: “two”

  new Alfresco.Widget2(${args.htmlId}).setMessages(${messages});
  new Alfresco.Widget3(${args.htmlId});

In the example shown we have been able to control exactly how each widget is instantiated:

    • 'Alfresco.Widget1' has explicitly set all properties.
    • 'Alfresco.Widget2' has taken all defaults.
    • 'Alfresco.Widget3' has taken some defaults but has elected not to set options or messages.


Here is a breakdown of the properties:


nameThe fully qualified name of the JavaScript widget to be   instantiated.
assignTo (optional)The name of the variable to assign to. Used if   additional JavaScript is required to access the widget after instantiation.   This can then be used in the post- instantiation JavaScript code.
initArgs(optional)The majority of widgets take just the unique id   assigned to the outer <div> element of the HTML fragment, but this can   be changed by providing alternative arguments. This is limited to String   values.
useMessages (optional  - defaults to true)Indicates that the i18n messages associated with the   WebScript should be passed to the widget by the .setMessages() function call.
useOptions (optional - defaults to true)Indicates that the options object should be passed to   the widget by the .setOptions() function call.
options (optional - defaults to the empty object)An object containing all the options to pass to the   widget in the .setOptions() function call.


Explaining the new 'Boiler-Plate'


The following code is from “documentlist.get.html.ftl” which is one of the first Share WebScripts to be converted to the new “boiler-plate” template. The idea is that all of the WebScript rendered Components will adopt this template to introduce greater consistency to help understand and customize the Share code.


<#include 'include/documentlist.lib.ftl' />
<#include '../form/'>

<@markup id='css'>
  <#-- CSS Dependencies -->
  <@link rel='stylesheet' href='${url.context}/res/components/documentlibrary/documentlist.css' group='documentlibrary'/>

<@markup id='js'>
  <#-- JavaScript Dependencies -->
  <@script type='text/javascript' src='${url.context}/res/components/documentlibrary/documentlist.js' group='documentlibrary'/>

<@markup id='widgets'>
  <@createWidgets group='documentlibrary'/>

  <@markup id='html'>

The template is divided into 6 separate <@markup> directives:

    • “css” declares the CSS files required for the WebScript
    • “js” declares the JavaScript files required for the WebScript
    • “widgets” is used for instantiating all the client-side JavaScript widets
    • “html” defines the HTML fragment that acts as the placeholder for the widget to anchor to.


By introducing a greater number of <@markup> directives into the template we make it easier to make finer-grained changed to the template – e.g. to remove, replace or add new dependencies or to modify the HTML fragment.


The New Directives

There are 4 new directives being used in the boiler-plate (although at first glance that might not be obvious). In previous versions of Share <@script> has been a macro – but now it is a fully fledged extensibility directive and the <@link> directive has also changed.


Surf is now able to process dependencies added via the '*.html.ftl' files by virtue of the extensibility model. Whereas before it would process all of the '*.head.ftl' WebScript files to gather all the required CSS and JavaScript dependencies before generating the page output, but now the <@script> and <@link> directives are able to add content into previously processed directives. This will facility will ultimate allow us to disable this double-pass processing to improve page rendering performance (although at the moment it is still enabled for backwards compatibility).


The <@createWidgets> directive is used to generate all of the JavaScript required to instantiate the client-side widgets defined in the model setup by the WebScript’s controller (“documentlist.get.js”) which now looks like this:


<import resource='classpath:/alfresco/site-webscripts/org/alfresco/components/documentlibrary/include/documentlist.lib.js'>


function main()
   var documentList = {
      id : 'DocumentList',
      name : 'Alfresco.DocumentList',
      options : {
         siteId : ( != null) ? : '',
         containerId : != null ? : 'documentLibrary',
         rootNode : model.rootNode != null ? model.rootNode : 'null',
         usePagination : args.pagination != null ? args.pagination : false,
         sortAscending : model.preferences.sortAscending != null ? model.preferences.sortAscending : true,
         sortField : model.preferences.sortField != null ? model.preferences.sortField : 'cm:name',
         showFolders : model.preferences.showFolders != null ? model.preferences.showFolders : true,
         simpleView : model.preferences.simpleView != null ? model.preferences.simpleView : 'null',
         viewRendererName : model.preferences.viewRendererName != null ? model.preferences.viewRendererName : 'detailed',
         viewRendererNames : model.viewRendererNames != null ? model.viewRendererNames : ['simple', 'detailed'],
         highlightFile : page.url.args['file'] != null ? page.url.args['file'] : '',
         replicationUrlMapping : model.replicationUrlMapping != null ? model.replicationUrlMapping : '{}',
         repositoryBrowsing : model.repositoryBrowsing != null,
         useTitle : model.useTitle != null ? model.useTitle : true,
         userIsSiteManager : model.userIsSiteManager != null ? model.userIsSiteManager : false

   if (model.repositoryUrl != null)
      documentList.options.repositoryUrl = model.repositoryUrl;

   model.widgets = [documentList];



The call to 'doclibCommon()' defined in the 'documentlist.lib.js' library file does the basic controller setup and the remainder of the code is defining the metadata object for instantiating the 'Alfresco.DocumentList' widget that was extended in the example included in the previous post.



This should hopefully be a good introduction into the changes that we're starting to make and how the widget instantiation metadata is set in the JavaScript controller and rendered via a custom directive into the FreeMarker template. There are still several concepts and details that need to be covered, the following will be discussed in forthcoming posts:

    • Handling pre and post widget instantiation JavaScript
    • Defining JavaScript object references in the the widget metadata
    • The purpose of the <@uniqueIdDiv> directive
    • JavaScript and CSS dependency aggregation to reduce HTTP requests
    • Dependency ordering and grouping

    This no longer works in the most recent releases of Alfresco but an issue has been raised to restore it


    The WebScript libraries can be used independently of Surf and this is what the Alfresco Repository uses to provide its REST API. The Surf libraries build on the WebScript libraries and although some of extensibility classes and interfaces have been defined in the WebScripts libraries the actual extensibility container is defined as part of Surf. This meant that it was not possible to customize the Repository WebScripts which forced you to use the old approach of copying the relevant folders and files into the “web-extension” path and make the changes on the copies (which then introduced maintenance issues).

    In an attempt to address this issue we’ve created a simple Extensibility Container for the WebScripts library which gives you the ability to perform some of the same customizations. Essentially you can perform the same types of customization described in my earlier blogs for extending i18n properties files, JavaScript controllers and the FreeMarker templates of a WebScript.

    There are some important differences to be aware of though…

      • The new container isn’t the default container – if you want to make use of it then you’ll have to explicit update your Spring application context configuration.
      • There is no module deployment process – if the configuration for a module is found on the extensions search path then it will be automatically deployed.  The Surf layer provides all sorts of additional persistence options which aren’t available in the base WebScripts layer so it wasn’t possible to provide that level of flexibility.
      • Currently there are no evaluations although it may be possible to add this capability at a later date.
      • There are no <@markup> directives anywhere in the Repository code. This means that you cannot extend the FreeMarker templates as they are out-of-the-box. The only way to make use of this type of customization would be to manually update the original files yourself. You could argue that this defeats the purpose of the exercise but we’re not really sure how useful a feature this might be (we’re relying on feedback from the Community with in this regard!) and secondly, the use-case being considered is where the JSON response has additional content added to it. This can be achieved by simply adding a basic <@markup> directive to the end of the '.json.ftl' file which would require a negligible amount of maintenance.


    Configuring the Container

    To configure your Repository to use the ExtensibilityContainer you need to edit the 'webapps/tomcat/alfresco/WEB-INF/classes/alfresco/web-scripts-application-context.xml' file and update the definition for the 'webscripts.container' bean so that this line:


    <bean id='webscripts.container' parent='webscripts.abstractcontainer'>


    <bean id='webscripts.container' class='org.alfresco.repo.web.scripts.ExtensibilityContainer' parent='webscripts.abstractcontainer'>
      <property name='extensibilityModuleHandler' ref='extensibility.handler'/>

    The  'extensibility.handler' bean is defined within the WebScript library JAR.



    This is a simple example of something that can be done (there are other ways you could implement this, I'm just using this approach for the benefit of the tutorial)...


    Let's say that you want to update the 'Sites' drop-down menu on the header bar of Share to show not only the users favourite sites but also all the sites that they are a manager of.  The Share WebScript used to generate that drop-down menu is 'org.alfresco.modules.header.sites.get' where the controller 'sites.get.js' makes a remote call to the '/api/people/{user_ id}/preferences' REST API on the Repository which returns a list of the users favourite sites.


    This REST API is served by the 'org.alfresco.repository.preference.get' WebScript which we can now extend to provide the additional data we'll need to show the sites that the user manages.


    As with the Share customizations we're going to create a module in a JAR which we drop into the 'WEB-INF/lib' folder of the application (in this case 'webapps/alfresco/WEB-INF/lib'.  The module configuration file needs to be placed in the package 'alfresco.webscripts.extension.config' (although this is a default location for module configuration files can be overridden in the Spring application context).


    Create a file called 'preferences-extension.xml' containing the following code (this should be familiar from the earlier Share related customization tutorials):


             <id>User Sites Menu Customization</id>
             <description>Add additional data to the users sites menu</description>


    Now create a file called 'preferences.get.js' and place it in the JAR at the package 'webscripts.repository.module'. The file should contain the following code:

    // Check that user details have been provided and that the user exists...
    var userid = url.templateArgs.userid;
    var person = people.getPerson(userid);
    if (person != null){
       // Get all the sites that the use belongs to and find the ones that they are a manager for...
       var managedSites = [];
       var allSites = siteService.listUserSites(userid, 100);
       for (var i = 0; i < allSites.length; i++)
          if (allSites[i].getMembersRole(userid) == 'SiteManager')
             // Construct a new object with the site details we want...
                shortName: allSites[i].getShortName(),
                title: allSites[i].getTitle()

       // Convert the original JSON data string back to an object...
       var preferences = jsonUtils.toObject(model.preferences);
       preferences.managedSites = managedSites;
       model.preferences = jsonUtils.toJSONString(preferences);


    In the default 'preferences.get.js' file (the one that we're extending) the preferences data is stored in the model as a String representation of a JSON object. Our JavaScript file is run after the default one has finished processing and whilst it doesn't have access to any of the local variables used it does have access to the same model. We therefore need to convert the preferences object (stored at 'model.preferences') back into a JSON object so that we can add our additional data to it and then convert it back to a String.


    PLEASE NOTE: In this particular example we are lucky because the associated FreeMarker template ('preferences.get.json.ftl') simply outputs the JSON String.  Ideally all JSON responses would be handled this way but the original WebScript authors could never have known that one day their code would be extended in this way and so some of them construct a JSON response directly. To work around this problem it will be necessary to update the original WebScript to include a <@markup> directive and then extend the template to reference the additional data.


    Build your JAR file and copy it into the 'webapps/alfresco/WEB-INF/lib' directory and when you restart the server and view a users dashboard you can check the response in FireBug or other Web Developer Tools (see screenshot below):



    Updating Share

    To complete the customization we need to make some updates to the Share WebScripts as well - but this isn't a tutorial on customizing Share and there should be enough information in the other blog posts to help you do this. However, I did note when looking at this example that it's not as easy as it could be (a copy and paste to the Share 'web-extension' path would still be required) - fortunately the changes we're in the process of making to the Share WebScripts should mean that this won't be the case in the future!!



    The engineering team at Alfresco has been working hard over the past year or so to make it easier to customize the Share user-interface.  We've now started work on what I’d consider to be the last major piece of the extensibility puzzle – customizing the instantiation of client-side JavaScript widgets for each WebScript rendered Component.

    The only way it was previously possible to change the widget(s) instantiated for a WebScript rendered Component was to copy and paste the supporting code to the “web-extension” path and then make the necessary changes to that copy. The major downside of this approach was that it generated an additional maintenance task to keep the copied code up-to-date with any changes that resulted from upgrades, service packs and hot-fixes.

    The approach that we’re taking is to move all the logic and metadata about widget instantiation out of the FreeMarker template and to move it into the JavaScript controller as it is inherently easier to customize.  The metadata will be stored as a standardized object structure in the model which will then be processed by a new custom directive in the FreeMarker template to output the JavaScript code necessary to instantiate the specified widgets.

    This allows us to utilize the existing JavaScript controller extension capabilities so that extension modules can modify the default metadata object(s) to change the following:

      • The name of the JavaScript widget to be instantiated
      • The arguments passed when instantiating the widget
      • The variable that the instantiated widget is assigned to
      • Whether or not i18n messages are set for the widget
      • Whether or not additional options are applied to the widget
      • The additional options that should be applied to the widget

    As part of these changes we will also be updating the FreeMarker template to use a common “boiler-plate” structure to ensure consistency across WebScript rendered Components. We will utilize updated resource handling features in Surf to move all the CSS and JavaScript dependency requests into the template and remove the associated *.head.ftl file.  A consistent pattern of <@markup> directives will be used throughout the template to further enhance customization options.

    The first WebScripts converted are those on the Document Library page as these as this is the area most frequently customized – these changes have already been committed to the latest Community source code in SVN.




    Let’s consider a simple example of what can be done….

    Say we want to pop-up a message every time that a Document Library filter is changed.  The obvious place to handle this would be at the end of the “onFilterChanged” function in the “documentlist.js” file. Up until now we had the following options:

      1. Edit the local copy of “documentlist.js” (and then have to maintain any changes to it that result from fixes and upgrades)
      2. Create a custom DocumentList JavaScript subclass and then copy and paste the “documentlist.get” WebScript to instantiate it instead of the default Alfresco implementation (and then have to maintain any changes to the WebScript that result from fixes and upgrades).

    Now though we have a slightly better option – we can create our subclass as with option 1, but we can create an Extension Module to the WebScript to both import our new library file and to instantiate our custom widget.


    Our module configuration looks like this:


      <id>Custom DocumentList Widget</id>
      <description>Instantiate a custom DocumentList widget</description>


    This identifies the WebScript package that we want to target – in this case it’s the Document Library related WebScripts. We then create our custom Document List widget in our Extension JAR (“META-INF/doclib/extension/custom-documentlist.js”):


    // Declare namespace...
    if (typeof Blog == undefined || !Blog) { var Blog = {}; }
    if (!Blog.custom) { Blog.custom = {}; }
      // Define constructor...
      Blog.custom.DocumentList = function CustomDocumentList_constructor(htmlId)
      {, htmlId);
        return this;

      // Extend default DocumentList...
      YAHOO.extend(Blog.custom.DocumentList, Alfresco.DocumentList,
        onFilterChanged: function CustomDL_onFilterChanged(layer, args)
          // Call super class method...
, layer,args);

          // Pop-up a message...
            text: 'Filter Changed!'


    Then we create an extension to the “documentlist.get.html.ftl” template to create a dependency to the file containing our custom widget (“webscripts/blog/demo/customization/documentlist.get.html.ftl” in our extension JAR):


    <@markup id='custom-documentlist-dependencies' target='js' action='after' scope='global'>
      <@script src='${url.context}/res/doclib/extension/custom-documentlist.js' group='documentlibrary'/>

    Finally we create an extension to the “documentlist.get.js” controller to change the widget that is instantiated ('webscripts/blog/demo/customization/documentlist.get.js'):


    // Find the default DocumentList widget and replace it with the custom widget
    for (var i=0; i<model.widgets.length; i++)
      if (model.widgets[i].id == 'DocumentList')
        model.widgets[i].name = 'Blog.custom.DocumentList';

    Finally, deploy the module, restart Alfresco and each time you change a filter in the Document Library you should see the popup.


    Extended Document List

    Whilst preparing for a Tech Talk Live presentation on customizing Alfresco Share I realised that there was another extensibility capability that I’d not actually blogged about. This was a feature that had been requested internally and will be available in Alfresco Enterprise 4.0.2 and is in the latest Community source code on SVN.



    If you look in the “webapps/share/WEB-INF/classes/alfresco” folder of your web server hosting Alfresco then you’ll notice a number of files ending “-config.xml” (i.e. “share-config.xml”). These files contain configuration that is loaded into a Spring bean when Alfresco starts and is accessed by Share code to dictate many different aspects of its behaviour. This is purely static configuration in and changes to the files will only be applied with a server restart.


    However, it is now possible to dynamically control the configuration through the use of extension modules. A module configuration file now supports the  element which allows you to change the configuration without restarting the server by deploying and un-deploying modules or to use evaluations to determine when specific configuration is applied.



    Let’s say that you want to use the Flash uploader for some sites and the new HTML5 uploader for others. The Flash uploader is normally only used when Flash is enabled in the “share-document-library-config.xml” file.


    However if we create a module with the following configuration:

      <id>Site Conditional Flash Upload</id>
      <description>Applies config based on site id</description>
      <evaluator type='site.module.evaluator'>
        <config evaluator='string-compare' condition='DocumentLibrary' replace='true'>



    We are able to disable the Flash uploader for the site called “noflash”. The evaluator used is available “out-of-the-box” and for further information on evaluators read this post. The  element contains a copy of the configuration taken from the “share-document-library-config.xml” file but changes the default <adobe-flash-enabled> value from “true” to “false”.


    The configuration is only applied when the module is deployed and the user is uploading to the Document Library in the “noflash” site (see the screenshots below).


    Flash uploader


    HTML uploader


    Important Notes

    It is important to note that the same rules of statically extending configuration apply here, i.e. that it is not an augment capability. Note that I have had to include the  <in-memory-limit> element even though it has not changed, because otherwise it will be lost. If the “replace” attribute were removed from the <config> element then the result would be that two <adobe-flash-enabled> elements will be included in the configuration and it would almost be guaranteed that only the original statically defined element would be used.

    Filter Blog

    By date: By tag: