Skip navigation
All Places > Alfresco Content Services (ECM) > Blog > 2013 > September
2013

A powerful new feature in Alfresco Enterprise 4.2 is the addition of a user friendly Repository Administration Console. This post describes some of the features present in the console and how it can be easily extended.

 

 

Introduction

It is common knowledge that configuring features such as directory management (e.g. LDAP) or for instance tweaking the correct inbound email server settings is tricky in Alfresco due to the number of scattered, complex XML and property file changes that may be required. Previous versions of Alfresco have some admin tools available in Alfresco Explorer and in Alfresco Share but not much that helps with initial server set-up.

 

A JMX interface to many beans has been available in Alfresco Enterprise for a while and is accessible via something like JConsole. Although this is great for run-time changes compared to XML/properties file edits (which require a server restart, JMX does not), it is still a rather mysterious interface with little help or documentation available.

 

The Repository Admin Console goes a long way to improving this situation. A graphical interface with in-line help text and links directly to the appropriate on-line Alfresco documentation.

 

Under the covers, the JMX interface is used to retrieve and persist configuration changes made via the console. This means changes are persisted to the DB and will take precedence over XML and property file settings, it will also mirror changes still made via JMX interfaces.

 

It should also be noted that the console is an 'in process' application, part of the /alfresco web-app context. This means set-up of a remote application like Share is not needed to use it. Also it has complete access to the rich set of internal Alfresco APIs beyond what would be possible via a REST interface back to Alfresco. It also removes a point of failure - no longer having to ensure a remote application is working and can be authenticated before the system can be managed and configured.

 

System Summary

When you first enter the admin console a System Summary screen is presented that displays a quick overview of the status of the various sub-systems in Alfresco. Including information on clustering, AMPs applied and the current authentication chain. It also displays instance data such as the memory usage, content store disk space, Java and OS version information. The current host-name and IP is always displayed in the header area for all pages.

System Summary

 

General

There are a number of other general information pages, including information on the current license details and what server features are enabled on the license, plus meta like the unique repository ID and the installed/current DB schema numbers.

 

The License page allows you to upload and apply a new Alfresco license instance immediately without having to restart the server.

 


 

Directory Management

One of the most featured areas of the Admin Console is the ability to configure and test connections to various directory services. The Directory Management page provides an interface to create, configure and manage internal Alfresco directories, OpenLDAP, Active Directory and configure authentication chain options for services such as CIFS and browser SSO. Connections to various services can be tested before activating them in the authentication chain and common user synchronization settings are also managed here. It is a powerful new mechanism to set up directory services for Alfresco that anyone who had previously configured LDAP or similar before via property files with far-too-many server restarts should be much happier with.

Directory Management

 

Repository Services

Many of the important sub-systems in Alfresco have a page in the new Admin Console. Features such as Activities, Clustering, Process Engines (workflow) and Transformation services can all be configured via the interface. There is very rich Search Service page with lots of in-line help for the advanced options for Lucene and SOLR and settings for the new Transactional Query feature in 4.2. Finally there are pages for inbound and outbound email server plus File Servers such as CIFS and IMAP.

 

Extending the Admin Console

Each page of the Admin Console is a simple WebScript component. There are no new client-side JavaScript libraries to learn and no new REST API. Each page is built from a library of useful functions and macros imported into each Admin Console WebScript.

 

The JS library functions are responsible for doing the hard work, retrieving the JMX MBean properties and then handing over to flexible FreeMarker macros which automatically render the appropriate control for a JMX property in a consistent way. Assuming no additional processing logic is required, the WebScript library functions will automatically persist them back to the correct property. If you are building simple JMX Form style pages then it really is extremely simple to do. The Alfresco Support team have already been building new pages that they will make available as needed, including; Thread Dump, Active Sessions, Log4J settings and Test Transforms.

 

A sample commented page is included as a starting point for creating new pages.

 

Example controller code from admin-example.get.js:

<import resource='classpath:alfresco/enterprise/webscripts/.../admin-common.lib.js'>

/* Repository Admin Console - Example GET method */

Admin.initModel(

   'Alfresco:Name=License',

   ['Subject', 'Issued', 'RemainingDays'],

   'admin-example'

);

 

This will retrieve the 'Subject', 'Issued' and 'RemainingDays' properties from the 'License' JMX bean.

 

Example template code from admin-example.get.html.ftl:

<#include 'admin-template.ftl' />

<@page title='Example Page'>

   <div class='column-left'>

      <@section label='Some Values' />

      <@control attribute=attributes['Subject'] />

   </div>

   <div class='column-right'>

      <@section label='More Values' />

      <@control attribute=attributes['Issued'] />

      <@control attribute=attributes['RemainingDays'] />

   </div>

