Author Archives: Dave Draper

Under The Hood of the Surf Updates

Introduction

In a previous post I described a simpler approach to constructing a page. This post will delve deeper into exactly what is happening to render that page, how the dependency analysis works and what ultimately ends up on the page.

Page Rendering

The URL “/share/page/dp/ws/my-new-page” was used to access the page. Once this request is mapped to the Share application the following occurs:

  1. /share/page” is Spring MVC dispatcher servlet context
  2. /share/page/dp/ws/my-new-page” is matched to the template-uri “{pageid}/ws/{webscript}” (“dp” is the id of a page)
  3. Surf maps the “dp” page to the Template-Instance “share-template
  4. The “share-template” is an instance of the “share-template-type” template which (unlike the standard Share page template) is rendered by the WebScript “full-page.get
  5. The FreeMarker template for “full-page.get” sets up the skeleton of the page (including all the standard Share JavaScript variables and resources) and also creates a region the Surf Dojo bootstrap WebScript. It creates the Component and Region for the WebScript passed as the “webscript” argument in the template-uri
  6. Surf binds the Components to the Regions and renders them.
  7. The controller for the WebScript passed on the URL creates a JSON model defining the structure of the page
  8. The WebScript template uses the <@processJsonModel> directive to convert that JSON model into JavaScript, CSS, HTML and i18n resources that are automatically loaded into the page.

If you view the source of a page you should see the Dojo bootstrap configuration and a request to load the main “dojo.js” bootstrap module.

Screenshot of page source showing Dojo bootstrap

If you inspect the resources loaded into the page (e.g. in the Web Developer Tools or Firebug) you should also see the following files imported:

  • “/share/res/890525bee5cf4228da1a0e45b491ed.css”
  • “/share/res/40ef2558ae7e1d0a225e277cc6672d0.js”
  • “/share/res/js/surf/b4dc46167c64e043e227df402bf7bc1.js”

The names of the resources are MD5 checksums generated from the resource contents. This allows the browser to cache each resource indefinitely to avoid the need to download it again. Should the contents of the page change (e.g. an update to an individual modules source code or the page model being customized) then the resource contents will change, a new checksum will be generated and the browser will download the updated version.

Screenshot of Web Development Tools showing resources imported

 

“/share/res/890525bee5cf4228da1a0e45b491ed.css”

This contains all of the CSS resources referenced by widgets included in the page. Note that images are directly encoded into the resource to avoid additional HTTP requests.

Screenshot showing Web Development Tools showing aggregated CSS resource

“/share/res/40ef2558ae7e1d0a225e277cc6672d0.js”
This contains the messages object construction code and the call to instantiate the main “page” widget (shown highlighted). Note that it is declaring a requirement on the final resource loaded – it is requesting our dynamically built resource that includes all the dependencies.

Screenshot showing Web Development Tools with Page widget instantiation

“/share/res/js/surf/b4dc46167c64e043e227df402bf7bc1.js”
This is the layer that has been built through dynamic dependency analysis. Note how it contains core Dojo modules that have been identified as dependencies.

Screenshot showing Web Development Tools showing dependencies source

Dependency Analysis Beans

If you look in the “spring-surf-services-context.xml” file (this is included in “spring-surf-<n>.<n>.<n>.jar”) you’ll find a number of new bean definitions. Of these new beans the “dojo.dependency.handler” defines the main class for analysing the dependencies on a page. It has a property called “dependencyRules” that is a list of all the individual dependency handling beans. By default the following beans are referenced:

  • “define.dojo.js.dependency.rule”
  • “define.dojo.css.dependency.rule”
  • “define.dojo.widgets.dependency.rule”
  • “define.dojo.i18n.dependency.rule”
  • “define.dojo.non.amd.dependency.rule”

The purpose of each bean is to analyse the source code of each widget and identify one or more of 4 different types of dependency to include in the page:

  • Text (e.g. HTML templates)
  • JavaScript (both AMD and non-AMD modules)
  • CSS
  • i18n properties

All of these beans extend the “org.springframework.extensions.surf.DojoDependencyRule” class and use regular expressions configured in the bean context to identify the different dependencies. The “dojo.dependency.handler” passes the minified source of each module to each rule processor to identify additional dependencies (this means that the Regular Expression doesn’t need to address whitespace characters or comments).

It’s possible to either reconfigure the regular expressions for the beans provided or create and add entirely new beans to the “dependencyRules” list to satisfy your specific requirements.

Dependencies are recursively processed until they have all been analysed. As each module is analysed it is added to a cache and if it is referenced again the cached version will be used. Surf does not allow a module to be analysed twice so even if circular dependencies are declared it will not fall into an infinite loop.

Even if it is not possible to identify all of the dependencies it is not a major problem. Ultimately Surf is just making an attempt to construct a layer containing all of the resources required for that page. If a resource is missing then the Dojo loader will simply asynchronously request it. In fact if you view the source on Share pages you will notice that the occasional AMD module is still getting asynchronously requested – there’s obviously a little bit of tightening up we could do on the dependency analysis expressions!

Page Widget Configuration

By default Share uses the “alfresco/core/Page” module as the root widget on the page (the default in a “vanilla” Surf application is “surf/core/Page” which is a much simpler version). However it is possible to update the “surf.xml” configuration to use any widget that you would prefer – simply update the “page-widget” element to map to whatever widget you’d like to use.

Summary

Hopefully this post has gone some way to explaining how Surf performs dependency analysis of the widgets included in a page in order to construct a build layer that can then be requested on the page. Although this series of posts relates specifically to Alfresco the Dojo dependency analysis code is part of Surf and as such could be used in independent applications.

Localization Considerations

Introduction

In my second post in this series I showed how to create a page from existing widgets and in my last post I showed how to create a custom widget. In custom widget showed how to specify i18n properties files of different locales in order to ensure that the widget labels could be rendered in different languages, however I didn’t demonstrate how to localize pages nor how the two approaches work together.

How it Works Currently…

If you’re familiar with WebScripts you’ll know that you can provide i18n properties files with the same prefix that can then be accessed in the JavaScript controller by calling:

msg.get(“<key>”)

or from the FreeMarker template by calling:

${msg(“<key>”)}

You may also know that traditionally Share would pass all of the messages from a WebScript into the widgets that it instantiated by calling its “.setMessages()” function.

Finally, you should also be aware that there is a global properties files that can be used throughout Share (“common.properties” and “slingshot.properties” that can be found in “/share/WEB-INF/classes/alfresco/messages“).

The contents of all of these files ultimately end up on the page in the JavaScript global variable “Alfresco.messages”. This is a map with the following attributes, “global” and “scope”.

  • “global” contains all the messages from the global properties
  • “scope” is a map of widget name to messages map

The Share widgets “.setMessages()” function adds its own name as a key into the scope map and assigns all the supplied messages as an object against that key. For example, if the “Alfresco.DocumentList” widget is instantiated then “Alfresco.messages.scope['Alfresco.DocumentList']” can be used to access it’s messages.

How the Updated Approach Works…

We’ve ensured that the updated development approach is consistent with this old pattern and have intentionally not followed the standard Dojo pattern. The new approach uses the same “Alfresco.messages” object (although this can be reconfigured if you want to use a different root variable) and still sets the “global” and “scope” attributes.

If you create a widget with an “i18nScope” attribute then this is the scope into which the widgets encapsulated messages will be added. If no “i18nScope” attribute is defined then the messages will go into a scope called “default” (unless the widget extends another widget in which case it will inherit its “i18nScope” attribute).

The i18n properties from the WebScript that processes the JSON model will automatically be placed into a new attribute of “Alfresco.messages” called “page”.

Whenever the “.message” function is called from “Alfresco/core/Core” (see previous post) all applicable scopes are searched, e.g.

  • global
  • page
  • default scope
  • all inherited scopes
  • widget scopes

…and the most specific result will “win”.

When creating a custom widgets there is obviously a distinction to be drawn between

  • labels that never change
  • variable labels that can be selected from
  • completely custom labels

For example, the label for a menu item cannot realistically be included as part of the widget but an error message could be. When accepting configurable labels its worth passing them through the “.message()” function in case a message key (rather than a localized message) has been provided as if no match has found then the supplied value is returned.

This means that when constructing the JSON model for a page you could provide:

config: {
   label: “message.key”
}

or

config: {
   label: msg.get("message.key")
}

At first glance these might appear identical, but if the widget defines a message with the key “message.key” then this will “win” over any message that the WebScript might be able to resolve.

Language Variations

It’s also worth bearing in mind that because the widgets process locale specific properties files in exactly the same way as WebScripts it is possible to simply reference a WebScripts properties file in the “i18nRequirements” attribute of a Widget. In a future post you’ll see how this can help you to wrap existing widgets easily so that they can be mixed with the our new library of widgets.

Summary

Hopefully this post has explained how i18n properties are handled in the new approach to page and widget construction. We have made efforts to ensure that the updates are compatible with the existing messages handling and have deliberately kept with the Alfresco approach rather than adopting the standard Dojo approach to avoid creating a divide. Ultimately we’ve done as much as possible to ensure that Surf takes care of all of the hard work for you.

Creating Custom Share Widgets

Introduction

In my last post I explained the basics of creating a new page by a defining a JSON model of existing Alfresco widgets. The library of widgets that Alfresco provides is currently small but is constantly growing. Ideally it would be nice if you could build the page you want just using out-of-the-box widgets but at some point you might need to write your own. This post will describe how to create a simple widget that defines its own template, CSS and localization resources.

If you have an understanding of Dojo (1.7+) or the AMD pattern then the JavaScript code should all be very obvious, but if you’ve no experience of these then hopefully this post will be sufficient for you to make progress. At the end of the day, it’s all just JavaScript!

The Basics

The AMD (Asynchronous Module Definition) pattern is a method of defining small JavaScript resources that you can easily declare dependencies of. Your AMD framework (e.g. Dojo or require.js) will be able to dynamically request those dependencies as they’re identified. This can result in a large number of HTTP requests from the browser but this can be avoided by building resource “layers” containing multiple modules. Surf avoids this problem by dynamically performing dependency analysis to avoid both excessive HTTP requests and the necessity for a build. As well as the benefits of a well-defined dependency network AMD also provides enhanced security by not polluting the global context so that the module code cannot easily be manipulated by malicious attacks.

Modules and Packages

Each widget is considered a module and modules are located relative to the package in which they live. The package is defined as the first element in the module identifier (or MID) so the “alfresco/logo/Logo” widget can be found in the “alfresco” package. Package locations are defined in the configuration used to bootstrap Dojo but Surf will take care of that for you. The Surf “web-framework” configuration element defines the core package of “dojo”, “dijit”, “dojox” and “surf” and Share augments that with the “alfresco” package. Look in the “<webapps>/share/WEB-INF/surf.xml” file and you’ll see the following:

<dojo-pages>
   <bootstrap-file>/res/js/lib/dojo-1.8.3/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.8.3/dojo"/>
      <package name="dijit" location="js/lib/dojo-1.8.3/dijit"/>
      <package name="dojox" location="js/lib/dojo-1.8.3/dojox"/>
      <package name="alfresco" location="js/alfresco"/>
   </packages>
</dojo-pages>
  • “bootstrap-file” defines the version of Dojo to be used
  • “base-url” defines where all packages are relative to
  • “page-widget” it the name of the widget to use to construct the page
  • “packages” defines the map of packages

If you want to use your own package then this is where you’ll need to define it.

Creating Your First Widget

For simplicity let’s just add a new widget to the “alfresco” package. These can be found under “<webapps>/share/js/alfresco”. Add a new folder called “blog” in that location and then add the following files:

A screenshot of the file structure and the files to add

“MyWidget.js”

define(["dojo/_base/declare",
        "dijit/_WidgetBase",
        "dijit/_TemplatedMixin",
        "dojo/text!./MyWidget.html",
        "alfresco/core/Core"], function(declare, _Widget, _Templated, template, Core) {
   return declare([_Widget, _Templated, Core], {
      cssRequirements: [{cssFile:"./MyWidget.css",mediaType:"screen"}],
      i18nScope: "BlogExamples",
      i18nRequirements: [{i18nFile: "./MyWidget.properties"}],
      templateString: template,
      buildRendering: function alfresco_blog_MyWidget__buildRendering() {
         this.greeting = this.message("greeting.label");
         this.inherited(arguments);
      }
   });
});

“MyWidget.html”

<div class="blog-example-widget">${greeting}</div>

“MyWidget.css”

.blog-example-widget {
   padding: 10px;
   color: orange;
   font-weight: bold;
}

“MyWidget.properties”

greeting.label=Hello

“MyWidget_fr.properties”

greeting.label=Bonjour

Update the JavaScript controller of the WebScript described in my last post to the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/blog/MyWidget"
   }
]};

Refresh the WebScripts and reload the page and you should see the following:

Screenshot of browser showing custom widget in English

Switch your browser into the French locale and refresh and you’ll then see:

Screenshot of browser showing custom widget in French

Although your widget is made up of multiple files all of them are linked together through the main JavaScript module that gets included in the JSON model of the page. The CSS and template is automatically pulled into the page and the correct i18n properties file is used for the requested locale (and only the i18n properties of the browser locale are loaded into the page).

With just a single widget on the page this might not seem particularly impressive but expand this to a page containing hundreds of widgets from multiple sources and you should begin to appreciate how much simpler this will make development. You just need to know how to write JSON to be able to create your own page from 3rd party widgets without worrying about coding, localization and styling.

Understanding the Code

The most important thing to understand in the JavaScript file is the mapping between each declared dependency (in the array argument of the “define” function) and the variable that it maps to in the callback function (that is the second argument of the “define” function). See the screenshot below for a colour-coded examples (apologies to any colour-blind readers, hopefully you get the gist).

Screenshot showing JavaScript file with colour matched dependencies

The “define” function returns a module which is instantiated by the “declare” function. The declare function takes an array of modules to inherit from and an object that defines the module. Even if you have no experience of Dojo or AMD that’s really all you need to know – everything else is just JavaScript. Obviously it helps to understand the standard Dojo widget lifecyle so I would advise at least reading some of the excellent Dojo documentation (I’ve included a few pages that I’d suggest are worth reading at the end of this post).

You don’t need to use any of the Dojo modules if you don’t want to, but there’s lots of great utilities that and widgets that you can extend or include in your own code. In a future post I’ll explain how we’ve made it easy to include non-AMD dependencies in your widgets – as I said in my last post, our intention is not to limit Share to using just Dojo, but whatever your previous opinion of it may be – I can assure you that it’s an excellent place to start.

The breakdown of the example widgets attributes are:

  • “cssRequirements” – an array of CSS files to include in any page that uses the widget. You can specify multiple files and can specify different files for different media types. There is no need to include CSS files required by extended or mixed in widgets as Surf will automatically include them.
  • “i18nRequirements” – an array of the i18n properties files that the widget should have access to. I’ll explain localization in more detail in a future post
  • “i18nScope” – a identifier for where to store the properties in the page. This can be used to prevent widgets overriding each others properties
  • “templateString” – this should be set to the variable assigned with the HTML dependency (as has been done in the example). See the Dojo documentation for a more detailed explanation
  • “buildRendering” – this is one of the Dojo widget lifecyle functions. We’re extending the default implementation to get the localized message to set in the template

 ”alresco/core/Core”

One of the strengths of Dojo is it’s inheritance model. This allows your widget to directly extend another module and then “mixin” the capabilities of others. When you create a custom widget you should always mixin the “alfresco/core/Core” module. This module provides a number of useful functions and the one used in our example widget is the “message” function which is used to set the “greeting” attribute. This function will search through all the appropriate i18n scopes to try and map the supplied key to the translated message. Again, this will be covered in greater detail in a future post but its important to remember that if you don’t mixin in the “alfresco/core/Core” module then you will struggle to access the widgets i18n properties correctly.

Summary

The purpose of this post is simply to familiarise you with the basics of how to create a widget that can be included in the JSON model of a Share page. We’re still just scratching the surface of widget creation but hopefully by following the example you’ll be able to begin creating your own widgets. In future posts I’ll describe:

  • more of the function provided by “alfresco/core/Core”
  • how to extend and wrap both Dojo and other JavaScript library widgets (including existing Share widgets)
  • how the dependency analysis works and how you can configure it
  • the i18n process in more detail
  • how widgets should fire and handle events to work with the Share services

Suggested Further Reading

In addition to the linked articles, I’d recommend reading the following:

For those of you that love JQuery, I’d recommend taking a look at the following:

 

 

Simple Page Creation in Share

Introduction

The purpose of this post is to introduce the steps required to add a page to Share using some of the updates outlined in my previous post. Hopefully if you’ve had previous experience of adding pages to Share you’ll see how this approach simplifies the process. As this series of posts continues the examples will get progressively more detailed – I just want to get the basic concepts nailed down first!

As with the last post, this information refers to the latest code in HEAD SVN and will appear in future 4.2 Community and Enterprise releases.

Create a New Page

To create a new page in Share using the new capabilities added by recent updates you can now do the following:

  1. Create a new WebScript (made up of the following files)
    • “new-page.get.desc.xml” (WebScript descriptor)
    • “new-page.get.js” (WebScript controller)
    • “new-page.get.html.ftl” (WebScript template)
  2. Place these files in any path under “<tomcat-home>/webapps/share/WEB-INF/classes/alfresco/site-webscripts”

The WebScript Descriptor should contain the following:

<webscript>
   <shortname>My New Page</shortname>
   <url>/my-new-page</url>
</webscript>

The WebScript Controller should contain the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/logo/Logo”
   }]
};

The WebScript Template should contain the following:

<@processJsonModel group="share"/>

Now open the web browser of your choice and point it at http://<server>:<port>/share/page/dp/ws/my-new-page and you should see the following…

Screenshot of page with Alfresco logo

Not the most exciting page in the world granted, but implemented with fairly minimal effort.

Widget Creation Configuration

What if we don’t want to just display the Alfresco logo? What if we want to display a different logo? Fortunately the “alfresco/logo/Logo” widget declares a number of different CSS rules that allow us to easily change the logo that is rendered. Update the JavaScript controller to be the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/logo/Logo",
      config: {
         logoClasses: "surf-logo-large"
      }
   }]
};

