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.

 

Aikau Mini Examples – Data List (part 2)

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, showcase how to use existing widgets or how to create new ones.

Real World Use Case

There are already pages in Share for handling Data Lists, but they haven’t been updated for many releases.  They are therefore a good candidate to demonstrate how it’s possible to re-use Aikau widgets to rebuild (and enhance) existing Share features. It may also be necessary to extend the existing Aikau widgets to achieve specific use cases.

Example

This post picks up where the last one left off. We’ve now got the basic Data List functionality for reading Data Lists and all that remains is to tweak the layout and handle creation and deletion of Data List items.

Using Library Files

We want our page to look like it belongs in Share and this means adding in the header and footer (unless of course you want to use the “hybrid” template that automatically adds these for you – to do this, render the page using “share/page/hdp/…” rather than “share/page/dp/…”). To include the header and footer we need to import the library files provided and then call the functions they provide to set up the initial page model:

<import resource="classpath:/alfresco/site-webscripts/org/alfresco/share/imports/share-header.lib.js">
<import resource="classpath:/alfresco/site-webscripts/org/alfresco/share/imports/share-footer.lib.js">

var services = getHeaderServices(),
    widgets = getHeaderModel("Data Lists");
model.jsonModel = getFooterModel(services, widgets);

It’s essential that imports as the first lines of your WebScript controller (not even comments can come before or between them) otherwise an error will be generated. When calling “getHeaderModel” we can optionally provide a page title as we are doing here.

Layout

We’re going to tweak the layout by using a combination of “alfresco/layout/HorizontalWidgets” and “alfresco/layout/VerticalWidgets” to place our widgets as we want them on the screen. There are other widgets in the “alfresco/layout” package that we could use as well – one option would have been to use the “alfresco/layout/AlfSideBarContainer” widget to add in a resizeable sidebar – but in this case I’m just fixing a sidebar using applying a fixed width to a child widget in a HorizontalWidgets instance as described in this previous post. Since it’s already been covered I’m not going to include the code here, but please download the example code to review it.

Adding a New Item Button

This was actually the most interesting part of this example because we need to vary the published payload of a “New Item” button for all of the different Data List types and instances. This is because we needed to include the NodeRef of the Data List to add an item to as well as the specific form controls for the Data List type that was currently being shown.

I considered a variety of alternatives to solve this and each had their merits, but I decided to extend “alfresco/button/AlfButton” to support the use case but eventually decided that the new widget was such a valuable addition that I added it straight into the main Alfresco code base. This in itself demonstrates another benefit of Aikau – because all the code is so decoupled it is so much easier to accept contributions of individual widgets without causing any major disruptions.

Anyway… the new widget is “alfresco/buttons/AlfDynamicPayloadButton” and allows you to create subscriptions to topics that update the payload on publication. In this case we are able to subscribe to the “BLOG_LOAD_DATA_LIST” topic that was setup in the previous blog post to set the “alf_destination” request parameter as follows:

...
publishPayloadSubscriptions: [
  {
    topic: pubSubScope + "BLOG_LOAD_DATA_LIST",
    dataMapping: {
      nodeRef: "formSubmissionPayloadMixin.alf_destination"
    }
  }
],...

In this example we listen for publications on the (scoped) “BLOG_LOAD_DATA_LIST” topic and then update the publication payload of the button when the topic is published on. The “nodeRef” attribute of the published payload is mapped to the “formSubmissionPayloadMixin.alf_destination” attribute of the buttons “publishPayload” attribute (note that we can use dot-notation syntax to set nested attributes).

Dynamic Button Visibility

We need to create a new button for every Data List type (because each Data List type expects different properties and therefore we need to specify different form controls) so we end up with more than one button. So that only one button is displayed at a time we use the “visibilityConfig” (described in this previous blog post) to only show the appropriate button for the currently selected Data List:

...
visibilityConfig: {
  initialValue: false,
  rules: [
    {
      topic: pubSubScope + "BLOG_LOAD_DATA_LIST",
      attribute: "itemType",
      is: [dataListTypes[i].name]
    }
  ]
}
...

Note that we’re triggered off the same topic that is published whenever a user clicks on a specific Data List and that our rule is based on the name of the current data list (see the full code, but “dataListTypes” is the array of Data Lists being iterated over to build the buttons).

We also set an initial visibility of false so that it’s not possible for a user to try and create a new Data List item until a Data List has been selected.

Finishing Touches

I’ve also added an “alfresco/renderers/PublishAction” widget to each rendered item as was done for the Data List list. However, since that has already been described in this previous blog post I won’t go into it again. However it is worth mentioning that I could have added another instance for opening an edit dialog.

This obviously isn’t the finished article and doesn’t provide all the capabilities that the existing Data List page in Share provides. The idea is to illustrate that it’s relatively easy to build functional pages for an Alfresco Repository using Aikau by simply re-using existing REST APIs and widgets. Hopefully we’ll be able to use Aikau to re-write Data Lists (as well as the other site pages) in a future Alfresco release.

Example In Action

This is defined in a JavaScript controller for a WebScript mapped to the /datalist2 URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/datalist2

This screenshots shows the page in action:

DataList2_1

DataList2_2

DataList2_3

Download the complete example code here.

 

 

Aikau Mini Examples – Data List (part 1)

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, showcase how to use existing widgets or how to create new ones.

Real World Use Case

There are already pages in Share for handling Data Lists, but they haven’t been updated for many releases.  They are therefore a good candidate to demonstrate how it’s possible to re-use Aikau widgets to rebuild (and enhance) existing Share features. It may also be necessary to extend the existing Aikau widgets to achieve specific use cases.

Example

This post picks up where the last one left off. Since I’d started using Data Lists as a case study for demonstrating the “alfresco/services/CrudService” I realised that I may as well continue on the path of re-creating the page using Aikau – especially since it demonstrates more techniques that might be required by Alfresco developers, partners and community members.

Extending AlfList

We’ve already recreated the part of the Data List page sidebar and we now want to create the main section of the page that will show the currently selected Data List. It would be nice if we could just use another “alfresco/lists/AlfList” widget for this main section but the problem is that each Data List uses different properties for their items. However, it is possible to re-use the same list but switch views for each Data List type.

Therefore we’re going to create a custom widget that extends “alfresco/lists/AlfList” and to do so we should add in our “blog” package for our custom widget (see this previous post for details).

The main requirements of our custom list is to handle Data List selection and switch views to match the appropriate Data List type. Our “blog/DataList” widget is declared as follows:

define(["dojo/_base/declare",
        "alfresco/lists/AlfList",
        "dojo/_base/lang"],
        function(declare, AlfList, lang) {

  return declare([AlfList], {

Now we need to override and add functions to satisfy our requirements:

postMixInProperties: function blog_DataList__postMixInProperties() {
  this.inherited(arguments);
  this.alfSubscribe("BLOG_LOAD_DATA_LIST", lang.hitch(this, this.loadDataList))
},

Note that we call “this.inherited(arguments)” to execute the extended modules function of the same name. This can be omitted if you want to override rather than extend the function. We then call the “alfSubscribe” function and bind the “BLOG_LOAD_DATA_LIST” topic to the “loadDataList” function using Dojo’s lang.hitch utility function (so that when “BLOG_LOAD_DATA_LIST” is published on, the “loadDataList” function will be called).

loadDataList: function blog_DataList__loadDataList(payload) {
  this.dataListToLoad = payload;
  this.onViewSelected({
    value: payload.itemType
  });
  this.loadData();
},

The “loadDataList” function assigns the published payload to the “dataListToLoad” instance variable (so that it can be referred to later) and switches to the appropriate view by calling the “onViewSelected” function inherited from the “alfresco/lists/AlfList” widget. The the “loadData” function (also inherited) is called to request the data.

loadData: function alfresco_lists_AlfList__loadData() {
  if (this.dataListToLoad != null)
  {
    this.inherited(arguments);
  }
},

This function needs to be extended to prevent attempts to load data when no Data List has been set. Note that we are able to conditionally decide whether or not call the inherited function.

updateLoadDataPayload: function blog_DataList__updateLoadDataPayload(payload) {
  if (this.dataListToLoad != null)
  {
    payload.url = "slingshot/datalists/data/node/" + this.dataListToLoad.nodeRef.replace("://", "/");
  }
}

Finally we override the “updateLoadDataPayload” extension point function to set the URL to load. This is a good example of how Aikau intentionally provides functions purely for developers to extend so that they don’t need to copy and paste existing code.

Pub/Sub Scoping

Since we’re going to be having two instances of the AlfList on the page we need to ensure that their publication/subscription (pub/sub) communications are scoped to prevent cross-communication. Therefore when we define our “blog/DataList” in the JSON model we will set a specific “pubSubScope”.

var pubSubScope = "DATA_LIST_SCOPE";

var dataList = {
  name: "blog/DataList",
  config: {
    pubSubScope: pubSubScope,
    loadDataPublishTopic: "ALF_CRUD_CREATE",
    itemsProperty: "items",
    widgets: buildViews()
  }
};

Configuring Views

Note that we’re also calling a “buildViews” function that you can review in the full example (see download link at end of post) but I won’t list here. In summary we need to build a view for each Data List type that we know about. Each view is given an ID of the Data List type (e.g. “dl:eventAgenda”). Views were described in this earlier post so I won’t describe them here, other than to say that we are adding an “alfresco/renderers/Property” for each property in the Data List type.

One addition to the view construction previously described is that we are also setting the “widgetsForHeader” attribute for each widget (to create column headers) and setting the “additionalCssClasses” attribute on each view to make it more tabular in app%MCEPASTEBIN%earance.

Updating the Link

Lastly we need to update our previously created list to make our “alfresco/renderers/InlineEdityProperty” an “alfresco/renderers/InlineEdityPropertyLink” so that when it is clicked it publishes the request to load the Data List it represents.

dataListTitle.name = "alfresco/renderers/InlineEditPropertyLink";
dataListTitle.config.linkPublishTopic = pubSubScope + "BLOG_LOAD_DATA_LIST";
dataListTitle.config.linkPublishPayloadType = "CURRENT_ITEM";

Note that we’re re-using the “pubSubScope” variable so that we’re publishing into the scope that our “blog/DataList” is using.

Example in action

This is defined in a JavaScript controller for a WebScript mapped to the /datalist URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/datalist

These screenshots shows the page in action:

DataList1

 

DataList2

 

DataList3

Download the complete example here.

Next Steps

In the next post we’ll make further updates to improve the layout and add support for creating Data List items.

Aikau Mini Examples – CRUD Service (create)

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, showcase how to use existing widgets or how to create new ones.

Real World Use Case

Sometimes you want to be able to create things. And sometimes you want to be able to do this via a form dialog without going to a new page.

Example

This post picks up where the last one left off. I’m going to assume that you’ve already worked your way through the post or have at least downloaded the example JAR and taken a look at the code. We’ve already built a basic list of our Data Lists with the ability to delete individual items and inline edit the title of each Data List. We now want to provide the ability to create new Data Lists to add to our list.

Getting the Data List Container

Data Lists in Share are created within sites and each Data List is a new node created within a “container” folder within the site. When creating a new Data List we need to know the nodeRef of that container to include in our POST request. Since the REST API doesn’t support creation using site and container IDs we’ll need to obtain the container nodeRef in our WebScript. This doesn’t actually relate to Aikau in any way other than to show you that you can build data on the server before sending it to the client:

var alfDestination = null;
var result = 
  remote.call("/slingshot/datalists/lists/site/datalistexample/dataLists");
if (result.status.code == status.STATUS_OK)
{
  alfDestination = JSON.parse(result).container;
}

If I was doing this for real, I’d probably want to use more defensive code but the snippet above is sufficient for the purposes of this example.

Create a form

We now want to create the form controls to capture the data required for our POST request:

var formControls = [
  {
    name: "alfresco/forms/controls/DojoValidationTextBox",
    config: {
      name: "alf_destination",
      value: alfDestination,
      visibilityConfig: {
        initialValue: false
      }
    }
  },
  {
    name: "alfresco/forms/controls/DojoValidationTextBox",
    config: {
      label: "Title",
      name: "prop_cm_title",
      requirementConfig: {
        initialValue: true
      }
    }
  },
  {
    name: "alfresco/forms/controls/DojoTextarea",
    config: {
      label: "Description",
      name: "prop_cm_description"
    }
  },
  {
    name: "alfresco/forms/controls/DojoSelect",
    config: {
      label: "List Type",
      name: "prop_dl_dataListItemType",
      value: "dl:event",
      optionsConfig: {
        publishTopic: "ALF_GET_FORM_CONTROL_OPTIONS",
        publishPayload: {
          url: url.context + "/proxy/alfresco/api/classes/dl_dataListItem/subclasses", 
          itemsAttribute: "",
          labelAttribute: "title",
          valueAttribute: "name"
        }
      }
    }
  }
];

Since i’ve already covered the basics of defining forms I’ll focus on just the last control which is the “alfresco/forms/controls/DojoSelect”.  The key thing to note about this is the “optionsConfig” section which allows us to define how to retrieve and render the options to make available to the user.

We’re going to use the “alfresco/services/OptionsService” (which will be added into the page’s JSON model later) to handle requests for options. This service is designed to return data in the structure that form controls expect and allows you to specify the:

  • itemsAttribute – a dot-notation property to look-up in the JSON response body that identifies an array of options (setting as the empty string indicates that the entire response is an array)
  • labelAttribute – the dot-notation property to use in each option as the label
  • valueAttribute - the dot-notation property to use in each option as the value

Use a dialog

We want to add a button to the page that will popup a dialog containing our form. Dialogs can be created using the “alfresco/dialogs/AlfDialogService” and the is a topic just for form specific dialogs:

var button = {
  name: "alfresco/buttons/AlfButton",
  config: {
    label: "New List",
    additionalCssClasses: "call-to-action",
    publishTopic: "ALF_CREATE_FORM_DIALOG_REQUEST",
    publishPayloadType: "PROCESS",
    publishPayloadModifiers: ["processCurrentItemTokens"],
    publishPayload: {
      dialogTitle: "New List",
      dialogConfirmationButtonTitle: "Save",
      dialogCancellationButtonTitle: "Cancel",
      formSubmissionTopic: "ALF_CRUD_CREATE",
      formSubmissionPayloadMixin: {
        url: "api/type/dl%3AdataList/formprocessor"
      },
      fixedWidth: true,
      widgets: formControls
    }
  }
};

When clicked the “alfresco/buttons/AlfButton” will publish the configured payload on the specified topic. Hopefully you now recognise many of the attributes from the previous blogs posts (e.g. “publishTopic”, “publishPayload”, “publishPayloadType”, “publishPayloadModifiers”) so I’ll focus on the dialog specific attributes.

The “dialogTitle”, “dialogConfirmationButtonTitle” and “dialogCancellationButtonTitle” should hopefully be self-explanatory. The “widgets” are just the form controls that we have previously defined (it’s not necessary to create the “alfresco/forms/Form” since the DialogService handles this for us).

Whenever the confirmation button on the dialog is clicked the value of the form will be published on the “formSubmissionTopic” (which in this case will be handled by our trusty “alfresco/services/CrudService”) and additional data can be “mixed in” via the “formSubmissionPayloadMixin” (in this case the actual URL that the CrudService will need to POST to).

Put it all together

Now we just need to add the new definitions and services into our page:

model.jsonModel.services.push("alfresco/dialogs/AlfDialogService",
                              "alfresco/services/OptionsService");
model.jsonModel.widgets.splice(0, 0, button);

Example in action

This is defined in a JavaScript controller for a WebScript mapped to the /crudServiceCreate URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/crudServiceCreate

These screenshots shows the page in action:

DataListCreate1

DataListCreate2

DataListCreate5

DataListCreate4

You can download the entire example here.

 

Aikau Mini Examples – CRUD Service (update)

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, showcase how to use existing widgets or how to create new ones.

Real World Use Case

Sometimes you want to be able to update things. And sometimes you want to be able to update things directly from where you see them listed.

Example

This post picks up where the last one left off. I’m going to assume that you’ve already worked your way through the post or have at least downloaded the example JAR and taken a look at the code. We’ve already built a basic list of our Data Lists and provided the ability to delete Data Lists by clicking an icon associated with each entry. We now want to switch the existing “alfresco/renderers/Property” widget for an “alfresco/renderers/InlineEditProperty” widget to allow inline editing, this is done as follows:

var dataListTitle = widgetUtils.findObject(model.jsonModel.widgets, "id", "DATA_LIST_TITLE");
dataListTitle.name = "alfresco/renderers/InlineEditProperty";
dataListTitle.config = {
  propertyToRender: "title",
  postParam: "prop_cm_title",
  refreshCurrentItem: true,
  requirementConfig: {
    initialValue: true
  },
  publishTopic: "ALF_CRUD_CREATE",
  publishPayloadType: "PROCESS",
  publishPayloadModifiers: ["processCurrentItemTokens", "convertNodeRefToUrl"],
  publishPayloadItemMixin: false,
  publishPayload: {
    url: "api/node/{nodeRef}/formprocessor",
    noRefresh: true,
    successMessage: "Update success"
  }
};

Let’s break down the configuration as we did before:

Publication Updates

The changes here are largely similar to those that were made in the DELETE example. One curiosity here is that we have to use the “ALF_CRUD_CREATE” topic to perform a POST request rather than an “ALF_CRUD_UPDATE” topic because the FormsProcessor doesn’t support PUT ! The other configuration attributes of note are that we set “publishPayloadItemMixin” to false to change the default behaviour of including the “currentItem” object in the publication (because it would confuse the FormsProcessor) and that we set “noRefresh” to true in the payload to override the default behaviour of publishing a reload topic to refresh the list.

InlineEditProperty

As it’s name suggests this widget allows inline editing of properties. This widget (like all Aikau widgets) has been written to be extendable (the “alfresco/renderers/InlineEditSelect” is an example of extending it). Essentially it just uses an “alfresco/forms/Form” widget (again – an example of how Aikau aims to re-use as much code as possible) so that we leverage all the power of the forms handling widgets. The main configuration settings to note are that we set “postParam” when the “propertyToRender” doesn’t match the expected request parameter on the REST API (if “postParam” is omitted then the request parameter name will be the configured “propertyToRender” value. Setting “refreshCurrentItem” to true we ensure that any changes are applied to any other widgets dependant upon the “currentItem” object. The “requirementConfig” is the same as is used in Aikau form widgets and this piece of configuration is just delegated onto the underlying form control (in this case an “alfresco/forms/controls/DojoValidationTextBox”).

Example In Action

This is defined in a JavaScript controller for a WebScript mapped to the /crudServiceUpdate URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/crudServiceUpdate This screenshots shows the page in action: DataListsUpdate DataListsUpdateInline DataListsUpdated   You can download the complete example here.

Aikau Mini Examples – CRUD Service (delete)

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, show case how to use existing widgets or how to create new ones.

Real World Use Case

Sometimes you want to be able to delete things. And sometimes you want to be able to delete things directly from where you see them listed.

Example

This post picks up where the last one left off. I’m going to assume that you’ve already worked your way through the post or have at least downloaded the example JAR and taken a look at the code.

We’ve already built a basic list of our Data Lists and now we want to provide an action icon that when clicked will allow us to delete a Data List. This is achieved by using the “alfresco/renderers/PublishAction” widget. We’re going to update the JSON model that was defined in the previous blog post to add a new cell to each row of data in our view:

var deleteCell = {
  name: "alfresco/documentlibrary/views/layouts/Cell",
  config: {
    widgets: [
      {
        name: "alfresco/renderers/PublishAction",
        config: {
          iconClass: "delete-16",
          propertyToRender: "title",
          altText: "Delete {0}",
          publishTopic: "ALF_CRUD_DELETE",
          publishPayloadType: "PROCESS",
          publishPayload: {
            requiresConfirmation: true,
            url: "slingshot/datalists/list/node/{nodeRef}",
            confirmationTitle: "Delete Data List",
            confirmationPrompt: "Are you sure you want to delete '{title}'?",
            successMessage: "Successfully deleted '{title}'"
          },
          publishPayloadModifiers: ["processCurrentItemTokens", "convertNodeRefToUrl"]
        }
      }
    ]
  }
};
var viewRow = widgetUtils.findObject(model.jsonModel.widgets, "id", "VIEW_ROW");
viewRow.config.widgets.push(deleteCell);

There’s quite a lot of configuration in this example, so let’s break it down into small chunks.

Configuring the DELETE Payload

Firstly we want to define the action itself. We’re going to publish on the “ALF_CRUD_DELETE” topic to which the “alfresco/services/CrudService” subscribes and we want to identify the specific item that we want to delete.

When defining a publication payload you can optionally select a number of different payload types and here we are setting the “publishPayloadType” attribute to be “PROCESS” to indicate that we want to perform some processing on the payload.

A number of processors have been initially included in Aikau and we specify two of them in the “publishPayloadModifiers” array:

  • “processCurrentItemTokens” looks for all string data wrapped in curly braces (e.g. the {nodeRef} in “slingshot/datalists/list/node/{nodeRef}”) and converts it to the matching entry in the “currentItem”.
  • “convertNodeRefToUrl” replaces any occurence of “://” with “/” to make NodeRef data URL friendly.

Confirmation Prompting

The CrudService recognizes that DELETE actions may require a confirmation so we include a “requiresConfirmation” attribute in the payload as well as the messages to use in the confirmation dialog title, confirmation dialog body and the subsequent success message (note that we’re able to include “currentItem” tokens in the message strings because the “currentItem” is always included in the publication to the service).

Icon and Accessibility

The last part of the configuration to describe is that we specify the icon using the “iconClass” attribute and the “altText” for the icon. The “propertyToRender” attribute is available for use as a message token in the alt text so we can provide meaningful text on the action.

Example In Action

This is defined in a JavaScript controller for a WebScript mapped to the /crudServiceDelete URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/crudServiceDelete

This screenshots shows the page in action:

DataListDelete

DataListDeletePromptYou can download the complete example here.

 

 

Aikau Mini Examples – CRUD Service (get)

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, showcase how to use existing widgets or how to create new ones.

Real World Use Case

At some stage you’re going to need to work with some data and Aikau is specially created to work with Alfresco repositories. The widgets are de-coupled from data by using client-side services as a go-between for getting and setting data on over the Alfresco Repository REST API. Most web applications are ultimately just lists and forms and Share is no different – one area that hasn’t had a lot of work recently is the Data Lists in Sites. This area has lots of examples of getting and setting data in a number of different ways. This post and following posts will show how we can use Aikau to work with Data List data.

Example

In this example we’re going to do nothing more complicated than just retrieving the list of Data Lists from the Repository. The purpose is just to introduce the “alfresco/services/CrudService” module as well as basic list building.

Prerequisites

We could have started by actually using the CrudService to create some data lists, but it makes more sense to actually create some data to start of with so that you can compare the existing implementation with the Aikau version, so please do the following:

  1. Log into Alfresco Share
  2. Create a site
  3. Customize the site and add Data Lists to it
  4. Create some data lists (doesn’t matter what they are).

Share Data Lists

Pick an API, any API…

Aikau provides a number of dedicated services for working with data (e.g. “alfresco/services/DocumentService”, “alfresco/services/SearchService”, etc.) which typically perform some additional work or error checking to the basic data manipulation. The “alfresco/services/CrudService” is different in that it provides the raw capability to work with a REST API. Currently there is no dedicated service for working with Data Lists so we’re going to use the CrudService for accessing the Data List APIs.

Service Dependencies

First we need to make sure that the CrudService is available on the page. This is done by including in in the list of services in the JSON model for the page.

model.jsonModel = {
  services: [
    "alfresco/services/CrudService"
  ]
};

The majority of services don’t require any configuration so can be added to the list as a string. However, it’s also possible to add services into the array as objects with name/config attributes as is done with widgets.

Define a List

The most simple form of list in Aikau is the “alfresco/lists/AlfList” widget that provides basic list capabilities. This is extended by the “alfresco/lists/AlfHashList” (that allows requests to be manipulated by the URL hash fragment) and the “alfresco/lists/AlfSortablePaginatedList” (that provides support for sorting and pagination) which is in turn extended by specialist lists such as the “alfresco/documentlibrary/AlfDocumentList” and “alfresco/documentlibrary/AlfSearchList”, etc. For this example we just need simple capabilities so the basic “AlfList” widget is sufficient.

var list = {
  name: "alfresco/lists/AlfList",
  config: {
    loadDataPublishTopic: "ALF_CRUD_GET_ALL",
    loadDataPublishPayload: {
      url: "slingshot/datalists/lists/site/datalistexample/dataLists"
    },
    itemsProperty: "datalists"
  }
};

In this example we are essentially defining the data that we want to work with. The “loadDataPublishTopic” defines the topic to publish to request data, and the “loadDataPublishPayload” defines the payload to send when requesting data. We are using a topic defined by the CrudService and which is designed to work with the Alfresco Repository REST API so we do not need to include the full address, just the WebScript declared URL fragment.

It would be nice if all the Alfresco REST APIs used a consistent attribute for the arrays of data that they return but since this is not the case, we can instruct the AlfList to use a specific property in the response body to get the array from using the “itemsProperty” configuration attribute (note: this will also accept dot-notation values).

Define a View

An “alfresco/lists/AlfList” widget (and all it’s descendants) can render multiple views of data. These are set as the “widgets” config attribute and in order to actually display any data an AlfList needs to have a least one view configured.

It’s possible to build almost any view and you can either re-use the existing view and data rendering widgets or build your own. In this case we’re just going to build our view of data as a list of rows, each containing a single cell with a single data renderer.

var views = [
  {
    name: "alfresco/documentlibrary/views/AlfDocumentListView",
      config: {
      widgets: [
        {
          id: "VIEW_ROW",
          name: "alfresco/documentlibrary/views/layouts/Row",
          config: {
            widgets: [
              {
                name: "alfresco/documentlibrary/views/layouts/Cell",
                config: {
                widgets: [
                  {
                    name: "alfresco/renderers/Property",
                    config: {
                      propertyToRender: "title"
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    ]
  }
}];

View definitions can get quite long and if you define a view that you want to re-use then it makes sense to extend “alfresco/documentlibrary/views/AlfDocumentListView” and declare the widget model in your own subclass (see “alfresco/documentlibrary/views/AlfSimpleView” and “alfresco/documentlibrary/views/AlfDetailedView” as examples of this).

The reason that a lot of the list and view modules are defined in the “alfresco/documentlibrary/” package is purely historical since most of the list and view code has been abstracted from Document Library specific use cases.

Finally we just need to pull all the individual bits together:

list.config.widgets = views;
model.jsonModel.widgets = [list]

Example In Action

This is defined in a JavaScript controller for a WebScript mapped to the /crudService URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/crudService

This screenshot shows the result:

Aikau Data Lists

Obviously this isn’t especially exiting or indeed useful… the purpose is just to demonstrate how to access data. You can check out the page models for the document and search views for more complex examples of what is possible.

Download the complete example here.

 

 

Aikau Mini Examples – Simple Form

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, show case how to use existing widgets or how to create new ones.

Real World Use Case

Ultimately most web applications boil down to just being lists and forms (given the REST based nature of HTTP) so it stands to reason that at some point you’re going to need to define a form. It’s quite likely that you want to build in some dynamic behaviour into your forms from simply not allow the user to POST invalid data to changing the state form fields as data is changed.

As well as providing many different form controls (and the means to easily implement complimentary additional controls) Aikau allows you to define complex form behaviour in a declarative way. This example will show a couple of form controls and a fraction of the capabilities to highlight the benefits of the publication/subscription model that Aikau utilises.

Example

In this example we’re going to defined a relatively simple form containing a text box for capturing an e-mail address and a checkbox that controls whether or not that text box is visible.

Define a form

Let’s start by defining the form widget:

var form = {
  name: "alfresco/forms/Form",
  config: {
    showOkButton: true,
    okButtonLabel: "Save",
    showCancelButton: false,
    cancelButtonLabel: "Doesn't Matter",
    okButtonPublishTopic: "PUBLISH_TOPIC",
    okButtonPublishGlobal: true,
    widgets: []
  }
};

In actual fact you could omit all of these attributes and Aikau would provide you with some sensible defaults, but for educational purposes I’ve included (almost) all the attributes available.

Define a text box

Now let’s add a basic text box and add it into the form:


var textBox = {
  name: "alfresco/forms/controls/DojoValidationTextBox",
  config: {
    fieldId: "EMAIL",
    name: "email",
    label: "Contact",
    description: "Your e-mail address",
    placeHolder: "e-mail",
    validationConfig: [
      {
        validation: "regex",
        regex: "^([0-9a-zA-Z]([-.w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-w]*[0-9a-zA-Z].)+[a-zA-Z]{2,9})$",
        errorMessage: "Valid E-mail Address Required"
      }
    ]
  }
};
form.config.widgets.push(textBox);

With the single exception of the “placeHolder” attribute all of the attributes are applicable to any Aikau form control. All form controls inherit from the “alfresco/forms/controls/BaseFormControl” module which defines the structure of form controls (e.g. labels, options, validation, behaviour, etc) it means that form widgets can be easily swapped without needing to re-write the configuration.

It should be relatively obvious what each attribute is doing. There is a short-hand version of defining validation configuration, but this style allows us to define multiple validators (although we’re only using the “regex” validator to check that the data maps to a valid e-mail address pattern).

Define a check box

Now define a checkbox that will control the visibility of the text box:

var checkbox = {
  name: "alfresco/forms/controls/DojoCheckBox",
  config: {
    fieldId: "SHOW",
    name: "showEmail",
    label: "Show E-mail",
    description: "Uncheck to hide the e-mail field",
    value: true
  }
};
form.config.widgets.push(checkbox);

Note that the attributes are the same as before but assigned different values. This time we’re also providing an initial value of true to ensure that the checkbox is initially in the checked state.

Define a visibility rule

Whenever a form control changes value it will publish the change within the scope of the form and other widgets can define rules that trigger updates based on those changes. Let’s update the text box with a rule that controls it’s visibility:

textBox.config.visibilityConfig = {
  initialValue: true,
  rules: [
    {
      targetId: "SHOW",
      is: [true]
    }
  ]
};

It’s possible to set multiple rules, multiple “is” values (and indeed “isNot” values) to compare against. It is also possible to use the same rule structure for “requirementConfig” (whether or not the field is required) and “disablementConfig” whether or not the field is disabled.

Here we are simple defining a rule that says if the form control with the “fieldId” of “SHOW” is set with the value “true” then this form control should become visible (and should be hidden if it is set to any other value).

Create the page

Finally we need to add our widget definitions into the main page model:

model.jsonModel = {
  widgets: [
    form
  ]
};

Example In Action

This is defined in a JavaScript controller for a WebScript mapped to the /formExample URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/formExample

Here are some screenshots of the page. Note how when the e-mail address in invalid the “Save” button is automatically disabled. When the checkbox is not checked the e-mail address field is hidden.

AikauEx5_1

AikauEx5_2

AikauEx5_3

Download the entire example here.

Download a PDF version of this post here.

 

Aikau Mini Examples – Role Based Rendering

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, show case how to use existing widgets or how to create new ones.

Real World Use Case

One of the most frequently asked questions we seem to be getting lately is whether or not we can render a different UI depending upon what groups the logged in user is a member of (also known as “role based rendering”).

This can range from simply displaying a warning instead of the page contents if the current user doesn’t have the privileges to be there, right up to fine grained differences between what different users see on the page.

Example

All widgets can be configured with a set of “renderFilter” rules that determine whether or not the widget is rendered (note: rendered not visible – if the renderFilter does not pass the widget will not be created). The rules are not constrained to just groups, it’s possible to define rules for any property that the widget has access to. However, two key pieces of information are always cascaded through the widget hierarchy and these are “groupMemberships” and “currentItem”.

This will be quite a convoluted example (aren’t they all?) but should at least show the options available for configuring renderFilters.

Pre-Requisites

First of all, log in as the Administrator and create a new group with the identifier “EXAMPLE” (under the covers Alfresco will actually call it “GROUP_EXAMPLE”). Then create two new users and add one (not both) to the group.

Create Warning Banner

In the JavaScript controller for the page define a warning widget that should be displayed if the logged in user is neither an Administrator or a member of the new example group.

var warning = {
  name: "alfresco/header/Warning",
  config: {
    renderFilterMethod: "ALL",
    renderFilter: [
      {
        target: "groupMemberships",
        property: "GROUP_ALFRESCO_ADMINISTRATORS",
        renderOnAbsentProperty: true,
        values: [false]
      },
      {
        target: "groupMemberships",
        property: "GROUP_EXAMPLE",
        renderOnAbsentProperty: true,
        values: [false]
      }
    ],
    warnings: [
      {
        message: "You aren't in right group",
        level: 3
      }
    ]
  }
};

The “renderFilterMethod” defines whether to use AND or OR logic for the rules (e.g. ALL rules must pass in order for the widget to be rendered or ANY one of the rules must pass). There are two rules defined, one for the main Administrators group and one for the our newly created group. The “target” is set to be “groupMemberships” (one of the previously mentioned key attributes) and we also declare set “renderOnAbsentProperty” to be true so that the rule is passed if the group doesn’t appear in the “groupMemberships” object. Finally we set an array of values that the target property must be in order for the rule to be satisfied.

Create Menu Bar

Now define a menu bar that will be displayed if the user is an Administrator or a member of the new group.

var menuBar = {
  name: "alfresco/menus/AlfMenuBar",
  config: {
    renderFilterMethod: "ANY",
    renderFilter: [
      {
        target: "groupMemberships",
        property: "GROUP_ALFRESCO_ADMINISTRATORS",
        values: [true]
      },
      {
        target: "groupMemberships",
        property: "GROUP_EXAMPLE",
        values: [true]
      }
    ],
    widgets: []
  }
};

This time we are inverting the rules. The “renderFilterMethod” is set to ANY as they user just needs to be a member of one of the groups (although being a member of both groups is also fine). The “renderOnAbsentProperty” attribute is omitted because we want to be sure the user is a member of the group and the target value is switched from false to true.

Current Item Data

We’ll now define a custom “currentItem” object for our menu bar:

menuBar.config.currentItem = {
  test: {
    value: "show"
  }
};

This data can then be used to determine whether or not to render a couple of menu bar items:

var menuBarItems = [
  {
    name: "alfresco/menus/AlfMenuBarItem",
    config: {
      label: "Should Appear",
        renderFilter: [
          {
            property: "test.value",
            values: ["show"]
          }
        ]
      }
    },
    {
      name: "alfresco/menus/AlfMenuBarItem",
      config: {
        label: "Should NOT appear",
        renderFilter: [
          {
            property: "test.value",
            values: ["visible"]
          }
        ]
      }
    }
];
menuBar.config.widgets = menuBarItems;

We’re able to omit the “target” attribute because the default behaviour is to check the “currentItem” object. This also shows how we can use dot-notation to address nested attributes in the “currentItem” object.

The first menu bar item should be displayed (because test.value is “show”) but the second should not be rendered (because test.value is not “visible”).

Build the Page

Finally let’s combine all the definitions into the JSON model for the page:

model.jsonModel = {
  widgets: [
    {
      name: "alfresco/layout/VerticalWidgets",
      config: {
        widgets: [ warning, menuBar ]
      }
    }
  ]
};
model.jsonModel.groupMemberships = user.properties["alfUserGroups"];

The last line is the most important – we need to ensure that the “alfUserGroups” property is retrieved from the current user data (available in the user object since Alfresco 5.0).

Example In Action

This is defined in a JavaScript controller for a WebScript mapped to the /renderFilter URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/renderFilter

These screen shots show the two states depending upon which user has logged in:

 

AikauEx4_groupfail

 

AIkauEx4_menu_bar_item

Download the entire example here.

Download a PDF version of this document here.