</@page>

 

Example output from admin-example WebScript:

 

admin-example

 

The values from the License JMX bean are read-only and the template macros are smart enough to know this and display read-only text. However, if they were editable or you wanted to show a different form field a simple change to the template is all that's needed:

      <@attrtext attribute=attributes['Subject'] />

Example output:

admin-example

Final Words

You can still use XML/properties files to set-up Alfresco if that's your preference - we haven't removed anything related to this, just added the new interface over the top.

Already we've had suggestions from Alfresco partners on how to improve it going forward, and we will certainly be looking at features in the future such as:

    • Import/export of data, allowing a server set-up via the console to be replicated across multiple instances.
    • Read-only mode for Admin Console - to ensure an instance can be examined in detail but no accidental changes made.


If you have any further suggestions, feel free to raise an Improvement request on JIRA or post a comment here. We hope you find it useful!

This post won’t tell you much new if you are already experienced in Alfresco but even then you might found something curious somewhere. So be warned that the audience for this post is mostly the Alfresco newbie that just started playing with this great Open Source ECM platform.



I am often asked about things that are related in a way or another with the parent-child relationship between nodes in Alfresco (folders or documents). Not surprising this is a core concept in Alfresco. So I thought it deserved a dedicated post going through some of the nuances.



 



Javascript API Root Variables 



One of the most common places where a conversation about a node’s parents and children in Alfresco will start is the Alfresco’s server side Javascript API. If we look into it [1]:



 





































Root ObjectType in Script RuntimeDescription
documentorg.alfresco.repo.jscript.ScriptNodeThe current document ScriptNode (if any)
spaceorg.alfresco.repo.jscript.ScriptNodeThe current space ScriptNode (if any). For a script executing from a rule, the space object is the space in which the rule resides. If the rule is inherited, this may not be the expected space.


 



The newbye will naturally assume document always refers to a (content) document and space to a folder. But that’s very far from the truth (although the original root variables names themselves bear a big amount of fault for this). It is in fact better to map it conceptually as following:



 































Root ObjectDescription
documentThe current node ScriptNode.
spaceThe parent node ScriptNode of the current node.


 



If you want to see this in action just create a simple Javascript script as the following:



 

function main() {



     return 'Node: ' + document.name +'\tParent: '+space.name+ '\tDate: ' + new Date().toUTCString();



}



main();


 



Call it as myscript.js (by the way this would be a terrible name for a real production script...) and store it in the Company Home/Data Dictionary/Scripts folder in Alfresco.



This server-side script basically returns a line with the details about the node name and its parent name, besides the date of execution.



So now, if we use the Script Command Processor of Alfresco we can call the script for any node on our repository. We just need to call from our browser an URL like this [2]:



 



http://localhost:8080/alfresco/command/script/execute/workspace/SpacesStore/b9f89106-acb3-458c-9151-0318b1c613dc/workspace/SpacesStore/98ef0384-d882-4839-89d2-40026edd89f9



 



The first uuid belongs to the script’s node reference and the second to the node we want to execute the script against (so you should replace according to your installation and setup). You should get a response similar to this:



Node: test Parent: documentLibrary Date: Wed, 25 Sep 2013 10:10:05 GMT



 



document can be a Space



Just execute the script for a folder in Alfresco and you will see that document will reference the folder’s ScriptNode and less strangely space to its parent folder. This is in fact a very common case with server-side scripting in Alfresco.



 



space can be a Document



This will be most probably more unexpected to some. But if you call the script for an inner child of a document, the space variable will refer to the document and document to the inner child node. And yes, documents can have children in Alfresco and in fact they are very common.



For example when you upload a document in Alfresco Share Document Library you can see with the Node Browser [3] that these documents have children for things like thumbnail, preview and rating. You can use a node reference for one of these child nodes to run the script against and see how your document will be referenced by the space root variable in the Javascript.







The fact that these document children (doclib, webpreview, etc) are not available during normal navigation, be it through Share or file protocols like CIFS, is just because those interfaces were specifically designed (or by their own nature as in case of CIFS wouldn’t adapt to it very easily...) to ignore those objects and corresponding particular kinds of parent-child association used. They just know about the standard parent child association that associates a folder to other children folders or documents.



 



What about rules? (an experiment)



Why rules can’t be defined for documents just like they are for spaces? In fact you can, just configure Share to show the Manage Rules action in a document. If you want to try (you definitely don’t want to do that for real projects) just edit the share-documentlibrary-config.xml and add the corresponding action entry for the action group of document-details. Something like this:



 

<actionGroup id='document-details'>



<action index='100' id='document-download' />



<action index='105' id='folder-manage-rules' />



...


 



When you restart Alfresco you should be able to see now the Manage Rules action on the document details pages. You can create the rule and execute for example a variation [4] (see bellow) of the previously used for any “incoming node inside” your document.



 

var logFile = companyhome.childByNamePath(document.id+'.txt');



if (logFile == null) {



      logFile = companyhome.createFile(document.id+'.txt');



}



if (logFile != null) {



      logFile.content += 'Node: ' + document.name +'\tParent: '+space.name+ '\tDate: ' + new Date().toUTCString() + '\r\n';



}






 



If you execute the rules explicitly from the Manage Rules interface for the document you should see that the script executes as before with the Script Command Processor for each of the document children (doclib, webpreview and rating if there’s one). Except for the rules child itself (created cause of the definition of the document rule), because Alfresco ignores the rules folders as expected (imagine if a rule in a folder was sensitive to the node where it’s defined, it wouldn’t make any sense and that would be definitely unexpected to the end user that don’t see it).



But although this might be fun as an experiment it is not really useful because any rules automation won’t be triggered for non standard folder-document parent child related nodes. So for documents rules will never be triggered for its children (only explicitly as in the experiment we just did). [5]



In any case a script that references a document as space and its inner children as document can still be useful in other contexts.



 



Multiple Parents



This fact is many times ignored by new comers to Alfresco platform, but Alfresco does not limit the number of parents a node may have. Although parents of a node are not completely equivalent between themselves. There’s always one considered to be the primary parent while the others if any are considered to be the secondary parents. The difference between a primary parent and the other secondary parents, is that if a primary parent is deleted the child node is deleted with it while for secondary parents this cascading deletion does not happen.



If you want to see multiple parents in action just save the following javascript as a new script (same as before but with a different name) [6]:



 

var childNodeRef = 'workspace://SpacesStore/08bba651-81d6-4f17-bf8b-06def398b7b8';  



var childNode = search.findNode(childNodeRef); 



document.addNode(childNode); 


 



Replace on the script before the value for node reference by one of a document in your repository.



This script adds your document as a child to any folder you run the script against it, so that the folder becomes a secondary parent of your document.



Execute the script against a folder that is not the actual parent of the document from which you copied the node reference inside the script. You should then see that the same document (it could also be a folder) shows up besides in its original location also as a child of the new folder. This is not a copy but exactly the same document as you can confirm by editing it (and seeing same changes on metadata or content reflected on both places) or just by checking its node reference [7].



And from the node browser you can check that the document has in fact two parents now (one, the first one, which is the primary).







On the Alfresco Share UI you will notice on the head breadcrumb of the document’s page details that, doesn’t matter how you navigate till it (from the primary or secondary parent), the path shown is always the one from the primary parent (which not necessarily may always be ideal but it’s consistent with Share URL’s addressability).



 



An example: Link to Rule Set 



When in Share you Link to Rule Set [8] (in place of creating local rules) Alfresco is in fact leveraging multiple parents for making the ‘hidden’ rule folder of the first folder to gain a new secondary folder parent. You can confirm that again by navigating through the node browser.



 



Javascript Add Node Extension



By the way the javascript API out of the box is limited to what concerns creating parent child associations that not the standard one (as we did before in our script with the addNode method). To have that would be useful in the case we wanted to link a rule set by ourselves through some automated script for example. But Alfresco allows us to extend its Javascript server side API fairly easily with some coding. [9]



You can check this project for an example on how to extend the API for allowing that: https://code.google.com/p/rules-scriptnode-extension-demo/



This extension once applied to Alfresco allows you to use a new root variable called extScriptNode and its method linkRuleSet to do that, or if you wanted to add another specific custom parent child association of your own the method addNode of the new root variable could be leveraged. [10]



Another example: In Situ Records



With Alfresco Enterprise 4.2 and Alfresco Records Management 2.1 we offer the capability to declare in situ records. So that the records can be declared without leaving the original edition collaboration context. This is great of course and make records management so much more useful for the generic end business user. And by the way it’s in fact implemented leveraging multiple parents.



The collaboration document after declared as record gains a new parent (belonging to the RM site) which becomes in fact its primary parent, and the original parent collaboration folder his secondary parent. Also the new option available for hiding a record (available on the original collaboration context after the record declaration) what in fact does is to remove the secondary parent-child association of the record, so that the record can now only be discovered on the Records Manager site.



Back to Javascript API



Now back to our Javascript server side API of Alfresco we are in conditions to refine the initial conceptual description/mapping of those two root variables to:



 































Root ObjectDescription
documentThe current node ScriptNode.
spaceThe primary parent node ScriptNode of the current node.


 



I hope it was useful (or at least relatively fun) this ‘small’ walkthrough by some details around Alfresco’s parents and children nodes.

