Following discussions with various Alfresco Community members over recent weeks I've realised that there is possibly a lack of understanding about the relationship between the controller and template in a WebScript related to when JavaScript controller extensions are processed. This post will attempt to clarify the relationship and explain how and why to use a JavaScript controller extension as well as providing another concrete example of how to extend the new header bar in Alfresco Share 4.2
The JavaScript controller (or the Java bean controller if you're writing a Java backed WebScript) has one primary purpose; to create a model object that is then passed to the template renderer (typically FreeMarker).
Even if your WebScript has an additional configuration file (e.g. one that ends '*.config.xml') then it the controller is still responsible for processing that configuration and adding it to the model ... a configuration file achieves nothing by itself!
In Alfresco 4.0 we introduced the ability to apply extension modules that could dynamically manipulate the controller, localization properties and FreeMarker templates. Customizing the FreeMarker templates was by far the most limited of these approaches as it relied on additional <@markup> directives being included in the templates. Unfortunately most of WebScript logic and client-side JavaScript widget instantiation code was defined in the FreeMarker templates which often made it difficult to achieve the desired results through pure extensions.
We completed refactored the Share WebScripts in Alfresco Community 4.2c to move all of the logic and client-side widget instantiation configuration into the controller which made it possible to have greater control over customizations via extension modules. In particular it meant that for the first time you could either reconfigure or replace the client-side JavaScript widgets with an extension module.
In Alfresco 4.2.e Community and Alfresco 4.2 Enterprise we have introduced a new approach to page creation where a JSON model of widgets to render is defined entirely in the JavaScript controller and the FreeMarker template is reduced to a single custom FreeMarker directive. This allows extensions much finer control over the page that make it possible to achieve a wider range of customizations through model manipulation.
When writing a JavaScript controller you have access to a variable called 'model'. Any attributes added to this object are available in the associated FreeMarker template, e.g.
model.myVariable='This is a variable'
...in a controller can be referenced in a FreeMarker template by:
${myVariable}
The controller can add any variables it likes to the 'model' object but the refactored WebScripts make use of the 'widgets' attribute (e.g. 'model.widgets') and the new page creation approach relies on the 'jsonModel' attribute (e.g. 'model.jsonModel').
Surf will check for the existence of any extension controllers mapped to the WebScript before the FreeMarker template is renderer is called.
This means that one or more controller extensions can change the contents of the 'model.widgets' or 'model.jsonModel' attributes before they are processed by the custom directives in the template. The core controller will always be run to setup the default model and all applicable extension controllers will have their turn at updating the model before it is passed to the template.
If you're writing an extension module that you want to distribute around the Alfresco Community then you need to be aware that other extensions might have changed the model in an unexpected way before your extension has it's turn.
Let's see this in action by adding a new menu item to the header in Alfresco 4.2 Enterprise. Information on how to create and deploy your extension module have been covered in detail before so I'm not going to go over those now, instead I'll just focus on the controller extension itself.
If we want to add a new menu item (or indeed any new widget) we need to identify a parent widget to add it to. At the moment this still requires some knowledge of the Alfresco source code (although it is easy enough to identify the source files using SurfBug).
In this case we're customizing the 'share-header.get' WebScript whose controller imports the 'share-header.lib.js' file which provides functions that return a model fragment of the header menu. It's important to note that you don't want to (and indeed can't) extend the lib file directly - but instead much extend the controller that imports the lib file.
All of the widgets are given unique IDs in the JSON model so that they can be easily referenced. In this case we want to add a new 'alfresco/menus/AlfMenuBarItem' widget to the 'alfresco/header/AlfMenuBar' widget identified as 'HEADER_APP_MENU_BAR'. For the purposes of this example it doesn't really matter where the menu item takes the user, but in this case we're going to use it to navigate to the user's trashcan.
The controller extension should contain the following code:
var menuBar = widgetUtils.findObject(model.jsonModel, 'id', 'HEADER_APP_MENU_BAR');
if (menuBar != null)
{
menuBar.config.widgets.push({
name: 'alfresco/menus/AlfMenuBarItem',
config: {
label: 'My Trashcan',
targetUrl: 'user/' + encodeURIComponent(user.name) + '/user-trashcan'
}
});
}
...and when the module is deployed you will see the following added to the Share menu:
The key thing to take away from this post is how JavaScript controller extensions fit into the the processing order of WebScript files and that the sole purpose of a JavaScript controller is to set up a default model that extending controllers can then manipulate.
Ask for and offer help to other Alfresco Content Services Users and members of the Alfresco team.
Related links:
By using this site, you are agreeing to allow us to collect and use cookies as outlined in Alfresco’s Cookie Statement and Terms of Use (and you have a legitimate interest in Alfresco and our products, authorizing us to contact you in such methods). If you are not ok with these terms, please do not use this website.