Skip navigation
All Places > Alfresco Content Services (ECM) > Blog > 2016 > October > 05

Introduction

In my last blog post I showed how easy it is to add new items into the Aikau Document Library "Create Content..." menu. In this particularly example I was showing how to create markdown content but in order to do this I actually needed to create a new widget. It's been suggested in the past that I should try to provide more examples of how to create custom widgets so I figured that it would be as good a time as any...

 

Aikau already had widgets for both editing content with syntax highlighting and previewing markdown but these didn't yet exist as a single entity. The great thing about Aikau is that it incredibly easy to combine existing widgets together to make a new widget that can be referenced as an individual entity. We learned our lessons from the original YUI2 widgets that it is important to define fine-grained widgets that can be easily composed rather than creating large-grained coarse widgets that become increasingly hard to customize over time. The Dojo _WidgetBase module that Aikau builds upon also provides excellent inheritance capabilities that we are able to leverage.

 

So our objective was to create a new widget that allowed editing of markdown and showed a preview of the changes as they happened. As this widget is to be used for creating content submitted through a form then it needed to also inherit from BaseFormControl.  This meant that obvious candidate to extend would be the existing CodeMirrorEditor form control as it had the correct ancestry and could be configured to show markdown syntax highlighting.

 

Creating the Widget

The initial code to extend the widget looks like this:

 

define(["dojo/_base/declare",
        "alfresco/forms/controls/CodeMirrorEditor"],
        function(declare, CodeMirrorEditor) {
   return declare([CodeMirrorEditor], {
   });
});

 

This essentially defines a new widget that is exactly the same as the CodeMirrorEditor, now we can start to change a few things. First we know that we want to use markdown highlighting - this is done by setting the editMode:

 

define(["dojo/_base/declare",
        "alfresco/forms/controls/CodeMirrorEditor"],
        function(declare, CodeMirrorEditor) {
   return declare([CodeMirrorEditor], {
      editMode: "markdown"
   });
});

 

