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.

Re-using and Customizing Aikau Pickers

Introduction

At Alfresco Summit 2014 in London this week I was presented with many different use cases that various Business Partners and Community members would like to implement on Share using Aikau and I was mostly able to either point them to existing blogs or examples in 5.0.

As always though there were a few use cases that made me curious to investigate and one of those was about the ability to pick different versions of a document. We are often asked about easily re-usable pickers and had already implemented a Document Picker widget with the intention of it being configurable and extendable and I wanted to check that the use case was possible. 

The rule of thumb that I recommend to anyone using Aikau is that if a widget isn’t used in Share out-of-the-box then it can’t be guaranteed to be production quality. The “alfresco/form/control/DocumentPicker” is not used out-of-the-box but I am pleased to report that investigation enabled me to identify a few issues and fix them to make it much more suitable for use in 5.0 customizations. However, as a result you’re going to a need a nightly build, 5.0.c Community or 5.0 Enterprise (when released) for this example to work.

Picking Documents

There is a common requirement when using Share to be able to select a document to work with (e.g. when creating a workflow for example) and as such we need to ensure that Aikau provides widgets to satisfy this requirement. For 5.0 there was no specific feature that needed a document picker but I wanted to ensure that we had at least explored how we could implement a configurable picker.

Typically you’re going to want to capture the Document nodeRef information within a form, so the simplest way to achieve this would be with the following JSON model:

services: [
  "alfresco/dialogs/AlfDialogService",
  "alfresco/services/DocumentService",
  "alfresco/services/SiteService"
],
widgets: [
  {
    name: "alfresco/forms/Form",
    config: {
      okButtonPublishTopic: "FORM_CONTENTS",
      widgets: [
        {
          name: "alfresco/forms/controls/DocumentPicker",
          config: {
            label: "Choose a document",
            name: "document"
          }
        }
      ]
    }
  }
]

Here were defining a form that contains a single form control for selecting documents: “alfresco/forms/controls/DocumentPicker”. This form control works with dialogs, documents and sites so the appropriate services need to be included in the page.

This will result in the following screenshots during use:

Document Picker in Form

Document Picker in Form

Document Picker Dialog Opened with Document Selected

Document Picker Dialog Opened with Document Selected

Document Picker showing the selected document after closing the dialog

Document Picker showing the selected document after closing the dialog

So hopefully this provides a really easy way in which to select documents within a form. But what about extending the default implementation to make it possible to select a specific version of a document?

Customizing the Picker

As always in AIkau we try to make it possible to get a long way through JSON configuration and this example is no different. However, this is by no means necessarily the most simple configuration to follow compared with other examples.

The “alfresco/forms/controls/DocumentPicker” extends the more abstract “alfresco/forms/controls/Picker” widget which provides the following configuration points that we’re going to make use of:

  • configForPickedItems – this is the JSON model that declares how you see picked items when the dialog is not displayed
  • configForPicker.widgetsForPickedItems – this is the JSON model the declares how you see picked items within the picker dialog
  • configForPicker.widgetsForRootPicker – this is the JSON model that defines how the dialog picker is constructed.

configForPickedItems

Given how much we like code re-use in Aikau you probably won’t be surprised to learn that since the picked documents is just a list of items we’ve re-used the “alfresco/lists/AlfList” module and that the JSON model required here is just for the “alfresco/documentlibrary/views/AlfDocumentListView” that is renders the selected documents. Since I’ve covered lists and views in previous blog posts [1, 2] I won’t go over it again, except to say that we’re defining a view where each item is represented by a row containing 3 cells that show the name, label (version) and an action for removing the item:

configForPickedItems: {
  widgets: [
    {
      name: "alfresco/documentlibrary/views/layouts/Row",
      config: {
        widgets: [
          {
            name: "alfresco/documentlibrary/views/layouts/Cell",
            config: {
              widgets: [
                {
                  name: "alfresco/renderers/Property",
                  config: {
                    propertyToRender: "name"
                  }
                }
              ]
            }
          },
          {
            name: "alfresco/documentlibrary/views/layouts/Cell",
            config: {
              widgets: [
                {
                  name: "alfresco/renderers/Property",
                  config: {
                    propertyToRender: "label"
                  }
                }
              ]
            }
          },
          {
            name: "alfresco/documentlibrary/views/layouts/Cell",
            config: {
              width: "20px",
              widgets: [
                {
                  name: "alfresco/renderers/PublishAction",
                  config: {
                    iconClass: "delete-16",
                    publishTopic: "ALF_ITEM_REMOVED",
                    publishPayloadType: "CURRENT_ITEM"
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ]
},

 widgetsForPickedItems

It is possible that you might want to display different information within the dialog than in the form (e.g. you might choose to add more document information in the form such as a title and description or render the view as a table) in this case we use exactly the same JSON model to show the name, version and an action to remove the selected item.

 widgetsForRootPicker

This is where it gets a bit more complicated…

The idea is that we can define a starting point for our picker and then add additional exploratory panes into the dialog until we reach a point where we have items to select. For example in the default picker our starting point is the ability to select from the current user’s recently visited sites, their favourite sites, all sites to which they have access, the repository root, the share files folder and their user home folder. Depending upon the selection the next pane will either contain a list of sites or a document list view of a selected location.

The change that we want to make is to add an additional exploratory pane so that selecting a document allows further selection of version rather than just selecting that document as a picked item.

To do this we currently need to copy and paste some of the default picker JSON model – this is not ideal and is something I’d like to improve in future versions. The JSON model is to large to easily fit into this blog but can be found in a JAR that you can download to try out this example.

The JAR contains the file “alfresco/site-webscripts/docversionpicker.get.js” which is the JavaScript controller for the WebScript that defines our sample page. The key lines in this file are as follows:

Line 154:

Here is where start changing the default “alfresco/pickers/Picker” JSON model to override the default configuration for the “alfresco/pickers/DocumentListPicker” that is displayed in the second picker pane.

Lines 157 – 160:

Here the publication data that defines picker is modified to change picked items from publishing the item data to making a request for a new picker to be added.

Line 161:

The “currentPickerDepth” attribute is used to ensure that picker panes don’t simply keep getting appended into the dialog. By specifying an incrementally greater depth we ensure that the version picker is added to a new pane rather than replacing the “alfresco/pickers/DocumentListPicker”

Line 162:

This defines the picker that we want to add to use to select a document version.  The picker is just an “alfresco/lists/AlfList” that is configured to publish requests for an “alfresco/services/CrudService” to retrieve version information for the document selected in the previous pane.

By changing the publish payload type to “PROCESS” and specifying the “processCurrentItemTokens” publish payload modifier we are able to define a URL that will include the correct nodeRef to retrieve version information for.

Line 201:

The “alfresco/renderers/PublishAction” is configured to publish on the “ALF_ITEM_SELECTED” topic to which the “alfresco/pickers/PickedItems” widget subscribes. This is selected version appears in the picked items pane.

Example In Action

If you try out the example you should see the updated picker appear as shown in the following screenshots:

The initially displayed dialog with just a single root option

The initially displayed dialog with just a single root option

Having selected the Repository...

Having selected the Repository…

Having selected a folder containing the target document

Having selected a folder containing the target document

Having selected the target document you see the available versions

Having selected the target document you see the available versions

Having selected the required version

Having selected the required version

Having confirmed selection of the required version

Having confirmed selection of the required version

 Summary

This is probably the most complicated example I’ve attempted to blog about in Aikau so far – certainly the most complicated to try to describe anyway. Hopefully though it demonstrates that we will provide a default document picker that can be easily re-used without a lot of effort and that we’re trying to take a more open-ended approach towards pickers that will make it easier to both create and customize pickers through declarative JSON modelling rather than needing to write code.

I still think that this is an area that we can further improve on going forwards, but it’s always challenging to provide Aikau widgets without having a specific Alfresco product feature that they’re required for.

Download the example code to review or try out here.