Table of Contents

Author: Martin Bergljung

What is Aikau?

What is Dojo and Why?

Object Oriented Programming with JavaScript

Object Oriented Programming with Dojo

Object Oriented Programming with Dojo AMD Loader

Creating User Interface Components with Dojo

What is Spring Surf?

Installing Alfresco

Developing Aikau Pages

Aikau Page using Existing Widgets

Aikau Page Showing and Hiding Existing Widgets

Aikau Page using Custom Widgets

Aikau Page Using a Form and a Custom Service

Introduction

Implementing the Send Email with Files Aikau Page

Implementing the Email Service component

Implementing the Send Email Repo Web Script

Displaying a Message for Successful or Failed Operation

Turning on Console Logging

Turning on Event Logging

Aikau Page Using Lists

Updating the Send Email Repo Web Script to store Email Info

Creating a Repo Web Script to return Email Info

Updating the Send Email Aikau page with a List of Sent Emails

Other Aikau Examples that Might be of Interest

Aikau Page Display Based on Alfresco Role

Aikau Page Using Composite Widget

Adding an Aikau Page to the Share Site Pages

Aikau Dashlets

Unit Testing Aikau Pages

Customizing Existing Aikau Pages in Share

Version 5.0 has the following Aikau implementations:

Using the Page Creator to Create Aikau Pages

Debugging Aikau Pages

What is Aikau?

Aikau is the name of a new UI development framework from Alfresco. It has been created to make custom Alfresco UI development easier, faster, and more tolerant to upgrades and changes in the Alfresco source code. Aikau will also make page loads much faster by only loading what is needed and aggregating JavaScript and CSS files. Images are also put directly into the CSS code.  

Aikau is not a start-from-scratch UI development framework, it comes with a widget library that enables you to very quickly build stand-alone UI clients and extensions to standard Alfresco Share UI. Here is an example of what you can expect to find in the library:

All these widgets/components can quite easily be wired together via events to create new “Alfresco User Interfaces” without too much coding. For example, you don’t have to figure out how to create a widget that can be used to pick documents from the repository, it has already been developed for you, and you just need to know how to use it.

Aikau supports a number use-cases that you will come in contact with as an Alfresco consultant/developer:

  1. If you are thinking of building a custom UI client for the Alfresco repository because of client requirements that deviate a lot from the standard Alfresco Share UI, then you should definitely look at Aikau. Aikau provides a getting-started, stand-alone project, which you can use to quickly get up to speed on creating your own “Alfresco Repo UI”. And there is a User Guide to get you going immediately.
  2. One of the more common scenarios that an Alfresco developer will come in contact with is a customer that want to add new pages and dashlets to the Alfresco Share UI, this can easily be done with Aikau, and this is what this article focuses on.
  3. Finally you will have projects where the client wants you to change or remove standard out of the box Alfresco Share functionality, this article has some links at the end for how to do that.

Aikau is not a brand new JavaScript framework, it makes use of the Dojo JavaScript toolkit and the Spring Surf MVC model. Aikau is however not tied to Dojo, other JavaScript frameworks can be used, and has been used, such as JQuery, TinyMCE, CodeMirror, YUI, etc.

So is Aikau easy to use? Yes definitely, if you know how to write a Web Script, which I would imagine most of you do, then you know how to implement an Aikau page. It is all done in the controller with some JSON.

There are two ways of reading this article, you can dive straight into implementing Aikau pages, or you can start from the beginning of the article with a deep dive into the inner workings of Aikau, and then continue with implementing Aikau pages.

Source code is available from here.

What is Dojo and Why?

Dojo is the JavaScript framework that sits behind the scenes of Aikau and it is vital to know how it works if you are going to understand how Aikau works. It is also quite interesting if you are coming from a Java or C++ background as it supports classes, packages, and modules. But before we dive into Dojo code and how it supports classes, let’s step back and look at Object Oriented Programming (OOP) with pure JavaScript.

Object Oriented Programming with JavaScript

It is beneficial to have a look at how you can do OOP with pure JavaScript before having a look at how it is done in Dojo. Let’s start with pure JavaScript and look at how it supports prototype-based Object Oriented Programming. Take the following example:

// Define a new object prototype

function Boat(name) { this.name = name; };

Boat.prototype.name = '';

Boat.prototype.currentSpeed = 0;

Boat.prototype.accelerate = function(increment) {

   this.currentSpeed += increment;

}

// Create a new object instance

var myBoatObject = new Boat('Moby Dick');

myBoatObject.accelerate(10);

console.log(myBoatObject);

Here we are creating a so called constructor function to be used for creating new objects, we do new on it to create an object. It’s not really a class in the Java sense, as you can easily alter it during runtime as we can see. You basically “inject” properties and functions into it via the prototype object. Every JavaScript object has a prototype and it is also an object. All JavaScript objects inherit their properties and functions from their prototype.

JavaScript is a dynamically typed language so I don’t have to specify types for my objects and variables. If I run the code, it will print out something like this (using Firebug JavaScript console):

To support inheritance in pure JavaScript, I can extend an existing object prototype when creating a new one:

// Define a new object prototype that extends another one

function PowerBoat(name) { Boat.call(this, name); };

PowerBoat.prototype = new Boat();

PowerBoat.prototype.engineType = '';

PowerBoat.prototype.setEngineType = function(type) {

   this.engineType = type;

}

PowerBoat.prototype.constructor = PowerBoat;

// Create a new object instance

var myPowerBoatObject = new PowerBoat('Blue Thunder');

myPowerBoatObject.setEngineType('Mercury 275hp');

myPowerBoatObject.accelerate(200);

console.log(myPowerBoatObject);

If I run this code, it will print out something like this:

Object Oriented Programming with Dojo

OK, now to the interesting bit, in Dojo you can emulate class based OOP in JavaScript. This is particularly interesting for us who come from a Java or C++  background as we would like to stick to class based OOP because we know it inside out. The examples above will look like follows in Dojo, where you can use the dojo.declare function to create a “class”:

// Define a new object class

dojo.declare("Boat", null, {

 name: '',

 currentSpeed: 0,

 constructor: function(name) {

   this.name = name;

 },

 accelerate: function(increment) {

   this.currentSpeed += increment;

 }

});

// Create a new object instance

var myBoatObject = new Boat('Moby Dick');

myBoatObject.accelerate(10);

console.log(myBoatObject);

If I run this code, it will print out something like this:

As we can see, using Dojo to create “classes” is quite similar to Java, even though in the implementation it is quite different. You can also see that there are some other properties available when using Dojo, such as inherited.

Note. To be able to run the above code in, for example, Firebug, you will need to first load an HTML file that makes the declare function available:

<!DOCTYPE html>

<html>

<head>

   <meta charset="utf-8">

   <title>Dojo!</title>

</head>

<body>

<!-- HTML goes here... -->

<!-- Configure Dojo -->

<script>

   var dojoConfig = {

       async: true  

   };  

</script>

<!-- Load Dojo -->

<script src="//ajax.googleapis.com/ajax/libs/dojo/1.10.3/dojo/dojo.js"></script>

<script>

   require([

       'dojo/_base/declare'

       ], function(declare) {

           <!-- Dojo code -->

       });

</script>

</body>

</html>

Let’s come back to this HTML file later on to explain it a bit more.

Now to create the PowerBoat object class that should inherit from the Boat class, we can do the following in Dojo:

// Define a new object class that extends another one

dojo.declare("PowerBoat", Boat, {

 engineType: '',

 constructor: function(name) {

this.name = name;

 },

 setEngineType: function(type) {

   this.engineType = type;

 }

});

// Create a new object instance

var myPowerBoatObject = new PowerBoat('Blue Thunder');

myPowerBoatObject.setEngineType('Mercury 275hp');

myPowerBoatObject.accelerate(200);

console.log(myPowerBoatObject);

If I run this code, it will print out something like this:

So this is pretty cool stuff. We can now create classes similar to how we do it in, for example, Java, and it is easy to understand. Now, what about packaging, modular development, and loading of the scripts that contain these “classes”? Do we need to include truckloads of <script> tags, or is there a better way how we can manage this stuff? There certainly is when using Dojo. Let’s start with packages and keeping different classes in different files like we are used to in Java.

If we define our Boat and PowerBoat classes in Java within a specific package, it might look like this:

package org.alfresco.ecmstuff.dojotraining;

public class Boat {}

This class would be in a file called Boat.java and located in the /org/alfresco/ecmstuff/dojotraining directory. We can then import this class when defining the PowerBoat class:

package org.alfresco.ecmstuff.dojotraining;

import org.alfresco.ecmstuff.dojotraining.Boat;

public class PowerBoat extends Boat {}

This class would be in a file called PowerBoat.java and also located in the /org/alfresco/ecmstuff/dojotraining directory. Now, over to Dojo, how can we do this in Dojo? Here is how you can define the Boat class in a specific package and file:

dojo.provide("org.alfresco.ecmstuff.dojotraining.Boat");

dojo.declare("org.alfresco.ecmstuff.dojotraining.Boat", null, {

   // Boat class code goes here

});

As you can see, this looks a lot like the Java code, although there is a big difference in that you have to specify the entire package path for the class in the dojo.declare statement, rather than just the class name. The package path, provided by the dojo.provide statement, is important as it also determines where Dojo will look for this class when it attempts to load it.

This means that the Boat.js file should be stored in a relative directory path org/alfresco/ecmstuff/dojotraining. If it is not stored in this location, Dojo will not be able to load the class correctly when it needs to.

Next, let's look at how we can import the Boat class and have the PowerBoat extend it:

dojo.provide("org.alfresco.ecmstuff.dojotraining.PowerBoat");

dojo.require("org.alfresco.ecmstuff.dojotraining.Boat");

// Define a new object class that extends another one

dojo.declare("org.alfresco.ecmstuff.dojotraining.PowerBoat",

  org.alfresco.ecmstuff.dojotraining.Boat, {

   // PowerBoat class code goes here

});

JavScript files with class declarations are loaded using dojo.require, which is similar to the <script> include tag on an HTML page. It dynamically pulls in only the resources required by the page synchronously.

Object Oriented Programming with Dojo AMD Loader

The dojo.declare, dojo.provide, and dojo.require functions are actually used with Dojo’s legacy code loader. From version 1.7 of Dojo it uses an implementation of the Asynchronous Module Definition (AMD) loader specification. It has a number of advantages:

Implementing the Boat class module with the new AMD Loader syntax looks like this:

define([

       // Any dependencies required by this module goes here

       'dojo/_base/declare'

   ], function(declare) {

       // Once all modules in the dependency list have loaded, this

       // function is called to define the  

       // org/alfresco/ecmstuff/dojotraining/Boat module.

       // This returned object becomes the defined value of this module

       return declare(null, {

           name: '',

           currentSpeed: 0,

           constructor: function(name) {

               this.name = name;

           },

           accelerate: function(increment) {

               this.currentSpeed += increment;

           }

       });

});

This module should be stored in a file called Boat.js in the org/alfresco/ecmstuff/dojotraining directory. A couple of things to take notice of here:

To try out the new module we need to use an HTML page looking something like this:

<!DOCTYPE html>

<html>

<head>

   <meta charset="utf-8">

   <title>Dojo Tutorial: Boats!</title>

</head>

<body>

<!-- HTML goes here... -->

<!-- Configure Dojo -->

<script>

   // Instead of using data-dojo-config, we're creating a dojoConfig

   // object *before* we load dojo.js; they're functionally identical

   // it's just easier to read this approach with a larger configuration

   var dojoConfig = {

       async: true,

       // This code registers the correct location of the "dojotraining"

       // package so we can load Dojo from the CDN whilst still

       // being able to load local modules

       packages: [{

           name: "dojotraining",

           location: location.pathname.replace(/\/[^/]*$/, '') + '/org/alfresco/ecmstuff/dojotraining'

       }]

   };  

</script>

<!-- Load Dojo -->

<script src="//ajax.googleapis.com/ajax/libs/dojo/1.10.3/dojo/dojo.js"></script>

<script>

   require([

       'dojotraining/Boat',

       ], function(Boat) {

           // Create a new object instance

           var myBoatObject = new Boat('Moby Dick');

           myBoatObject.accelerate(10);

           console.log(myBoatObject);

       });

</script>

</body>

</html>

Here we are using the configuration object dojoConfig to set up asynchronous mode for the AMD loader (i.e. we are not running in legacy synchronous mode). We also define where all our dojotraining packages are located via the packages configuration property. We then load the Dojo AMD loader via script tag. After this we use another AMD specification function called require to load the Boat module. We pass the Boat class into the anonymous function and create a new Boat object.

Note. I’m running Apache HTTP Server to test this and I have put the Boat.js file in the /var/www/html/dojo/org/alfresco/ecmstuff/dojotraining directory and the HTML file in the /var/www/html/dojo directory.

The PowerBoat class can be defined in its own module in a similar way as follows:

define([

       // Any dependencies required by this module goes here

       'dojo/_base/declare',

       './Boat'

   ], function(declare, Boat) {

       // Once all modules in the dependency list have loaded, this

       // function is called to define the  

       // org/alfresco/ecmstuff/dojotraining/PowerBoat module.

       // This returned object becomes the defined value of this module

       return declare(Boat, {

           engineType: '',

           constructor: function(name) {

               this.name = name;

           },

           setEngineType: function(type) {

               this.engineType = type;

           }

       });

});

Notice the use of the relative identifier used to import the Boat class (./Boat). This is in contrast to the legacy loader where we had to use full paths which each element separated by a dot. To create a new PowerBoat object we can update the HTML page as follows:

<!DOCTYPE html>

<html>

<head>

   <meta charset="utf-8">

   <title>Dojo Tutorial: Boats!</title>

</head>

<body>

<!-- HTML goes here... -->

<!-- Configure Dojo -->

<script>

   // Instead of using data-dojo-config, we're creating a dojoConfig

   // object *before* we load dojo.js; they're functionally identical

   // it's just easier to read this approach with a larger configuration

   var dojoConfig = {

       async: true,

       // This code registers the correct location of the "dojotraining"

       // package so we can load Dojo from the CDN whilst still

       // being able to load local modules

       packages: [{

           name: "dojotraining",

           location: location.pathname.replace(/\/[^/]*$/, '') + '/org/alfresco/ecmstuff/dojotraining'

       }]

   };  

   </script>

<!-- Load Dojo -->

<script src="//ajax.googleapis.com/ajax/libs/dojo/1.10.3/dojo/dojo.js"></script>

<script>

require([

   'dojotraining/Boat',

   'dojotraining/PowerBoat',

   ], function(Boat, PowerBoat) {

       // Create a new Boat object instance

       var myBoatObject = new Boat('Moby Dick');

       myBoatObject.accelerate(10);

       console.log(myBoatObject);

     

       // Create a new PowerBoat object instance

       var myPowerBoatObject = new PowerBoat('Blue Thunder');

       myPowerBoatObject.setEngineType('Mercury 275hp');

       myPowerBoatObject.accelerate(200);

       console.log(myPowerBoatObject);

   });

</script>

</body>

</html>

By now, you should be up to speed on the Dojo AMD loader, how to create classes, and how to implement modules.

Creating User Interface Components with Dojo

Next thing to look at is the Dojo user interface (UI) widget library Dijit and template based widgets. Aikau uses template based widgets so we need to get on top of how they work. Dijit widgets encapsulates both the view (HTML + CSS) and the controller (JavaScript). A widget is a class so we already know quite a bit about how to create one in a module. Common practice is to create one widget per module representing a UI component. Let’s create a page that has a list of book widget instances displaying different Alfresco books. The page will look something like this when finished:

The content for this page will be available in a JSON file on disk and look like this:

[

   {

       "title": "Alfresco CMIS",

       "author": "Martin Bergljung",

       "coverImage": "https://d1ldz4te4covpm.cloudfront.net/sites/default/files/imagecache/ppv4_main_book_cover/3527OS_Alfresco%20CMIS_0.jpg",

       "summary": "Learn how to build applications that talk to content management servers in a standardized way using this superb course on getting the best from Alfresco CMIS. This is a highly practical, step-by-step guide."

   },

   {

       "title": "Alfresco Developer Guide",

       "author": "Jeff Potts",

       "coverImage": "https://www.packtpub.com/sites/default/files/bookimages/1847193110.jpg",

       "summary": "Learn to customize the entire Alfresco platform, including both Document Management and Web Content Management. Jam-packed with real-world, step-by-step examples to jump start your development. Content modeling, custom actions, Java API, RESTful web scripts, advanced workflow. This book covers Alfresco Enterprise Edition version 2.2"

   },

   {

       "title": "Alfresco 3 Business Solutions",

       "author": "Martin Bergljung",

       "coverImage": "https://www.packtpub.com/sites/default/files/3340OS_Alfresco%203%20Business%20Solutions_FrontCover.jpg",

       "summary": "Deep practical insights into the vast possibilities that exist with the Alfresco platform for designing business solutions. Each and every type of business solution is implemented through the eyes of a fictitious financial organization - giving you the right amount of practical exposure you need. Packed with numerous case studies which will enable you to learn in various real world scenarios. Learn to use Alfresco's rich API arsenal with ease. Extend Alfresco's functionality and integrate it with external systems."

   }

]

So what we have is a JSON array where each item has four properties:

We are going to have our page read this JSON file from disk and then create as many BookWidget instances as there are items in the book array.

It is probably a good idea to set up a project structure before we move on with creating classes, HTML etc. The following type of directory layout is commonly used when creating Dijit widgets:

The main directory for the Alfresco Books page is www/html/dojo/org/alfresco/ecmstuff/dojotraining/alfrescobooks. In this directory we have the HTML page file called alfresco-books.html and the data and widget directories with all the content that goes onto the page and the UI component that will render it. The JSON data will be contained in the data/books.json file. The widget directory will contain the Dojo Widget module with one class in a file called BookWidget.js and other subdirectories for different files associated with the widget. The widget HTML template is defined in the templates/BookWidget.html file and the widget styling is specified in the css/BookWidget.css file. The images directory contains a default cover image if the book would not have one specified in the JSON data.

Let’s start working on the Widget class in the BookWidget.js file, it should extend the Dijit widget base class and be using our HTML template:

define([

   "dojo/_base/declare",

   "dijit/_WidgetBase",

   "dijit/_TemplatedMixin",

   "dojo/text!./templates/BookWidget.html"

   ], function(declare, _WidgetBase, _TemplatedMixin, template) {

       return declare([_WidgetBase, _TemplatedMixin], {

        // The Widget HTML template = content of BookWidget.html

        templateString: template

     });

});

So what we are doing here is defining a new module with one anonymous widget class. Note that the class has more than one ancestor, and in contrast to Java multiple inheritance is supported in Dojo. The first ancestor is the prototypical ancestor (_WidgetBase) - think is-a relationship, while the second (and any subsequent functions) are mixins (_TemplatedMixin) - think has-a relationship. Mixins are special constructs that can be used to “mixin” extra properties and methods into a Dojo class during runtime.

The new widget class is using a number of new functions objects ( or classes, if you like):

Note here the use of dojo.text, which is a so called AMD plugin, identified by the exclamation mark (!) at the end. We supply the resource URL to load after the (!). The URL can be expressed in relative terms. Here are some other plugins that you are likely to encounter:

document.addEventListener("DOMContentLoaded",

function(event) {

// do something

});

require([

                 'dojo/has!touch?myPackage/touch:myPackage/mouse'

],

The HTML template BookWidget.html contains the following markup and it is best practice to keep it in a subdirectory called templates:

<div>

   <h3>${title}</h3>

   <h4>${author}</h4>

   <img src="" />

   <p>${!summary}</p>

</div>

This template will be parsed into a DOM structure by the _TemplatedMixin object, and it is important that it only has one root element (i.e. the div). The template is using substitution variables that will be substituted for the JSON property values upon widget instantiation, this is also done by the _TemplatedMixin. When we create the BookWidget instance from the Alfresco Books HTML page, we will pass in a Book JavaScript object that is read from our data JSON file, and only if the JSON property names matches the substitution variable names, will the substitution happen.

Note the "!" before the summary variable name, this prevents _TemplatedMixin from escaping quotations within a string.

As you might expect, it will not be possible to change the values of these variables after they have been set. So if you wanted to for example dynamically change the book summary after the widget was displayed, this would not be possible as there is no link between the widget instance and the specific paragraph node (i.e. there will be many paragraph nodes on the page as we will instantiate multiple BookWidget objects). So variable substitution like this is only recommended if the value will not change during the lifetime of the widget.

We can now try out the book widget by creating a minimal Alfresco Books page as follows:

<!DOCTYPE html>

<html>

<head>

   <meta charset="utf-8">

   <title>Tutorial: Alfresco Books</title>

</head>

<body>

<!-- HTML goes here -->

<h1>Alfresco Books</h1>

<div id="bookContainer">

   <!-- Books go here -->

</div>

<!-- Configure Dojo -->

<script>

   var dojoConfig = {

       async: true,

       // This code registers the correct location of the "alfrescobooks"

       // package so we can load Dojo from the CDN whilst still

       // being able to load local modules

       packages: [{

           name: "alfrescobooks",

           location: location.pathname.replace(/\/[^/]*$/, '') + ''

       }]

   };  

</script>

<!-- Load Dojo AMD Loader -->

<script src="https://ajax.googleapis.com/ajax/libs/dojo/1.10.3/dojo/dojo.js"></script>

<script>

require([

   'dojo/request',

   'dojo/dom',

   'dojo/_base/array',

   'alfrescobooks/widget/BookWidget'

   ], function(request, dom, arrayUtil, BookWidget) {

       // Load our Alfresco Books

       request("data/books.json", {

           handleAs: "json"

           }).then(function(books) {

               // Get a reference to the book container div

               var bookContainerDiv = dom.byId("bookContainer");

     

               arrayUtil.forEach(books, function(book) {

                   // Create the Book widget and place it

                   var widget = new BookWidget(book).placeAt(bookContainerDiv);

               });

             });

   });

</script>

</body>

</html>

We will recognize a lot of the stuff on this page from when we did the examples with Boat and PowerBoat. We got a couple of new functions/objects that we use though:

The request("data/books.json" object is used to read the books data from disk. We specify that we want the data to be handled as JSON. The request object will convert the response into a JavaScript object array that we call books. We want to display the books inside the bookContainer div so we grab a reference to it with the dom.byId function. The arrayUtils object is then used to loop through each book JavaScript object and pass it into the BookWidget when it is created and placed into the book container.

If we load the Alfresco Books page at this point, it will look something like this:

So almost there… We just need to do some styling and fix the cover image so it is loaded properly.  

Let’s add a stylesheet for our BookWidget in the widget/css/BookWidget.css file:

.bookWidget {

   border: 1px solid black;

   width: 400px;

   padding: 10px;

   overflow: hidden; /* Clear floats inside */

}

.bookWidget h3 {

   font-size: 1.5em;

   text-align: center;

   margin: 0px;

}

.bookWidget h4 {

   font-size: 1.2em;

   font-style: italic;

   text-align: center;

   color: blue;

   margin: 0px;

}

.bookWidgetCoverImage {

   float: left;

   margin: 4px 12px 6px 0px;

   max-width: 75px;

   max-height: 75px;

}

What we are doing here is assuming that there is a root level CSS class called bookWidget specified in our BookWidget.html markup. We are also assuming that there is a CSS class called bookWidgetCoverImage set on the cover image img tag. None of this is actually there yet. If we look at the widget HTML in the page, it will look something like this:

<div id="dijit__TemplatedMixin_0" title="Alfresco CMIS"

    widgetid="dijit__TemplatedMixin_0">

   <h3>Alfresco CMIS</h3>

   <h4>Martin Bergljung</h4>

   <img src="" />

   <p>Learn how to build applications that talk to content management servers in a standardized way using this superb course on getting the best from Alfresco CMIS. This is a highly practical, step-by-step guide.</p>

</div>

To fix this we need to first add the baseClass variable to the BookWidget class:

return declare([_WidgetBase, _TemplatedMixin], {

    // The Widget HTML template = content of BookWidget.html

    templateString: template,

    // A CSS class to be applied to the root node in our HTML template

    // (i.e. the top level div)

    baseClass: "bookWidget",

});

The baseClass variable is part of the dijit/_WidgetBase class and if you set it to a CSS class it will be injected it into the HTML root node (in our case the div) when the widget is instantiated. The div now looks like this:

<div id="dijit__TemplatedMixin_0" class="bookWidget" title="Alfresco CMIS" widgetid="dijit__TemplatedMixin_0">

Next we need to update the BookWidget.html and set the cover image CSS class:

<div>

   <h3>${title}</h3>

   <h4>${author}</h4>

   <img class="${baseClass}CoverImage" src="" />

   <p>${!summary}</p>

</div>

We make use of the baseClass variable value, and then just add the CoverImage text so it matches what we got in our CSS file. To use the stylesheet we just need to add it to our alfresco-books.html page head section:

<head>

   <meta charset="utf-8">

   <title>Tutorial: Alfresco Books</title>

   <link rel="stylesheet" href="widget/css/BookWidget.css">

</head>

If we load the Alfresco Books page now, it will look more like what we want:

The last thing we need to fix is setting the URL for the cover image. We only want to do this if there is a URL specified in the JSON object, otherwise we want to use the default image in the widget/images directory. We also want to set up some default values for the other book properties in case they are missing in the JSON object. Start with an update to the BookWidget.js class as follows:

 return declare([_WidgetBase, _TemplatedMixin], {

   // Some default values for our book properties,

   // in case they are missing in the JSON object

   title: "Unknown Title",

   author: "Unknown Author",

   summary: "",

   // Using require.toUrl, we can get a path to our BookWidget's space

   // and we want to have a default cover image, just in case

   coverImage: require.toUrl("./widget/images/defaultCoverImage.png"),

   // The Widget HTML template = content of BookWidget.html

   templateString: template,

   // A CSS class to be applied to the root node in our HTML template

   // (i.e. the top level div)

   baseClass: "bookWidget"

});

We can now add the URL substitution variable to the BookWidget.html and get the Cover Image properly displayed:

<div>

   <h3>${title}</h3>

   <h4>${author}</h4>

   <img class="${baseClass}CoverImage" src="${coverImage}" />

   <p>${!summary}</p>

</div>

To try out missing properties in the JSON object I will add one book as follows to the data/book.json:

{

   "title": "Alfresco 5",

   "summary": "Deep dive into Alfresco 5"

}

Refresh the page now and it will look look like this:

All done! Actually, we have not yet covered two important features, changing widget variables dynamically and using existing Dijit widgets. What if we wanted to have a button in each book widget, and when it is clicked, we update a count variable in the widget class and display the updated count. Let’s start by adding some stuff to the BookWidget.html template:

<div>

   <h3>${title}</h3>

   <h4>${author}</h4>

   <img class="${baseClass}CoverImage" src="${coverImage}" />

   <p>${!summary}</p>

   <button data-dojo-type="dijit/form/Button"

data-dojo-attach-event="click:viewed">View</button>

   <div data-dojo-attach-point="viewCountNode"></div>

</div>

There are two ways we can use to nest/mixin a widget inside our widget: declaratively like in the above HTML, or programmatically in the BookWidget.js class. When using the declarative approach the data-dojo-type attribute is used to tell the Dojo parser what widget type we want to instantiate and hook up to this element. At the same time as we do this we can use another attribute called data-dojo-attach-event to connect the widget with an event, in this case generate the viewed event when the button is clicked.

There will need to be a matching event handler in the BookWidget.js class as we will see. Finally, we define a new div to contain the text with current view count, it will be updated when the button is clicked. To insert a text node into this div, we define an attach point via the data-dojo-attach-point attribute. We can then use the viewCountNode to reference this attach point in the BookWidget.js class.

It might be tempting to start using id attributes for element identification but this will not work as a BookWidget will occur several times on the HTML page. We use the attach points and attach events for this instead to get unique identifiers per widget instance.

Now let’s update the BookWidget.js class with a new variable called viewCount, a button click event handler, some more dijit classes etc:

define([

   "dojo/_base/declare",

   "dijit/_WidgetBase",          

   "dijit/_TemplatedMixin",          

   "dijit/_WidgetsInTemplateMixin",      

   "dojo/text!./templates/BookWidget.html",

   "dijit/form/Button"            

   ], function(declare, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, template) {

     return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {

       // Some default values for our book properties,

       // in case they are missing in the JSON object

       title: "Unknown Title",

       author: "Unknown Author",

       summary: "",

       // Using require.toUrl, we can get a path to our BookWidget's space

       // and we want to have a default cover image, just in case

       coverImage: require.toUrl("./widget/images/defaultCoverImage.png"),

       // Keep track of how many has viewed this book

       // and clicked the Viewed button

       viewCount: 0,

       // The Widget HTML template = content of BookWidget.html

       templateString: template,

       // A CSS class to be applied to the root node in our HTML template

       // (i.e. the top level div)

       baseClass: "bookWidget",

       // Button click event handler

       viewed: function () {

         this.viewCount += 1;

         this._setText('Count is now ' + this.viewCount);

       },

 

       // Set the new view count as text node

       _setText: function (text) {

         var node = document.createTextNode(text);

         this.viewCountNode.innerHTML = '';

         this.viewCountNode.appendChild(node);

       }

 });

});

First thing we need to do when we nest/mixin widgets is to include the dijit/_WidgetsInTemplateMixin class in the require section. When it is included any declarative widgets are automatically parsed when the BookWidget is instantiated. The next thing we need in the require section is the dijit/form/Button

class to be used when the button is instantiated. Note that the dijit/form/Button is specified last in the require section, this is important otherwise we might mistakenly get some other variable like _WidgetsInTemplateMixin or template to contain a reference to a button. Note that we don’t need to include the dijit/_AttachMixin class to handle the attach attributes as it is already a dependency in the dijit/_TemplatedMixin class. 

We have added the viewCount variable and set it to default to 0. This variable is incremented in the button event handler function called viewed. This function also uses the “private” _setText function to update the current view count text under the button.

The final thing we need to do is in the alfresco-books.html page file, we need to add the Dijit CSS file so we get some styling for our button:

<head>

   <meta charset="utf-8">

   <title>Tutorial: Alfresco Books</title>

   <link rel="stylesheet" href="widget/css/BookWidget.css">

   <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.10.3/dijit/themes/claro/claro.css">

</head>

The page should now look something like this:

Note that I have clicked on the View button for each book a different number of times. While you are developing Dijit widgets it is possible to try things out with JSFiddle, here is an example of a configuration: http://jsfiddle.net/1pfsx561/13/ 

By now you should be well up to speed on AMD, Dojo, Dijit, and Template based Widgets. Time to move on to the next subject.

What is Spring Surf?

To fully understand how an Aikau page is constructed and rendered we need to know a little bit about the Spring Surf framework. The Spring Surf framework was originally developed by Alfresco and later on donated to the Spring Source foundation. It is based on the Spring MVC framework and provides a way of breaking a HTML page into re-usable component parts. This is also the framework that underpins the Alfresco Share web application.

The Spring Surf development framework is based on the Model View Controller (MVC) pattern where the controller is mostly implemented in server side JavaScript (or Java in some cases). The template is written in FreeMarker and the model is a hash map that is setup in the controller and available in the template.

Each page template defines one or more regions for things like header, footer, body, navigation etc, see the following picture:

To be able to reuse regions we can scope them to page, template, or global usage:

Each region is implemented as a reusable component. A component implementation is done with something called a Web Script, which is the same thing as the REST-based request and response model, the predominant Web Service design model:

With all these different objects we might expect there to be some form of model that makes up the whole Surf UI development framework. It looks like this:

When we are building Aikau pages, we do not actually define a Spring Surf page as one might expect, but instead we implement a component Web Script that represent the content for the Aikau Surf page. This simplifies page creation a lot as you don’t have to bother with page definitions, templates instances, template FTL, templates types, regions, components, scopes etc.

Alfresco comes with a couple of Spring Surf pages that we should use when rendering an Aikau page:

 

When we invoke one of these pages, we supply a so called template argument (i.e. one that is not supplied as <url>?arg1=val but instead as path elements with arg and value) representing the Web Script that implements the body of the Aikau page. The format for an Aikau page URL is as follows:

http://<host>:8080/share/page/{Aikau Page definition}/ws/{Body content Web Script URL path}

So, as we can see, there are two template arguments in the URL, one called page that tells Surf what Aikau page instance we want to use, and another called ws that tells Surf what Web Script to use when rendering the contents of the Aikau page.

The following picture gives an overview of what happens when we invoke the dp page with an Aikau Page Web Script implementation (i.e /ws/ecmstuff/helloworld-page) for a Hello World page:

So what happens here is that first the Dynamic Page implementation is invoked (dp.xml), which points to template instance (share-template.xml) for rendering an Aikau page without Share header and footer. This template type is implemented as a Web Script (full-page.get.desc.xml), which will start rendering the complete page. When it renders the page it will pull in some FreeMarker (full-page-template.ftl) that renders the head section plus brings in the body section. The body section is defined in the full-page.get.html.ftl template and it will create a new component and region (via autoComponentRegion macro) for the custom content. The component implementation is the Web Script we create (helloworld-page.desc.xml) that will define what we want to go onto this new Aikau page. The Hello World controller is used to set up a JSON model representing all the UI widgets that should be part of the content for the page. This model is then processed via the processJsonModel FreeMarker template macro and rendered as the component content.

So now it’s about time we implemented our first Aikau page!

Installing Alfresco

Note. 2015-03-23: Alfresco Community 5.0.d is out - https://wiki.alfresco.com/wiki/Alfresco_Community_5.0.d_Release_Notes, and it contains the latest and greates Aikau version (aikau-1.0.8.1.jar), this means that the below upgrade proceedure of 5.0.c is not necessary if you use 5.0.d.

 

Before we start implementing any Aikau pages we need an Alfresco installation to work with. At the time when I am writing this version 5.0.c (Community) and version 5.0.0.5 (Enterprise) is out. These releases do not however include the latest Aikau version. I would be able to implement most of the below examples, but some things like lists would not work, and some widgets have been deprecated and replaced with new ones.

Note. Nothing has been removed, the original widgets now extend the new widgets (which contain the code) and the original widgets have been deprecated. JSON models referencing the deprecated widgets will still work.

So it would make sense to try and use a newer version of Alfresco. We can do this by building the community source code, grab the generated WARs, and then drop them into a 5.0.c installation, effectively upgrading it to latest functionality and Aikau code available in version 5.1.0.

Steps you need to take to upgrade from 5.0.c to 5.1.0:

  1. Download and install Alfresco Community 5.0.c
  2. Start the installation and make sure you can access http://localhost:8080/share and login
  3. Stop Alfresco Tomcat: /opt/alfresco50c$ ./alfresco.sh stop tomcat
  4. Clone the latest Alfresco Community source code: $ git clone https://github.com/Alfresco/community-edition.git 
  5. Build the latest code: src/community-edition$ mvn install
  6. Wait for success: [INFO] BUILD SUCCESS
  7. Copy WARs to 5.0.c installation:
  1. community-edition$ cp projects/web-client/target/alfresco.war /opt/alfresco50c/tomcat/webapps
  2. community-edition$ cp projects/slingshot/target/share.war /opt/alfresco50c/tomcat/webapps
  1. Delete exploded WAR deployments: tomcat/webapps$ rm -rf alfresco/ share/
  2. Clean Tomcat working directory: tomcat/work$ rm -rf *
  3. Start Alfresco Tomcat: /opt/alfresco50c$ ./alfresco.sh start tomcat
  4. You should see in the logs that you are now on 5.1:  2015-03-12 10:39:47,280  INFO  [service.descriptor.DescriptorService] [localhost-startStop-1] Alfresco started (Community). Current version: 5.1.0 (r@scm-revision@-b@build-number@) schema 9,003. Originally installed version: 5.0.0 (c r91299-b145) schema 8,009.

Developing Aikau Pages

This section will take you through implementing a number of Aikau pages, demonstrating different features of the Aikau development framework. As we saw in the introduction to this article, there are many applications of Aikau. This article focus on extending the Alfresco Share web application with new Aikau pages, simulating what you might have to do in a new Alfresco 5.0 customization project for a customer.

When you go through and work with the examples it is beneficial to have the source code for Aikau handy, so you can quickly search through Widget module implementations etc. You might for example be interested in what widgets publish what events and what widgets subscribe to what events.

To get the source code, which is available in GitHub do the following:

martin@gravitonian:~/src$ git clone https://github.com/Alfresco/Aikau.git 

Aikau Page using Existing Widgets

As we have seen, from our point of view, an Aikau page is just a Web Script that refers to one or more widgets. As usual, let’s start with a Hello World page with some out-of-the-box widgets. We are not going to use a build project in this article, instead we will just pop the necessary files into the alfresco/tomcat/shared/classes/alfresco/web-extension/site-webscripts/aikau-pages directory, and then just refresh the Web Scripts (i.e. http://localhost:8080/share/page/index). You will have to create the site-webscripts/aikau-pages directories.

The following files are part of the page Web Script:

helloworld-page.get.desc.xml:

<webscript>

   <shortname>Hello World Aikau Page</shortname>

   <description>Hello World Aikau page using out of the box Widgets  

   </description>

   <family>ECM Stuff</family>

   <url>/ecmstuff/helloworld-page</url>

</webscript>



The Web Script descriptor is just a straightforward standard descriptor, nothing extra to do with Aikau. I have set the family element to “ECM Stuff” so this Web Script will show up under this family on the
http://localhost:8080/share/service/index page:




helloworld-page.get.html.ftl:

<@processJsonModel />

The FreeMarker template is really simple, just execute the processJsonModel
marcro so whatever widgets have been defined in the controller are processed and converted into JavaScript, CSS, HTML and i18n resources and then include as content on the page.

helloworld-page.get.js:

model.jsonModel = {

   widgets: [

       {

           id: "SET_PAGE_TITLE",

           name: "alfresco/header/SetTitle",

           config: {

               title: "This is a Hello World page!",

           }

       },

       {

           id: "MY_HORIZONTAL_WIDGET_LAYOUT",

           name: "alfresco/layout/HorizontalWidgets",

           config: {

               widgetWidth: 50,

               widgets: [

                   {

                       name: "alfresco/logo/Logo",

                       config: {

                           logoClasses: "alfresco-logo-only"

                       }

                   },

                   {

                       name: "alfresco/logo/Logo",

                       config: {

                           logoClasses: "surf-logo-large"

                       }

                   }

              ]

           }

       }

       ]

};


The controller just needs to define one variable called
jsonModel, which should contain one property called widgets, which is an array of widgets where each one can be a nested structure of widgets. In this example we got a page that will have two widgets, one page title widget and one layout widget. The layout widget also has two nested Alfresco logo widgets. Note the different logoClasses configurations.

When a widget is added to the JSON model you specify three properties for it:

Note that you can only nest widgets if your parent widget mixin a module that extends alfresco/core/CoreWidgetProcessing, such as the alfresco/core/ProcessWidgets module. This is what the layout widgets do, but not for example the alfresco/header/SetTitle widget. If you were to add the widgets property to the configuration for the SetTitle widget, it would simply be ignored.

The configuration that we can use for each widget is best found out/discovered by looking at the source code for each Widget class: https://github.com/Alfresco/Aikau/tree/master/aikau/src/main/resources/alfresco. Definitely have a read through the documentation for the widgets you are using. You will find out more about the available configuration for each widget, specifically for the layout widget used in this example. 

The above Hello World page will look like this using the dp page:

You might have noticed that the Page Title is not showing up when using the dp page. If you read the documentation for the alfresco/header/SetTitle widget, it will tell you that it is intended to be used with a Hybrid page. To bring in the Share header, with the title and footer, use the hdp page:

The Alfresco Share Header is one of the components of Share that has been converted to Aikau and the title is set via the alfresco/header/Title widget. By using the alfresco/header/SetTitle widget we can update the title after it has been set by the alfresco/header/Title widget.

Aikau Page Showing and Hiding Existing Widgets

In the next page example we will have a look at how you can control when a widget is displayed or not. This is done with a configuration property called visibilityConfig. This property should point to a JavaScript object with information about initial visibility and rules for when the widget should be visible or not. Here is an example visibilityConfig configuration:

var visibilityConfig = {

   initialValue: false,

   rules: [

       {

           topic: "SHOW_SUB_PROJECTS_DROPDOWN_TOPIC",

           attribute: "showSubProjects",

           is: ["SHOW_SUB_PROJECTS"],

           isNot: ["DONT_SHOW_SUB_PROJECTS"]

       }

   ]

};

The different properties have the following meaning:

Aikau widgets are very decoupled by nature and they communicate with each other via asynchronous events. This makes it possible to easily add new Widgets and have them do stuff based on existing events. This is also very scalable and maintainable. In the following example we will extend the previous Hello World example with two new property link widgets. When either one of these property links is clicked, an event will be sent via a configured topic to both of the logo widgets. The event will contain information about which one of the logo widgets we want to show and which one that should be hidden. It will also control which one of the property link widgets that should be visible.

As you can imagine, when you start putting together your Aikau page with a bit more widgets and logic the JSON Model can become quite big and difficult to overview. One way of getting around this is to define each widget that you intend to use in the page as its own JavaScript object. We will also see how this can look like in this example.

Continue adding the following files to the alfresco/tomcat/shared/classes/alfresco/web-extension/site-webscripts/aikau-pages directory and then just refresh the Web Scripts from the http://localhost:8080/share/service/index page. No Tomcat restart is needed. Start with the descriptor:

helloworld-page2.get.desc.xml:

<webscript>

   <shortname>Hello World Aikau Page 2</shortname>

   <description>Hello World Aikau page using out of the box Widgets to create upload and association setup</description>

   <family>ECM Stuff</family>

   <url>/ecmstuff/helloworld-page2</url>

</webscript>

The Web Script descriptor is pretty much the same as for the first example, we just changed the URL a bit to differ it from the other one. The template is the same as before:

helloworld-page2.get.html.ftl:

<@processJsonModel />

And here is the controller which starts out by defining two visibility configurations that we will use for the two property link widgets and for the two logo widgets. Note how we define some “constants” in the beginning (nothing is ever constant but we can denote the variables which should not be changed by using uppercase) to be used in the visibility configuration. The two visibility configurations state when the next logo to display is Alfresco and when the next logo to display is Surf:

helloworld-page2.get.js:

var TOGGLE_LOGO_TOPIC_NAME = "TOGGLE_LOGO_TOPIC";

var SHOW_ATTRIBUTE_NAME = "show";

var SHOW_ATTRIBUTE_ALFRESCO_VALUE = "SHOW_ALFRESCO_LOGO";

var SHOW_ATTRIBUTE_SURF_VALUE = "SHOW_SURF_LOGO";

var showAlfrescoLogoNextVisibilityConfig = {

   initialValue: false,

   rules: [

       {

           topic: TOGGLE_LOGO_TOPIC_NAME,

           attribute: SHOW_ATTRIBUTE_NAME,

           is: [SHOW_ATTRIBUTE_SURF_VALUE],

           isNot: [SHOW_ATTRIBUTE_ALFRESCO_VALUE]

       }

   ]

};

var showSurfLogoNextVisibilityConfig = {

   initialValue: true,

   rules: [

       {

           topic: TOGGLE_LOGO_TOPIC_NAME,

           attribute: SHOW_ATTRIBUTE_NAME,

           is: [SHOW_ATTRIBUTE_ALFRESCO_VALUE],

           isNot: [SHOW_ATTRIBUTE_SURF_VALUE]

       }

   ]

};

var showAlfrescoLogoLinkWidget = {

   name: "alfresco/renderers/PropertyLink",

   config: {

       visibilityConfig: showAlfrescoLogoNextVisibilityConfig,

       currentItem: {

           label: "Show Alfresco Logo Instead"

       },

       propertyToRender: "label",

       useCurrentItemAsPayload: false,

       publishTopic: TOGGLE_LOGO_TOPIC_NAME,

       publishPayloadType: "CONFIGURED",

       publishPayload: {

           show: SHOW_ATTRIBUTE_ALFRESCO_VALUE

       }

   }

};

var showSurfLogoLinkWidget = {

   name: "alfresco/renderers/PropertyLink",

   config: {

       visibilityConfig: showSurfLogoNextVisibilityConfig,

       currentItem: {

           label: "Show Surf Logo Instead"

       },

       propertyToRender: "label",

       useCurrentItemAsPayload: false,

       publishTopic: TOGGLE_LOGO_TOPIC_NAME,

       publishPayloadType: "CONFIGURED",

       publishPayload: {

           show: SHOW_ATTRIBUTE_SURF_VALUE

       }

   }

};

var alfrescoLogoWidget = {

   name: "alfresco/logo/Logo",

   config: {

       logoClasses: "alfresco-logo-only",

       visibilityConfig: showSurfLogoNextVisibilityConfig

   }

};

var surfLogoWidget = {

   name: "alfresco/logo/Logo",

   config: {

       logoClasses: "surf-logo-large",

       visibilityConfig: showAlfrescoLogoNextVisibilityConfig

   }

};

model.jsonModel = {

   widgets: [

       {

           id: "MY_HORIZONTAL_WIDGET_LAYOUT",

           name: "alfresco/layout/HorizontalWidgets",

           config: {

               widgetWidth: 25,

               widgets: [ alfrescoLogoWidget, showAlfrescoLogoLinkWidget,

                          showSurfLogoLinkWidget, surfLogoWidget ]

           }

       }

   ]

};

The new widgets here are the alfresco/renderers/PropertyLink widgets. They take the following configuration:

The logo widget definitions are similar to what we did in the first example, we just added visibility configurations. The jsonModel configuration is now quite clear and, as we can see, we add all four widgets to be displayed in the horizontal layout widget. However, only the Alfresco logo and the Show Surf logo property link will be displayed when we first access the page. This is because the visibility configuration for the Surf logo and Show Alfresco logo property link has an initialValue set to false.

If you load the page, you should see something like this:

Now, click on the Show Surf Logo Instead link and you should see:

You can go back and forth between the logos by clicking on the links. So this example has taught us about how to control when widgets should be displayed via visibility configurations, it has touched on the event model that is used to send information between widgets in a decoupled way, and finally showed us how we could structure the code a bit to get it more reusable and clear.

Aikau Page using Custom Widgets

Now when you have started to get the hang of Aikau development you are probably wondering how one would go about creating a custom widget and using it in an Aikau page. Let’s continue on the Hello World theme and create our own Hello World widget in a new Hello World page, which will be located in the same place as the other Hello World pages.

Add the following files to the alfresco/tomcat/shared/classes/alfresco/web-extension/site-webscripts/aikau-pages directory:

helloworld-page3.get.desc.xml:

<webscript>

   <shortname>Hello World Aikau Page 3</shortname>

   <description>Hello World Aikau page using a custom Hello World Widget</description>

   <family>ECM Stuff</family>

   <url>/ecmstuff/helloworld-page3</url>

</webscript>

helloworld-page3.get.html.ftl:

<@processJsonModel group="share"/>

helloworld-page3.get.js:

model.jsonModel = {

   widgets: [{

      id: "SET_PAGE_TITLE",

      name: "alfresco/header/SetTitle",

      config: {

          title: "This is a Hello World page!"

      }

   },

   {

      id: "MY_HORIZONTAL_WIDGET_LAYOUT",

      name: "alfresco/layout/HorizontalWidgets",

      config: {

          widgetWidth: 50,

          widgets: [

              {

                  name: "alfresco/logo/Logo",

                  config: {

                      logoClasses: "alfresco-logo-only"

                  }

              },

              {

                name: "ecmstuff/widgets/HelloWorldWidget"

              }

          ]

      }

   }]

};

Ok, so what we got here is a page that will have two widgets, one title widget and one layout widget. The layout widget also has two nested widgets, an Alfresco logo and the Hello World widget. The Hello World widget is our custom widget so we need to implement it.  

When implementing this widget we can continue without a build project for simplicity and speed. Add the following file to the alfresco/tomcat/webapps/share/js/ecmstuff/widgets directory (you will have to create some of the directories, and yes I know, never add stuff in an exploded webapp directory, this is just a way you can use when playing around with Aikau).

The Hello World widget implementation starts of with the JavaScript file that defines the new Dojo module that should represent the widget:

HelloWorldWidget.js:

define(["dojo/_base/declare",

       "dijit/_WidgetBase",

       "alfresco/core/Core",

       "dijit/_TemplatedMixin",

       "dojo/text!./HelloWorldWidget.html"],

   function(declare, _Widget, Core, _Templated, template) {

       return declare([_Widget, Core, _Templated], {

          templateString: template,

   i18nScope: "ECMStuff",

          i18nRequirements: [ {i18nFile: "./HelloWorldWidget.properties"} ],

          cssRequirements: [{cssFile:"./HelloWorldWidget.css"}],

    postMixInProperties: function  

         mycompany_widgets_HelloWorldWidget__postMixInProperties() {

            this.greeting = this.message("helloworld.label");

          }

       });

   }

);

You will recognize most of the stuff here as it is very similar to the way we implemented the BookWidget.js earlier on. Creating an Aikau Widget is pretty much the same thing as creating a Dojo/Dijit widget. If you don’t remember what the dojo/_base/declare, dijit/_WidgetBase, dijit/_TemplateMixin, or dojo/text! modules does, then please go back in the article and read through the explanations again. We will now focus on the Alfresco Aikau specifics of this widget implementation.

There are a couple of properties that are read by the Spring Surf dependency rules when the page is assembled, and when the styling and localization resources should be included in the page (see the spring-surf-services-context.xml file located in the  /projects/surf/spring-surf/spring-surf/src/main/resources/org/springframework/extensions/surf directory of the Alfresco source code).

The first property is called i18nRequirements and you use it in your widget to specify what localization resource files that should be read. Each file is treated as a ResourceBundle properties file and used to generate a JavaScript object with the properties from the file. This JavaScript object will be injected into the widget under a specific scope defined by the i18nScope property. We are setting the scope to ECMStuff to protect our helloworld.label property from clashing with any other widget that happen to bring in a resource file containing a property with the same name. If the scope is not specified then it is set to default. The path to the resource files (i.e. the i18nFile value) should be relative to the JavaScript file (i.e. relative to the location of the HelloWorldWidget.js file in this case).

The other property is called cssRequirements and you use it to load the CSS files that are needed by the widget for styling. You can specify multiple CSS files as follows:

cssRequirements: [

  {cssFile:"./css/Chart.css"},

  {cssFile:"/js/lib/ctools/tipsy.css"}

],

The CSS file location set by the cssFile property should also be specified relative to the location of the widget JavaScript file. When specifying a CSS file we can also configure for what media type it is valid:

cssRequirements: [{cssFile:"./css/Logo.css", mediaType:"screen"}],

For more information about media types see: http://www.w3schools.com/css/css_mediatypes.asp 

The alfresco/core/Core module is the core Alfresco class that all Aikau widgets should extend or mixin, it provides core functionality such as logging, event handling, and i18n resource management. More specifically it provides the message() function used in above widget definition.

The postMixInProperties function is part of the Dijit Widget lifecycle and when we provide this function for our widget, it will be invoked before rendering occurs, and before any DOM nodes are created. So this is the function to use if we want to add or change the widget instance’s properties before it is rendered.

Our widget will use some custom styling as specified by the cssRequirements property, so let’s add the following file in the same place as the Widget JavaScript file:

HelloWorldWidget.css:

.helloworld-widget {

   border: 1px #000000 solid;

   padding: 1em;

   width: 100px;

}

And then add the i18n resource file as specified by the i18nRequirements property:

HelloWorldWidget.properties:

helloworld.label=Hello World!

The last file we need to create is the markup template for the widget, it is loaded into the widget via the dojo/text! AMD plugin:

 

HelloWorldWidget.html:

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

If you remember from the implementation of the book widget earlier on, this template uses a substitution variable that is set once before the widget is rendered. If you wanted to be able to change the greeting, via for example a button, then you would have to bring in the data-dojo-attach-event and data-dojo-attach-point attributes as we talked about when developing the book widget.

Now, when we used this Hello World widget in the Aikau page definition, we referred to it in the following way:

name: "ecmstuff/widgets/HelloWorldWidget"

If we don’t do anything more now and try and access the page we will get an error message looking something like this:

What this basically means is that the Dojo AMD loader cannot find the ecmstuff package. Looking at the package definition in the Aikau page we can see it looks like this:

var dojoConfig = {

  baseUrl: "\/share/res/",

  tlmSiblingOfDojo: false,

  async: true,

  parseOnLoad: false,

  packages: [

     { name: "alfresco", location: "js/aikau/1.0.8/alfresco"},

     { name: "cm", location: "js/lib/code-mirror"},

     { name: "service", location: "../service"},

     { name: "jqueryui", location: "js/lib/jquery-ui-1.11.1", main: "jquery-ui.min"},

     { name: "surf", location: "js/surf"},

     { name: "dojo", location: "js/lib/dojo-1.9.0/dojo"},

     { name: "dijit", location: "js/lib/dojo-1.9.0/dijit"},

     { name: "jquery", location: "js/lib/jquery-1.11.1", main: "jquery-1.11.1.min"},

     { name: "dojox", location: "js/lib/dojo-1.9.0/dojox"}

  ]

};

So what we are missing is a package definition looking like this:

    { name: "ecmstuff", location: "js/ecmstuff"}

An ecmstuff package definition like the above tells Dojo where it can load anything starting with the package path ecmstuff. So when we include the ecmstuff/widgets/HelloWorldWidget module in our page’s jsonModel, Dojo will go and grab the tomcat/webapps/share/js/ecmstuff/widgets/HelloWorldWidget.js file. So how can we configure this, it’s done via a Surf extension module configuration. Add a file called dojo-packages-extension.xml to the tomcat/shared/classes/alfresco/web-extension/site-data/extensions directory. The content should look like this:

<extension>

 <modules>

   <module>

     <id>ECM Stuff Tutorial - Dojo Packages</id>

     <version>1.0</version>

     <auto-deploy>true</auto-deploy>

     <configurations>

       <config evaluator="string-compare" condition="WebFramework" replace="false">

         <web-framework>

           <dojo-pages>

             <packages>

               <package name="ecmstuff" location="js/ecmstuff"/>

             </packages>

           </dojo-pages>

         </web-framework>

       </config>

     </configurations>

   </module>

 </modules>

</extension>

This completes the implementation of our Aikau page with a custom widget, it should look like this if it is all working properly (note you will need a server restart after adding the surf extension module with package info - $ ./alfresco.sh restart tomcat):

There is an important concept to emphasize here, Aikau widgets extend the Dijit widget functionality and allows you to encapsulate both JS, HTML, CSS, and i18n code together in one component. This is going to be quite powerful in the future where you could see the community developing loads of reusable Aikau widgets and services. And ideally these components should be possible to plug-in during runtime.

Aikau Page Using a Form and a Custom Service

In this example we are going to create an Aikau page where you can select one or more files from different folders and then send an email with links to these files. This example will bring in a new type of component that does not have a user interface, and it is referred to as a service. You can compare these services to the Public services in the Java Alfresco Foundation API.

Introduction

When this example is completed, the user interface for it will look something like this:

This page uses a number of widgets. At the top we got the Set Title widget that we used before to set the title of the page, under it is a form widget with four internal widgets. At the top of the form is a Document Picker widget that will be used to pick one or more documents from the repository. When you click the Add button, a dialog opens up where you can select files:

Under the picker widget are two Text Box widgets that are used to collect the email address we want to send the email to and what the subject of the email should be. The text for the body of the email will be specified via the last widget, which is a Text Area.

Clicking the Send Email button will submit the form data as payload for an event, which will be picked up by a custom service widget that will deal with sending the email. The service widget will not actually send the email but instead invoke a Repository Web Script that will do the job for us. The result is an email looking like this:

Ok, so let’s get started.

Implementing the Send Email with Files Aikau Page

Continue adding the following files to the alfresco/tomcat/shared/classes/alfresco/web-extension/site-webscripts/aikau-pages directory and then just refresh the Web Scripts from the http://localhost:8080/share/service/index page. No Tomcat restart is needed. Start with the descriptor:

sendemailwithfiles-page.get.desc.xml:

<webscript>

   <shortname>Sending Email with File Links Aikau Page</shortname>

   <description>Aikau page that can be used to select files from the repo and then send them as links in an email</description>

   <family>ECM Stuff</family>

   <url>/ecmstuff/sendemailwithfiles</url>

</webscript>

The Web Script descriptor is straightforward and the template is the same as before:

sendemailwithfiles-page.get.html.ftl:

<@processJsonModel />

As you might have expected, the controller is going to contain loads of widget definitions and model specifications. Let’s start by defining the Document Picker widget:

sendemailwithfiles-page.get.js:

var documentPickerWidget = {

   name: "alfresco/forms/controls/DocumentPicker",

   config: {

       label: "Select files to email",

       name: "selected_file_nodeRefs"

   }

};

Here we are using a Document Picker widget from the Aikau form control widget library. To see what widgets are available to use in your form go to https://github.com/Alfresco/Aikau/tree/master/aikau/src/main/resources/alfresco/forms/controls.

Note that some form control widgets, such as DojoCheckBox.js, have been deprecated, their name start with Dojo...

It’s a good idea to read through the source code for the widget you are going to use. This will give you information around what functions are available and what properties are available. For example, a lot of the widgets like pickers and dialogs will have internal widgets that build the look and feel of the picker or dialog. Some day you might want to override this. For the Document Picker (https://github.com/Alfresco/Aikau/blob/master/aikau/src/main/resources/alfresco/forms/controls/DocumentPicker.js) look up the widgetsForControl property and you will see what widgets it uses to let you pick files:

 widgetsForControl: [

  {

     name: "alfresco/layout/VerticalWidgets",

     assignTo: "verticalWidgets",

     config: {

        widgets: [

           {

              name: "alfresco/pickers/PickedItems",

              assignTo: "pickedItemsWidget",

              config: {

                 pubSubScope: "{itemSelectionPubSubScope}"

              }

           },

           {

              name: "alfresco/buttons/AlfButton",

              assignTo: "formDialogButton",

              config: {

                 label: "picker.add.label",

                 publishTopic: "ALF_CREATE_DIALOG_REQUEST",

                 publishPayload: {

                    dialogTitle: "picker.select.title",

                    handleOverflow: false,

                    widgetsContent: [

                       {

                          name: "alfresco/pickers/Picker"

                       }

                    ],

                    widgetsButtons: [

                       {

                          name: "alfresco/buttons/AlfButton",

                          config: {

                             label: "picker.ok.label",

                             publishTopic: "ALF_ITEMS_SELECTED",

                             pubSubScope: "{itemSelectionPubSubScope}"

                          }

                       },

                       {

                          name: "alfresco/buttons/AlfButton",

                          config: {

                             label: "picker.cancel.label",

                             publishTopic: "NO_OP"

                          }

                       }

                    ]

                 },

                 publishGlobal: true

              }

           },

           {

              name: "alfresco/buttons/AlfButton",

              config: {

                 label: "picker.removeAll.label",

                 additionalCssClasses: "cancelButton",

                 publishTopic: "ALF_ITEMS_SELECTED",

                 publishPayload: {

                    pickedItems: []

                 },

                 pubSubScope: "{itemSelectionPubSubScope}"

              }

           }

        ]

     }

  }

]

We don’t need to change or override anything here but it gives you an idea how easy it would be to change the behaviour of the Document Picker. If we go back to our picker widget config it specifies a name property as follows:

 name: "selected_file_nodeRefs"

This field is important as it will contain an array of node references for the files that we selected. The name property is the same thing as the name attribute of an HTML form control such as an input field. The label config property represent the label that will be displayed for the picker in the UI.

Next widget definition is for the email address field and looks like this:

var emailAddressTextBoxWidget = {

  name: "alfresco/forms/controls/TextBox",

  config: {

     fieldId: "EMAIL_ADDRESS",

     name: "email_address",

     label: "Send-To Email",

     description: "Send the selected files to this e-mail address",

     placeHolder: "name@address.com",

     requirementConfig: {

        initialValue: true

     },

     validationConfig: [

        {

           validation: "regex",

           regex: "^([0-9a-zA-Z]([-.w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-w]*[0-9a-zA-Z].)+[a-zA-Z]{2,9})$",

           errorMessage: "A valid E-mail address is required"

        }

     ]

  }

};

This text box widget demonstrates a couple of new things. For example, we can use the requirementConfig property to specify if this field is mandatory or not. It is mandatory in our case and you will not be able to submit the form until you have specified a value for this field. The placeHolder field can be used to display some grayed out text in the field as indication of what kind of value is expected.

The validationConfig property is used to control what is an accepted/valid value for this field, in our case it will match whatever we type against a regular expression that will only match properly formatted email addresses. The name field is important here also as it will contain the specified email address when the form is submitted.

The controls for the email subject and body text looks like this:

var emailSubjectTextBoxWidget = {

  name: "alfresco/forms/controls/TextBox",

  config: {

     fieldId: "EMAIL_SUBJECT",

     name: "email_subject",

     label: "Subject",

     description: "This is the subject text for the email",

     placeHolder: "Files from Alfresco Repo",

     requirementConfig: {

        initialValue: true

     }

  }

};

var emailBodyTextAreaWidget = {

  name: "alfresco/forms/controls/TextArea",

  config: {

     fieldId: "EMAIL_BODY_TEXT",

     name: "email_body_text",

     label: "Email Body Text",

     description: "This the text that should go into the email body",

     requirementConfig: {

        initialValue: true

     }

  }

};

The new thing here is the use of the TextArea widget to represent the HTML textarea control.

If you have a look at the source code for each one of these form widgets, you will notice that all of them extend the alfresco/forms/controls/BaseFormControl control. Looking at the source code (https://github.com/Alfresco/Aikau/blob/master/aikau/src/main/resources/alfresco/forms/controls/BaseFormControl.js) for this base form control we can see that it defines most of the properties we have used in the above widget configurations, such as fieldId, label, description, and name. Take a look at this class as it will have loads of information around these and other properties.

We now got the four widgets that should go onto our form so let’s define it:

var formWidget = {

   name: "alfresco/forms/Form",

   config: {

       showOkButton: true,

       okButtonLabel: "Send Email",

       showCancelButton: false,

       cancelButtonLabel: "Not used...",

       okButtonPublishTopic: "SEND_EMAIL_TOPIC",

       okButtonPublishGlobal: true,

       widgets: [ documentPickerWidget, emailAddressTextBoxWidget,

               emailSubjectTextBoxWidget, emailBodyTextAreaWidget ]

   }

};

To define a new form we use the alfresco/forms/Form widget and supply a list of widgets that should make up the form. Most of the configuration properties are self explanatory. However, the okButtonPublishTopic and okButtonPublishGlobal properties are interesting as they control what even topic we should publish on when the form is submitted, and what publishing scope should be applied, global in this case (more on scope later).  

So when we click the Send Email button, an event will be published on the SEND_EMAIL_TOPIC event queue with a payload built up of all the name property/value pairs from each form control widget. Basically, the event payload will look something like this:

{

   email_address: "martin.bergljung@alfresco.com",

   email_subject: "Project Information",

   email_body_text: "Hi, Can you have a look at the following project files:",

   selected_file_nodeRefs: [

       "workspace://SpacesStore/b2e2549a-44b5-4142-900d-bd7dcee3a525",

       "workspace://SpacesStore/a0a4eeea-08fd-475d-bc7e-de91510b5cfd" ]

}

This completes our form but we still got stuff to do. The form needs to actually go into a layout widget so we can display it with some margins for better look and feel. Here is the layout widget definition:

var layoutWidget = {

  name: "alfresco/layout/HorizontalWidgets",

  config: {

     "widgetMarginLeft": 25,

     "widgetMarginRight": 25,

     "widgets": [ formWidget ]

  }

};

The layout widget will just contain the form widget so whether you use a horizontal or vertical layout does not matter. Last step to complete our Web Script controller is to set up the jsonModel, the model will be quite clean now when we have specified each widget separately beforehand:

model.jsonModel = {

   services: [

       "alfresco/dialogs/AlfDialogService",

       "alfresco/services/DocumentService",

       "alfresco/services/SiteService",

       "ecmstuff/services/EmailService"

   ],

   widgets: [

       {

           id: "SET_PAGE_TITLE",

           name: "alfresco/header/SetTitle",

           config: {

              title: "Pick files and send as links in email",

           }

       },

       layoutWidget

    ]

};

This is where we bring in all the new services that we talked about in the introduction of this example. A new property called services is used to keep an array of all services used by our widgets. The Document Picker uses the AlfDialogService, DocumentService, and SiteService so we need to bring in all of them. The last service, called the EmailService, is a custom service that we have to write, it is supposed to invoke a custom Web Script when it detects an event sent on the SEND_EMAIL_TOPIC topic.

Our new Aikau page is now complete but it will not work as we have not implemented the EmailService. If you comment out the EmailService, you should be able to select files, fill in all the email related information, and click the Send Email button. However, nothing will happen as the event is just posted but there is no component grabbing it (i.e. subscribing to it).

Implementing the Email Service component

When implementing this component we can continue without a build project for simplicity and speed. Add the following file to the alfresco/tomcat/webapps/share/js/ecmstuff/services directory (you will have to create some of the directories, and yes I know, never add stuff in an exploded webapp directory, use a build project instead that builds the WAR).

EmailService.js:

define([

   "dojo/_base/declare",

   "dojo/_base/lang",

   "alfresco/core/Core",

   "alfresco/core/CoreXhr",

   "service/constants/Default"],

       function(declare, lang, AlfCore, AlfCoreXhr, AlfConstants) {

        return declare([AlfCore, AlfCoreXhr], {.....

So this starts as usual when defining a new Dojo AMD module, with an array of other modules/classes we need, in this case they are as follows:

We then declare the new EmailService class and have it extend the Alfresco core class (i.e. declare([AlfCore) . We also mixin the Alfresco CoreXhr class to be able to make AJAX calls. The next step in our service class is to define a constructor that sets up an event subscriptions for SEND_EMAIL_TOPIC:

constructor: function ecmstuff_services_EmailService__constructor(args) {

   lang.mixin(this, args);

   this.alfSubscribe("SEND_EMAIL_TOPIC", lang.hitch(this, "onSendEmail"));

},

The constructor starts by mixin of any arguments passed to it and then it uses the alfSubscribe function (from alfresco/core/Core)  to set up a subscription to events posted on the SEND_EMAIL_TOPIC and when an event is detected it will call the function onSendEmail with the payload:

onSendEmail: function ecmstuff_services_EmailService__onSendEmail(payload) {

   var ajaxCallPostData = {

       toAddress: payload.email_address,

       subject: payload.email_subject,

       bodyText: payload.email_body_text,

       nodeRefs: payload.selected_file_nodeRefs

   };

 

   var ajaxCallConfig = {

       url: AlfConstants.PROXY_URI + "/ecmstuff/sendemail",

       method: "POST",

       data: ajaxCallPostData,

       successCallback: this.onSendEmailSuccess,

       failureCallback: this.onSendEmailFailure,

       callbackScope: this

   };

 

   this.serviceXhr(ajaxCallConfig);

},

The payload should be familiar, it matches the form submission data we talked about previously. What we do here is just setup a different POST data structure with different property names that the Web Script expects. We then configure what Web Script we want to call with that data. In our case it will be a new custom Web Script registered on the /ecmstuff/sendemail URL. We also define two methods to be called on successful invocation of the Web Script and in case of failure:

onSendEmailSuccess: function

alfresco_services_ActionService__onSendEmailSuccess(

                                           response, originalRequestConfig) {

   // TBD

},

onSendEmailFailure: function

alfresco_services_ActionService__onSendEmailSuccess(

                                           response, originalRequestConfig) {

// TBD

}

});

});

This completes the first version of the EmailService. We will add logging and notification messages to the above methods in a later section below. However, we can not run anything yet because there is no Web Script registered on the /ecmstuff/sendemail URL. This is what we are going to fix next.

Note. The ecmstuff/services/EmailService uses the same package ecmstuff that we have defined for Dojo before when implementing the HelloWorld widget, so we don’t need to define anything extra to do with packages.

Implementing the Send Email Repo Web Script

This is going to be a simple standard Repo Web Script that will compile an email based on the information we collected via the form. This article is not about explaining Web Scripts, so I will just present the code for it without too much descriptions. In the tomcat/shared/classes/alfresco/extension/templates/webscripts directory add the following files and content:

sendemail.post.desc.xml:

<webscript>

   <shortname>Sending Email</shortname>

   <description>Sending Email Repo Web Script, POST body should include JSON such as:

  {

   "toAddress": "someone@dummy.com",

   "subject": "Files for review",

   "bodyText": "This is a bunch of files you should have a look at",

   "nodeRefs": [ "{nodeRef1}",  "{nodeRef2}" ]

  }

     

   POST example:

  curl -v -u admin:admin -d @sample.json -H 'Content-Type:application/json' http://localhost:8080/alfresco/service/ecmstuff/sendemail

   </description>

   <family>ECM Stuff</family>

   <url>/ecmstuff/sendemail</url>

   <authentication>user</authentication>

   <transaction>required</transaction>

   <format default="html">any</format>

</webscript>

The Web Script descriptor is straightforward and the template is as follows:

sendemail.post.html.ftl:

${toAddress}

${subject}

${bodyText}

${nodeRefArray}

The template is just displaying some property values that can be used when testing the Web Script. Here is the controller:

sendemail.post.json.js:

// Get the POSTed JSON data

var toAddress = json.get("toAddress");

var subject = json.get("subject");

var bodyTextStart = json.get("bodyText");

var nodeRefArray = json.getJSONArray("nodeRefs"); // org.json.JSONArray

// Compile the body of the email with links to each node

var bodyText = bodyTextStart + "\n\n";

var arrayLength = nodeRefArray.length();

for (var i = 0; i < arrayLength; i++) {

 var aNode = search.findNode(nodeRefArray.getString(i));

 bodyText += aNode.name + ": " +  "http://localhost:8080/alfresco/api/-default-/public/cmis/versions/1.1/browser/root?objectId=" + aNode.id + "\n";  

}

// Send the email

var mail = actions.create("mail");

mail.parameters.to = toAddress;

mail.parameters.from = "local@alfresco";

mail.parameters.subject = subject;

mail.parameters.text = bodyText;

mail.execute(companyhome);

// Put some stuff in the model for the HTML template

model.toAddress = toAddress;

model.subject = subject;

model.bodyText = bodyText;

model.nodeRefArray = nodeRefArray;

You need to refresh the Alfresco Repo Web Scripts for it to be registered and usable: http://localhost:8080/alfresco/service/index 

Now this Web Script is sending an email but Alfresco is not configured with an SMTP server, so it is not going to work. What you can do is this, download https://nilhcem.github.io/FakeSMTP/ and start it up on port 2525. Then configure Alfresco to send emails to a localhost:2525, open tomcat/shared/classes/alfresco-global.properties and set:

mail.host=localhost

mail.port=2525

Then restart Alfresco Tomcat. You should be able to run through the example now and get emails popping up in the FakeSMTP client as follows:

Displaying a Message for Successful or Failed Operation

Now, it is quite common for a lot of the operations in the Document Library to display a success or failure message after an operation has been completed. We might want to do the same with our EmailService. Remember the empty success and failure methods. Let’s have them display messages as follows:

define(["dojo/_base/declare",

       "dojo/_base/lang",

       "alfresco/core/Core",

       "alfresco/core/CoreXhr",

       "service/constants/Default",

       "alfresco/core/NotificationUtils"],

       function(declare, lang, AlfCore, AlfCoreXhr, AlfConstants,

                                           AlfNotificationUtils) {

           return declare([AlfCore, AlfCoreXhr, AlfNotificationUtils], {

           . . .    

   

             onSendEmailSuccess: function

                       alfresco_services_ActionService__onSendEmailSuccess(

                               response, originalRequestConfig) {

               var msg = "Successfully sent email to " +

                            originalRequestConfig.data.toAddress;

               this.displayMessage(msg);

             },

           

             onSendEmailFailure: function

                       alfresco_services_ActionService__onSendEmailSuccess(

                               response, originalRequestConfig) {

               var msg = "Failure sending email to " +

                            originalRequestConfig.data.toAddress;    

               this.displayMessage(msg);

             }

To display a message, we can use the displayMessage function in the alfresco/core/NotificationUtils module. What we need to do is just mixin the module and we are ready to go. To test this, refresh the Web Scripts and clear the Dependency Cache from the http://localhost:8080/share/page/index page.

Turning on Console Logging

The next thing you would probably want to do is to stick in some logging in your components. Let’s add some logging statements to the EmailService success and failure methods:

onSendEmailSuccess: function

       alfresco_services_ActionService__onSendEmailSuccess(

               response, originalRequestConfig) {

   var msg = "Successfully sent email to " +

                originalRequestConfig.data.toAddress;

   this.alfLog("log", msg, response, originalRequestConfig);

   this.displayMessage(msg);

},

onSendEmailFailure: function

       alfresco_services_ActionService__onSendEmailSuccess(

               response, originalRequestConfig) {

   var msg = "Failure sending email to " +

                originalRequestConfig.data.toAddress;  

   this.alfLog("log", msg, response, originalRequestConfig);

   this.displayMessage(msg);

}

The alfLog function is part of the alfresco/core/Core module so we are ready to go, almost. The alfLog function is not doing the actual logging, it just publishes an event requesting someone that there should be some logging done. This someone is the LoggingService. Add it to the page’s jsonModel as follows:

model.jsonModel = {

   services: [

     "alfresco/dialogs/AlfDialogService",

     "alfresco/services/DocumentService",

     "alfresco/services/SiteService",

     "ecmstuff/services/EmailService",

     {

         name: "alfresco/services/LoggingService",

         config: {

         loggingPreferences: {

             enabled: true,

             all: true

          }

          }

     }

   ],

   widgets: [

Here we can also see an example of how a service module can be configured while it is being defined as a dependency. Before the logging is going to work, we also need to turn on client debug logging in the Share web app. So stop the Alfresco Tomcat server and add the following to the share-config-custom.xml file located in the tomcat/shared/classes/alfresco/web-extension directory:

<alfresco-config>

<!-- Global config section -->

<config replace="true">

   <flags>

       <!--

          Developer debugging setting to turn on DEBUG mode for client scripts

in the browser

       -->

       <client-debug>true</client-debug>

       <!--

          LOGGING can always be toggled at runtime when in DEBUG mode (Ctrl,

Ctrl, Shift, Shift).

          This flag automatically activates logging on page load.

       -->

       <client-debug-autologging>false</client-debug-autologging>

   </flags>

</config>

 

Now restart the server and then re-run the example. You should see something like the following in the FireBug console:

Note, make sure you have debug logging turned on, otherwise you will only see Pub/Sub logs:

Turning on Event Logging

Now that you got the console debug logging going, it would be cool if we had a way to intercept all the event communication that is going on. Could be useful if, for example, the form submission did not work as we expected. There are PUBLISH/SUBSCRIBE logging available in the console window as we saw above, but we can also include them in the page without having to use FireBug.

For this we can use a widget called alfresco/logging/SubscriptionLog. This widget will display a widget communication log. Add it to the jsonModel for the page as follows:

model.jsonModel = {

   services: [

     "alfresco/dialogs/AlfDialogService",

     "alfresco/services/DocumentService",

     "alfresco/services/SiteService",

     "ecmstuff/services/EmailService",

     {

       name: "alfresco/services/LoggingService",

       config: {

         loggingPreferences: {

           enabled: true,

           all: true

         }

       }

     }

   ],

   widgets: [

    {

          id: "SET_PAGE_TITLE",

          name: "alfresco/header/SetTitle",

          config: {

              title: "Pick files and send as links in email",

          }

       },

   layoutWidget,

   {

     name: "alfresco/logging/SubscriptionLog"

   }

  ]

};

Now refresh the Web Scripts and then refresh the Aikau page, you should see something like this at the bottom of the page:

So as you can see there are lots of SUBSCRIBE logs, and if you scroll down a bit you will for example be able to find a subscription log for the EmailService setting up subscription to the SEND_EMAIL_TOPIC.

SUBSCRIBE                SEND_EMAIL_TOPIC                undefined

There will also be loads of PUBLISH logs if you scroll down the page. If you now fill in the form and submit it, you should be able to see the PUBLISH log for the SEND_EMAIL_TOPIC:

This is quite nice, we can see exactly what payload is published and on what topic.

So as you might have guessed, this widget is just there for debugging purpose, you would not deliver the page with it enabled.

Aikau Page Using Lists

When you are building extensions for the Alfresco Share web application, you are sooner or later going to have requirements for functionality that includes lists. To get an idea of how to implement lists with Aikau, we are going to extend the Send Email page with an extra window that will contain a list of the emails that we have sent so far. The page will look something like this when we are finished:

When we implement the list, we will also do some more work around the page layout and have a look at some widgets that can be used to organize the page. As you might have expected, the content for the Sent Email list has to come from somewhere. We are going to update the Web Script that sends the emails to also store information about the sent emails in a JSON file. Then we will create a new Web Script that can be used by the list to retrieve the JSON with the information about the sent emails.

Updating the Send Email Repo Web Script to store Email Info

Open up the sendemail.post.json.js controller file and add the following at the end of it:

. . .

// Store the sent email info in the /Guest Home/sentEmails.json file

var fileNames = "";

for (var i = 0; i < arrayLength; i++) {

 var aNode = search.findNode(nodeRefArray.getString(i));

 fileNames += aNode.name + ",";  

}

var emailInfoObj =

  {

   'toAddress': toAddress,

   'subject': subject,

   'bodyText': bodyTextStart,

   'files': fileNames

  };

var sentEmailsFileName = "sentEmails.json";

var guestHomeFolder = companyhome.childByNamePath("Guest Home");

var sentEmailsFile = guestHomeFolder.childByNamePath(sentEmailsFileName);

if (sentEmailsFile == null) {

 sentEmailsFile = guestHomeFolder.createFile(sentEmailsFileName);

 sentEmailsFile.mimetype = "application/json";

} else {

 sentEmailsFile.content += ",\r\n";

}

sentEmailsFile.content += jsonUtils.toJSONString(emailInfoObj);

So this piece of code will look for a file called sentEmails.json in the /Company Home/Guest Home folder. If it is not found it will be created. The information about the email that was sent is compiled into a JSON object and appended to the file. So basically when we have sent a couple of emails we end up with a file looking something like this:

{

 "toAddress": "martin.bergljung@alfresco.com",

 "subject": "Have a look at this file",

 "bodyText": "Hi,\n\nCan you review this file and comment:",

 "files": "project-list.txt,"

},

{

 "toAddress": "gravitonian@gmail.com",

 "subject": "Have a look at these files",

 "bodyText": "Hi,\n\nCan you read through these files and add any comments:",

 "files": "project-list.txt,Project Objectives.ppt,"

}

This is basically the information we want to display in our list.

Creating a Repo Web Script to return Email Info

Now when we have update the sendemail.post.json.js to store the email information in the repository we can create another Web Script to return it as proper JSON. In the tomcat/shared/classes/alfresco/extension/templates/webscripts directory add the following files and content:

sentemails.get.desc.xml:

<webscript>

   <shortname>Get sent emails</shortname>

   <description>Returns info about sent emails, info is stored in /Guest Home/sentemails.json, response is in JSON such as:

    {

     emails:

      [

       {

        "toAddress": "someone@dummy.com",

        "subject": "Files for review",

        "bodyText": "This is a bunch of files you should have a look at",

        "files": "fileA.doc,  fileB.txt"

       },

       {

        "toAddress": "another@dummy.com",

        "subject": "A file for review",

        "bodyText": "This is a file you should have a look at",

        "files": "fileA.doc"

       }

     ]

    }

    GET example:

  curl -v -u admin:admin http://localhost:8080/alfresco/service/ecmstuff/sentemails

   </description>

   <family>ECM Stuff</family>

   <url>/ecmstuff/sentemails</url>

   <authentication>user</authentication>

   <transaction>required</transaction>

   <format default="json">any</format>

</webscript>

The Web Script descriptor is straightforward and the template that should return JSON is as follows:

.get.json.ftl:

{

 "emails":

  [

    ${emailsJson}

  ]

}

The emailsJson property used in the template is set in the controller as follows:

sentemails.get.js:

// Get the sent email info from the /Guest Home/sentEmails.json file

var sentEmailsFileName = "sentEmails.json";

var sentEmailsFile = companyhome.childByNamePath("Guest Home/" + sentEmailsFileName);

var emailInfo = "";

if (sentEmailsFile != null) {

 emailInfo = sentEmailsFile.content;

}

model.emailsJson = emailInfo;

We are now ready to start updating the Send Emails Aikau page with the list.

Updating the Send Email Aikau page with a List of Sent Emails

All the Repo Web Scripts are now there to support the list implementation that we want. Let’s start adding the necessary widgets in the sendemailswithfiles-page.get.js file:

var emailListWidget = {

name: "alfresco/lists/AlfList",

config: {

   loadDataPublishTopic: "ALF_CRUD_GET_ALL",

   loadDataPublishPayload: {

     url: "ecmstuff/sentemails"

   },

   itemsProperty: "emails",

   widgets: [

When you want to add a list to your page you start by adding the alfresco/lists/AlfList widget, which is a sort of container for the rest of the widgets that will make up the list. If you access the page with just this widget, it will not display anything. The AlfList widget is there as a container and is used to configure things like from where the data for the list should be fetched and how.

To configure what Web Script that should be used to load data for the list we use the loadDataPublishPayload property and set the nested url property to match the new Repo Web Script we just created. It’s not enough to just set what Web Script to use, we also need to tell the AlfList what to grab in the JSON that this Web Script returns. By default AlfList looks at the itemsProperty value, which is data by default, and expects the JSON to look like:

{

data:

 [

  {

Our Web Script returns JSON looking like:

{

emails:

 [

  {

So we need to set the itemsProperty value to emails. Now when we got the Web Script configured and have set what to grab from the response, we need some service to actually make the Web Script call, the AlfList widget is not going to do that. This is what the loadDataPublishTopic property is for. As usual, everything is decoupled and we communicate between widgets and services via events. The property is set to ALF_CRUD_GET_ALL, which is going to be picked up by a service called CrudService.

The CrudService is a convenience service that is provided out-of-the-box to be able to get going quickly when you are building pages and need to make simple Web Script calls like this one. So we need to include this service as follows in the page:

model.jsonModel = {

   services: [

     "alfresco/dialogs/AlfDialogService",

     "alfresco/services/DocumentService",

     "alfresco/services/SiteService",

     "alfresco/services/CrudService",

     "ecmstuff/services/EmailService",

     {

       name: "alfresco/services/LoggingService", . . .

We have now setup so the email list JSON data is fetched with the help of the CrudService.

Note. The CrudService cannot be used as soon as you need more elaborate URLs with parameters, or if you want to process the response JSON. You are then better off making the Web Script call from your own service, such as our EmailService.

We can now start to put together the list view. You do this by adding the alfresco/lists/views/AlfListView to the AlfList container:

var emailListWidget = {

 name: "alfresco/lists/AlfList",

 config: {

   loadDataPublishTopic: "ALF_CRUD_GET_ALL",

   loadDataPublishPayload: {

     url: "ecmstuff/sentemails"

   },

   itemsProperty: "emails",

   widgets: [

   {

    name: "alfresco/lists/views/AlfListView",

    config: {

      additionalCssClasses: "bordered",

At the same time as we are adding the list view we are configuring it to be bordered. We are now ready to start adding headers and data rows. Let’s start with the list header, it’s defined with the AlfListView’s widgetsForHeader property, and for each column header you want in your list you add a alfresco/lists/views/layouts/HeaderCell widget:

name: "alfresco/lists/views/AlfListView",

config: {

 additionalCssClasses: "bordered",

 widgetsForHeader: [

  {

    name: "alfresco/lists/views/layouts/HeaderCell",

    config: {

additionalCssClasses: "mediumpad",

      label: "To-Address"

    }

  },

  {

    name: "alfresco/lists/views/layouts/HeaderCell",

    config: {

additionalCssClasses: "mediumpad",

      label: "Subject"

    }

  },

  {

    name: "alfresco/lists/views/layouts/HeaderCell",

    config: {

additionalCssClasses: "mediumpad",

      label: "Body Text"

    }

  },

  {

    name: "alfresco/lists/views/layouts/HeaderCell",

    config: {

additionalCssClasses: "mediumpad",

      label: "Files"

    }

  }

 ],

At this point we got a header row looking like:

Next step is to fix the data row, which is done with the AlfListView’s widgets property. The first widget, and the only widget you need to add at this level, is the alfresco/lists/views/layouts/Row widget. The widgets property for the data row will then contain a alfresco/lists/views/layouts/Cell widget for each column we want in the list. When we define the columns with the Cell widget we also specify mediumpad styling so the text in each cell is padded a bit for better look and feel:

widgets: [

    {

      name: "alfresco/lists/views/layouts/Row",

      config: {

        widgets: [

         {

           name: "alfresco/lists/views/layouts/Cell",

           config: {

              additionalCssClasses: "mediumpad",

              widgets: [

               {

                 name: "alfresco/renderers/Property",

                 config: {

                   propertyToRender: "toAddress"

                 }

               }

              ]

           }

         },

         {

           name: "alfresco/lists/views/layouts/Cell",

           config: {

              additionalCssClasses: "mediumpad",

              widgets: [

               {

                 name: "alfresco/renderers/Property",

                 config: {

                   propertyToRender: "subject"

                 }

               }

              ]

           }

         },

         {

           name: "alfresco/lists/views/layouts/Cell",

           config: {

              additionalCssClasses: "mediumpad",

              widgets: [

               {

                 name: "alfresco/renderers/Property",

                 config: {

                   propertyToRender: "bodyText"

                 }

               }

              ]

           }

         },

         {

           name: "alfresco/lists/views/layouts/Cell",

           config: {

              additionalCssClasses: "mediumpad",

              widgets: [

               {

                 name: "alfresco/renderers/Property",

                 config: {

                   propertyToRender: "files"

                 }

               }

              ]

           }

         }

       ]

      }

    }

  ]

To display the data for each column/cell we use a renderer matching the data type of the field. We are just going to use a standard string field rendered called alfresco/renderers/Property. The configuration for this renderer tells it what JSON property it should render, i.e. one of:

{

   toAddress: "someone@dummy.com",

   subject: "Files for review",

   bodyText: "This is a bunch of files you should have a look at",

   files: "fileA.doc,  fileB.txt"

},

We now got a list looking like this:

It would be nice to have it in its own window with a title. We can do this with a bit more layout work. Add an email list window widget as follows:

var emailListWindowWidget = {

 name: "alfresco/layout/VerticalWidgets",

 widthPc: "65",

 config: {

   widgets: [

     {

       name: "alfresco/layout/ClassicWindow",

       config: {

         title: "Sent Emails",

               widgets: [ emailListWidget ]

       }

     }

   ]

 }

};

We use a vertical layout and configure it to be 65% of the screen width. We then use a classic window widget to enclose the list and put a title on it. It now looks like this:

Much better! Now we only have to add the form widget and the list window widget in another layout widget to bring it all together:

var horizontalLayoutWidget = {

  name: "alfresco/layout/HorizontalWidgets",

  config: {

     "widgetMarginLeft": 25,

     "widgetMarginRight": 25,

     "widgets": [ formWidget, emailListWindowWidget ]

  }

};

However, we are not done yet, we also want a top margin, so the form and list window has a bit of padding from the Title bar:

var verticalLayoutWidget = {

  "name": "alfresco/layout/VerticalWidgets",

  "config": {

     "widgetMarginTop": 25,

     "widgetMarginBottom": 25,

     "widgets": [ horizontalLayoutWidget ]

   }

};

Now just add the verticalLayoutWidget widget to the JSON model for the page:

model.jsonModel = {

   services: [

. . .

   widgets: [

  {

          id: "SET_PAGE_TITLE",

          name: "alfresco/header/SetTitle",

          config: {

              title: "Pick files and send as links in email",

          }

       },

  verticalLayoutWidget /*,

  {

    name: "alfresco/logging/SubscriptionLog"

  }*/

  ]

};

We are now pretty much done with adding the Sent Emails list to the page. It would be nice though, if after we send an email, the list was updated automatically. At the moment we got to refresh the page to see a new entry in the list.

To achieve this we can publish the ALF_DOCLIST_RELOAD_DATA event from the EmailService, that will make the list reload with current configuration:

onSendEmailSuccess: function  

           alfresco_services_ActionService__onSendEmailSuccess(

                   response, originalRequestConfig) {

   var msg = "Successfully sent email to " +

  originalRequestConfig.data.toAddress;

   this.alfLog("log", msg, response, originalRequestConfig);

   this.displayMessage(msg);

   this.alfPublish("ALF_DOCLIST_RELOAD_DATA", {});

},

If it does not work properly, make sure to clean dependency caches and look at the logs and make sure the event is published when an email has been sent successfully:

Other Aikau Examples that Might be of Interest

The following section contains links to other articles that might be of interest now when you are up to speed on Aikau. In general you should walkthrough the comprehensive tutorials available at the Aikau GitHub wiki: https://github.com/Alfresco/Aikau/tree/master/tutorial/chapters, this will give you that extra knowledge you need before starting your next Aikau project.

Aikau Page Display Based on Alfresco Role

Sometimes you don’t want a page to be accessible by all users. Here is an article describing how to use role based access control:

http://blogs.alfresco.com/wp/developer/2014/09/26/aikau-mini-examples-role-based-rendering/ 

Aikau Page Using Composite Widget

When you get going with Aikau page development, and are developing multiple pages, you will sooner or later come to a situation where you need the same group of widgets on multiple pages. Say, for example, a header and a menu that should appear on each page. In this case, you do not want to copy and paste the same header and menu widgets to each and every page. You want to instead create a composite widget and use it on each page as a reusable component.

See: https://github.com/Alfresco/Aikau/blob/master/tutorial/chapters/Tutorial3.md 

Adding an Aikau Page to the Share Site Pages

One common customization that you might have to do as a developer is to make an Aikau page available to include in a Share site via the Site configuration options | Customize Site | Available Site Pages configuration.

See: http://blogs.alfresco.com/wp/developer/2014/11/18/creating-aikau-site-pages-for-share/ 

Aikau Dashlets

Any serious Alfresco project will most likely include some custom Dashlets, have a look at these Dashlet implementations that comes with the Aikau source code: https://github.com/Alfresco/Aikau/tree/master/aikau/src/main/resources/alfresco/dashlets 

Unit Testing Aikau Pages

See the following articles:

http://blogs.alfresco.com/wp/developer/2014/02/26/unit-testing-aikau/  http://blogs.alfresco.com/wp/developer/2014/10/30/getting-started-with-functional-unit-testing-of-aikau/ 

Customizing Existing Aikau Pages in Share

This is a common request in most Alfresco projects, you got an existing out of the box page that the client want to change a bit. What you need to do first in this case is to figure out if it is an Aikau page or not.

Version 4.2 has the following Aikau implementations:

Version 5.0 has the following Aikau implementations:

4.2 areas, plus:

See the following article for how to add stuff to the top menu: http://blogs.alfresco.com/wp/developer/2013/11/25/webscript-javascript-controller-extensions-recap/ 

For adding stuff to a page see: http://blogs.alfresco.com/wp/developer/2013/09/04/customizing-the-share-header-menu-part-1/ http://blogs.alfresco.com/wp/developer/2013/09/06/customizing-the-share-header-menu-part-2/ 

For removing stuff from a page see: http://blogs.alfresco.com/wp/developer/2013/09/16/customizing-the-share-header-part-3/ 

Using the Page Creator to Create Aikau Pages

With all the different kinds of widgets available with the Aikau framework it might be difficult to get a grip on all of them and when and how they should be used. There is work going on to create an “Aikau Designer” tool that can be used to put together pages visually.

Have a look at the page creator: http://localhost:8080/share/page/hdp/ws/page-creator 

More info:

http://blogs.alfresco.com/wp/developer/2014/03/ 

http://blogs.alfresco.com/wp/developer/2014/02/24/share-page-creation-code/ 

Debugging Aikau Pages

Here are some more information about logging and debugging of pages:

http://blogs.alfresco.com/wp/developer/2013/11/07/debugging-in-alfresco-share-4-2/