Adding the Aikau Document Library as a Site Page in Share

Introduction

In previous blog posts I’ve shown examples of how the Aikau version of the Document Library can be used both within Share and in standalone clients. In this blog post I thought it might be useful to provide a more in-depth guide on how to add the Aikau Document Library as a site page in Share, as well as to provide a general status update on progress.

Background

It’s important to understand that the Aikau team is a “Component Team” and not a “Feature Team”. This means that we provide the components (in this case Aikau widgets and services) for other teams to deliver features or products.

At the time of writing there is no feature team driving the replacement of the old YUI2 Document Library with the Aikau version – nor indeed has there ever been. Aikau has always predominantly been about re-use and the Document Library has progressed because other features have required Document Library related widgets.

There are two epics in JIRA that list the current tasks required to achieve feature parity with the YUI2 Document Library: one for general features and one for action support. Reviewing these epics should give an overview of what work remains.

In general the Document Library is quite functional and should be more than adequate for covering a large number of use cases. The following steps and code is available within the following GitHub repository and all of the files described before are linked to the specific files in that repository.

Create the Aikau Document Library Page

The first step is to create the WebScripts for the new page. Create the WebScript descriptor file…

DocLib.get.desc.xml

<webscript>
  <shortname>Document Library Example </shortname>
  <description>This provides an example of building the standard Document Library using the doclib.lib.js library file.</description>
  <family>Aikau</family>
  <url>/DocLib</url>
</webscript>

Now let’s create the template…

DocLib.get.html.ftl

<@processJsonModel/>

The properties file is slightly more involved…

DocLib.get.properties

surf.include.resources=org/alfresco/share/imports/share-header.lib,org/alfresco/aikau/{aikauVersion}/libs/doclib/doclib.lib

This file just contains an instruction to import two other properties files:

  • org/alfresco/share/imports/share-header.lib
  • org/alfresco/aikau/{aikauVersion}/libs/doclib/doclib.lib

This process is described in more detail in this previous blog post.

The last WebScript file required is the JavaScript controller… this is where most of the code goes:

DocLib.get.js

The first thing is to import the required controller files…

<import resource="classpath:/alfresco/site-webscripts/org/alfresco/share/imports/share-header.lib.js">
<import resource="classpath:/alfresco/site-webscripts/org/alfresco/share/imports/share-footer.lib.js">
<import resource="classpath:alfresco/site-webscripts/org/alfresco/aikau/{aikauVersion}/libs/doclib/doclib.lib.js">

Now you need to import the services and widgets that the header uses…

var services = getHeaderServices();
var widgets = getHeaderModel(msg.get("aikau.doclib.title"));

The header services need to be combined with the services required by the Document Library. These can be retrieved by calling getDocumentLibraryServices.

services = services.concat(getDocumentLibraryServices());

Now use the getDocLib function to create the model for the Document Library. The main data to provide is the site id which is available from a template argument and the name of the folder in which the Document Library files can be found within the site folder (typically “documentLibrary”).

var docLib = getDocLib({
  siteId: page.url.templateArgs.site, 
  containerId: "documentLibrary"
});

This model needs to be added to the header model….

widgets.push(docLib);

Finally we need to call getFooterModel passing in the header and Document Library services and widgets. This is required because the footer model wraps everything else on the page.

model.jsonModel = getFooterModel(services, widgets);

Make the Document Library a Site Page

In this previous post I describe the process for adding Aikau pages in general – so please refer to that for a slightly more in-depth description of the process.

Create a new Surf Page definition that will provide the title and description of the page as shown when customizing sites.

AikauDocLib.xml

<?xml version='1.0' encoding='UTF-8'?>
<page>
  <title>Aikau Document Library</title>
  <description>The Aikau Document Library</description>
</page>

Now create an extension module to ensure that the site page is an option when customizing sites.

Aikau-document-library.xml

<extension>
  <modules>
    <module>
      <id>Aikau Document Library Site Page</id>
      <auto-deploy>true</auto-deploy>
      <evaluator type="default.extensibility.evaluator"/>
      <configurations>
        <config evaluator="string-compare" condition="SitePages" replace="false">
          <pages>
            <page id="AikauDocLib">dp/ws/DocLib</page>
          </pages>
        </config>
      </configurations>
    </module>
  </modules>
</extension>

Now you need to pull these files together into a single JAR and copy them to the “share/WEB-INF/lib” folder.

AikauDocLib4

Restart Share and go to any site and customize it – you should see the “Aikau Document Library” in the list of available pages:

AikauDocLib1

Drag it into the “Current Site Pages” list and click “OK”

AikauDocLib2

Now you should have a new link for the “Aikau Document Library” (if you have a lot of site pages, try looking under the “More” drop-down menu!). Click on the link and you’ll be taken to your the Aikau Document Library:

AikauDocLib3

What Are The Benefits and Limitations?

One of the main reasons for creating an Aikau version of the Document Library was to make customization much simpler. Through the use of the Aikau model it is significantly easier to make fine-grained changes to the Document Library. Some things you could do for example would be:

  • Add custom views
  • Edit existing views (add or remove the metadata that is displayed)
  • Filter the displayed actions or add entirely new actions
  • Render only specific sections of the Document Library (the tree, breadcrumb trail or list for example)
  • Edit the menu options that are displayed

The Aikau version also has a few features that are not included with the original Document Library:

  • Drag-and-drop upload new version
  • Inline document preview
  • Inline commenting
  • Inline content creation

As of Aikau 1.0.68 the Document Library uses a new non-modal upload file design. See screenshot below:

AikauDocLib5

The main limitation is that there is not yet support for all the actions that you would find in the existing Document Library (you can review the missing actions in the previously linked epic).

The other potential limitation is the lack of integration with the forms runtime. This means that when editing properties for custom types the XML forms configuration will not be represented.

Despite these issues the Aikau Document Library should still be useful for many use cases – especially where serious customization of the existing Document Library is required.

Alfresco REST API Explorer is now live!

We’ve been discussing REST APIs at Alfresco for a while now. If you haven’t already seen Gavin Cornwell’s Tech Talk Live session “The Future of REST APIs”  I highly recommend you check it out now.

It’s hard to sum up Gavin’s presentation, but the gist is that we are working on a new set of REST APIs that all follow a common set of guidelines. They are consistent, well tested, versioned which makes them extremely easy to consume. We call these “the v1 REST APIs”, however this concept is not new. We introduced the foundation for these APIs back in 4.2 along with our Cloud offering. The idea back then was to fill the missing features of CMIS so a combination of these APIs + CMIS would give you everything you need. These APIs have been documented in our official docs, however they were a bit hard to navigate and hard to consume.

This raises the question of how do we do API documentation in an efficient way. OpenAPI Specification (formerly known as Swagger) seems to do the trick in terms of describing our APIs. The cherry on the top is Swagger UI – a cool project that generates interactive documentation from an OpenAPI Specification – that even allows you to test out the APIs directly from the API Explorer.

We’ve put a lot of work into documenting our existing API’s, which is what we’re releasing today. The new “v1 APIs” are now marked up with OpenAPI Specification (you can find the source here). We do realise that these API’s don’t look like much – but as mentioned above, the scope of the existing APIs was to fill the gap.

As we’re adopting an “API First” approach, we’re also moving to a “contract first” approach. This means that before we start coding any APIs we mock them up in OpenAPI Specifications, then have a review. Once the review is complete and we have ensured it conforms to our standards and guidelines we will start the work. We hope to see proposals for new APIs following this approach in the future.

We’ve made much progress on additional APIs, and we’re continuing to invest heavily in stable, rich and powerful REST APIs this year. We already have a great set of APIs that cover file and folder operations, trashcan, renditions, live search, ticket authentication and site creation. We’re adding new APIs every week and we’re hoping to release a great set of APIs to community edition over the next couple of months.

We are working on a “bleeding edge” version of the REST API Explorer – this will be a snapshot of the latest development. We hope this will be a great tool for our developer ecosystem to test the API’s and provide feedback. We don’t currently have a fixed date for the “bleeding edge” version, but we will make sure to make some noise when it’s ready. In case you can’t wait, you can clone the REST API Explorer source on GitHub and check out the “develop” branch. Run “mvn tomcat7:run-war” to get it up and running, however you will not be able to interact with the APIs.

You can find the online version of the REST API Explorer here: https://api-explorer.alfresco.com. It also comes bundled with the Alfresco SDK 2.2, be sure to check it out!

To report a bug please open an issue in the ALF project over at http://issues.alfresco.com, please set the “REST API” component if possible.

Top 10 JQL Tips – from first steps to seasoned traveler


By Joe Emett

In this post, I’d like to give some tips on some useful JIRA Query Language (JQL) queries.  By no means exhaustive, they are things I’ve come to use and love in Jira.  It will focus on getting started with JQL and is not meant as a Jira tutorial.  Syntax rules are outside the boundaries of this blog.  Rather than giving descriptions on JQL keywords I hope this acts as a quick-start guide for you to build your own queries.

These tips assume the user is able to navigate to a Jira project backlog and range from rookie to more seasoned advice.

1.  Create your own JQL with 2 character presses

In the top right of any Jira screen is the QuickSearch text box:

1QuickSearch

When you enter my, you will redirected to the Issue Navigator and be presented with Jira issues that are currently assigned to you.

You can then tweak your JQL to refine the set of results, for example if you want just the open issues you could modify the query by including AND status = “Open” :
1CreateYourOwnJQL

You will find other smart query shortcuts here, at the very least QuickSearch offers fast access to the Issue Navigator and by entering free text into QuickSearch, this will search all Jira projects for instances of the text in these fields: Summary, Description and Comments.

This blog now assumes you are comfortable with accessing the Issue Navigator.

2.  Build on what is there already

Every backlog has an associated Jira query.  If you have access to a Jira project backlog, you are looking at the results of a JQL query.  On every Jira Scrum board, there is a ‘Board’ dropdown list:

BoardConfigure

On pressing ‘Configure’, on the ‘General’ tab there are the filter details the board is based on:2FilterQuery

Even though you may not have privileges to edit the query, you will be able to press View Filter Query, change the JQL query and Save as, as you wish.

Jira Reports and Gadgets on Dashboards, are other good places to look, as these are based on existing JQL queries you can dip into and change.

Look out for View in Issue Navigator to see more JQL.

3.  Just created an issue and you’ve forgotten the Key?

3ForgottenKey

You’ve created an issue, gone and made a cup of coffee and can’t remember the Key, and it’s not on your board.

In the Issue Navigator enter:

project = ACE and created >= startOfDay() ORDER BY Key DESC3OrderKeyDescending

4.  Use Empty to find forgotten issues

Find issues with no fix version.  In the Issue Navigator enter:

project in (ACE, MNT) AND status = Open AND fixVersion is EMPTY ORDER BY created DESC4Empty

5.  Commented by a colleague

You know the project and the colleague who commented on an issue, you’re just not sure which issue. You think the comment was made in the last 30 days, but you query 60 days back, just in case:  In the Issue Navigator enter:

project = ACE and issueFunction in commented(by cthompson after startOfDay(-60d))5CommentedBy

Please note, you require the ‘Adaptavist SciptRunner for Jira’ plug-in to include issueFunction in your query. issueFunction is a search mechanism allowing you to perform searches that you would not be able to do with ‘out of the box’ JQL.

6.  Tracking Assignee

You need to locate a Jira Issue that was assigned to a member of your team, but you’re not sure who. In the Issue Navigator enter:

project = ACE AND fixVersion in versionMatch(5.1) AND assignee WAS IN (cthompson, tbagnall, vhemmert, jemett) ORDER BY Rank6AAssignee
You can subscribe to any filter, where Jira will run the search and email you the results.  This is particularly useful when your project is in a stabilization phase and you would like daily updates containing the latest bugs.  In the filter view, click on Details then New subscription:

6CSubscribeToFilter

7.  Multiple teams in one project

You are sharing a project with another team, but want your own board, sprints and version control. Create some JQL that will track your Epics, Stories and Sub-Tasks.  In the Issue Navigator enter:

project = SHA AND (key in (sha-847, sha-856, sha-860, sha-445) OR Epic Link in (sha-847, sha-856, sha-860, sha-445)) OR issueFunction in subtasksOf(\Epic Link\=sha-847) OR issueFunction in subtasksOf(\Epic Link\=sha-856) OR issueFunction in subtasksOf(\Epic Link\=sha-436) OR issueFunction in subtasksOf(\Epic Link\=sha-445) ORDER BY Rank ASC7MultipleTeams

8.  Bugs fixed after a certain date

You would like to track the bugs where status has been changed to Review or Verify after a date and want to exclude issues that have been pinged back to the Developers.  In the Issue Navigator enter:

project = SHA AND fixVersion in versionMatch(5.1) AND (status changed to Review after 2015/11/29 OR status changed to Verify after 2015/11/29) AND Status != Open AND status != In Progress ORDER BY priority DESC8BugsFixed

9.  More on Empty

For some fields, such as ‘Labels’ you need to explicitly query if the field is ‘Empty’ as part of your criteria, for example .. (labels !=CMM) .. will only return issues with the label field that has been populated, ignoring issues with no labels.  To fully capture the set of records you’re really after use: .. (labels is empty or labels != CMM) ..

Incorporating this logic into a larger query, in the Issue Navigator enter:

project = SHA and fixVersion=5.1 and (labels is empty or labels !=CMM) and type in (Story,Task,Bug) order by rank9MoreEmpty

10.  Aggregate Expressions

JQL is not great for aggregation, but I find totaling points of stories that fall into certain sprints useful. In the Issue Navigator enter:

project = SHA and sprint in(CMM Sprint 5, CMM Sprint 6, CMM Sprint 7) and issueFunction in aggregateExpression(Total points, storyPoints.sum())10AggregateExpressions

As elsewhere in this post, you need the ‘Adaptavist SciptRunner for Jira’ plug-in to include issueFunction in your query.

Material Designed Aikau

Introduction

It was recently announced at BeeCon 2016 that Alfresco would be adopting Angular 2 and Google Material Design for all future applications that it develops (but that Aikau would continue to be developed to support Alfresco Share and Records Management).

Obviously Google Material Design is not compatible stylistically with Share currently, but Aikau can be used to build standalone Alfresco clients as well, so I thought it might be interesting to see if I could combine the two.

There are specific implementations of Material Design for Angular which were unlikely to be compatible with Aikau so I just used Material Design Lite (MDL) to quickly build some widgets and construct a page.

The purpose of this blog is to demonstrate that these types of integrations are possible and to provide further examples of how Aikau is able to easily integrate 3rd party libraries.

Base Material Design Lite Widget

MDL is provided in the form of a JavaScript file and a CSS file. The JavaScript file should be included to process the DOM once it has been loaded. MDL is primarily aimed at static web pages rather than dynamic ones which presented a minor issue for Aikau as the DOM is dynamically constructed after the page has been loaded.

MDL does support dynamic construction of some of its elements (unfortunately tabs was not one of them which was something of a disappointment) but it was simple enough to “upgrade” widget elements after they had been created.

I created a base widget “mdl/BaseMdlWidget” that handled all the dependencies and dynamic upgrading:

define(["dojo/_base/declare",
        "dijit/_WidgetBase", 
        "dijit/_TemplatedMixin",
        "dojo/text!./templates/Header.html",  
        "alfresco/core/Core",
        "alfresco/core/CoreWidgetProcessing",
        "dojo/_base/array"], 
        function(declare, _WidgetBase, _TemplatedMixin, template, AlfCore, CoreWidgetProcessing, array) {
 
  return declare([_WidgetBase, _TemplatedMixin, AlfCore, CoreWidgetProcessing], {

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

    templateString: "<div>No template provided</div>",

    nonAmdDependencies: ["./material.js"],

    postCreate: function mdl_BaseMdlWidget__postCreate() {
      if (this.widgets) {
        this.processWidgets(this.widgets, this.domNode);
      }
    },

    allWidgetsProcessed: function mdl_BaseMdlWidget__allWidgetsProcessed(widgets) {
      if (widgets) {
        array.forEach(widgets, function(widget) {
          if (widget.domNode) {
            componentHandler.upgradeElement(widget.domNode);
          }
        });
      }
    }
  });
});

This widget includes the references to both the “material.css” and “material.js” files that are stored relative to the widget (Surf ensures that they are only loaded once onto the page regardless of how many Aikau widgets declare a dependency on them). Note the use of the “nonAmdDependencies” attribute to load a JavaScript file that is not AMD compatible.

All the subsequent MDL based widgets I created extended this module.

The majority of the widgets I created were just simple representations of the various layout containers, for example:

header.js

define(["dojo/_base/declare",
        "mdl/BaseMdlWidget", 
        "dojo/text!./templates/Header.html"], 
        function(declare, BaseMdlWidget, template) {
 
    return declare([BaseMdlWidget], {
      templateString: template
    });
});

…with the template, header.html

<header class="mdl-layout__header "></header>

You might wonder what the point of doing this is?

Well, it’s quite simple really… although it would be simpler to just write out an HTML page, you would lose all the dynamic customization options that Aikau provides. If Alfresco were to ship an MDL based Aikau client it would be possible to add, remove and reconfigure the various elements on the page through an extension module.

Mixing in Aikau

Some of the other widgets were much more interesting though and demonstrate the power of the Aikau mixin modules.

For example the “mdl/MenuItem” used for the logout option in the header menu mixes in the “alfresco/renderers/_PublishPayloadMixin” to gain access to all the payload manipulation capabilities for publications.

define(["dojo/_base/declare",
        "mdl/BaseMdlWidget", 
        "dojo/text!./templates/MenuItem.html",
        "dijit/_OnDijitClickMixin",
        "alfresco/renderers/_PublishPayloadMixin"], 
        function(declare, BaseMdlWidget, template, _OnDijitClickMixin, _PublishPayloadMixin) {
 
  return declare([BaseMdlWidget, _OnDijitClickMixin, _PublishPayloadMixin], {

    templateString: template,

    title: "Menu Item",

    onClick: function mdl_MenuItem__onClick(evt) {
      this.publishPayload = this.getGeneratedPayload();
      this.alfPublish(this.publishTopic, this.publishPayload, !!this.publishGlobal, !!this.publishToParent);
      evt.stopPropagation();
    }
  });
});

Another example was the “mdl/CreateContentFabButton” that mixed in the “alfresco/documentlibrary/_AlfCreateContentMenuItemMixin” and “alfresco/documentlibrary/_AlfCreateContentPermissionsMixin” modules to be able to generate content creation dialogs and automatically disable itself when a folder is viewed that the current user cannot create content in.

define(["dojo/_base/declare",
        "mdl/FabButton",
        "alfresco/documentlibrary/_AlfCreateContentMenuItemMixin",
        "alfresco/documentlibrary/_AlfCreateContentPermissionsMixin",
        "alfresco/documentlibrary/_AlfDocumentListTopicMixin",
        "dojo/_base/lang"], 
        function(declare, AlfFilteringMenuItem, _AlfCreateContentMenuItemMixin, _AlfCreateContentPermissionsMixin, _AlfDocumentListTopicMixin, lang) {
 
  return declare([AlfFilteringMenuItem, _AlfCreateContentMenuItemMixin, _AlfCreateContentPermissionsMixin, _AlfDocumentListTopicMixin], {

    postCreate: function alfresco_documentlibrary_AlfCreateContentMenuBarPopup__postCreate() {
      this.alfSubscribe(this.hashChangeTopic, lang.hitch(this, this.onFilterChange));
      this.alfSubscribe(this.userAccessChangeTopic, lang.hitch(this, this.onUserAcess));
      this.alfSubscribe(this.metadataChangeTopic, lang.hitch(this, this.onCurrentNodeChange));
    },
 
    filter: function alfresco_documentlibrary_AlfCreateContentMenuItem__filter(payload) {
      if (this.hasPermission(this.permission, payload.userAccess)) {
        this.domNode.setAttribute("disabled");
      }
      else {
        this.domNode.removeAttribute("disabled");
      }
    }
  });
});

Building a Document Library

Having created some modules I then set about composing a page using them with the Aikau Document Library. In a previous blog post I described how the doclib.lib.js and doclib.lib.properties files could be imported into an Aikau page WebScript. I briefly mentioned that this library file provided functions that could be called to build specific parts of the Document Library.

I’ve used this approach to place the controls normally found in a sidebar (filters, tree, tags and categories) into the MDL drawer and placed a breadcrumb trail and document list into the main content section.

Other Steps

There a few other steps that were necessary…

The Java based LESS engine in Surf compressor was having an issue with the material.css file so I swapped out the “css.theme.handler” bean with a custom version that uses a Node based LESS processor by adding the following definition into the “web-application-config.xml” file (PLEASE NOTE: This is only available in Surf 6 and can’t be used in Alfresco 5.0 or 5.1 out-of-the-box).

<bean id="css.theme.handler" parent="css.theme.handler.abstract" class="org.springframework.extensions.surf.ExternalLessCssThemeHandler">
  <property name="cmd"><value>lessc -</value></property>
</bean>

This required me to install LESS globally via NPM.

npm install less -g

I also updated the “theme_1-theme.xml” to set a LESS variable to disable legacy button design:

<less-variables>
  @use-legacy-buttons: false;
</less-variables>

It was also necessary to update the “page-template.ftl” file to include:

<meta name="viewport" content="width=device-width, initial-scale=1.0">
 <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

To get the necessary icon font from and set the viewport property for scaling to other devices.

Test it out

All the code is available on a GitHub repository for you to try out. Simply clone the repository and run:Test it out

mvn clean install jetty:run

Make sure you have an Alfresco Repository running locally on port 8080 and when the Jetty server has started go to http://localhost:8090/aikau-sample/page/ap/ws/home

Once you’ve logged in you should be taken to the main page which shows the content from the sample site.

This video shows what you can expect to see:

Summary

This is just another example of how Aikau can make use of 3rd party libraries and should demonstrate that it is possible to easily take advantage of the capabilities that Aikau and Surf provides with your own custom widgets. It is by no means a statement of direction for Aikau but hopefully shows what is possible in a short amount of time and hopefully highlighted a few useful tricks along the way.

Aikau 1.0.66 – Angular 2 Integration

Introduction

One of the greatest misconceptions about Aikau is that it is somehow aiming to compete with other UI frameworks and libraries such as Angular and React. This really isn’t the case – Aikau is designed to address specific use cases that no other UI framework meets.

This isn’t trying to say that Aikau is in any way better – it’s just that it can do things that other frameworks can’t. We have integrated other frameworks such as Dojo, JQuery and YUI2 into Aikau – but Angular has been somewhat conspicuous by its absence.

There are a couple of key reasons for this:

  1. Angular 1 doesn’t really “play nicely” with other frameworks, although Angular 2 does a much better job of this
  2. Quite honestly there has never been a truly compelling reason to integrate it.

It’s worth remembering the granularity of Aikau widgets and how they are fully decoupled from both each other and the data that they use. Whilst this approach has been key to supporting the dynamic customization use cases, it has not lent itself particularly well to making use of the capabilities that Angular provides.

As you may have heard from recent announcements at BeeCon, Alfresco is looking to start making greater use of Angular 2 for the development of new applications but that it intends to continue to develop Aikau for use in Share.

Recent Announcements

That doesn’t mean that Share is not capable of making use of Angular 2 (or indeed any of the other modern UI frameworks such as React, Ember, etc). In my previous post I showed how modern web development practices could be integrated into Share and in this post I’m going to demonstrate how to make use of some new capabilities available in the 1.0.66 release of Aikau that will allow you to seamlessly insert Angular 2 code into an Aikau page in Share.

One important thing to be aware of… the widget that will be demonstrated resides under a new “alfresco/experimental” package in the Aikau library – anything under this package does not fall into the usual backwards compatibility rules of Aikau and as such maybe changed or removed at any time. If this widget appears useful then you should let us know and we’ll look to make it a fully fledged Aikau widget so that it gets all the backwards compatibility guarantees that this brings.

In this example we’re going to be integrating an example from the Angular 2 tutorial into the faceted search page in Share. Doing so holds no value other than to demonstrate that it can be done.

Step 1. Get Aikau release

First of all, you need to make sure you have downloaded the 1.0.66 release of Aikau and have placed it in the “share/WEB-INF/lib” folder.

Step 2. Create extension

Now you need to create an extension module for the faceted search page. The quickest way to do this is to follow the steps in this blog post to download an extension JAR file for the faceted search page.

Step 3. Create a package definition

Unpack the JAR file somewhere and edit the the “alfresco/site-data/extensions/extension.xml” file to add in a new AMD package declaration for the Angular 2 tutorial code (background information on defining new AMD packages via extensions can be found in the Alfresco Documentation here.

<extension>
  <modules>
    <module>
      <id>Angular 2 Tutorial Extension</id>
      <auto-deploy>false</auto-deploy>
      <evaluator type="default.extensibility.evaluator"/>
      <configurations>
        <config evaluator="string-compare" condition="WebFramework" replace="false">
          <web-framework>
            <dojo-pages> 
              <packages> 
                <package name="blog" location="js/blog"/> 
              </packages>
            </dojo-pages>
          </web-framework>
        </config>
      </configurations>
      <customizations>
        <customization>
          <targetPackageRoot>org.alfresco.share.pages.faceted-search</targetPackageRoot>
          <sourcePackageRoot>org.alfresco.share.pages.faceted-search.customization</sourcePackageRoot>
        </customization>
      </customizations>
    </module>
  </modules>
</extension>

Next you want to copy the “main.ts” and “app.component.ts” (these can be copied from here) into the “META-INF/js/blog” folder.

Step 4. Edit the controller extension

Now you want to update the generated JavaScript controller extension (“alfresco/site-webscripts/org/alfresco/share/pages/faceted-search/customization/faceted-search.get.js”) to make a dynamic request to add the new Angular 2 widget into the page.

Add the following code into the file:

var verticalStack = widgetUtils.findObject(model.jsonModel.widgets, "id", "FCTSRCH_MAIN_VERTICAL_STACK");
if (verticalStack && verticalStack.config && verticalStack.config.widgets)
{
  verticalStack.config.widgets.unshift({
    name: "alfresco/experimental/ng2/Bootstrap",
    config: {
      main: "blog/main.ts",
      templateString: "<my-app>Loading...</my-app>"
    }
  });
}

The Bootstrap Widget

The widget being added is the “alfresco/experimental/ng2/Bootstrap” widget. The purpose of this widget is to bootstrap the example from the Angular 2 tutorial. Here we are choosing to insert the example above the main search controls.

There are two configuration attributes:

  • “main” is the root Angular 2 component to load that should include the call to bootstrap Angular 2
  • “templateString” is the DOM fragment that contains the custom elements that the Angular 2 component will be looking to parse during bootstrapping.

Once you’ve made these changes you can re-pack the JAR file and copy it to the “share/WEB-INF/lib” folder.

Restart Share and perform a search. Initially you won’t see anything different – this is because the module has not been applied. More importantly, a second extension module provided by the Aikau JAR is also required.

Step 5. Deploy the modules

Navigate to the Module Deployment page (“/share/page/modules/deploy”) and add the “Angular 2 Tutorial Extension” and the “Angular 2 Support (version 1.0.66) module and click the “Apply Changes” button.

Screenshot from 2016-05-03 09:35:54

The “Angular 2 Support” module adds all of the required Angular 2 JavaScript dependencies that are required – most importantly it loads System.js that is used to transpile the TypeScript and handle the ES6 import calls.

Once you have applied these modules you can reload the search page and you’ll see the tutorial example displayed like so:

Screenshot from 2016-04-28 21:23:02

Summary

Obviously this example has no practical purpose whatsoever. However the technique could be used to for much more sensible use cases.

It’s worth noting that, from the moment you bootstrap into an Angular 2 component, you are completely leaving Aikau behind and all future imports should be done via the ES6 import approach as shown in the Angular 2 tutorials.

It’s also important to recognize that this is not suitable for production purposes, because in reality you would want to transpile and compress the Angular 2 code before using it.

If you think the “alfresco/experimental/ng2/Bootstrap” widget would be useful to be made a first class Aikau widget then please let us know – if enough people are interested then we’ll move it to an appropriate package and make it product ready.

Download the Source

You can download the extension module source from this folder in a new GitHub repository that I’ve setup.

Writing a “Next Generation” Surf Client

Introduction

This post is going to describe how you can build next-generation web client technologies into Share and standalone web-clients. I’ve taken a pre-existing tutorial that I found on the Smashing Magazine web site and have adapted it for use in Surf. This is simply a demonstration that Share does not prohibit the use of any of these technologies and the method shown here is by no means the only way in which this task could be accomplished. The intention here is to shed further light on some of the Surf concepts.

You might be wondering why would you want to do this? Why not just go the whole hog and re-implement everything on a Node stack?

One reason is that you might simply want to add an existing page into Share and that you don’t want to have to reimplement all the features that Share currently provides. It’s also important to remember that through its use of Surf, Share is able to take care of a host of issues that you never need worry about – a single point of authentication to an Alfresco Repository across multiple REST APIs (CMIS, WebScripts, Public API) being the most obvious.

Rather than working with Share though, in this instance we’re going to create a standalone client using the Aikau Maven Archetype. We’re not going to be using Aikau, but it’s worth being aware that the archetype will build you a client that has everything you need to authenticate against an Alfresco Repository.

I’m not going to go through the details of the next-generation concepts as these are well documented in the Smashing Magazine article and you can read up on them there if you’re not familiar with them yourself.

Essentially we’re going to be writing ES6 JavaScript that is transpiled to ES5 via Babel, compiled into a single module (with associated source maps) using WebPack and minimized using Uglify. The whole JavaScript build process will be taken care of using Gulp and will all be wrapped within a Maven build.

Short Cut to Code

If you don’t want to manually follow all the steps you can find the source code that you would otherwise build in this GitHub repository.

Pre-Requisites

You have Node.js, NPM and Maven installed

Create a new client

Execute the following command in a location where you want to create your project

mvn archetype:generate -DarchetypeCatalog=https://artifacts.alfresco.com/nexus/content/groups/public/archetype-catalog.xml -DarchetypeGroupId=org.alfresco -DarchetypeArtifactId=aikau-sample-archetype -DarchetypeVersion=RELEASE

Enter suitable suitable group and artifact ids and accept the remaining defaults (for example, I’ve used “org.alfresco” as the groupId and “next-gen” as the artifactId).

Building from an archetype is described in more detail in the Aikau tutorial.

Install gulp and webpack globally

Run the following command:

npm install -g gulp, webpack

Create Build Files

Create your package.json file in the root of the project

{
  "name": "next-gen",
  "version": "0.0.1",
  "devDependencies": {
    "babel": "^5.8.23",
    "babel-core": "^5.8.24",
    "babel-eslint": "^4.1.1",
    "babel-loader": "^5.3.2",
    "eslint": "^1.4.1",
    "gulp": "^3.9.0",
    "gulp-babel": "^6.1.2",
    "gulp-rename": "^1.2.2",
    "gulp-sourcemaps": "^1.5.2",
    "gulp-uglify": "^1.4.1",
    "webpack": "^1.12.1",
    "webpack-stream": "^2.1.0"
  }
}

Create a webpack.config.js file in the root folder:

module.exports = {
  entry: "./src/js/index.js",
  output: {
    library: "legoQuotes",
    libraryTarget: "umd",
    filename: "lib/legoQuotes.js"
  },
  externals: [
    {
      lodash: {
        root: "_",
        commonjs: "lodash",
        commonjs2: "lodash",
        amd: "lodash"
      }
    }
  ],
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel",
        query: {
          compact: false
        }
      }
    ]
  }
};

Create a gulpfile.js file in the root folder:

var gulp = require( "gulp" );
var webpack = require( "webpack-stream" );
var sourcemaps = require( "gulp-sourcemaps" );
var babel = require("gulp-babel");
var rename = require( "gulp-rename" );
var uglify = require( "gulp-uglify" );

gulp.task("default", function() {
  return gulp.src( "src/js/index.js" )
    .pipe( babel() )
    .pipe( webpack( require( "./webpack.config.js" ) ) )
    .pipe( gulp.dest( "./src/js/dist" ) )
    .pipe( sourcemaps.init( { loadMaps: true } ) )
    .pipe( uglify() )
    .pipe( rename( "legoQuotes.min.js" ) )
    .pipe( sourcemaps.write( "./" ) )
    .pipe( gulp.dest( "./src/js/dist/" ) );
  });

For more information on what these files do, you should read the Smashing Magazine article – the purpose of this blog is not to explain these things, simply to show how they can be used with Share/Surf.

Create JavaScript resources

This should be placed in the “src/js” folder of your project – note that it is intentionally not in the “src/main” folder as we are going to build the JavaScript separately from the rest of the project. The files you want are:

  • benny.js
  • emmet.js
  • index.js
  • LegoCharacter.js
  • utils.js
  • wyldstyle.js

These can be found in the linked source from the Smashing Magazine article, they are only provided for something to build!

Create CSS resources

Copy the “style.css” from the linked source into a new folder called “css” in “src/main/webapp/“. We could use WebPack to bundle up CSS resources but we’re going to let Surf take care of this as we’ll see later.

Maven Updates

We now want to update our pom.xml to allow us to build our next-gen resources as part of the Maven build. Add the following to the <build> <plugins> element:

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.8</version>
  <executions>
    <execution>
      <id>Installation</id>
      <phase>validate</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <target>
          <echo>NPM Package Installation</echo>
          <exec executable="npm" dir="${project.basedir}">
            <arg line="install" />
          </exec>
          <echo>Run Gulp Build</echo>
          <echo>Build and overlay JS</echo>
          <exec executable="gulp" dir="${project.basedir}">
          </exec>
        </target>
      </configuration>
    </execution>
  </executions>
 </plugin>

Here we’re using the Maven Ant Run plugin to get all the required Node packages (defined in the package.json file) and call Gulp to perform the build.

So somewhat impressively we’re using Ant, Maven and Gulp!

Next we want to ensure that we copy our JavaScript and required Node modules into the web application that we’re going to build. Add the following plugin configuration after the previous entry that you just added:

<plugin>
  <artifactId>maven-resources-plugin</artifactId>
  <version>2.7</version>
  <executions>
    <execution>
      <id>copy-node-modules</id>
      <phase>validate</phase>
      <goals>
        <goal>copy-resources</goal>
      </goals>
      <configuration>
        <outputDirectory>${basedir}/target/aikau-sample/node_modules</outputDirectory>
        <resources>
          <resource>
            <directory>${basedir}/node_modules</directory>
          </resource>
        </resources>
      </configuration>
    </execution>
    <execution>
      <id>copy-javascript</id>
        <phase>validate</phase>
        <goals>
          <goal>copy-resources</goal>
        </goals>
        <configuration>
          <outputDirectory>${basedir}/target/aikau-sample/js</outputDirectory>
          <resources>
            <resource>
              <directory>${basedir}/src/js</directory>
            </resource>
          </resources>
        </configuration>
      </execution>
    </executions>
 </plugin>

Finally we need to make one more change. The Aikau archetype configures the Jetty plugin to use the “src” rather than “target” location to enable fast development, but for a cleaner build we are copying the “node_modules” into target so need to change the plugin configuration appropriately.

Update the <webApp> section so that all instances of “src/main/webapp” are replaced with “target/aikau-sample”

Create Surf Objects

We now have our build in place, so it’s time to create the required Surf objects.

Surf has a complex but powerful set of objects for constructing pages. One of the goals of Aikau was to hide these objects away from developers to allow them to focus on creating pages with just WebScripts. We’re now going to build some of these objects and it may become apparent why we wanted to hide this complexity away.

The first thing we need to create is a Page object. This is defined as XML file and the key thing you can define in this file is what level of authentication the user requires to access the page. Here we’re going to use “none” because we want to avoid a login step – however, you could just as easily set the value to be “user” or “admin”.

Create a file called “next-gen-page.xml” in the “src/main/webapp/WEB-INF/surf-config/pages/” folder. It should contain the following XML.

<?xml version='1.0' encoding='UTF-8'?>
<page>
  <id>ngp</id>
  <template-instance>next-gen-template-instance</template-instance>
  <authentication>none</authentication>
</page>

The Page references a Template-Instance to be rendered. This is the next file to declare. Create a file called “next-gen-template-instance.xml” in the “src/main/webapp/WEB-INF/surf-config/template-instances” folder. It should contain the following XML:

<?xml version='1.0' encoding='UTF-8'?>
<template-instance>
  <template-type>next-gen-template-type</template-type>
</template-instance>

The Template-Instance references a Template-Type – this needs to be declared. Create a file called “next-gen-template-type.xml” in the “src/main/webapp/WEB-INF/surf-config/template-types” folder. It should contain the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<template-type>
  <title>Next Gen Page</title>
  <processor mode="view">
    <id>webscript</id>
    <uri>/next/gen/page/template</uri>
  </processor>
</template-type>

A Template-Type can have a number of different processor modes. We really only care about the “view” mode (the other modes were created for WCM purposes that are not widely used anymore). Surf supports a number of different processors out-of-the-box and it is possible to configure in additional processors. Here we’re using a WebScript processor and are providing the URI to match against a WebScript.

This WebScript needs to be defined. In the “src/main/webapp/WEB-INF/webscripts” folder create the following files:

next-gen-page.get.desc.xml

<webscript>
  <shortname>Template For Next Gen Pages</shortname>
  <family>Page Templates</family>
  <url>/next/gen/page/template</url>
</webscript>

next-gen-page.get.html.ftl

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Lego Quote Module Example</title>
  </head>
  <body>
    <div class="container">
      <blockquote id="quote"></blockquote>
      <button id="btnMore">Get Another Quote</button>
    </div>
    <script src="${url.context}/node_modules/lodash/index.js"></script>
    <script src="${url.context}/node_modules/babel-core/browser-polyfill.js"></script>
    <script src="${url.context}/js/dist/legoQuotes.min.js"></script>
    <script>
      (function(legoQuotes) {
        var btn = document.getElementById("btnMore");
        var quote = document.getElementById("quote");

        function writeQuoteToDom() {
          quote.innerHTML = legoQuotes.getRandomQuote();
        }

        btn.addEventListener("click", writeQuoteToDom);
        writeQuoteToDom();
      })(legoQuotes);
 </script>
 </body>
</html>

Create Reusable Page Structure

We don’t want to have to create all of these files (page, template-instance, template-type, WebScript) for every single page in our application (of course if you’re building a Single Page Application then you’re only going to need to do this once). So instead we’re going to abstract the WebScript file contents into an include template and then make it possible to parametrize a WebScript to run as is done in Aikau.

Let’s update the template file so that is looks like this:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Next Gen Example</title>
    <script src="${url.context}/node_modules/lodash/index.js"></script>
    <script src="${url.context}/node_modules/babel-core/browser-polyfill.js"></script>
    <@outputJavaScript/>
    <@outputCSS/>
  </head>
  <body>
    <#assign regionId = page.url.templateArgs.webscript?replace("/", "-")/>
    <@autoComponentRegion uri="/${page.url.templateArgs.webscript}"/>
  </body>
</html>

In this example we are using 3 custom FreeMarker directives that Surf provides:

  • <@outputJavaScript/>
  • <@outputCSS/>
  • <@autoComponentRegion>

The first two mark the location where page specific JavaScript and CSS will be output – in a moment we’ll show how to define what should be output.

The <@autoComponentRegion> is used to automatically create a Surf Region and a Surf Component for the WebScript that we’re going to use to define the page. The URI for the WebScript to use is taken from the same UriTemplate that is configured for Aikau pages.

Let’s now create the WebScript for our page. Create the following files in the “src/main/webapp/WEB-INF/webscripts/pages” folder.

First the descriptor: “LegoQuotes.get.desc.xml”

<webscript>
 <shortname>LegoQuotes</shortname>
 <family>Next Gen Pages</family>
 <url>/legoQuotes</url>
</webscript>

The key thing to note here is the <url> – we’re going to be using that when we load our page.

Now the template, “LegoQuotes.get.html.ftl”

<@link rel="stylesheet" type="text/css" href="${url.context}/res/css/style.css"/>
<@script type="text/javascript" src="${url.context}/res/js/dist/legoQuotes.min.js"/>
<@inlineScript>
  (function(legoQuotes) {
    document.addEventListener("DOMContentLoaded", function(event) {
      var btn = document.getElementById( "btnMore" );
      var quote = document.getElementById( "quote" );

      function writeQuoteToDom() {
        quote.innerHTML = legoQuotes.getRandomQuote();
      }

      btn.addEventListener( "click", writeQuoteToDom );
      writeQuoteToDom();
    });
  })(legoQuotes);
</@>

<div class="container">
 <blockquote id="quote"></blockquote>
 <button id="btnMore">Get Another Quote</button>
</div>

Again, we’re using 3 new FreeMarker directives

  • <@link>
  • <@script>
  • <@inlineScript>

The <@link> directive is how we reference CSS files to be output into the location of the <@outputCSS/> directive that we declared in our template.

The <@script> directive references JavaScript files to be output into the location of the <@outputJavaScript/>.

The <@inlineScript> directive allows us to write snippets of JavaScript that will be inserted at the location of the <@outputJavaScript/> directive.

There are two benefits to be aware of here – firstly all JavaScript and all CSS will be combined so that only a single resource of each type will be loaded onto the page – this reduces HTTP handshaking and improves performance.

Secondly the generated resources will have a name that is an MD5 checksum matched to their content. This means that these files can be infinitely cached on the browser as if the content changes a different resource name will be generated.

The End Result

Now run the following command:

mvn clean install jetty:run

Be aware, it will probably take a few minutes to build and startup – please be patient.

Once you see the message: “[INFO] Started Jetty Server”, open the URL “http://localhost:8090/aikau-sample/page/ngp/ws/legoQuotes” in your browser and you should see the following:

Next-Gen-2

 

You can now click the button to generate random Lego Movie quotes.

Summary

In this post I’ve demonstrated that it is possible to make use of the current crop of tools for building web applications without discarding the benefits that Surf brings to the table with regards to creating clients for Alfresco. In the process I’ve hopefully been able to provide some useful information on creating Surf objects.

Aikau 101

Introduction

A complaint that we have heard on occasion is that it is hard to find and hire people with Aikau skills. It’s probably not unexpected that there are few people with a lot of experience in Aikau – after all it’s only really existed as a named entity for a couple of years and is specific to a single vendor in a relatively niche market of IT.

…But “skills” ?

If you have working knowledge of HTML, JavaScript and CSS then you’re pretty much halfway there. The other half of the puzzle is getting your head around 3 concepts:

  • Modules
  • Pub/Sub
  • Services

It’s also vitally important to understand the problem that Aikau is trying to achieve. It is not intended to be the go-to solution for building any web application:

It is about providing a way in which a page can be defined in such a way that anyone can make small or large alterations to that page with the minimum of effort. It is about providing a way in which Alfresco can quickly iterate on its own use cases (as well as those of its partners, customers and community) in a backwards compatible way. Finally, it is about providing re-usable components to build solutions that are specifically targeted for Alfresco.

Modules

Aikau aims to decompose solutions into the smallest possible, reusable components (or widgets). Each widget is typically independent of any other in order that it can be tested as a single atomic unit.

Being able to test each widget is essential.

By defining a widget as a configurable “black box” we are able to write tests to verify how a widget behaves when configured in a certain way. We do not have to write full integration tests of how every widget interacts with every other type of widget – simply how a widget responds to publications (more on that later).

When we first started writing Aikau, there was no native module loading available (unlike now) so we used the AMD paradigm as provided by Dojo (also implemented in RequireJS). Native JavaScript imports (to the best of my knowledge) are still constrained to just JavaScript files – but some frameworks (such as Angular 2) are providing the ability to define components that reference external CSS and HTML templates (for the record, Aikau was doing this 3 years in earlier !)

Although it is the Surf framework that handles the CSS, HTML (and i18n) dependency loading it is the Aikau widget that defines the dependencies. When creating an Aikau widget you simply need to understand how these dependencies can be declared.

It’s important to be able to recognize the “boiler plate” of an Aikau widget. This is the most simple example of a widget that defines HTML, CSS and i18n dependencies.

define(["dojo/_base/declare",
        "dijit/_WidgetBase", 
        "dijit/_TemplatedMixin",
        "dojo/text!./templates/MyWidget.html",
        "alfresco/core/Core"], 
       function(declare, _WidgetBase, _TemplatedMixin, template, AlfCore) {
 
  return declare([_WidgetBase, _TemplatedMixin, AlfCore], {
    i18nRequirements: [{i18nFile: "./i18n/MyWidget.properties"}],
    cssRequirements: [{cssFile:"./css/MyWidget.css"}],
    templateString: template,
    postCreate: function() {
      // TODO Put your code here
    }
  });
});

All your code (any JavaScript you like) should go into the postCreate function.

Let’s say that you don’t want an external template and have no CSS or i18n dependencies. This reduces the basic widget to this:

define(["dojo/_base/declare",
        "dijit/_WidgetBase", 
        "alfresco/core/Core"], 
       function(declare, _WidgetBase, AlfCore) {
 
  return declare([_WidgetBase, AlfCore], {
    buildRendering: function() {
      // TODO: Create some HTML for the widget and assign to this.domNode
    },
    postCreate: function() {
      // TODO Put your code here
    }
  });
});

The buildRendering function should be implemented to construct the DOM for the widget – again, using any JavaScript that you’d like.

The buildRendering and postCreate functions are part of the Dojo widget life-cycle that Aikau make use of. It is necessary to extend “dijit/_WidgetBase” and to use the define and declare functions when writing a widget – but literally everything else your widget does is entirely up to you. We just happen to use a lot of other Dojo capabilities because we find them useful – whether or not you choose to is entirely up you.

But there is no magic here – it is just JavaScript.

The define function tells the AMD loader what modules to load, the declare function creates the JavaScript Class. The buildRendering and postCreate functions are called when an instance of the class is created. A more in-depth description of widget creation can be found here.

In order to make it possible for a page to be easily customized it is essential that the building blocks of that page (the widgets) be completely decoupled from one another. This is where the publication/subscription model comes in.

The Publication / Subscription Model

It would undoubtedly be much easier to implement a solution where there are fixed references between widgets, but that would greatly constrain what a 3rd party could do a page.

Instead Aikau widgets communicate with one another by publishing requests on topics and subscribing (or listening) to and responding to other requests that are published.

In order to ensure that communications between widgets can be constrained to a subset of widgets it is possible to define a scope. This allows one group of widgets that share a common topic type to communicate with each other without impacting another group of widgets.

Imagine a scenario where you have two lists on the page each with their own sorting controls – changing the a sort order in one list should not update the order of the other list. This is where scoping comes in.

A publication/subscription scope is set on a widget by setting its “pubSubScope” attribute with a string value. This value is prefixed onto all topics published and only topics prefixed with that value are subscribed to.

The “pubSubScope” attribute is one of a handful of attributes that is passed down from parent widget to child widget. A child will have the same scope as its parent unless specifically configured otherwise.

Services

As well as being decoupled from each other via the publication/subscription model, widgets are also decoupled from data. This allows a widget to work with multiple data sources – so for example the “alfresco/lists/AlfList” widget can be used to render any list – documents, sites, people, properties, aspects, etc. All data should be accessed via a service.

Services in Aikau can be considered the glue that binds the page together. They can perform a number of roles as well as handling XHR requests – the “alfresco/services/DialogService” for example is purely responsible for the creation and management of modal dialogs. If you don’t like the default dialogs in Aikau then an alternative service can be used instead – widgets don’t “care” what services their request for a dialog, only that it is serviced.

The most common role of a service though is to make asynchronous (XHR) requests for data from the Alfresco Repository. A widget will publish a request for some data on a specific topic and if a service exists that subscribes to that topic then it will process that request, retrieve the requested data from the Alfresco repository and return it to the widget on the requested response topic.

Dipping back very briefly into the subject of publication/subscription scoping…. Services are typically not scoped. However, in order that widgets are able to communicate with them all publication configuration can include an option to publish “globally” – or without scope. This ensures that it is not necessary to have instances of services at every scope used by widgets on the page.

Putting It Altogether

An Aikau page is declared as a model in a WebScript JavaScript controller. I typically describe this as a JSON model and am frequently criticized by my colleagues for describing it as such because in the WebScript controller you are technically creating a JavaScript Object Literal – but on it’s way to being processed by Surf it does get converted into JSON, so I’m going to keep calling it JSON.

The structure of the model in your JavaScript controller should look like this:

model.jsonModel = {
  services: [],
  widgets: []
};

…services and widgets being added into the appropriate arrays.

The “widgets” model will typically become a deeply nested tree with widgets placed inside other widgets.

By constructing a page like this is means two things:

  1. It is possible to intercept and customize a default model before it is processed. This means that extensions can add, remove and reconfigure widgets and the fact that widgets are decoupled means that this can be done without causing reference errors
  2. Page models can be dynamically constructed and rendered safely on a running server (as demonstrated numerous times over the last year at various Alfresco conferences and Tech Talk Live sessions)

The disadvantage of this approach is that for simplicity you normally have to rely on the various widgets in the “alfresco/layout” package to build the structure of your page rather than just using HTML/CSS.

However it is possible to embed Aikau into any DOM node on the page by including a “rootNodeId” attribute in your model. This would allow you to construct the layout for your page in your WebScript template using HTML into which the Aikau model could be built.

It’s also possible to have multiple Aikau models embedded into different locations on the page – the best approach for this would be to use the standard Surf region/component development approach. Effectively this is what you see on most pages in Share – the header is a Surf Component rendering an Aikau model and the remainder of the page is made up of other Surf Components rendering YUI2.

Summary

Aikau is really just a way of packaging HTML, CSS and JavaScript into reusable modules. It isn’t a new language, but is merely a new way of thinking about web development. This approach has its advantages (reuse, extensibility, customization, dynamic page creation) but also has its drawbacks (requires a different way of thinking, is a layer of abstraction away from traditional web development).

It’s not a framework for any use case – it’s targeted specifically for Alfresco development and depending upon your specific use case it may or may not be the right choice for you. It is worth remembering that unless we know your use cases we’ll never be able to address them – so if there’s something you’d like Aikau to do better then please let us know!

Aikau 1.0.63 – Support for versioned library imports

Introduction

This blog post describes how you can now safely import Aikau library files into WebScript controllers when you have multiple versions of Aikau available in your application. PLEASE NOTE: This information only applies if you’re using Alfresco 5.1 or 5.0.3 onwards.

Background

In the Aikau 1.0.63 release we have completed the final stage of fixing AKU-411. This bug was raised because there was no reliable way of knowing which version of an Aikau library file would be imported into a WebScript JavaScript controller.

Although Alfresco Share will only ship with a single version of Aikau (included as a JAR file in the WEB-INF/lib folder) additional JARs might be added when applying AMP files (such as Records Management). This meant that there would be duplicate versions of library files available on the classpath to be imported and as a result you could not guarantee which version would be loaded.

We made some changes in the Alfresco 5.0.3 service pack to remedy this problem (also available in Alfresco 5.1) and have now updated Aikau to take advantages of these capabilities.

At the time of writing there is only a single library file shipped with Aikau which can be used for building Document Libraries within a page – but in the future we hope to provide many more library files to address a variety of common use cases.

A New Way to Import Library Files

Previously you would have needed to imported the library file as follows:

<import resource="classpath:alfresco/site-webscripts/org/alfresco/aikau/webscript/libs/doclib/doclib.lib.js">

But you can now import the file as follows:

<import resource="classpath:alfresco/site-webscripts/org/alfresco/aikau/{aikauVersion}/libs/doclib/doclib.lib.js">

The {aikauVersion} token will automatically be swapped out for the version of Aikau that is being used by Surf. By default the most recent version of Aikau available will always be used, however it is possible to select and use an older version from the Module Deployment page (found at /share/page/modules/deploy – see screenshot below)

1.0.63-BlogPost-1

If you use the {aikauVersion} token then you are effectively stating that you always want to use the same version of the library file that is shipped with the currently used version. However, it is still entirely possible to use a specific version, e.g.

<import resource="classpath:alfresco/site-webscripts/org/alfresco/aikau/1.0.63/libs/doclib/doclib.lib.js">

However, this does mean that you need to ensure that the version specified is available.

This means that we are now able to make incremental improvements and fixes to the Document Library import file, it also means that it now makes sense for us to start providing more files that can be imported.

How To Use The Document Library Import

The doclib.lib.js file has been written with re-use in mind. It provides a number of different functions that can be called giving you the choice of building an entire Document Library or just fragments of the Document Library (such as the list, the toolbar, the tree, etc) and all the functions take a configuration object so you can control how the Document Library will be built.

1.0.63-blogpost-2

So for example if you wanted to build a full Document Library for company home you could use the following in your WebScript controller:

<import resource="classpath:alfresco/site-webscripts/org/alfresco/aikau/{aikauVersion}/libs/doclib/doclib.lib.js">

model.jsonModel = {
  services: getDocumentLibraryServices(),
  widgets: [
    getDocLib({
      rootNode: “alfresco://company/home”,
    })
  ]
};

There are a variety of other options that you can provide when calling the function, for example:

  • idPrefix – (string) A prefix for the IDs of all the widgets created
  • siteId – (string) the site shortName attribute of the site to build a Document Library for
  • containerId – (string) the name of the folder in which the Document Library content resides within the site (typically this would be “documentLibrary”
  • rawData – (boolean) whether or not to make XHR requests directly to the Alfresco Repository and bypass the Alfresco Share web-tier
  • rootNode – (string) A nodeRef to root the Document Library
  • rootLabel – (string) The label for the root of the Document Library as shown in the breadcrumb trail and in the navigation tree
  • useHash – (boolean) Whether or not to update the browser URL hash with Document Library state
  • getUserPreferences – (boolean) indicates whether or not the user preferences should be retrieved from the Alfresco Repository
  • docLibPreferences – (object) custom preferences for the Document Library (sortField, sortAscending, showFolders, hideBreadcrumbTrail, showSidebar).

The functions that you can call include:

  • getDocLib (builds the whole Document Library calling the following functions as appropriate)
  • getDocLibFilters
  • getDocLibTree
  • getDocLibTags
  • getDocLibCategories
  • getDocLibToolbar
  • getDocLibBreadcrumbTrail
  • getDocLibList
  • getDocLibCreateContentMenu
  • getDocLibSelectedItemActions
  • getDocLibSortOptions
  • getDocLibConfigMenu

Localization Properties Importing

As well as providing an import for the Document Library model and additional properties import is provided for localization purposes. For the background on importing properties files you should read this related blog post.

These imports support the {aikauVersion} token in exactly the same way. Previously you would have imported the file as follows:

surf.include.resources=org/alfresco/aikau/webscript/libs/doclib/doclib.lib

But now you can do so using the token:

surf.include.resources=org/alfresco/aikau/{aikauVersion}/libs/doclib/doclib.lib

Applying the Open Core Model in Alfresco 5.1

With the recent release of Alfresco Community Edition 201602, and Alfresco One 5.1, the latest version of Alfresco is ready for broad adoption. The 5.1 release has exciting new capabilities that will benefit organizations large and small, and we want to be clear about which capabilities are in which product edition. This lengthy blog post explains how we decided which innovations to open source, and which we reserved for our Alfresco One subscribers. It illustrates how we have been applying the principles laid out by Alfresco founder John Newton when he reiterated Alfresco’s commitment to a strong open source product. By being transparent in our approach to open source, we hope that people who adopt Alfresco Community Edition will have confidence that it will continue to meet their needs.

For those who want to skip the rationale, here are the highlights:

  • The technology for Smart Folders is included in Community Edition and is completely open source.
  • The technology for Custom Model Management is included in Community Edition and is completely open source.
  • Alfresco Office Services (AOS) is included as an optional proprietary module in Alfresco Community Edition.
  • Records Management for Community Edition continues to improve, but we are also creating a proprietary Records Management module for Alfresco One that will contain advanced features tailored to specific use cases.

This post focuses on how we make decisions about differentiation between our open source and proprietary products. There are lots of additional improvements to Alfresco Community Edition and Alfresco One that are beyond the scope of this post. Also beyond the scope of this post are the new features of Activiti and our mobile apps.

Objectives of Alfresco Community Edition

Alfresco Software is a business with ambitious goals for growth and impact. Open source is an important part of our strategy because of the value it provides to customers, how it contributes to adoption of our products, and how it increases our rate of innovation.

We have four specific objectives for Alfresco Community Edition:

  • Be the default ECM solution, increasing ECM competence throughout the industry.
  • Foster product innovation as we bring the best contributions back into the product.
  • Spread community contributions to benefit all users of the product.
  • Be an on-ramp to our paid offerings.

There is a natural tension between some of these objectives. We want broad adoption of Community Edition, which requires a full featured product that everyone wants to use. But in order to continue to invest in innovation, we need to maintain a healthy business which requires us to reserve some value exclusively for our paid offerings. Openness is a core value at Alfresco Software, and we want to be transparent in how we draw this line so that people can participate in our open source community with accurate expectations.

Since Alfresco’s first statement about our open source strategy in 2009, we have been clear that Alfresco One is the product we design for scaled and production environments. But we are also proud that we produce an open source Community Edition product that is not crippled or unstable. Clearly defining the target audience for Community Edition helps us to make product design decisions. We have adopted an “open core” model that is distinctive because we do not limit the “open core” to a library or platform that is only useful to developers. Our “open core” is a collection of ECM use cases that we preserve as open source and which becomes the foundation of our proprietary products.

Defining Alfresco Community Edition

We have identified three use cases for Alfresco Community Edition:

  • Small deployments where the advanced capabilities of Alfresco One Enterprise Edition are not necessary,
  • Evaluation of new features and exploration of Alfresco on new projects,
  • Companies who want to keep ECM completely in-house, and have the IT expertise committed to expanding, customizing, maintaining , and collaborating around the open source product.

We believe that if we design for the first use case, we will also meet the other use cases while preserving the incentive to convert to our paid products as deployments grow and customers realize value from the platform.

So when evaluating a feature for Alfresco Community Edition, we ask if it meets the following criteria:

  • Important for deployments of less than approximately 100 users,
  • Single-server,
  • Non-mission critical, since we do not offer formal support,
  • Consists of a horizontal capability with broad applicability.

Examples of horizontal capabilities include document management, collaboration, case management, compliance, and business process management

We further reserve functionality for Alfresco One that fits one of these criteria:

  • Proprietary connections and 3rd party plugins. If our product is being used alongside other products that require subscriptions, we should share in the value of the solution. Examples include integration with MSSQL, Oracle DB, Amazon S3, Kofax, and Centera.
  • Scalability, high availability, advanced security, and related configuration. These capabilities are important for enterprise use cases running at enterprise scale. Examples include clustering, high performance content transformations, the LDAP configuration assistant, JMX administration, and encryption at rest.
  • SaaS products and 3rd party plugins that increase our delivery costs on a per-client basis.
  • Specialist applications and niche use cases, such as Contract Management and Media Management.

It is important to recognize that none of these criteria are absolute, rather they are intended to guide our thinking around product design. Specifically, we have no intention of limiting the product to 100 users, as we understand that any artificial restriction to an open source product would just be removed. Choosing a specific number helps us in our calculations around pricing and support, and it orients us when discussing features. Our research shows that the overwhelming majority of Alfresco Community Edition deployments are well under 100 users, and those deployments are usually successful with that product. When deployments grow larger, or when an organization can not tolerate even a short disruption to their systems managing content and processes, then the importance of advanced features, expert support, and Certified Partners grows. Of course some smaller deployments will still need to buy Alfresco One to meet their specific needs, and some larger deployments will be inclined to invest the effort to continue with Alfresco Community Edition as the basis of their ECM strategy.

In order to make Alfresco Community Edition be the default content repository in the industry, we need to market it broadly and aggressively. Organizations of all types should see immediate value in Alfresco Community Edition, and be free to evaluate Alfresco One when their use cases grows to the point that they can realize the value in the offering.

Specific Innovations in Alfresco 5.1

The above criteria leads us to include in Alfresco Community Edition features like Smart Folders and Custom Model Management. These are broad capabilities that are important for a wide variety of ECM solutions. We are proud to open source these innovations.

The latest release of Alfresco Community Edition also includes a significant upgrade to our integration with Microsoft Office through the replacement of the implementation of the SharePoint Protocol provided by the VTI module with the newer Alfresco Office Services (AOS) module.  AOS is a complete reimplementation of the latest standard of the protocol, and includes significant testing to ensure interoperability. It will be easier to maintain and improve as Alfresco and Microsoft Office continue to evolve.

Integration with Microsoft Office is important for broad adoption of our product because it is broadly applicable, including in small deployments. But it is also a proprietary integration that we can use as the foundation for advanced niche capabilities targeted at large enterprises. This leads us to include AOS in Alfresco Community Edition as an optional proprietary module so that most users can benefit, but those who want a pure open source platform are not required to use it. For those who want to collaborate around an open source implementation of the SharePoint protocol, the VTI library is still available under the LGPL and we are willing to assist you in your efforts.

The AOS functionality is currently identical between Alfresco Community Edition and Alfresco One. Though the source is not available, AOS is included as a default module in Alfresco Community Edition. This is a change from our previous stance toward ubiquitous proprietary software, but we think it is the right approach in a world of cloud services and bring-your-own-app IT.

A Product Tailored to Its Intended Use Case

The criteria we use to define Alfresco Community Edition might also lead us to remove some features. As an example, in Alfresco 5.0 we deprecated the social publishing framework. It was a proprietary integration and served a niche use case, but we didn’t consider removing it until we were faced with the challenge of keeping these integrations current with the various publishing services. Now it is part of our proprietary Media Management module where the investment in maintenance and development is tied to the specific customers who purchase it.

Similarly, we have discussed whether multi-tenancy or replication really belong in Community Edition, as these are not single server uses cases that are important in small installations. This is a good illustration of how we think about product design, but we don’t currently have plans to remove these capabilities.

In our analysis of Alfresco Community Edition, we did not identify any other existing features that we felt should be removed based on the use cases we are targeting.

The Future of Records Management

Some of the customers of our Records Management module have been asking us for advanced features which are tailored to specific governmental use cases and that don’t apply to smaller deployments. We have decided to make these part of a new Alfresco  Records Management  module that is intended to work on licensed versions of Alfresco One. Specifically, these features are:

  • Security clearance management and property controlled access,
  • Management of classified content, including classification guides and declassification review (necessary for the DOD5015.02 certification for classified records),
  • Delegation of RM administration, which is useful in the management of very large scale deployments,
  • Capstone email compliance, which is an email archiving capability designed to meet the requirements of the U.S. Federal Government,
  • Verifiable wipe and data destruction as required by certain national governments.

In order to implement the new security clearance capability, we will deprecate the “supplemental markings” feature, and the related “lists of values” administration screen that were previously part of the open source release. We don’t think this impacts our open source community, as these features were not widely used. We plan to remove them and only maintain our proprietary security clearance capability that takes an entirely different approach to managing security clearance markups. The open source module will also gain a clearer way of managing Transfer Locations.

The next release of the Records Management module for Alfresco Community Edition will be compatible with Alfresco Share 5.1 and the 5.1 Platform. It will contain bug fixes and the improvements to the APIs and core services that are necessary to implement the new Enterprise-only features. As we better understand the use cases around these new features, some aspects of them might move into the Community RM module in future releases.

We will continue to enhance the Records Management module for Alfresco Community Edition, and it will be the core of the Enterprise RM module. We are working to move our RM development to git so that it is easier for you to track our enhancements and easier for you to contribute through pull requests. We are increasingly taking this approach with projects like Activiti, Aikau, and our mobile applications. We plan to incorporate these improved practices into our development for Alfresco Community Edition and Alfresco One.

Closing

We are working to focus Alfresco Community Edition on its target market, while also producing features for advanced use cases that can drive the Alfresco One business necessary for us to continue to invest in our products. As John Newton said in 2009: we aim to be guided by principles that are fair and accountable, as we believe that our model will result in a stronger open source product.

By clarifying the role Alfresco Community Edition plays in our product portfolio, we saw the need to increase our pace of open source innovation. In the six months before the Generally Available (GA) release of Alfresco 5.1, we released Alfresco Community Edition four times. Two of those releases incorporated major new features. We completed more QA testing on Alfresco Community Edition 201602 than for any previous release. And now that the product is out the door, we are excited to start pushing our next round of development to the public source tree.

I look forward to hearing your thoughts and feedback on our efforts, and to working with you in the Alfresco open source community. If you want to continue this conversation in person, join my session at BeeCon. We have many more innovations to share with you in the months and years to come.