If you make the changes to the source files in the deployed web application you can apply these changes simply by refreshing the WebScripts by clicking the “Refresh Web Scripts” button on the Web Scripts home page (http://<server>:<port>/share/service/index)

When you refresh the page you should now see:

Screenshot of page showing Surf logo

What we’ve done is simply add some instantiation arguments to the “alfresco/logo/Logo” widget to override the default “logoClasses” attribute with a different CSS class that a selector was defined in the CSS resource associated with it. In the JSON model the “name” attribute refers to the name of the widget that you want to instantiate (technically it refers to the Module Identifier or “MID”) and “config” attribute is an object passed during instantiation of that widget.

A Simple Menu Bar

Let’s build something a bit more interesting; replace the contents of the JavaScript controller with the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "One"
            }
         },{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "Two"
            }
         }]
      }
   }
]};

This now creates a basic menu bar with two menu items (don’t worry for the moment that these menu items don’t do anything yet, we’ll get onto that in a future post!).

Screenshot showing page with 2 item menu bar

The key thing to note here is the use of the “widgets” attribute in the “config” object of the “alfresco/menus/AlfMenuBar”. Where one widget can be the parent to child widgets it is always possible for the model for those children to be defined in an array assigned to the “widgets” attribute. This repeating pattern is one of the many ways in which Surf is able to establish all the dependencies to load onto the page.

Adding a Drop Down Menu

Let’s make the menu bar a bit more detailed, update the model to be the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [{
            name: "alfresco/menus/AlfMenuBarPopup",
            config: {
               label: "One",
               widgets: [{
                  name: "alfresco/menus/AlfMenuItem",
                  config: {
                     label: "Popup item 1"
                  }
               },{
                  name: "alfresco/menus/AlfMenuItem",
                  config: {
                     label: "Popup item 2"
                  }
               }]
            }
         },{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "Two"
            }
         }]
      }
   }
]};

The result should be:

Screenshot showing page with menu bar where 1st item is a drop-down menu

Adding Cascading Menus and Icons

We’ve now converted the first menu item to be popup menu containing two more menu items (note again the repeating config/widgets/config/widgets pattern). Let’s now add some icons to the menu items, do some grouping and add a cascading menu… try this model in your JavaScript controller:

model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [
         {
            name: "alfresco/menus/AlfMenuBarPopup",
            config: {
               label: "One",
               widgets: [
               {
                  name: "alfresco/menus/AlfMenuGroup",
                  config: {
                     label: "Group 1",
                     widgets: [{
                        name: "alfresco/menus/AlfMenuItem",
                        config: {
                           label: "Popup item 1",
                           iconClass: "alf-edit-icon"
                        }
                     },{
                        name: "alfresco/menus/AlfCascadingMenu",
                        config: {
                           label: "Popup item 2",
                           iconClass: "alf-cog-icon",
                           widgets: [
                           {
                              name: "alfresco/menus/AlfMenuItem",
                              config: {
                                 label: "Cascaded menu item 1",
                                 iconClass: "alf-leave-icon"
                              }
                           },{
                              name: "alfresco/menus/AlfMenuItem",
                              config: {
                                 label: "Cascaded menu item 2",
                                 iconClass: "alf-help-icon"
                              }
                           }]
                        }
                     }]
                  }
               },{
                  name: "alfresco/menus/AlfMenuGroup",
                  config: {
                     label: "Group 2",
                     iconClass: "alf-logout-icon",
                     widgets: [{
                        name: "alfresco/menus/AlfMenuItem",
                        config: {
                           label: "Popup item 3",
                           iconClass: "alf-profile-icon"
                        }
                     }]
                  }
               }]
            }
         },{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "Two"
            }
         }]
      }
   }
]};

Once the WebScripts are refreshed and the page is reloaded you should see:

Screenshot of page showing menu with dropdowns, cascaded and icons

Summary and Next Steps

Without writing a single line of (traditional) HTML, JavaScript or CSS it has been possible to construct a page containing a reasonably detailed menu bar. OK, so at the moment this menu bar doesn’t actually do anything but we’re just getting started. The purpose of this post is simply to explain a new way of adding a page to Share and how to start building a JSON model for the contents of that page. In future posts I’ll be explaining how…

  • To create your own widget to include in a model
  • The various localization options for your pages and widgets
  • To define events in your model and introducing some of the out-of-the-box event handling services
  • The model is converted into dependencies and how to configure the dependency analysis
  • To wrap existing JavaScript widgets to work in the model

If you’ve previously done any JavaScript controller extensibility it should be fairly obvious how the existing mechanisms can be used to easily add, change and remove things from a default model. One of the main advantages of the new header bar in Share is not that you can easily change it without copying and pasting all of the XML that defined it.

The “Hybrid Page”

The final thing I’d like to quickly point out is the availability of the “hybrid” view of page. By simply changing the URL to be http://<server>:<port>/share/page/hdp/ws/my-new-page (note the additional “h”) you will get the page content rendered between the standard Share header and footer.

Screenshot of menu bar between Share header and footer

Latest Updates to Share and Surf

Introduction

I’m pleased to announce the arrival of a number of significant improvements to the UI framework in Alfresco Share. It will be part of the next Alfresco 4.2 Community and Enterprise releases and is available now in HEAD SVN to try out. Rather than trying to explain all of the details in a single blog post I’m going to start by just going over the concepts of what we’re trying to achieve. Over the coming weeks I’ll be posting more information explaining how it all works and how you’ll be able to make use of it.

Goals

Our ultimate aim is to provide a library of widgets that can be easily assembled into a web application for accessing an Alfresco repository. We didn’t want to replace Share but we needed a way to migrate away from the original implementation (based around the Surf paradigms of Templates, Components and WebScripts) towards something faster to develop and easier to customize.

This is summary of what we wanted to achieve:

  • Provide a library of fine-grained, decoupled widgets that completely encapsulate all of their function, styling and localization behaviour
  • For those widgets to be easily unit testable across multiple browsers
  • To reduce the complexity of Surf rendered pages by removing Page, Template and Component configurations and replacing them with pages defined by an easily customisable JSON model in the JavaScript controller of a single WebScript
  • To be able to dynamically build pages both for and within the running UI and render them without restarting any servers
  • To provide a foundation for our business partners and customers to build their own solutions on

Building on Previous Work

Those of you that have been following the changes in Share from version 4.0 to 4.2 (Community) will have noticed significant enhancements to the Surf framework and the deliberate refactoring of logic from the FreeMarker template into the JavaScript controller of the Share WebScripts. We’ve also focused on improving page load performance by reducing HTTP requests by:

All of these features are leveraged in the improvements and we believe it will make it easier to produce faster, more reliable pages and in less time for the Alfresco Share and the Alfresco in the Cloud web applications. The improvements build on top of Dojo and provide (with the greatest of respect to the Dojo developers) a couple of significant enhancements…

Zero Build

The first enhancement is a zero build approach. Surf is able to dynamically analyse the JSON model that defines the page being rendered and resolve all JavaScript dependencies that are then compressed and written into a single JavaScript resource that simulates a built Dojo layer. Surf caches all the dependency relationships as it finds them so it never traverses a dependency path a second time. It also caches the individual layers it generates so although the very first page rendered after server start up may take a few seconds to render, subsequent page rendering will be incredibly fast.

Encapsulation

When you render a web you are normally expected to take care of the styling via separately referenced CSS files (for example you might import a theme style sheet for the framework that you are using). We’re taking a different approach.

Each widget can optionally define its own CSS resources (including the ability to specify different resources for different media types). If that widget is used on a page then Surf will automatically include those CSS resources within a single aggregated CSS resource loaded on the page. The same principle is applied to localization files so the use of a widget ensures that its NLS properties automatically built into a JavaScript resource loaded on the page. Widgets can scope their message bundles to ensure that there are no collisions and the core message handling function is smart enough to work through all of the message scopes in a widgets ancestry to ensure that the most specific message is displayed.

Cross JavaScript Library Support

At Alfresco we believe in using the right tool for the job. Although Share was originally built using YUI2 we have also introduced JQuery plugins and we have no intention of restricting Share to just Dojo. The framework is designed to easily support widgets provided by other libraries and by design we can easily swap out Dojo widgets for those provided by other libraries. We have also provided a mechanism for wrapping our existing YUI2 centric widgets to that they can be referenced in the JSON model for the page. If you go to the Calendar of any Site you’ll see YUI2, JQuery and Dojo all playing nicely together.

Continued Extensibility

One of Share’s greatest strengths is its ability to be easily customized. This is something that we’ve taken great efforts to enhance over the last few releases and is essential that we continue to support. The last significant improvement we made was to make it easier to customize the client-side widgets that are instantiated on each page by customizing the JavaScript controller of a Component WebScript. We’re continuing to support this paradigm but instead of customizing a single coarse-grained widget it is now possible to customize the numerous fine-grained widgets that comprise a page or Component. Widgets can be reconfigured, added or removed easily and because the widgets are decoupled (such that they don’t rely on the existence of other widgets) making changes won’t cause any errors.

Mighty Oaks and Little Acorns

At the moment we’ve only used these new capabilities to build a header bar to accompany the “Light” theme that arrived in 4.2 Community. However this is the first step along a longer path towards making Alfresco Share better than ever. Our library of widgets currently only consists of the handful required to construct our header (but already allow you to build your own header or customize the default one without writing a line of JavaScript, CSS or HTML) but as we add new capabilities to Share this widget library will continue to grow. As I promised at DevCon 2012 – we’re not going to discard all the hard work we’ve done over the last 5 years, hopefully this shows our commitment in respect of that goal.

Create a New Surf Application With One Command

This is going to be just a quick post to announce the availability of a new Spring Surf application archetype that is now available on artifacts.alfresco.com. I’d like to say a big thank you to my colleages Samuel Langlois and Gabriele Columbro for playing a huge part in making this happen!

It’s now possible to create a new Surf application with one command (assuming that you have Maven 3 installed on your machine). Simply enter the following at a command prompt:

mvn archetype:generate -DarchetypeCatalog=https://artifacts.alfresco.com/nexus/content/groups/public-snapshots/archetype-catalog.xml

Select the “spring-surf-archetype” (at the time of writing its number 8) and provide a “groupId” and “artifactId” when prompted (you can just hit enter to accept the defaults for the other options) and a new application will be created for you.

You can then build the application by running (in the folder created):

mvn install

And can then run the application with the following command:

mvn jetty:run

If you open your browser at the URL http://localhost:8080/<artifactId> then you’ll see a sample Surf page.

The point of this archetype is twofold…

  1. It can give you a head start if you want to write your own web-client for working with your Alfresco repository
  2. As an educational tool to help you understand Surf applications (and therefore Alfresco Share) better

If you browse through the source of your new application you should find that all of the configuration files are heavily commented to explain what all the settings are for. There’ll be more blog posts in the future with more information and examples of how to use the application but for now it should at least give you something to experiment with !

Introducing the Alfresco Activities browser extensions

Several weeks ago there was some discussion within Alfresco about how often we fall back to e-mail for discussions rather than using our own Discussions feature available within Share. It seemed that the reason for this ultimately came down to the need to repeatedly check your dashboard for updates whereas participants of an e-mail are passively notified of new exchanges.

There have been discussions around adding the capability for Alfresco to be interacted with via e-mail but that is an issue for the Alfresco Project Management team and would be something for a future release.  It occurred to me that e-mail also requires constant checking for new data and that it is only background polling and notifications provided by our e-mail clients that make it appear otherwise.  I regularly use Googlemail in Chrome and use the “Google Mail Checker” extension to ensure that I don’t spend all day hitting the “Refresh” button in my browser…. it seemed that an equivalent approach could be used to receive updates on Alfresco activities.

I spent a few hours writing a Chrome extension that I have been using to keep abreast of new content on our internal Alfresco installation. I later ported this to Firefox and following a number of feature requests from other users at Alfresco have created a Google Code project for both extensions.

The extension provides the same data as the “Everyone else’s activities” feed from the “My Activities” user dashlet (in fact it uses the same WebScripts to obtain the data) and provide the following features:

  • Desktop notifications of unacknowledged activities
  • A badged toolbar button showing the number of unacknowledged activities
  • A pop-up activity stream to allow quick access to updated content (accessed via the toolbar button)

The extension means that I don’t need to keep checking Alfresco for updates to documents, sites, discussions, etc. that I’m interested in – this information is pushed directly to me.

Implementation Details

The current code is completely functional and highly useful in its current state but it’s worth remembering that it’s only the result of a day’s effort.  There were many features that I considered adding but in the end stripped everything back to its core functionality which is notification and access.

Ideally it would allow custom filtering, configurable options for target servers and polling intervals, improved UI in the panel and on the button, etc… but these are all things for the future (should the extensions become popular).

It’s also necessary to be aware of the following requirements…

Target server specified via build.properties

In order to minimize the potential for XSS attacks the target server is specified via the build.properties file. This means that you need to checkout the project, set your own Alfresco server location and then build it if you want to use the extension (this is a fairly trivial exercise). The default server is the Alfresco Cloud so if you want to use the extensions against Alfresco Cloud then you can just download a pre-built version from the Google Code site.

Initial Sign-in to Share

The extension does not persist user credentials so it is always necessary to sign-in to Share the first time you open your browser. This has been done for security reasons as I wasn’t certain how safe it would be to store user credentials in browser local storage. This is the same approach as used by the Google Mail Checker extension so hopefully users will be comfortable with this.

Firefox needs the HTML Desktop Notifications add-on

Firefox doesn’t implement the desktop notification API out-of-the-box so it is necessary to install the HTML Desktop Notifications add-on if you want to receive notifications.

Try it out with the Cloud

The default target server is the Alfresco Cloud but at the moment the implementation is limited to only receive activity updates from your Home Network (so you won’t receive any notifications from other Networks into which you’ve been invited).

Contributions Wanted

If this is a project that you’re interested in then I’d definitely welcome your involvement as there’s lots of features that need to be added (if anyone out there is interested in writing an IE port then that would also be good!) – I’d also value any feedback you have on this project so please leave comments and suggestions below.

Customizing Share JavaScript Widget Instantiation (Part 4)

This post refers to code committed to the latest Alfresco Community source – it is not yet available in any official release but you can try it out against the nightly Community builds.

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!)

Customizing Share JavaScript Widget Instantiation (Part 3)

This post refers to code committed to the latest Alfresco Community source – it is not yet available in any official release but you can try it out against the nightly Community builds.

Introduction

In the first post of this series I showed how to extend the “documentlist.get.html.ftl” and “documentlist.get.js” files to instantiate a custom JavaScript widget that extends the default “Alfresco.DocumentList“. This is a fragment from the “webview.get.html.ftl” file:

<@markup id="widgets"> <@inlineScript group=”dashlets”> var editDashletEvent = new YAHOO.util.CustomEvent("onDashletConfigure"); </@> <@createWidgets group=”dashlets”/> <@inlineScript group=”dashlets”> editDashletEvent.subscribe(webView.onConfigWebViewClick, webView, true); </@> </@> 

Note that another new FreeMarker directive is being used: <@inlineScript> – this directive is used to demarcate sections of JavaScript to be included on the rendered HTML page but allows the specified script to be moved around the page or into aggregated generated resource files (more on this in a future blog).

Setting Object References

This extra code is required in this WebScript to create a custom event that is triggered when a user clicks on a button in the title bar. The <@createWidgets> directive is able to pass a reference the “editDashletEvent” object by way of a special data structure that can be used when creating the instantiation metadata in the JavaScript controller.

The problem is that when creating the model it is impossible to distinguish between a String that is genuinely a String and a String that is a reference to a JavaScript variable defined in the FreeMarker template because the controller has no awareness of that variable (the JavaScript controller is processed before the FreeMarker template).

To work around this problem the “webview.get.js” controller sets a reference by including the following object to the widgets “options” metadata object:

eventOnClick: { _alfValue : "editDashletEvent", _alfType: "REFERENCE"},

When the <@createWidgets> directive encounters a JSON object with the attributes “_alfValue” and “_alfType” and ONLY those attributes it converts that object into a variable reference instead of a String (effectively it omits the quotes).

PLEASE NOTE: There were many different ways in which we could have solved this problem but based on the perceived frequency of it being required we took this approach, as opposed to (for example) forcing developers to specify all Strings with escaped quotes.

Generated JavaScript

Ultimately the final source for the generated page will contain the following:

<script>//<![CDATA[ var editDashletEvent = new YAHOO.util.CustomEvent("onDashletConfigure"); //]]></script> <script type="text/javascript">//<![CDATA[ var webView=new Alfresco.dashlet.WebView("page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default").setOptions({"webviewTitle":"","webviewURI":"/share/page/webview-default","isDefault":true,"webviewHeight":"","componentId":"page.component-1-2.site~site1~dashboard#default"}).setMessages({"label.noWebPage": "No web page to display.", "dashlet.help": "<p>This dashlet shows the website of your choice. Click the edit icon on the dashlet to change the web address.</p><p>Clicking the dashlet title opens the website in a separate window.</p>", "label.header": "Web View"}); new Alfresco.widget.DashletResizer("page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default", "page.component-1-2.site~site1~dashboard#default"); new Alfresco.widget.DashletTitleBarActions("page_x002e_component-1-2_x002e_site_x007e_site1_x007e_dashboard_x0023_default").setOptions({"actions":[{"eventOnClick":editDashletEvent,"cssClass":"edit","tooltip":"dashlet.edit.tooltip"},{"bubbleOnClick":{"message":"dashlet.help"},"cssClass":"help","tooltip":"dashlet.help.tooltip"}]}); //]]></script> <script type="text/javascript">//<![CDATA[ editDashletEvent.subscribe(webView.onConfigWebViewClick, webView, true); //]]></script> 

Buried in amongst the generated instantiation script for the “Alfresco.widget.DashletTitleBarActions” widget is the following call to the “.setOptions()”  function:

.setOptions({ "actions": [{ "eventOnClick" : editDashletEvent, "cssClass" : "edit", "tooltip" : "dashlet.edit.tooltip" }, { "bubbleOnClick": { "message" : "dashlet.help" }, "cssClass" : "help", "tooltip" : "dashlet.help.tooltip" } ] }); 

The key thing to notice is that the “eventOnClick” attribute is being set as an object reference and NOT a String.

Summary

This post should have hopefully explained how to address the issue of referencing JavaScript objects declared in the FreeMarker template in the widget instantiation metadata (although we expect it to be something of an edge case) and the reason for the “pre” and “post” <@markup> directives in the new boiler-plate template. In the next post I’ll describe how the <@link>, <@script>, <@inlineScript> and <@createWidgets> directives work in the different runtime modes now available in Surf .

Customizing Share JavaScript Widget Instantiation (Part 2)

This post refers to code committed to the latest Alfresco Community source – it is not yet available in any official release but you can try it out on the nightly Community builds.

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:

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.

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