______________________________________

1. Source: http://docs.alfresco.com/4.1/index.jsp?topic=%2Fcom.alfresco.enterprise.doc%2Freferences%2FAPI-JS-rootscoped.html



2. In general for creating a custom REST HTTP API you should leverage the Alfresco Web Scripts framework



http://docs.alfresco.com/4.1/index.jsp?topic=%2Fcom.alfresco.enterprise.doc%2Fconcepts%2Fws-architecture.html



But for our purposes the Script Command Processor is very handy and does in fact what we want it to do: execute scripts against specific nodes.



3. http://docs.alfresco.com/4.1/index.jsp?topic=%2Fcom.alfresco.enterprise.doc%2Ftasks%2Fadminconsole-nodebrowser.html



4. The script now is writing the same line as before to a document in the repository (with name equal to id of the corresponding node). It it will create the document before writing or append it if already existing. This way we have a “registry” of the script execution.



5. For cases like this you might need to leverage the powerful policies capability of Alfresco:



http://wiki.alfresco.com/wiki/Policy_Component



6. By the way you can notice how in this sample script the variable document is actually referencing a folder.



7. A copy of a node generates another different node, that can be acted separately (for editing, deleting, etc), even if the binary content path for the copied node points to the same location (till an update in one of the nodes happens, branching the content between the nodes).



8. http://docs.alfresco.com/4.1/index.jsp?topic=%2Fcom.alfresco.enterprise.doc%2Ftasks%2Flibrary-folder-rules-manage.html



9. Check http://wiki.alfresco.com/wiki/4.0_JavaScript_API#Adding_Custom_Script_APIs



10. Check the class code for details:



https://code.google.com/p/rules-scriptnode-extension-demo/source/browse/src/main/java/demo/rf/alfresco/script/node/extension/ExtScriptNode.java .

 

Introduction

A comment on a previous post questioned why it was necessary to update the 'surf.xml' configuration file in order to declare a new AMD package. This was an obvious requirement that I had completely overlooked and have now implemented a simple solution to address this for the Alfresco Community 4.2.e and Alfresco Enterprise 4.2 releases.

 

Understanding the Problem

The package declaration configuration defined in 'surf.xml' is transformed into a simple JavaScript object that is created before the Dojo kernel is loaded (the Dojo kernel provides the AMD loader). By default Alfresco Share will declare the following packages:

    • alfresco
    • surf
    • dojo
    • dijit
    • dojox


If a 3rd party wants to extend Share by providing a JAR file containing WebScript page definitions that use modules defined in their own package then they would need to get the Admin to update the 'surf.xml' in order for the modules to be resolved.

 

Dynamic Configuration to the Rescue

Surf supports the capability to dynamically modify configuration on a per-request basis (as described here) but unfortunately the org.springframework.extensions.surf.DojoDependencyHandler class was only using the static configuration. An update to Surf (revision 1323) has fixed this so that it is now possible for extension modules to add additional packages dynamically.

 

Example

This sample JAR file contains an example of how to declare an extension that adds an additional package. It contains the following structure:

    • alfresco/site-data/extensions/extension.xml (you can call it whatever you want!)
    • META-INF/js/lib/blogs (this is our package root for widgets)


In 'extension.xml' you'll see the following:

 

<extension>
  <modules>
    <module>
      <id>Add a Custom Package</id>
      <version>1.0</version>
      <auto-deploy>true</auto-deploy>
      <configurations>
        <config evaluator='string-compare' condition='WebFramework' replace='false'>
          <web-framework>
            <dojo-pages>
              <packages>
                <package name='blogs' location='js/lib/blogs'/>
              </packages>
            </dojo-pages>
          </web-framework>
        </config>
      </configurations>
    </module>
  </modules>
</extension>


Copy the JAR file into the '<tomcat-home>/webapps/share/WEB-INF/lib' directory and restart the server and when you view the source of a page you should see that the additional package element has been added.

 

Page source showing the additional package

 

The Result

The upshot of all this is that you can use a widget in the 'blogs' package when defining page structures via JSON as the loader will be able to resolve the dependencies. In the sample JAR I've created a page called 'greeting' which can be accessed via the URL https://<host>:<port>/share/page/hdp/ws/greeting and renders the 'blogs/greeting/HelloWorld' widget (modelled on the widget described in this blog post)

 

Screenshot showing Share with the HelloWorld widget

 

There's nothing especially remarkable about this widget beyond the fact that it is loaded from a package defined in a module and the code is all contained within the JAR.

 

Summary

From Alfresco Community 4.2.e and Alfresco Enterprise 4.2 it will be possible to dynamically add AMD packages through extension modules to avoid the need to update 'surf.xml' and restart the server.

 

Again, I'm grateful to the Alfresco Community for highlighting this omission and hopefully have reinforced our commitment to respond to the Community needs!

Introduction

In the first two posts in this series I've focused on how to add or modify items in the new Alfresco Share header bar and in this post I'm going to focus on how to hide items.

This will be demonstrated in two different ways:

  • Using a built-in helper function
  • Through configuration exposed by the 'alfresco/header/AlfSitesMenu' widget.


To get the most out of this post you should ideally read the previous two entries in the series as the first provides detailed information on creating a JAR file for your extension and the second provides some useful information on the 'Sites' drop-down menu.

 

The examples described in this post can be downloaded as a JAR file from here.

 

Removing Menu Items

After we released Alfresco Community 4.2.d it was very quickly noticed by observant community members that whilst there was a built-in helper method for finding widget entries that was helpful when adding or changing entries, it didn't actually help with removing entries.

To resolve this issue we have introduced the 'widgetUtils.deleteObjectFromArray' helper function. Unfortunately this helper method is NOT available in Alfresco Community 4.2.d but will be available in the Alfresco Community 4.2.e and in Alfresco Enterprise 4.2 releases (...but you can always try it out in the nightly builds!).

The function is very simple to use. It takes 3 arguments:

    1. The object to remove the widget definition from (you can typically just use 'model.jsonModel')
    2. The attribute to search for (you should use 'id')
    3. The value to match against the target attribute (the 'id' attribute of the widget definition to remove)


So for example to remove the 'My Files' link from the menu bar you would need to include the following code in your 'share-header.get.js' extension:

widgetUtils.deleteObjectFromArray(model.jsonModel, 'id', 'HEADER_MY_FILES'); 

 

Before the module is deployed the header is as follows:

 

Screenshot of header menu showing the My Files link

 

...and after the module is deployed the link has disappeared:

 

Screenshot of header without the My Files link

 

This is obviously a very simple example and you can add finer grained control through JavaScript in the controller extension, e.g:

if (!user.isAdmin)
{
  widgetUtils.deleteObjectFromArray(model.jsonModel, 'id', 'HEADER_MY_FILES');
}

 

...that would hide the 'My Files' link if the user isn't the Admin, or by deploying the Module with an evaluator (which pretty much allows you complete control over when the module is applied).

 

Customizing the Sites Drop Down Menu

The topic of hiding specific items from the Sites menu was raised on the Alfresco forums and I was embarrassed to discover that I'd not provided adequate configuration for toggling what is displayed (e.g. 'Recent Sites', 'Favorites', 'Site Finder', etc) so I made some updates which unfortunately were also done after Alfresco Community 4.2.d release.

One of the goals for the Alfresco widget library is that the widgets are either completely atomic and can be added and removed without causing errors (although clearly removing widgets can change functionality) and that where a widget encapsulates child widgets it provides configuration for them.

The 'alfresco/header/AlfSitesMenu' falls into this latter category (as described in the last post) so it was important to add that missing configuration. The following boolean attributes have been added:

    • 'showCreateSite' (controls whether or not the 'Create Site' menu item is displayed)
    • 'showSiteFinder' (controls whether or not the 'Site Finder' menu item is displayed)
    • 'showUsefulGroup' (controls whether or not the entire 'Useful' group is displayed - will override 'showCreateSite', 'showSiteFinder' and 'showFavourites')
    • 'showRecentSites' (controls whether or not the 'Recent Sites' group is displayed)
    • 'showFavourites' (controls whether or not the 'Favorites' and favourite controls, e.g. add/remove, are displayed)


So to make use of these attributes you can create a extension to 'share-header.get.js' as follows:

// Find the 'Sites' menu...
var sitesMenu = widgetUtils.findObject(model.jsonModel, 'id', 'HEADER_SITES_MENU');
if (sitesMenu != null)
{
  // Hide the site finder...
  sitesMenu.config.showSiteFinder = false;
}

 

Before the module is deployed the 'Sites' menu appears as:

 

Screenshot showing the Sites menu with the Site Finder displayed

 

...and after the module is deployed it appears as:

 

Screenshot of Sites menu without the Site Finder

 

The Importance of the Community

It's a shame that neither of these examples can be applied to Alfresco Community 4.2.d but it does highlight the powerful symbiotic nature of the community. As a company Alfresco (and ergo its Engineering team) has to prioritise it's customers but where possible we do listen to our open-source community and strive to respond to it's needs.

 

One of the biggest challenges when working on the extensibility features in Share and Surf is identifying the real world use cases that they will be required for. The more input we receive the better the product will become and if it is missing a feature then we will try our best to deliver it.

 

Summary

Hopefully this series of posts has provided enough information for you to start creating extensions to dynamically customize the new header menu. The header and it's implementation are new for Alfresco and its understandable that there it will take time to adapt. I'll be writing more posts over the coming weeks and months that provide more information on the recent changes with more examples on how to customize Alfresco Share. I'm also going to be covering the framework updates in detail at Alfresco Summit in November.

 

Finally, if you have any ideas for topics that you'd like me to blog about then please feel free to add sub-tasks to this JIRA issue. I can't guarantee that I'll definitely write up your suggestion but I will endeavour to cover as many as I can... and if nothing else, your suggestions will help us to understand where the pain points are for customizing Alfresco Share.

Introduction

In my last post I explained the basic differences between the old (4.1) and new (4.2) Share header bars. In this post I'm going to provide a slightly more complex example that demonstrates the following:

 

  • How to create a custom widget by extending an existing widget in the Alfresco library
  • How to configure additional widget library packages in Surf
  • How to insert that custom widget into the new header


The current header menu features a 'Sites' drop-down menu that allows you to quickly return to one of the last few sites that you previously visited. The example extension I'm doing to demonstrate (provided in full in this JAR file) will update the 'Sites' drop-down menu to convert each recent site link into a cascading menu that allows the user to go straight to a specific page within the site (e.g. the Document Library or Wiki).

 

Recent Sites

I've personally found the Recent Sites list an incredibly valuable addition in my day-to-day development and testing of Share. The recent sites are stored in each users' preferences and are updated as they visit different sites. As well as appearing in the header menu they also are available for selection in many of the file pickers (e.g. 'Copy' 'Move', etc). Only the last 5 sites are shown by default but this can be changed by updating the 'max-recent-sites' element found in the 'share-config.xml' file.

 

Screenshot showing the recent sites in the Sites drop-down menu

 

The Sites Menu

The 'Sites' drop-down is unlike the other items in the menu as it is generated asynchronously when the user clicks on it. This provides a very small performance enhancement as it is unlikely as a user will want to access the menu on every page they visit. Similarly the 'Favourites' cascade menu isn't populated until a user actually clicks on it.

 

This functionality is too complex to define in a basic JSON model so it is controlled by the widget 'alfresco/header/AlfSitesMenu'. It is important to note that the all of the items within the 'Sites' menu are still built in exactly the same way, using exactly the same widgets that are used in the JSON model.

 

To achieve our use case we will need to customize the 'alfresco/header/AlfSitesMenu' widget. This can be achieved in two different ways:

 

  1. By changing the default configuration (which I'll cover in a future post)
  2. By extending the default widget, overriding some of it's functions and then replacing the default widget with the customization.

 

Extending a Widget

We're going to create a new widget called 'blogs/BlogSiteMenu' and we're going to place this widget in the 'META-INF/js' folder of the JAR that we're going to build (as this is the only location that Surf is able to load resources from within JAR files). I've previously described custom widget creation in this blog post but here is a recap of the basic steps with a focus on extending an existing widget.

 

Create the following file in the JAR: 'META-INF/js/blogs/BlogSiteMenu.js' and add the following code to it:

// Define module requirements and callback...
define(['dojo/_base/declare','alfresco/header/AlfSitesMenu'],
       function(declare, AlfSitesMenu) {

  // Module returns a new widget that extends AlfSitesMenu
  return declare([AlfSitesMenu], {
   
// Override the '_addMenuItem' function of AlfSitesMenu...
    _addMenuItem: function(group, widget, index) {
       this.inherited(arguments);
    }
  });
});

 

This code is the absolute minimum required to define a new widget that extends an existing one. The 'define' statement declares the dependent modules and passes them as arguments to a callback function that will be executed when the module is required.

 

The callback function declares a new widget that extends the 'alfresco/header/AlfSitesMenu' widget (which has been assigned to the 'AlfSitesMenu' argument). This new widget overrides the '_addMenuItem' function.

 

The function override simply calls the inherited function (i.e. calls the version defined in 'alfresco/header/AlfSitesMenu') so doesn't actually achieve anything... but the point is to illustrate how to create the extension. The full source is contained in the JAR file which you should download and read through (even if you don't deploy it) - this isn't a tutorial on writing JavaScript !! ;-)

 

Declaring Packages in Surf

Our custom widget is defined in the 'blogs' package (the part of the path that we're going to consider as the package root) and we need to add this to the Surf configuration so that the Dojo AMD loader can locate it.

 

Edit the '<tomcat-home>webapps/share/WEB-INF/surf.xml' configuration file and look for the 'dojo-pages' element. Within this element you'll find a list of packages and its here that you will need to add the location of the 'blogs' package as shown in the example below:

<dojo-pages>
   <bootstrap-file>/res/js/lib/dojo-1.9.0/dojo/dojo.js</bootstrap-file>
   <page-widget>alfresco/core/Page</page-widget>
   <base-url>/res/</base-url>
   <packages>
      <package name='dojo' location='js/lib/dojo-1.9.0/dojo'/>
      <package name='dijit' location='js/lib/dojo-1.9.0/dijit'/>
      <package name='dojox' location='js/lib/dojo-1.9.0/dojox'/>
      <package name='alfresco' location='js/alfresco'/>
      <package name='blogs' location='js/blogs'/>
   </packages>
</dojo-pages>

 

Swapping in the Custom Widget

The previous post provided detailed instructions on how to create the extension module so I'm going to jump straight into working with the 'share-header.get.js' JavaScript controller extension. To replace the default 'Sites' menu widget with our own we simply need to add the following code:

// Find the 'Sites' menu...
var sitesMenu = widgetUtils.findObject(model.jsonModel, 'id', 'HEADER_SITES_MENU');
if (sitesMenu != null)
{
  // Change the widget to our custom menu...
  sitesMenu.name = 'blogs/BlogSitesMenu';
}

 

The controller extension finds the default sites menu (identified as 'HEADER_SITES_MENU') and then changes the name that defines the module to use to that of our custom widget. When we deploy our module and refresh the page we'll see that the 'Sites' menu has been updated:

 

Screenshot of updated sites menu 1

 

Screenshot of updated sites menu 2

 

Screenshot showing updated menu 3

 

Additional Notes

Hopefully the comments in the JavaScript source should be sufficient for you to understand exactly how the new widget creates the new menus. Extending a widget obviously requires an understanding of the widget being extended and a good knowledge of JavaScript.

 

Although I've used other Dojo modules in the code it's not mandatory to do so. It's important to remember that Dojo is merely the tool used for resolving the AMD modules and that you're free to use whatever JavaScript libraries that you choose.

 

You might also note that the JAR file contains a new WebScript for retrieving the site page information. The majority of the work is actually done in the 'share-header.lib.js' file that is imported at the beginning of the 'site-pages.get.js' file and provides another example of how we're trying to make it easy for 3rd parties to re-use Alfresco code.

 

Summary

This tutorial should have demonstrated that you are not limited to any configuration schema when customizing the new Share header. The Surf framework now allows you to easily plug-in completely custom JavaScript code into Share which allows you to make fine-grained changes to existing code or to add entirely new functionality.

Introduction

Now that Alfresco Community 4.2d has been released I thought it would be useful to begin providing some examples of how to customize the new header bar. The new header bar is the first feature in Share to use the updated widget processing framework provided by Surf. In this post I will demonstrate how to update the default menu to convert the 'Admin Tools' link into a drop-down menu with links to all of the individual Admin Console pages. In later posts I'll show how to remove menu items and how to add custom widgets into the header.

 

Comparing the Headers

Let's first compare the old and new header menus...

 

Screenshot of the header from Alfresco Share 4.1

 

Screenshot of the Alfresco Share 4.2 header

 

As well as the obvious stylistic differences there are some major implementation differences. The original header was rendered by a combination of the 'header', 'title' and 'navigation' regions whereas these have been replaced by a single region called 'share-header'.

 

This new region is bound to the 'share-header.get' WebScript which uses the previously described approach to define the JSON model of widgets that comprise the header.

 

The structure of the old header was defined in the 'share-config.xml' configuration file whereas the new header is entirely defined by the 'share-header.get' WebScript's JavaScript controller. It is possible to render the header using the configuration from the 'share-config.xml' file by changing the 'legacy-mode-enabled' element value from 'false' to 'true'.

 

Creating the Extension Module

To customize the header you should follow the approach described in this blog post to extend the JavaScript controller of the 'share-header.get' WebScript. To briefly recap, follow these steps:

    1. Create a JAR containing the following folder structures:
      1. /alfresco/site-data/extensions
      2. /alfresco/site-webscripts
    2. In the '/alfresco/site-data/extensions' folder create a file called 'blog-extension.xml' (you can call it anything you like of course!)
    3. In the 'alfresco/site-webscripts' folder create a package path for the controller extension (e.g. 'blogs/admin-menu' - but again you can use any package you like)
    4. In the 'alfresco/site-webscripts/blogs/admin-menu' folder create a file called 'share-header.get.js'

 

Your 'blog-extension.xml' file should contain the following:

 

<extension>
  <modules>
    <module>
      <id>Update Admin Menu</id>
      <version>1.0</version>
      <customizations>
        <customization>
          <targetPackageRoot>org.alfresco.share.header</targetPackageRoot>
          <sourcePackageRoot>blogs.admin-menu</sourcePackageRoot>
        </customization>
      </customizations>
    </module>
  </modules>
</extension>

 

The 'targetPackageRoot' element identifies the location of the 'share-header.get' WebScript and the 'sourcePackageRoot' element identifies the location of your extension file. You now have the basic elements required to customize the header - everything else will be done in the '/alfresco/site-webscripts/blogs/admin-menu/share-header.get.js' file.

 

Customizing the Header

The purpose of a JavaScript controller is to construct a model object that the FreeMarker template uses when generating a fragment of DOM for the page. The model object created by the default JavaScript controller is made available to each extension controller before it is passed to the FreeMarker template and this provides them the opportunity to change the model before it is processed.

 

The model object is assigned an attribute called 'jsonModel' with a value that is a JSON object containing all the widgets to render in the header. Each widgets is given a unique 'id' attribute so that they can easily be found by extensions using the 'widgetUtils.findObject' helper function.

 

In this example we're going modify the 'alfresco/menus/AlfMenuBarItem' widget that creates the 'Admin Tools' menu item. The original definition of this can be found in the 'share-header.lib.js' library file (that is imported by 'share-header.get.js') and is:

 

{
  id: 'HEADER_ADMIN_CONSOLE',
  name: 'alfresco/menus/AlfMenuBarItem',
  config: {
    label: 'header.menu.admin.label',
    targetUrl: 'console/admin-console/application'
  }
}

We can work with this widget by adding the following to our 'share-header.get.js' extension file:

var adminMenu = widgetUtils.findObject(model.jsonModel, 'id', 'HEADER_ADMIN_CONSOLE');

if (adminMenu != null)
{
  adminMenu.name = 'alfresco/header/AlfMenuBarPopup';
  delete adminMenu.config.targetUrl;
}

 

This will find the widget (which will only be present in the model if the currently logged in user is the Admin) and then change the widget definition's 'name' attribute to be 'alfresco/header/AlfMenuBarPopup' which will convert it into a drop-down menu. We then delete the 'targetUrl' attribute because it is not required.

 

The final step is to add in the contents of the drop-down menu (I'm only going to show the first sub-group defined but you can find a JAR file containing all the source here):

 

var adminMenu = widgetUtils.findObject(model.jsonModel, 'id', 'HEADER_ADMIN_CONSOLE');
if (adminMenu != null)
{
  adminMenu.name = 'alfresco/header/AlfMenuBarPopup';
  delete adminMenu.config.targetUrl;
  adminMenu.config.widgets = [
    {
      name: 'alfresco/menus/AlfMenuGroup',
      config: {
        label: 'Tools',
        widgets: [
          {
            name: 'alfresco/header/AlfMenuItem',
            config:
            {
              label: 'Application',
              targetUrl: 'console/admin-console/application'
            }
          },
          {
            name: 'alfresco/header/AlfMenuItem',
            config:
            {
              label: 'Category Manager',
              targetUrl: 'console/admin-console/category-manager'
            }
          }
        ]
      }
    }
  ];
}

 

Here we're adding a group with the label 'Tools' which contains two menu items labelled 'Application' and 'Category Manager' which each have a page relative (that is relative to '/share/page') URL for the page that they represent.

 

Copy the JAR to the '<tomcat-home>/webapps/share/WEB-INF/lib', restart your server and go to the 'share/page/modules/deploy' page and deploy your new module.

 

Screenshot showing the Module Deployment page

 

Login to Share as 'Admin' and you'll see that the 'Admin Tools' menu has now been converted into a drop-down menu (the screenshot shows the extension provided in my sample JAR):

 

Screenshot showing the updated "Admin Tools" menu

 

Summary

Although I haven't yet explained exactly what all the widget definitions mean, which widgets are available or how you can configure each of them... this example should at least show you how you can easily update the new header to suit your needs.

You may wonder how exactly this is more beneficial than updating the menu via configuration? The main advantages are as follows:

    1. It is possible to target specific parts of the menu without including the entire menu definition. This means that multiple definitions can be deployed together that independently alter the menu without overriding the each others changes
    2. It is possible to to leverage the existing extension module features to dynamically alter the menu based on a variety of criteria including user group, page context, user privileges, etc. by deploying modules with evaluators.
    3. It is possible to dynamically add and remove features without restarting the server.
    4. There is complete flexibility in what gets added to the header - you are not constrained to any particular widget set and can easily add custom widgets that are entirely new or extend existing widgets in the Alfresco library.
    5. It's very easy to re-use all or parts of the existing menu when creating new pages or applications.


    Hopefully this information is useful as a starting point. In my next post I'll demonstrate how you can extend an existing widget and use it in the menu.

    Filter Blog

    By date: By tag: