Customizing Share JavaScript Widget Instantiation (Part 2)

cancel
Showing results for 
Search instead for 
Did you mean: 

Customizing Share JavaScript Widget Instantiation (Part 2)

ddraper
Intermediate II
0 3 3,368

Introduction

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});
</script>
‍‍‍‍‍‍‍‍‍


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/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:

    • “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'>

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.

Summary

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
    3 Comments
    blog_commenter
    Active Member
    David,

    Really great stuff you guys keep adding to make Share more modular and extensible. Actually, the best news I've taken from this post is the 'death' of that ugly cousin called '.head.ftl' - something that appeared to be a webscript template but could have easily been a static HTML fragment or configuration file. Now, components can finally support fully dynamic dependencies. Wouldn't this also allow for the Forms API to no longer require JS/CSS dependency configuration? Control templates could include their dependencies when they are actually rendered in a form and not force global inclusion via the Form dependencies config section...
    ddraper
    Intermediate II
    Thanks Axel, I appreciate the feedback.... I think you're right that we should no longer need that forms dependency configuration, although to be honest we haven't gotten around to looking at that yet. I'll make sure we make a post if/when we do make those changes.
    blog_commenter
    Active Member
    [...] that the the wrapping <@markup> directive there indicates that we want to inject the Javascript after  [...]