ddraper

Customizing Share JavaScript Widget Instantiation (Part 4)

Blog Post created by ddraper on Jun 1, 2012

Introduction

In the past three posts in this series I've introduced some of the new custom FreeMarker directives that we've added to Surf and how we're going to use them to make it easier to customize the client-side JavaScript widgets that are instantiated on each page in Share. In the post I'm going to try to explain some of the less obvious changes that we've made and some of the behaviour that should be expected as a result.

 

The “group” attribute

In the “documentlist.get.html.ftl” example of the new boiler-plate template you may have noticed the “group” attribute in the <@link>, <@script>, <@inlineScript> and <@createWidgets> directives.  This attribute has an important bearing upon the order in which dependency requests and JavaScript code are output into the rendered HTML page.

 

Surf now supports the ability to aggregate multiple files into a single resource to reduce the number of HTTP requests made by the client to increase page loading performance. The “group” attribute is used to determine how dependencies are aggregated into the generated resources. Managing the groups is important because once generated a resource is cached on the server to improve response times to subsequent requests so a balance to achieve optimum performance. If a single group were to be used then only one HTTP request would be made per page, but the performance gained through reduced requests would be lost to server side aggregation for each request.

 

PLEASE NOTE: At this moment in time aggregation is not a major concern as it is NOT recommended to be used (and may not fully work) until all of Share is converted to the boiler-plate code - this is definitely one for the future road-map.

 

However, in order for the same Share code to be able to support different Surf operation modes the “group” attribute is also applied when processing individual dependency requests.  The only thing that you need to know is that groups are output in the order they are requested and that all the dependency requests and code are output for each group in turn.

 

So the output on the HTML page for:

<@script src=”/aaa.js” group=”1”/>
<@script src=”/bbb.js” group=”2”/>
<@script src=”/ccc.js” group=”3”/>
<@script src=”/ddd.js” group=”2”/>
<@script src=”/eee.js” group=”1”/>

...will be:

<script src='/aaa.js'></script>
<script src='/eee.js'></script>
<script src='/bbb.js'></script>
<script src='/ddd.js'></script>
<script src='/ccc.js'></script>


Note that '/eee.js' is the second requested import despite appearing last in the list and that '/ccc.js' is last despite it appearing 3rd. This is because all of group “1” is output before any of group “2”, and all of group “2” is output before group '3”.

Mixing <@script> and <@inlineScript>

Let's say you have files 'A.js' and 'B.js'  and you have a WebScript template containing the following:

<@script src='${url.context}/res/A.js' group='calc'/>
<@inlineScript group='calc'>
// A comment between imports
</@>
<@script src='${url.context}/res/B.js' group='calc'>

When you the final page is rendered in the source you might see an import like this:

<script type='text/javascript' src='/share/res/A.js'></script>
<script type='text/javascript'>//<![CDATA[
   // A comment between imports
//]]>
</script>
<script type='text/javascript' src='/share/res/B.js'></script>


Note that the JavaScript from the <@inlineScript> directive is placed between the two imports because they are in the same group (the same is true for any custom directive that outputs JavaScript, e.g. the <@createWidgets> directive).

Configuring Surf to Aggregate Dependencies

Should you wish to enable the use of aggregate dependencies then you will need to make a Surf configuration change. By default the capability is disabled in Surf and is unlikely to ever enabled by default in future releases of Alfresco. However, once all of the Share WebScripts have been converted to the 'boiler-plate' template then it will be possible to run in this mode - HOWEVER, any 3rd party modules or add-ons that have been applied may not support this feature.

 

To enable it you simply need to set the following line within the Surf configuration (this can be found in the “webapps/share/WEB-INF/surf.xml”):

<web-framework>
   …
   <aggregate-dependencies>true</aggregate-dependencies>
   …
</web-framework>

 

Aggregated Dependency Output

If you do enable this capability then you can expect the following behaviour to occur. If the  file 'A.js' contains:

var a = 1;

and the file 'B.js' which contains:

var c = a + b;

...and you have a WebScript template containing the following:

<@script src='${url.context}/res/A.js' group='calc'/>
<@inlineScript group='calc'>
var b = 1;
</@>
<@script src='${url.context}/res/B.js' group='calc'>

When you the final page is rendered in the source you might see an import like this:

<script type='text/javascript' src='/share/res/20146f7250123ea2437a0d16d5c323.js'></script> <!-- Group Name: 'calc' -->

And if you viewed the source of that file you'd see:

var a = 1;
var b = 1;
var c = a + b;


(NOTE: The contents would actually be compressed, but there's not a lot of point in showing the compressed content in this blog!)

 

The resource name is an MD5 checksum generated from the combined source code (NOTE: I made the one up as an example purely to illustrate the point). The generated resource is cached on the server so that it doesn't need to be generated each time. If extra content is added to the group (even dynamically by a module) then the resource will be regenerated and the checksum will naturally change to ensure that the browser requests a different file.

Debugging

The '<client-debug>' setting (found in 'webapps/share/WEB-INF/classes/alfresco/share-config.xml' will still work when enabled. An aggregated resource will still be produced but each aggregated file will be separated by a comment similar to this:

/*Path=A.js*/


This will allow you to determine the source file in which errors are occurring whilst debugging.

The Output Directives

The current released version of Alfresco Share relies on the use of the '${head}'FreeMarker model property to output all the dependency requests generated on the first pass of all the WebScript '*.head.ftl' files. This property is populated during this first pass and then output in <head> HTML  element defined in the 'alfresco-template.ftl' template. If you look in the current committed version of that template you'll still see a reference to that property (which is still used to support legacy '*.head.ftl' files and any dependencies defined through any <dependencies> elements in extension module configuration) but also two new directives:  <@outputJavaScript/> and <@outputCSS/>.

 

As their names suggest these directives are used to output the JavaScript and CSS dependency requests made via the <@link>, <@script>, <@inlineScript> and <@createWidgets> directives.  Without wanting to go into too much detail at this stage the 'output' directives act as placeholders in extensibility model and accumulate requests to output content as the remainder of the Surf page is processed - only when the page has completely been processed is their final content rendered into the output stream.

 

Towards the end of the 'alfresco-template.ftl' file you will also see a commented out directive <@relocateJavaScript>. As it's name suggests the purpose of this directive is to change the location in the page where JavaScript output is rendered. It is suggested that moving JavaScript to the end of a page is desirable as it increases page performance.  It's only possible to use this directive if there is no hard-coded <script> elements on the page that depend on imported files or JavaScript dependencies output via the ${head} property. When uncommented though you will see that it produces a very clean source file for your page with all the JavaScript located at the end.  The <@relocateJavaScript> directive is something else we've created for the future and is unlikely to be used in the next release (although we'll probably make it configurable for those that wish to use it rather than needing to edit the template file!)

Outcomes