“Eddie would go”

by Dave Draper

Today we released version 1.0.6 of Aikau and would really like to get some feedback on what we’ve done so far. If you tuned into Tech Talk Live 83 then you’ll know that we’ve been busy breaking Aikau out of Share and the Alfresco release life-cycle and into its own GitHub project.

We’ve done this so that we can iterate on Aikau faster to support Alfresco modules (such as Records Management) and to try to engage better with the Alfresco Community. The Aikau team are working to one-week long Sprints with a release at the end of each Sprint (so you can expect a new release every Tuesday!). During each sprint we will be adding more features and fixing any bugs that have been found in the previous sprint but always prioritizing bugs over features.

For the last few releases we have predominantly been focusing on the infrastructure of the Aikau project, i.e. moving the code to GitHub and ensuring that Aikau can be developed and tested outside of the internal Alfresco eco-system. This is where you come in…

We’d be really grateful if you could do one of two things for us…

1. Clone the GitHub repository, follow the development environment setup instructions (available for Linux, Windows and Mac) and check that you can build Aikau, start the test app and run the unit test suite on the Vagrant test VM.

2. Start working your way through the tutorial that will take you through the process of creating a new standalone client using the new Maven archetype. We’ve written 20 chapters of the tutorial and have so far ported 6 into GitHub markdown format. In each sprint we’ll be porting more and then writing more chapters. The tutorial has been road tested by quite a few people internally but we’d really like some external feedback on it (e.g. if there are things that aren’t clear or steps that don’t work).

We’re also interested in your contributions, bug reports and feature requests. We’ve defined some contribution guidelines to try to make the criteria for accepting contributions as transparent as possible which we will probably adjust over time as necessary to encourage active participation.

There’s still a long way to go for the Aikau project – some of the widgets are still only beta quality, and some still only work when used within Alfresco Share – but we’re making good progress. Over the coming weeks you should hopefully see new and improved widgets, more tutorials, publicly accessible JSDocs and improved test coverage.

In the meantime, give it a go and let us know what you think. Please provide feedback via the comments section or Tweet me directly at @_DaveDraper – many thanks in advance!

The Alternate Realities of Share Development

By Dave Draper, Kevin Roast, Erik Winlöf and David Webster

Introduction

Over the last 4 years (from versions 4.0 through to 5.0) there have been a number of changes in relation to Share development and customization.

From an outside perspective the decisions that have been made might appear confusing or frustrating depending upon your particular use case of Share. If you’ve written Share extensions or customizations for previous versions then you might have hit breaking changes between major releases and you might be fearful of it happening again.

It might seem that we don’t care about these issues. We’re sorry if you have experienced such problems but we can assure you that we try our very best to move Share forwards without breaking what has gone before.

In this post we’re going to highlight how things are better than they would have been if different decisions were made.

Decision #1 – Extensibility model

Back in 2010 we introduced an extensibility model that enabled us to dynamically change a default Share page via an extension module. This was an initially coarse customization approach that enabled Surf Components to be added, removed or replaced as well as our custom FreeMarker directives to be manipulated.

This in turn paved the way for us to refactor the Share WebScripts in 4.2 to remove the WebScript .head.ftl files and push the widget instantiation configuration into the JavaScript controller.

If we hadn’t have taken this approach then the method of customization would still be copy and pasting of complete WebScript components to the “web-extension” path.

This wouldn’t have stopped breaking changes between versions (e.g. the REST API changing, the Component calling a different WebScript, different properties or URL arguments being passed, etc) and would have required constant manual maintenance of those WebScripts with code from service pack fixes as necessary.

Decision #2 – New Java based client

We know that a few customers still use heavily customised versions of Explorer and the fact that we’ve finally removed it from 5.0 is going to cause pain to a few.

It has been suggested at various points over the last 4 years that we could create a brand new client to replace Share – even though Share does not yet have complete feature parity with Explorer. We recognise now that we need to invest in Share and improve it over time since creating a new client would ultimately introduce more problems than it solves.

However, when calls were strongest for writing a new client, the recommendation was to move to either GWT or Vaadin. We’re fairly sure all those people that lament the fact that this week we’re not using Angular would be horrified if they were now stuck with a Java based client that doesn’t have feature parity with Share (let alone Explorer).

A new Java based client would have guaranteed that all those customizations would have to be re-written from scratch.

Decision #3 – Aikau

In our opinion it feels like some people miss the point of Aikau. Often we field questions along the lines of:

  • “Why do we have to use Dojo?” (you don’t)
  • “Why have you written your own JavaScript framework?” (we haven’t)
  • “Why aren’t you using Angular/Ember/React/Web Components?” (many good reasons; customization and configuration requirements, framework stability, performance etc.)

As it was said on the Product Managers Office Hours last week; “web technologies change every 3 years”. It’s probably even more often than that. In the short time that Share has existed there has been a constant changing of the guard for “best web development framework”.

Even if we started again tomorrow with Angular (the current populist choice), we’d be doing so in the knowledge that there will be breaking changes when Angular version 2.0 is released next year and that in a few years we’ll (allegedly) all be using Web Components anyway.

The long and short of it is that unless you’re writing an application that is only going to have the lifespan of the carton of milk in your fridge then binding yourself to a single JavaScript library will be a mistake.

With Aikau we’re trying to mitigate that problem through declarative page definition. Yes, you have to write a bit of AMD boilerplate but really the choice of JavaScript framework is entirely in your hands.

Aikau isn’t tied to Share (theoretically it’s not even tied to Surf) so if we do ever do switch to a new client then the existing widgets and your custom widgets will still be applicable. We’re also evaluating breaking Aikau out of the Share development life-cycle so that we can make new widgets and bug fixes available faster.

Will there be more breaking changes?

We’ve been talking about re-writing the Document Library using Aikau for a while now (and have a pretty good prototype already) along with the other site pages. However, just as the old header Component still exists in Share, the original pages will still remain so you’ll always be able to configure Share to use the current YUI2 Document Library with your customizations.

There is also a lot of talk about re-writing the rest of Share in Aikau. Whilst we think this is ultimately a good idea we don’t think it’s worthwhile until we’ve gone to the trouble of evaluating and improving the current design…. do you really only want a carbon copy of the current Wiki, Blogs and Data List pages? We’ll get there in time, but we’re not sure there’s any great rush to get this all done for 5.1.

Summary

Ultimately, any web interface needs to keep modifying its underlying technology. Aikau gives us a way to do that with the least possible pain. We understand that some developers have gone through some suffering with breaking changes in the last few releases, but through the use of Aikau we expect this pain to decrease and customisations to become more stable, transferable and powerful.

We’re working to add transparency to the development process that will hopefully make what we’re working on more obvious and make it easier for external developers to predict what changes there may be in future Share releases.

Aikau – Using the AlfDocumentPreview Widget

Introduction

This afternoon I saw a Tweet asking if there were any examples of how to use the AlfDocumentPreview widget.  Aikau documentation is currently very thin on the ground (as you’re probably no doubt painfully aware) so I thought it would be worth writing up a quick blog post to describe how we use it and how you can too. If there’s anything that you want more information on then it’s worth Tweeting me @_DaveDraper with a request – I can’t guarantee that I’ll be able to write it up as a blog post, but I will try to do my best as my time allows!

Background

Most of the Aikau widgets are completely new, some are “shims” around existing YUI2 based code and a few are direct ports of YUI2 widgets. The AlfDocumentPreview widget (and it’s associated plugins) is a good example of a ported widget. The original code was copied into an Aikau widget definition and then most of the YUI2 code was replaced, bugs were fixed and thorough JSLinting applied.

You might wonder why we’d go to such lengths when a widget already existed. This essentially gets right to one of the fundamental points of Aikau as a framework. The code inside the widget really isn’t important – what’s important is defining an interface to a widget that performs a single specific task that can be referenced in a declarative model. The widget becomes an API to a piece of UI functionality – in this case, previewing a widget.

Every Aikau page model that references it will never need to change – even if we decide to completely rewrite the widget to use JQuery, Angular, Web Components or whatever happens to be the current flavour of the month – the pages will function as they always have.

Where is the previewer used?

The rule of thumb that I tell anyone asks me, is that if Alfresco has used an Aikau widget in a product feature then it’s fair game for use in your application or extension. There are a number of widgets that are definitely beta quality (and we call these out in the JSDoc) which might be subject to change, but once it’s been used in a feature then we’re obliged to maintain backwards compatibility and fix any bugs with it.

The AlfDocumentPreview is currently being used in the new filtered search feature that is part of the 5.0 release (and you’ll also find it used in the Film Strip View that is part of the prototype Aikau based Document Library which is not yet a product feature!). If you click on the thumbnail of any document (that is not an image) then a new dialog is opened that contains a preview of that document. The preview will render the appropriate plugin (e.g. PDF.js, video, audio, etc) for the content type.

The filtered search page in Alfresco Share 5.0

The filtered search page in Alfresco Share 5.0

A preview of a search result

A preview of a search result

How it works

Each row in the search results is an AlfSearchResult widget that contains a SearchThumbnail widget. When you click on the thumbnail widget (of the appropriate type) then a payload is published on the “ALF_CREATE_DIALOG_REQUEST” topic to which the AlfDialogService subscribes. The payload contains a JSON model of widgets to render in the dialog when it is displayed. The model is an AlfDocument widget that contains an AlfDocumentPreview widget.

...
widgetsContent: [
  {
    name: "alfresco/documentlibrary/AlfDocument",
    config: {
      widgets: [
        {
          name: "alfresco/preview/AlfDocumentPreview"
        }
      ]
    }
  }
],
...

The point of the AlfDocument widget is to ensure that all of the relevant Node data is available to pass to a child widget (in this case the AlfDocumentPreview – but it could be something else) so to do something with.

One of the key things about the search page is that search requests only return a very limited amount of data about each node (unlike requests from the Document Library which are slower but contain much more information such as all the properties and the actions permitted for the current user).

An additional XHR request is required to obtain all the data required to preview the node. The payload published when clicking on the thumbnail also contains the publication to make once the dialog has been displayed:

...
publishOnShow: [
  {
    publishTopic: "ALF_RETRIEVE_SINGLE_DOCUMENT_REQUEST",
    publishPayload: {
      nodeRef: this.currentItem.nodeRef
    }
  }
]
...

The “ALF_RETRIEVE_SINGLE_DOCUMENT_REQUEST” is serviced by the DocumentService and the AlfDocument subscribes to successful document loaded publications (note that the SearchThumbnail will have a “currentItem” attribute set containing the limited data returned by the search request which will contain a “nodeRef” attribute).

The AlfDocument only processes it’s child widgets once it has some data about a specific node. Once the DocumentService has published the node data then it will process the AlfDocumentPreview widget. From that point on the AlfDocumentPreview will use the data that has been provided to create the appropriate plugin to preview the document.

Other Ways to use AlfDocumentPreview

You don’t have to use an AlfDocumentPreview within an AlfDocument, you just need to ensure that you provide it with node data as the “currentItem” configuration attribute. So if you already have the all the data (for example if you’ve made a request from within your JavaScript controller or if you are accessing it from a list that has been generated from the REST API used to service the Document Library) then you can configure it into the widget directly.

The following is an example of a simple Aikau page model that previews a document (obviously you need to swap in your own nodeRef!):

model.jsonModel = {
  services: ["alfresco/services/DocumentService"],
  widgets: [
    {
      name: "alfresco/documentlibrary/AlfDocument",
      config: {
        nodeRef: "workspace://SpacesStore/7d829b79-c9ba-4bce-a4df-7563c107c599",
        widgets: [
          {
            name: "alfresco/preview/AlfDocumentPreview"
          }
        ]
      }
    }
  ]
};

You also don’t need to display it in a dialog either.

Once again this should hopefully demonstrate how you can re-use Aikau widgets to achieve very specific objectives – try doing using the YUI2 previewer in isolation and then you’ll understand why it’s been ported!

Summary

Hopefully this has provided both a useful description of how we’re currently using the AlfDocumentPreview widget (as well as how we’ve configured pub/sub in the filtered page to link widgets and services). If anything isn’t clear or you have further questions then please comment below.

Creating Aikau Site Pages for Share

Introduction

The Aikau framework provides a simpler way of creating pages in Share where a page can be declaratively defined as a JSON model in a WebScript. This avoids the necessity to create the XML and FreeMarker files for Surf Pages, Templates and Components.

I’ve been asked how you would create an Aikau page such that it is available as a site page in Share (e.g. a page that can be added via the Site Customization tooling in Share). So thought it would be worth capturing this information in a blog post. This is one of those interesting use cases where the old and new approaches of Share development intersect…

Background

In Share we use “pre-sets” configuration to provide default User and Site dashboards. These are XML configurations that define the Surf objects that can be used to “cookie-cut” new page instances (which are then stored on the Alfresco Repository).

The pre-sets can be found in in this file and the “site-dashboard” pre-set contains a property (“sitePages”) that defines the initial set of pages for each site. Once the site is created a new Surf Page instance is created on the Repository and when you add or remove pages from the site it is this property that is updated (in the instance, not the pre-set).

The “Customize Site” page lists both the available “Site Pages” and the “Current Site Pages” and the list of pages to choose from is defined in the “share-config.xml” file under the “SitePages” condition, e.g:

<config evaluator="string-compare" condition="SitePages">
  <pages>
    <page id="calendar">calendar</page>
    <page id="wiki-page">wiki-page?title=Main_Page</page>
    <page id="documentlibrary">documentlibrary</page>
    <page id="discussions-topiclist">discussions-topiclist</page>
    <page id="blog-postlist">blog-postlist</page>
    <page id="links">links</page>
    <page id="data-lists">data-lists</page>
  </pages>
</config>

It’s possible to extend this configuration to include additional pages, however the underlying code currently assumes that each page is mapped to a Surf object. This means that if you want to add in an Aikau page to this list then you need to create a Surf Page object (even though it won’t actually be used to render the page at all).

Example

Say you want to add in a new Aikau page called “Example”. You need to create a Share configuration extension that defines the new page (one way of doing this would be to create a “share-config-custom.xml” file that you place in the “alfresco/web-extension” classpath).

The file would contain the following XML:

<alfresco-config>
  <config evaluator="string-compare" condition="SitePages" replace="false">
    <pages>
      <page id="example">dp/ws/example</page>
    </pages>
  </config>
</alfresco-config>

But you’d also need to create a Surf Page XML file (placed in the  “alfresco/site-data/pages” classpath) containing:

<?xml version='1.0' encoding='UTF-8'?>
<page>
  <title>Example Site Page</title>
  <description>Example of adding a new site page</description>
</page>

Which would result in the following being shown when customizing a site:

The Customize Site page showing an Aikau page.

The Customize Site page showing an Aikau page.

 

Alfresco Records Management 2.3.b Community

The latest community release of RM 2.3 is now available and provides support for 5.0.b.

There are, however, a couple of new features that are worth mentioning.

Move In-Place Records

It is now possible to move an in-place record once it has been declared.

The movement of an in-place record does not effect the location or management of the record in the file plan it just allows the user to reorganise their working environment, placing the in-place record wherever makes sense to them.

Improved Rejected Record Handling

When a record is rejected by the records management team it is now easy for the user to understand why and take action.

Once understood the rejection warning can be removed from the document allowing users to later resubmit the document as a record if the circumstances are right.

Annotated Behaviours

Support for annotated behaviours has been hiding in RM for a little while now. 2.3.b see’s this generally useful feature moved into 5.0.b and made available for everyone to use.

Look out for a separate post with more details.

Is that it?

No that isn’t everything, but the really interesting stuff isn’t quite ready so has been disabled in this build.

I’m not even going to talk about it yet, you will just have to wait for the next community release later this year!

Further options for functional unit testing of Aikau

This post follows on from the getting started post which you should run through first. It includes some free software that you may need to install before you can proceed.

Introduction

Earlier I introduced the basic functionality of the testing framework we have created for Aikau. In addition to testing Aikau on a couple of browsers within a predefined virtual machine, we have some options to test locally and to generate a coverage report on the spread of the Aikau testing. Here you will find some of these use cases described.

Local testing – why?

Testing against the browsers in a virtual machine is great because any individual can test against the same known set of browsers, irrespective of the platform they are using themselves. You might want to test a specific browser on a specific platform to analyse a platform-specific error and doing a local test could be the best way to do that. More important however is that the virtual machine testing is a little hidden. Whilst it is possible to interrogate selenium to see what it has been doing during a test, and Intern provides the capability to record a screen capture at a specific moment in a test, it can quite often just be easier to see a test running. Errors are often very easy to see when you are just looking at the browser window that Intern is driving.

Local testing – how

In my previous post about testing Aikau we used the following command to install an array of packages required for the testing:

>> npm install

One of the packages installed by that command is selenium-standalone. The selenium-standalone package installs a local instance of selenium with a wrapper and symbolic link to allow it to be launched from the command line, anywhere. You should be able to write the following command in any command prompt and see a small trace as selenium is started on ip and port 127.0.0.1:4444:

>> start-selenium

Note: All of these commands are run in directory:

{alfresco}/code/root/projects/slingshot

Note: Depending on the platform you are using you may need to reinstall selenium-standalone as a global package. If the command above does not work, try reinstalling selenium-standalone globally either by itself:

>> npm install selenium-standalone -g

…or with all of the other dependencies:

>> npm install -g

If you’re able to launch selenium using the standalone command shown above and you have the required browsers installed then you should be able to proceed. At time of writing the two browsers referenced by the ‘local’ intern file are Firefox and Chrome.

Launching a local Intern test is as simple as:

>> g test_local

Note: This command is ‘g’ for grunt and ‘test_local’ for run intern test suite (locally).

Now because this test scenario is going to drive the browsers on your machine you probably want to leave the mouse and keyboard alone whilst the tests are running. If you’re happy to watch the entire suite run then do just that. If however you are working on one particular test and want to keep quickly running that one, open the Suites.js file and comment out all of the tests you’re not interested in, from variable ‘baseFunctionalSuites':

{alfresco}/code/root/projects/slingshot/tests/config/Suites.js

The output from local testing is identical to that seen with the virtual machine.

Note: The structure of the selenium-standalone package is quite simple and if you have an unusual browser for which there is a selenium driver in existence, you can modify selenium-standalone to support it. All that remains is to add the required browser to the Intern configuration file and you’re good to go.

Generating a code coverage report

A code coverage report can be produced by running a suite of tests with the code pre-instrumented to report on it’s use. Commands have already been written into the grunt framework that perform all of the required steps for code coverage.

Code coverage reports are generated locally, so follow the instructions shown above but use the following command for the testing phase:

>> g coverage-report

Once this has completed, which will take slightly longer than basic testing, you will be able to visit the node-coverage console here:

http://localhost:8787/

You should see the report you have just generated with scoring and which you can look through all of the files touched by the testing. The tool shows areas of code that remain unvisited or through which not all of the pathways have been found. This feature can be very useful when writing tests to make sure edge cases have been considered.

Note: A particularly poor test result with numerous failed tests will distort the scoring of the coverage report, sometimes in an overly positive way. Make sure all of your tests are passing reliably before assuming that the scoring from the coverage report is accurate.

Aikau – Simple Picker Updates

Introduction

In my last blog post I described how I had updated the dynamic visibility configuration options in order to support a new picker widget that I was working on. In this post I’m going to describe how to use this new picker.

PLEASE NOTE: This widget has only just been added to the Alfresco SVN repository so will only be available in the nightly builds or in the Alfresco 5.0.c Community and Alfresco 5.0 Enterprise releases.

The Simple Picker Widget

This new picker widget is defined in the “alfresco/forms/controls/SimplePicker” module and is intended to be used in forms where the user might need to select one or more items from a list. This widget re-uses much of the same code implemented for the more complex form pickers (e.g. for selecting documents, folders or properties) but distils the underlying JSON model into a small configuration set that is intended to make it as simple as possible get a picker onto your page.

The this list of available items is unsurprisingly a re-use of the existing “alfresco/lists/AlfList” widget and the items can be populated either by directly configuring a “currentData” attribute or by configuring a “loadDataPublishTopic” (with an optional “loadDataPublishPayload”).

Let’s start with a really simple configuration and build up towards a more realistic use case. All these examples are included in this JAR file for you to try out for yourself.

Basic Hard-coded Data

Let’s start with some a really simple picker containing some hard-coded data that will be set using the “currentData” attribute:

...
{
  name: "alfresco/forms/controls/SimplePicker",
  config: {
    currentData: {
      items: [
        {
          name: "One"
        },
        {
          name: "Two"
        },
        {
          name: "Three"
        }
      ]
    }
  }
}
...

This configuration will result in the following picker:

This is a simple picker constructed with hard-coded data.

This is a simple picker constructed with hard-coded data.

Just by providing some data we’re able to build a picker, however it is worth nothing that our data has been tailored to fit the SimplePicker defaults (using “items” to identify the list and “name” to identify both the unique item key and displayable property) but I’ll show how these can be configured later.

Simple Picker is a Form Control

Let’s not forget that the Simple Picker is a form control and as such we can use any of the standard form configuration attributes… so let’s update our picker to include a label, description and the request parameter name that would be posted on form submission.

We’re also going to make the selected items re-orderable by setting the “reorderable” attribute to true:

...
{
  name: "alfresco/forms/controls/SimplePicker",
  config: {
    label: "Pick Some items",
    description: "This is a simple picker with some hard-coded data",
    name: "hardcoded",
    reorderable: true,
    currentData: {
      items: [
        {
          name: "One"
        },
        {
          name: "Two"
        },
        {
          name: "Three"
        }
      ]
    }
  }
}
...

This results in the following picker (with all the items selected to show the re-ordering controls):

The simple picker with label, description and re-ordering enabled

The simple picker with label, description and re-ordering enabled

Nothing to Display?

You have control over all of the messages that are displayed in the picker. One of which is the message shown when there is no data. This is done by setting the “noItemsMessage” attribute.

PLEASE NOTE: In my examples I’m using hard-coded strings, but these would typically be replaced with localization keys for multi-language support in a real-world example.

...{
  name: "alfresco/forms/controls/SimplePicker",
  config: {
    label: "No Items Picker",
    description: "This examples shows a custom message to show when no items are available for picking.",
    name: "nothingAvailable",
    noItemsMessage: "Nothing to pick!",
    currentData: {
      items: []
    }
  }
},
...

This will result in the following picker:

A Simple Picker with a custom message when no items are available for selection

A Simple Picker with a custom message when no items are available for selection

 One at a Time Please!

If we want to configure our picker to only allow a single item to be selected then we can set the “singleItemMode” to be true. This also has the effect of overriding the “reorderable” attribute (because what’s the point in having re-ordering controls when you can only ever have one item selected).

The effect of this is that if you have an item already selected, then selecting another will remove the previously selected item.

Accessing Server Side Data

For a slightly more real-world example let’s configure our Simple Picker to allow us to select from the User Groups that are configured in the Alfresco Repository. This means that we replace the hard-coded “currentData” object with a “loadDataPublishTopic”. We’re going to be using the trusty “alfresco/services/CrudService” for this so you’ll need to remember to include it in the list of services.

As well as configuring the data source we also need to configure how we handle the resulting data:

...
{
  name: "alfresco/forms/controls/SimplePicker",
  config: {
    label: "Pick User Groups",
    description: "This is a simple picker publishes a request to get data to display",
    name: "groups",
    loadDataPublishTopic: "ALF_CRUD_GET_ALL",
    loadDataPublishPayload: {
      url: "api/groups?zone=APP.DEFAULT"
    },
    itemsProperty: "data",
    itemKey: "shortName",
    propertyToRender: "displayName",
    availableItemsLabel: "Available Groups",
    pickedItemsLabel: "Currently Selected Groups"
  }
}
...

Here is a break-down of the attributes used:

  • “itemsProperty” identifies the attribute within the JSON response from the server that contains the list of items to display. This could be a dot-notation address in the JSON object if necessary.
  • “itemKey” should be set to an attribute in each item that is a unique identifier. This is important to get right because it could result in multiple items getting picked if there are duplicate keys
  • “propertyToRender” is the attribute in each item to use as the display value. Note that we use “shortName” as the “itemKey” (because it is unique) but “displayName” as the “propertyToRender” because it is the user-friendly group name

For added fun we’re also adding labels to the available and picked item columns using the “availableItemsLabel” and “pickedItemsLabel” attribute.

The resulting picker looks like this:

SImple Picker configured to access group data from the Alfresco Repository

SImple Picker configured to access group data from the Alfresco Repository

Still Not Enough?

Finally, if you’re still not happy with how the available items and picked items are displayed then you can set “widgetsForAvailableItemsView” and “widgetsForPickedItemsView” which allow you to declare JSON models of views for both columns.

Although configuring these attributes will override some of the previously mentioned configuration options it does give you complete flexibility for rendering the data as you see fit. Once again this is demonstrating the key-strength of Aikau which is re-usabiliy. The views you configure for items are defined in exactly the same way as all views of data – this means that you can easily re-use previously defined views or reference your own custom widgets.

Summary

Hopefully this gives another indication of where we’re trying to take Aikau. Maximising code re-use, attempting to distil complex widget models into simple to configure widgets but retaining the ability to fully customize them as required.

 

 

Getting started with functional unit testing of Aikau

Introduction

With the Aikau framework for Share construction we have created a large number of widgets with some underlying code from Dojo’s UI Library, Dijit. It makes sense to automatically test the Aikau widgets in isolation and therefore we have created a test framework using Intern (http://theintern.io/) to do just that. The tests run quite fast and with predictable results. It is now easy to see if a recent addition or update to an existing widget has caused a bug or regression.

To perform tests we have written a number of test web scripts that contain widget models. The widget models are usually quite simple but occasionally have a more complex structure to expose edge case behaviours. Some test web scripts also contain intentionally broken models to test the catching of errors. For testing purposes the test web scripts are run using Jetty as we do not need any of the more complex overhead from Share.

Process Overview

To run a suite of tests we do the following:

  1. Launch an instance of an Ubuntu virtual machine using Vagrant
    The virtual machine provides a set of test browsers, drivers for those browsers and a running instance of Selenium that Intern can target
  2. Run up the test web scripts using Jetty
    We do not need all of Share so a basic Jetty server is fine here
  3. Run tests against the test web scripts using Intern
    Each of the tests will be run according to the Intern configuration and against the browsers defined and requested therein

Note: One of the tools we install when setting up testing is Grunt (http://gruntjs.com/). Much of the process of running tests is handled for us in commands we have already added using Grunt. In fact, once we have set up the testing framework for the first time, we really only need two commands to start a test run – firstly launch the virtual machine and then secondly run the tests.

Step 1 – Prerequisites

The testing framework for Aikau makes use of a number of technologies and you will need the following free downloads to proceed. Please install with the default settings:

I am assuming that you can use a command line and have a vague idea of what a virtual machine and Vagrant are. If not, please read about it first: http://www.vagrantup.com/

Step 2 – Installation

Having downloaded and installed a current version of the Alfresco code base, open a command prompt and navigate to the following Alfresco directory:

{alfresco}/code/root/projects/slingshot

Run the following command to install Node dependencies:

>> npm install

If you’re interested to know what is being installed you can look at file package.json in the directory above which contains a list of the dependencies. Once you have installed all of the components required for the test framework, you should be able to launch a virtual machine with vagrant for the first time. The first time you run this process it may be slow as it has a lot to download:

>> g vup

Note: This command is ‘g’ for grunt and ‘vup’ for vagrant up.

When the ‘g vup’ command has completed, if it has been successful you should be able to observe the Selenium instance running here:

http://192.168.56.4:4444/wd/hub/static/resource/hub.html

The selenium console should look something like this:

selenium

Step 3 – Running the suite of tests

With an instance of Selenium available on the virtual machine, you should now be able to run Intern by issuing the following command:

>> g test

Note: This command is ‘g’ for grunt and ‘test’ for run intern test suite (virtual machine).

This command first checks if the test application server is running and launches it if not. Once it is happy that the server has started completely it will proceed to launch the Intern test runner. You should see the test suite run through a large number of tests (about 110 tests run twice for two different browsers at time of writing) and log them to the console. Hopefully they will all pass.

This is the sort of output you should expect to see once the initialisation steps have been performed:

...
>> Starting 'AccessibilityMenuTest' on chrome
>> Test page for 'AccessibilityMenuTest' loaded successfully
>> AccessibilityMenuTest: Find the menu element
>> AccessibilityMenuTest: Find the heading text
>> AccessibilityMenuTest: Find the menu items
>> AccessibilityMenuTest: Find the first target
>> AccessibilityMenuTest: Find the second target
>> AccessibilityMenuTest: Find the first menu link - which links the first target
>> AccessibilityMenuTest: Hit the browser with a sequence of different accesskey combinations and the letter 's' for a nav skip
>> Starting 'SemanticWrapperMixinTest' on chrome
>> Test page for 'SemanticWrapperMixinTest' loaded successfully
>> SemanticWrapperMixinTest: Check NO_WRAPPER dom is correct
>> SemanticWrapperMixinTest: Check GOOD_WRAPPER dom is correct
>> SemanticWrapperMixinTest: Check BAD_WRAPPER dom is correct
>> SemanticWrapperMixinTest: Check LEFT_AND_RIGHT_WRAPPER dom is correct
>> Starting 'Pie Chart Test' on chrome
>> Test page for 'Pie Chart Test' loaded successfully
>> Starting 'PublishPayloadMixinTest' on chrome
>> Test page for 'PublishPayloadMixinTest' loaded successfully
...

Note: When a test run is complete the Jetty server is left running. If there are any failures it is possible to immediately investigate if something catastrophic has occurred by simply observing the test web script in a browser.

Useful commands

There are several grunt commands that can be used with Vagrant, the test application server and Intern. Here are the ones we’ve already seen and a few more, all of which should be run from the directory shown above:

Command Function
g vup Short for ‘vagrant up’ this will launch a virtual machine instance with Vagrant
g vdown Short for ‘vagrant down’ this will stop a running instance of a Vagrant virtual machine
g vclean Short for ‘vagrant clean’ this will delete an existing instance of a Vagrant virtual machine
g test Run up an instance of the test application server and run the Intern test suite against it
g shell:startTestApp Start the test application server
g shell:stopTestApp Stop the test application server if it is running
g nt Short for ‘new test’ this command will restart the Vagrant virtual machine, restart the test application server and finally run the Intern test suite against them
g utd Short for ‘update test deployment’ this command will bring down the test application server, rebuild slingshot and then relaunch the test application server with any file modifications that have been made

Adding a test

If you wanted to add a test of your own there are three steps to the process:

  1. Create a test web script
  2. Create an Intern test
  3. Add the test to the Intern Suites.js file

Let’s investigate those steps individually:

Create a test web script

Test web scripts all live in this location or a sub-directory of it:

{alfresco}/code/root/projects/slingshot/tests/testApp/WEB-INF/classes/alfresco/site-webscripts/alfresco

Each web script requires three files – a JavaScript file, a Freemarker template file and an xml description file. There are examples in the directory that can be looked at and copied. As with web scripts in the main applications the format of the file names is important. With a correctly written test web script you should be able to view the test model in action at a URL such as:

http://localhost:8089/aikau/page/tp/ws/AccessibilityMenu

This example test web script has in it’s model an AccessibilityMenu widget. It isn’t very pretty as rendered here but it isn’t supposed to be.

Create an Intern test

The actual test files are created here or in a sub-directory of it:

{alfresco}/code/root/projects/slingshot/tests/alfresco

Intern tests are written in JavaScript using a promise-based dependency called Leadfoot which is provided by SitePen, the company who wrote Intern itself. You can read the Leadfoot documentation here. Strategies for writing Selenium tests are complex and I’m not going to investigate them here. Needless-to-say, one emulates the behaviour of an individual using a browser to interrogate the test web script as rendered.

The specific way in which an Intern test addresses a test web script can be seen if any of the existing tests is viewed. Pay close attention to this part of most tests which is the point at which the test web script is loaded:

...
var browser = this.remote;
var testname = "AccessibilityMenuTest";
return TestCommon.loadTestWebScript(this.remote, "/AccessibilityMenu", testname)
...

Add the test to the Suites.js file

Tests that should be run are all listed in this file in the array ‘baseFunctionalSuites':

{alfresco}/code/root/projects/slingshot/tests/config/Suites.js

Next steps

The testing framework supports testing against a local instance of Selenium rather than a VM instance as described above. It is also possible to run a coverage report on the test framework to indicate the amount of the code base being interrogated. The details of these alternative scenarios will follow in another post.

Aikau – Updates to Dynamic Visibility Config

Introduction

In this previous blog post I introduced the concept of dynamic visibility rules in Aikau page models. In the course of working on some prototype pages for features pencilled in for Alfresco 5.1 I realised that there were a few useful capabilities missing. This post is going to cover the new options that are available for declaratively configuring visibility rules. PLEASE NOTE: These will only be available in Alfresco Community 5.0.c or Alfresco Enterprise 5.0.

Strict Mode

I was doing some more work on pickers (see this post for more info) and was trying to re-use the code we had for defining the classic picker style of 2 vertical lists with add, remove and re-order controls.

In the old YUI2 style pickers we had (e.g. for selecting tags) we don’t hide the selected item (just the control for adding it) and I wanted to improve on this so that a picked item would be removed from the list of available items (but would be revealed when removed).

The current visibilityConfig options prevented this because the rules were defined such that if a rule was not evaluated to be true then the widget would be hidden. In actual fact what we want is a more flexible mode where only successfully evaluated rules will reveal a widget, but failed evaluation will have no effect.

I’ve now added in an additional attribute called “strict” that is a boolean value (that defaults to true if omitted for backwards compatibility) that implements this feature, when a widget is configured with the following visibilityConfig:

...
visibilityConfig: {
  initialValue: true,
  rules: [
    {
      topic: "ALF_REVEAL_WIDGET",
      attribute: "reveal",
      is: [true],
      strict: false
    }
  ]
}
...

…then publishing the following payload on the “ALF_REVEAL_WIDGET” topic:

{
  reveal: false
}

Will not hide the widget, because the “strict” attribute is set to false. If the “strict” attribute was set to true then the publication would result in the widget being hidden.

Invisibility Configuration

When trying to use the original visibilityConfig I realised that it was sometimes difficult (if not impossible) to configure a rule that just hid a widget.

When setting up the rules for the picker widget I wanted to define one rule that made an item appear (when removed from the picked item list) and another rule to make the item disappear (when added to the picked item list).

The problem was that I only knew what an attribute in the payload should be and not what it shouldn’t be. Therefore I added the ability to configure “invisibilityConfig” as well as “visibilityConfig”.

This takes exactly the same attributes but when the rule is satisfied the widget will be hidden instead of revealed, e.g:

...
invisibilityConfig: {
  rules: [
    {
      topic: "ALF_HIDE_WIDGET",
      attribute: "hide",
      is: [true]
    }
  ]
},
...

…will result in a widget being hidden when the following payload is published on the “ALF_HIDE_WIDGET” topic:

{
  hide: true
}

NOTE: Because we’ve omitted the “strict” attribute publishing any other payload on the topic will result in the widget being revealed.

Using Current Item Data

In this previous blog I described how we could use the “currentItem” attribute for controlling when widgets are rendered. The “currentItem” attribute is typically assigned when to widgets that are created when iterating over items in a list (although any widget can be defined with a “currentItem” attribute).

in the picker example I wanted to make use of the “currentItem” attribute when defining “visibilityConfig” and “invisibilityConfig” because the rules would need to be specific to each rendered row in the list of pick-able items.

This was solved by adding in support for a new “useCurrentItem” attribute (that defaults to false) that treats each value in the “is” and “isNot” arrays as a dot-notation property address in the “currentItem”.

Putting It All Together

The end result of these 3 new attributes was the following configuration:

...
visibilityConfig: {
  rules: [
    {
      topic: "ALF_ITEM_REMOVED",
      attribute: "name",
      is: ["name"],
      useCurrentItem: true,
      strict: false
    }
  ]
},
invisibilityConfig: {
  rules: [
    {
      topic: "ALF_ITEM_SELECTED",
      attribute: "name",
      is: ["name"],
      useCurrentItem: true,
      strict: false
    }
  ]
},
...

This defines the behaviour that when an item that has the same name as the current item is selected (published on the “ALF_ITEM_SELECTED” topic) it will be hidden, and when an item that has the same name as the current item is removed (published on the “ALF_ITEM_REMOVED” topic) it will be displayed again.

 

Aikau Development Process Example – Inline Comment Rendering

Introduction

I’ve previously provided a few examples and techniques of how to implement a variety of use cases using Aikau, but now I’d like to provide an example of the process I follow to implement a solution. Whilst this process might not be suitable for everyone it should at least be informative and show the ways in which you can leverage existing widgets to achieve your use case.

As a background exercise I’ve been working on re-implementing the Document Library using Aikau and my aim is not simply to achieve feature parity but to improve what is currently available. Something that I believe could be a potentially useful addition would be the ability to view and add comments on a document without needing to access the details page for that document.

Step 1 – Break Everything Down

Rather than trying to tackle everything in one go (e.g. as a single widget) it’s important to determine whether elements of your target solution already exist and whether or not it is worth abstracting other elements into widgets that could be re-used in other use cases.

Almost everything in a web application boils down to lists, forms and layout and this use case is another example of that. We know that we want to be able to…

  1. See a list of existing comments (obviously a list)
  2. Add a new comment (a form)
  3. Edit an existing comment (another form)
  4. Delete an existing comment (an action on a list)
  5. Only reveal (and load) comments on demand (layout)

Aikau provides a good support for list rendering as shown in the previous Data List blog post – and in fact steps 1, 2 and 4 are almost identical to uses cases covered in that blog (it’s only the data that is changing).

We can therefore re-use one the Aikau list widgets (e.g. “alfresco/lists/AlfList“) and the “alfresco/services/CrudService” as a means to render the list, the “alfresco/dialogs/AlfDialogService” to pop-up a form that can be used to add a new comment and an “alfresco/renderers/PublishAction” widget to issue the delete request.

At this stage it’s worth noting a couple of important points…

  1. There could very well be more comments than can easily be accommodated
  2. We don’t yet have all the widgets we need to render the comments view as we’d like (in this case we don’t have a widget for rendering user avatar thumbnails).

The nice thing about Aikau is that you don’t need to get bogged down in such details because it’s easy to come back and iterate over your page models without needing to throw anything away.

  • It’s simple to swap out an “alfresco/lists/AlfList” for an “alfresco/lists/AlfSortablePaginatedList” widget and add in some pagination controls.
  • It’s easy to update a list view to add in additional renderers.

We want to simply get our commenting use case implemented as quickly as possible. Focus on the key goals, get your solution working and then iterate to polish it up to the required level.

Step 2 – Use What You’ve Got

Rather immediately diving into writing widgets at this point I started with a new Aikau page WebScript to get as far as possible with the previously mentioned widgets and services at my disposal. You should probably use the Alfresco SDK for this, but I prefer to create and work with files directly in my Tomcat structure for maximum speed.

Rather than worrying about integrating this comments list into my detailed view, I just want to get some data to work with so I create a new document in my Alfresco repository using Share and then add some comments to it. I then take a note of the NodeRef and use that directly in my page model because I know that I can swap it out for contextual data at a later point.

Use a browsers developing tooling it’s easy to capture the XHR requests that are made when working with existing comments and I can build this simple page model for listing the comments of my target node.

{
  name: "alfresco/lists/AlfList",
  config: {
    loadDataPublishTopic: "ALF_CRUD_GET_ALL",
    loadDataPublishPayload: {
      url: "components/node/workspace/SpacesStore/eb98d7af-c311-4ab8-9596-6fcda675fe6e/comments?reverse=true&startIndex=0&pageSize=10",
      urlType: "SHARE"
    },
    widgets: [
    ...
    ]
  }
}

I’ve not included the view definition here (which would appear in as the “widgets” array) because views have previously been covered in other blog posts.

We now able to render a list of comments for a given NodeRef.

Step 3 – Identify What’s Missing

From our breakdown above and with some knowledge of the Aikau widget library (e.g. by reviewing the JSDocs) we know that there a couple of key widgets missing…

  1. A widget for revealing and dynamically building new Aikau components (this is different from simply toggling visibility of previously rendered widgets described in this previous blog post which would be inefficient).
  2. The ability to toggle between reading and editing comments in an HTML editor.

Well, as it happens I’d already been working on editor widgets for another use case (inline content creation) and have a TinyMCE based HTML editor form control ready to go, but with no means to toggle between read and edit mode. We also need a widget for revealing and building new AIkau widgets.

Step 4 – Create Missing Widgets

The widgets I ended up building for this example are now committed to the Alfresco source code so I won’t be including them here. I also don’t want to go into too much detail on how they were developed as I want to cover widget development in another blog post, however I do want to call out a view key points in their development process.

The first widget I created was the “alfresco/renderers/EditableComment” widget which allows your to switch between a read-only view and an editable view of data which can then be persisted.

This widget is essentially a wrapper around a form that contains the “alfresco/forms/controls/TinyMCE” form control. When developing widgets we try to recognize that they may want to be either extended or re-configured in the future so we try to write them so that they can be used as they are with a minimum amount of configuration but make define the default behaviour within configurable variables. So for example…

  • The topic the widget subscribes to that will trigger the edit mode is defined by the “subscriptionTopic” but has a default of “ALF_EDIT_COMMENT”
  • The form and form controls as defined in the “widgets” variable so that they can be easily be changed either in the page definition, by dynamic extension module or an extending widget
  • The labels for the save button is further abstracted so that it’s not necessary to re-define the entire form to simply change their labels.
  • The property of the editable content is defined in the “propertyToRender” variable so that the widget can be used with different data models
  • The property name that the updated comment will be POST back to the server as is defined in the “postParam” variable so that the widget can be used with different services.

Essentially our widget becomes a configurable container around previously created widgets as we try to maximise re-use out of the existing widget library capabilities.

Similarly the new “alfresco/layout/VerticalReveal” widget extends an existing widget and heavily uses existing widget processing capabilities for on reveal processing the JSON model that it can be configured with.

This widget was also written with respect to the fact that is is undoubtedly not the only use case that it would be suitable for. It contains no references to comments or indeed anything else relating to the use case it was written with. It’s important for us to recognise when we’re creating something that has multiple uses and not prevent ourselves from re-using it or extending it in the future.

Step 5 – Integrate New Widgets

Once the missing widgets have been developed they can be integrated into the our page WebScript to be tested out.  The first issue that presents itself is that all the EditableComment widgets are publishing and subscribing on common topics. This means that editing one comment puts all comments into edit mode.

The solution to this is to scope each row of the in the list of comments. This is done by setting “generatePubSubScope” to true on the Row widget in the view as this means that the widgets for each item in the list will have their own “pubSubScope” attribute value and prevent cross-talk between widgets.

...
{
  name: "alfresco/documentlibrary/views/AlfDocumentListView",
  config: {
    widgets: [
    {
      name: "alfresco/documentlibrary/views/layouts/Row",
      config: {
        generatePubSubScope: true,
        widgets: [
...

Comment context is largely addressed in the “url” attributes in the “publishPayload” objects created for the EditableComment, PublishAction widgets along with the button for creating a dialog to create a new comment. It’s worth noting that at some stage we’ll want to introduce a dedicated CommentService module which will make the JSON model much simpler as it would only require the “currentItem” to be published rather than there being a need to process payload configuration.

Step 6 – Create a Composite Widget

We don’t want to have to re-declare the JSON model that has been created every time we want to render a list of comments. Instead we want to create a new widget that is little more than a alias to our model – this will be the widget that we reference in our Document Library view and means that we can pervasively change how comments lists are rendered in a single widget.

The “alfresco/documentlibrary/views/AlfDetailedView” widget (another example of a composite widget) is then updated to include our CommentsList widget.

...
{
  name: "alfresco/documentlibrary/views/layouts/Cell",
  config: {
    widgets: [
      {
        name: "alfresco/layout/VerticalReveal",
        config: {
          subscriptionTopic: "ALF_REVEAL_COMMENTS",
          widgets: [
            {
              name: "alfresco/renderers/CommentsList"
            }
          ]
        }
      }
    ]
  }
}
...

Comments were previously shown as a simple count that when clicked take the user to the comments section of the detailed page. Since we wanted to retain the existing code that renders the comments count but change the behaviour to allow a click action to reveal the comments list I made an additive change so that if a specific “publishTopic” attribute is configured then the behaviour changes so that the link becomes a simple comment toggle.

Changing the widget in this way means that all existing uses of the Comment widget behave exactly as they did before but the AlfDetailedView is able to reconfigure it to use it in a new way.

...
{
  name: "alfresco/renderers/Comments",
  config: {
    publishTopic: "ALF_REVEAL_COMMENTS"
  }
},
...

Changing the widget in this way means that all existing uses of the Comment widget behave exactly as they did before but the AlfDetailedView is able to reconfigure it to use it in a new way.

Step 7 – Addressing Further Context and Scoping Issues

Now that our CommentsList widget is being repeated multiple times within a single page we need to ensure that there is no cross-talk between the widgets when revealing comments. The same approach as before was taken to generate a new “pubSubScope” for each item in the list.

We’re also able to update our previous JSON model for the list to use the NodeRef of the document to retrieve comments for.

...
{
  name: "alfresco/lists/AlfList",
  config: {
    waitForPageWidgets: false,
    loadDataPublishTopic: "ALF_CRUD_GET_ALL",
    loadDataPublishPayload: {
      url: "components/node/{nodeRef}/comments?reverse=true&startIndex=0&pageSize=10",
      urlType: "SHARE"
    },
    widgets: [
...

Again, adding in a dedicated CommentService module will make this section of the model much simpler.

The Current (Not Final) Result

Rendering the Aikau based Document Library page now allows us to view, add, delete and edit comments for a Document without needing to access it’s details page. It’s quite clearly not the finished article but it’s been relatively straightforward to get something working that we can being to polish into the finished article.

Aikau Document Library showing comment counts

Aikau Document Library showing comment counts

Having revealed the current comments

Having revealed the current comments

Prompt when deleting a comment

Prompt when deleting a comment

Inline editing a comment using TinyMCE

Inline editing a comment using TinyMCE

Creating a new comment

Creating a new comment

 

Next Steps

Hopefully an Aikau based Document Library will be a key feature of an Alfresco 5.1 release (whenever that might be) and this is just a starting point for making comment handling much simpler.

The next steps to be implemented would be:

  1. Create a dedicated “alfresco/services/CommentService” module and simplify the existing JSON model
  2. Switch to using the “alfresco/lists/AlfSortablePaginatedList” and add pagination controls
  3. Add a user avatar thumbnail widget and include it in the comments list view
  4. Improve the styling of the VerticalReveal widget.
  5. Update the Comment widget further to subscribe to commenting topics to allow it to refresh the count as comments are added and deleted.