We also know that we want to change how the form control itself is constructed. We do this by extending (through a call to this.inherited(arguments) the createFormControl function:

 

define(["dojo/_base/declare",
        "alfresco/forms/controls/CodeMirrorEditor"],
        function(declare, CodeMirrorEditor) {
   return declare([CodeMirrorEditor], {
      editMode: "markdown",

      createFormControl: function alfresco_forms_controls_MarkdownWithPreviewEditor__createFormControl(config, domNode) {
         var widget = this.inherited(arguments);
         return widget;
      }
   });
});

 

We want to also create the Markdown widget. This requires a new DOM element created, and a new Markdown widget created that binds to it:

 

createFormControl: function alfresco_forms_controls_MarkdownWithPreviewEditor__createFormControl(config, domNode) {
   var widget = this.inherited(arguments);
   this.previewNode = domConstruct.create("div", { 
      style: "height:" + this.height + "px;width:" + this.width + "px;",
      className: "alfresco-forms-controls-MarkdownWithPreviewEditor__previewNode"
   }, this._controlNode);
   var preview = this.createWidget({
      name: "alfresco/html/Markdown",
      config: {
         markdown: this.initialValue
      }
   });
   preview.placeAt(this.previewNode);
   return widget;
},

 

In order for the Markdown widget to show the changes as they happen it is necessary to define a subscription topic that it can uses to listen for changes. By calling the generateUuid function we can create a unique topic to prevent any "cross talk" between widgets.

 

this.generatePreviewTopic = this.generateUuid();

this.previewNode = domConstruct.create("div", { 
   style: "height:" + this.height + "px;width:" + this.width + "px;",
   className: "alfresco-forms-controls-MarkdownWithPreviewEditor__previewNode"
}, this._controlNode);

var preview = this.createWidget({
   name: "alfresco/html/Markdown",
   config: {
      markdown: this.initialValue,
      subscriptionTopics: [this.generatePreviewTopic]
   }
});
preview.placeAt(this.previewNode);

 

Now we need to ensure that when the user makes edits they are published on the generatePreviewTopic that the Markdown widget will be subscribed to. This means extending the onEditorChange function to publish the new value each time it is updated.

 

onEditorChange: function alfresco_forms_controls_MarkdownWithPreviewEditor__onEditorChange(editor, changeObject) {
   this.inherited(arguments);
   this.alfPublish(this.generatePreviewTopic, {
      markdown: this.lastValue
   });
}

 

Now we have an extended version of the CodeMirrorEditor that is configured for markdown editing, with an additional Markdown preview widget that is updated on changes.

 

Summary

Although this doesn't show every change (these can be reviewed in the associate pull request) I've tried to capture the essential elements of how two separate widgets were pulled together to make a new one. The steps I took to create this were obvious to me because of my familiarity with Aikau - but if you need help on getting started creating a new widget then just create a question in the discussion forums and I'll be happy to provide you with suggestions on how to get started.

I recently saw a really interesting blog post from Parashift on creating custom editors for Share. It was interesting because they were showing how to use the showdown.js markdown library as a previewer and that is exactly the same markdown library that Aikau uses for its Markdown widget.

 

I thought it might be an interesting exercise to compare the effort required to create the same editor in Aikau - this isn't to take anything away from what Parashift have shown which is a perfectly valid customization for Share - I just wanted to see what it would be like to do it using Aikau.

 

Full disclose: I had to create a new widget and make some modifications to Aikau to support this blog post, but these changes are all now available in the 1.0.89 release of Aikau

 

I'm going to show this as a customization of the Aikau Document Library (rather than the YUI2 version) and this means that the editor will be displayed inline rather than on a separate page. I've discussed the Aikau Document Library in previous blog posts here and here so I would recommend reading through those to understand how you can customize Share to make use of the Aikau Document Library.

 

The main thing we want to do is to define a new AlfCreateContentMenuItem that can be used to show a dialog for creating markdown content. The doclib.lib.js import file that you'll use to build the model for the Document Library contains the useful helper function generateCreateContentMenuItem. We can call this with configuration for the menu item like this:

 

var createMarkdownMenuItem = generateCreateContentMenuItem({
   menuItemLabel: "Create Markdown", 
   dialogTitle: "Create Markdown", 
   iconClass: "alf-textdoc-icon", 
   modelType: "cm:content", 
   mimeType: "text/markown",
   dialogWidth: "900px",
   contentWidgetName: "alfresco/forms/controls/MarkdownWithPreviewEditor", 
   contentWidgetConfig: { 
      width: 300, 
      height: 250
   }
});

 

The main thing to note here is that we're setting the contentWidgetName to the new MarkdownWithPreviewEditor widget and setting a mimeType to "text/markdown".

 

We now need to ensure that the menu item is included in the Document Library model, this can be easily achieved in the configuration passed to the getDocLib function that is called to build the main model:

 

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

 

...and that's it !!

 

When you access the page you'll see the following:

 

The full source of the JavaScript controller for the WebScript is here (the descriptor, template and properties files are the same as in the other blogs posts)...

 

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

// Get the services and widgets for the header...
var services = getHeaderServices();
var widgets = getHeaderModel(msg.get("aikau.doclib.title"));

// Build a list of services for both the header AND the Document Library...
services = services.concat(getDocumentLibraryServices());

// Create the markdown creation menu item...
var createMarkdownMenuItem = generateCreateContentMenuItem({
   menuItemLabel: "Create Markdown", 
   dialogTitle: "Create Markdown", 
   iconClass: "alf-textdoc-icon", 
   modelType: "cm:content", 
   mimeType: "text/markown",
   dialogWidth: "900px",
   contentWidgetName: "alfresco/forms/controls/MarkdownWithPreviewEditor", 
   contentWidgetConfig: { 
      width: 300, 
      height: 250
   }
});

// Create the Document Library model...
var docLib = getDocLib({
   siteId: page.url.templateArgs.site, 
   containerId: "documentLibrary",
   additionalCreateContentItems: [createMarkdownMenuItem]
});

// Add the Document Library model to the header widgets...
widgets.push(docLib);

// The footer wraps everything...
model.jsonModel = getFooterModel(services, widgets);

// Always include user membership data for any group membership based evaluations...
model.jsonModel.groupMemberships = user.properties["alfUserGroups"];

Filter Blog

By date: By tag: