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.
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:
The main variables in this process are:
Not all WebScripts are coded this way:
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});
</script>
In the example shown we have been able to control exactly how each widget is instantiated:
Here is a breakdown of the properties:
name | The 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. |
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/form.dependencies.inc'>
<@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'/>
</@>
<@uniqueIdDiv>
<@markup id='html'>
<@documentlistTemplate/>
</@>
</@>
The template is divided into 6 separate <@markup> directives:
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.
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'>
doclibCommon();
function main()
{
var documentList = {
id : 'DocumentList',
name : 'Alfresco.DocumentList',
options : {
siteId : (page.url.templateArgs.site != null) ? page.url.templateArgs.site : '',
containerId : template.properties.container != null ? template.properties.container : '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];
}
main();
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: