Edit me

Because of an embedded OSGi container (Apache Felix) and a growing set of extension points Communote can be customized easily with the help of OSGi bundles, or how we call them: plugins. With these plugins, which can be added and removed at runtime, it is for instance possible to:

  • add custom CSS or replace existing templates and thus, create your your own theme,
  • add your own JavaScript components to modify the behavior of the frontend,
  • add your own controller for a specific URL,
  • hook into the creation and rendering of notes and augment them with additional information,
  • provide a new authentication mechanism,
  • add your own languages and translations.

The next chapters will walk you through the creation of such a plugin and will show you some of the extension points. The full source code of this tutuorial can be found in the communote-examples repository.

2.1.1 Create a Plugin

The easiest way to create a new plugin is to use Maven’s Archetype mechanism.

  1. Make sure your development system is prepared. See preparation section for details. You should also install a Communote for testing your plugin.
  2. Open a command line
  3. Change to the folder in which you want to create the plugin
  4. Call the following command (change the values for groupId, artifactId, version and communoteVersion to your needs):

    mvn archetype:generate -DarchetypeGroupId=com.communote.plugins -DarchetypeArtifactId=communote-plugin-archetype -DarchetypeVersion=1.1 -DgroupId=yourGroupId -DartifactId=yourPluginsArtifactId -Dversion=1.0-SNAPSHOT -DcommunoteVersion=3.5

    where the parameters have the following meaning.

    • archetypeGroupId, archetypeArtifactId and archetypeVersion define that the Communote plugin archetype in Version 1.1 should be used
    • groupId defines the groupId of the Maven project of your Communote plugin
    • artifactId defines the artifactId of the Maven project of your Communote plugin
    • version (optional) defines the version of the Maven project of your Communote plugin
    • communoteVersion defines the version of Communote you want to build your plugin against

    The last 4 parameters can be omitted. In that case you will be asked by Archetype to type in the values.

After running the command a new directory will be created in your current working-directory. This directory contains a Maven project for developing your Communote plugin. You can already build this plugin by changing into the directory and invoking mvn on the command line. This will create the plugin, a JAR file, in the target directory. Although, it doesn’t do much, it can already be deployed to your Communote. How this can be done is explained in our installation manual.

2.1.2 Example 1 - Extend Note Rendering

In this example we will show you how the note rendering can be modified. We will define two placeholders named [logo] and [github] which when used in the body of a note will be replaced by our plugin with the Communote logo and a link to our GitHub page. Furthermore, we want to define an alternative text for the logo and a text for the link which can be localized. Before you start maybe it’s good to create a message via JavaScript, which will show the user that your Communote is under construction.

  1. To generate such a construction message you have to create a JavaScript file construction.js at src/main/resources/META-INF/resources/static/javascript:

    // Jumps in when the namespace communote is defined and the main layout is loaded
    if (window.communote && communote.environment && communote.environment.page == 'main') {
        // Add a callback that is invoked before the Communote JavaScript component is initialized
        communote.initializer.addBeforeInitCallback(function() {
            //... this snippet creates a container with a localized message and appends it to the top
            var constructionContainer  = new Element('div', {
                    id: 'construction-container',
                    html: '<h1>' + communote.i18n.getMessage("plugins.communote.tutorial.construction.text") + '</h1>'
            constructionContainer.inject($('cn-view-wrapper'), 'top');

    With the communote.i18n.getMessage() function you can get a localized text which is identified by the key given as argument.

  2. You need to create a properties file at src/main/resources/META-INF/resources/i18n to store the keys and a translation:
    • messages_en.properties (for the English translation) and insert the following line. The keys of the messages of your plugin should be unique so that another developer does not unintentionally overwrite them.

      plugins.communote.tutorial.construction.text=Warning! Your Communote is right now under construction.
    • messages_de.properties (for the German translation) and insert:

      plugins.communote.tutorial.construction.text=Achtung! Ihr Communote ist gerade im Aufbau.
  3. But you are not yet able to use this message key in the JavaScript context. You will first have to expose it with a special extension point. Therfore, go to your plugin and look for a directory which contains the file Activator.java file. Now delete this file because it is not needed in this example and create a Java file named TutorialJsMessages.java instead. Add the following content (and the package and import statements which we omitted for better readability) to it.

    public class TutorialJsMessages implements JsMessagesExtension {
       public Map<String, Set<String>> getJsMessageKeys() {
          HashSet<String> messageKeys = new HashSet<String>();
          // Add the message keys you need to access via your JavaScript file
          HashMap<String, Set<String>> mapping = new HashMap<String, Set<String>>();
          // Define the context where you have access to the message keys
          mapping.put(JsMessagesRegistry.CATEGORY_PORTAL, messageKeys);
          return mapping;
  4. You also need to let Communote know that the JavaScript file of your plugin should be included when the main page of Communote is rendered. This is achieved by creating a JSON file called javascript-categories.json in src/main/resources/META-INF/:

       "extensions": {
          "portal": ["javascript/construction.js"]
  5. Build and and deploy the plugin as explained at the end of the chapter Create a Plugin. Then reload the Communote main page in your browser which should now look like this:

    Figure “Result of the construction message with no style”

  6. You probably recognized that the construction message have no style. Therefore we create a style.css at src/main/resources/META-INF/resources/static/styles:

    #construction-container {
       background-color: #ee7f01;
       padding: 15px;
       text-align: center;
    #construction-container h1 {
       color: #FFFFFF;
       font-size: 16px;
       margin: 0;
  7. The CSS file needs also to register like the JavaScript files with a JSON file called css-categories.json at src/main/resources/META-INF/:

       "extensions": {
          "portal": ["styles/style.css"]

    If you build and deploy the plugin again it will looks like this:

    Figure “Result of the construction message with no style”

    Now you can start going deeper and extend the note rendering process to manipulate the content of a note.

  8. Go to your java folder to create a new java file called TutorialNoteRenderingPreProcessor.java, that implements the NoteContentRenderingPreProcessor to extend the note rendering process:

    @Instantiate(name = "TutorialNoteRenderingPreProcessor")
    public class TutorialNoteRenderingPreProcessor implements NoteContentRenderingPreProcessor {
         * @return the order value which is interpreted as the priority of the pre-processor. The higher
         *         the priority, the earlier this processor will be called.
       public int getOrder() {
          return DEFAULT_ORDER;
         * @return whether the result can be cached. This method has to return false if this
         *         PreProcessor modifies the content in a way that depends on other NoteRenderContext
         *         attributes than the NoteRenderMode (e.g. locale, request attributes or modeOptions).
         *         This PreProcessor should not modify the note metadata (NoteListData object) while
         *         processing the content if this method returns true because the PreProcessor will not
         *         be invoked if the processed content was cached.
       public boolean isCachable() {
          return true;
         * Processes a note for a specific render context. This method will only be called if the
         * processor supports the mode given by the render context.
         * @param context
         *            holds details about the render context to allow specific processing in different
         *            situations
         * @param item
         *            the item to be processed
         * @return true if the item was modified, false otherwise
         * @throws com.communote.server.plugins.api.NoteRenderingPreProcessorException
         *             in case something unexpected lead to the failure of the processor
       public boolean processNoteContent(NoteRenderContext context, NoteData item)
          throws NoteRenderingPreProcessorException {
          return false;
         * Denotes whether the pre-processor replaces the note content completely.
         * @return true if the processor replaces the content and the shortened content completely. If
         *         the processor only modifies parts of the content or other members of the item, false
         *         should be returned. When replacing content the new content must match the render
         *         mode.
       public boolean replacesContent() {
          return false;
         * Whether the processor supports a specific render mode. When returning true and the processor
         * modifies or replaces the note content it must ensure that it only inserts text which matches
         * the render mode, specifically the processor shouldn't insert HTML markup when called in PLAIN
         * mode.
         * @param mode
         *            the note render mode, never null
         * @param note
         *            the note to render. A pre-processor can use this parameter to decide based on note
         *            attributes whether the note content should be processed
         * @return true if the mode can be handled by the processor, false otherwise
       public boolean supports(NoteRenderMode mode, NoteData item) {
          return NoteRenderMode.PORTAL.equals(mode);
  9. Define a list of your replacements globally:

    // Definition of the replacements list
    private final static List<Pair<String, String>> REPLACEMENTS = new ArrayList<>();
    // On the left side of our pair are the placeholders and on the right side are the HTML replacements
    static {
       REPLACEMENTS.add(new Pair<>("[logo]", "<span class=\"communote-logo\"></span>"));
       REPLACEMENTS.add(new Pair<>("[github]", "<a href=\"http://github.com/communote\">Github Communote</a>"));
  10. Jump into the function processNoteContent(NoteRenderContext context, NoteData item) and you can use the item object to get and set the short and full content of a note:

    public boolean processNoteContent(NoteRenderContext context, NoteData item)
       throws NoteRenderingPreProcessorException {
       // Setting the new content of the note delivered by the replacement function
       // ... and do the same for the short preview content
       return true;
  11. You now able to create the new function processContent(content) with the current content as variable that needs to be replaced:

    private String processContent(String content, Locale locale) {
       // Do nothing if the content is null
       if (content == null) {
          return null;
       // Searching for the "[logo]" and "[github] placeholder to replace them with the HTML defined above
       for (Pair<String, String> replacement : REPLACEMENTS) {
          content = content.replace(replacement.getLeft(), replacement.getRight());
       return content;
  12. Nearly done. For the image replacement you need a style for the .communote-logo class and the logo image.

    1. Upload the logo.png at src\main\resources\META-INF\resources\static\images``
    2. Add to your CSS file style.css the following class:

      .communote-logo {
        background-image: url("../images/logo.png");
        display: inline-block;
        height: 16px;
        width: 106px;
  13. Build and redeploy your plugin again. After reloading your Communote main page you should see this:

    Figure “Result for Example 1 - Extend Note Rendering”

2.1.3 Example 2 - Create a view with a widget

This example will explain you how to create a view at the notes overview with one simple widget on it.

  1. At the horizontal navigation next to the bookmarklet link we want to register a new view with a widget as content. Lets start at your resources/static/javascript folder and place a new javascript file CreateHelloWorldView.js in it:

    // Jumps in when the namespace communote is defined and the main layout is loaded
    if (window.communote && communote.environment && communote.environment.page == 'main') {
      // Add a callback that is invoked before the Communote JavaScript component is initialized
      communote.initializer.addBeforeInitCallback(function() {
        var widgets, views;
        // parentViewId defines where the new subview should be added (other parent views defined by
        // the core are for example topicsOverview or topicSelected)
        var parentViewId = 'notesOverview';
        // Name of the new subview
        var viewName = 'helloWorld';
        var subViewName = parentViewId + '_' + viewName;
        // Register view link to horizontal navigation bar
        // Get list of all views
        views = communote.configuration.mainPageViewManagerConfig.views;
        // Add the new view to the list of views
        views[subViewName] = {
          // When the user changes the view and 'hide' is defined the view container will be hidden.
          // Another option is 'delete' for deleting it from the DOM
          previousViewAction: 'hide',
          // List all the widgets that should be shown in the new view
          visibleWidgets: ['EntityChooser','VerticalNavigation',
            'HorizontalNavigation', 'GlobalIdShowBannerImage', 'HelloWorld']
            // You can also add parentViewId: 'insertMainView' as an option
            // to extend the visible widgets of a main view
        // Get list of all widgets
        widgets = communote.configuration.mainPageViewManagerConfig.widgets;
        // Add our HelloWorld widget to the list of all registered widgets
        widgets['HelloWorld'] = {
          // The widgetType is used to identify the Java and JavaScript widget class. Moreover,
          // a CSS class with that name is added to the container of the widget.
          widgetType: 'HelloWorldWidget',
          // The CSS selector defines where the widget should be injected
          containerSelector: '#cn-list-posts'
  2. Localize the name of the new horizontal navigation point via the message properties:
    • messages_en.properties

      mainpage.horizontal.navigation.notesOverview.helloWorld=Hello World!
    • messages_de.properties

      mainpage.horizontal.navigation.notesOverview.helloWorld=Hallo Welt!

    The key notesOverview stands for the parent view and helloWorld for our new subview.

  3. In step 1 we also add a new widget to the view. A widget needs a Java and a JavaScript class.
    1. Lets start with the Java class HelloWorldWidget.java which needs to created at your Java folder:

      public class HelloWorldWidget extends EmptyWidget {
          public String getTile(String outputType) {
            // key of the vm.tiles-mappings.properties entry whose value holds
            // the path of the velocity file which should be rendered
            return "com.communote.tutorial.HelloWorld.widget";
    2. The tiles are mapped at the file resources/META-INF/vm.tiles-mappings.properties:

    3. The linked Velocity file HelloWorld.Widget.html.vm needs to be placed at resources/META-INF/resources/vm/widget/ and have the following content:

      ## $widgetInstance -> via the widget instance it is possible to access functions and variables of the JavaScript widget object
      #set($widgetInstance = "#jsCurrentWidget()")
      <div class="cn-form-container">
          <div class="cn-field-50">
              <label class="cn-label" for="${widget.WidgetId}_message">
                  ## In order to internationalize your code use the helper functions #t(key) and #t_args(key, arguments)
              <input type="text" class="cn-inputTxt" id="${widget.WidgetId}_message" name="${widget.WidgetId}_message" />
          <div class="cn-buttons">
              ## Using the widget instance to define an onclick event for the button
              <input type="button" id="${widget.WidgetId}-show" name="${widget.WidgetId}-show" class="cn-button main"
                     value="#t('plugins.communote.tutorial.widget.HelloWorld.button')" onclick="${widgetInstance}.showSuccessMessage()"/>

      With this widget template the user can write a text and click on the button to generate it as a Communote notification.

    4. In this case we your JavaScript folder you can create the widget class HelloWorld.Widget.js:

      (function(namespace) {
          var HelloWorldWidget = new Class({
              Extends: C_Widget,
              First part "plugin/" is required and marks the widget as one that is provided by a plugin.
              The maven placeholder is needed to create an unique widget group name based on the name of the OSGi bundle and
              will be replaced when building the plugin.
              widgetGroup: 'plugin/${maven-symbolicname}',
              //This function prints out the input value via a notification
              showSuccessMessage: function() {
                  var message = this.domNode.getElementById(this.widgetId + '_message').get('value')
                  showNotification(NOTIFICATION_BOX_TYPES.success, null, message);
          // to register the widget constructor within the Communote namespace
          namespace.addConstructor('HelloWorldWidget', HelloWorldWidget);
    5. You need to edit your pom.xml file and configure the filter process to get the maven placeholder to be replaced:

                  <!-- to replace maven placeholders in Widget files -->

      In order to filter also the minified JavaScripts add this plugin:

                  <!-- add bundle name to widgetGroup for minimized files -->
    6. It is time to register both JavaScript files at your plugin in the javascript-categories.json at resources/META-INF/:

        "extensions": {
          "portal": [
    7. We used some translation keys at the widget velocity template you need to add:

      • messages_en.properties

        plugins.communote.tutorial.widget.HelloWorld.input.label=Type in your message
        plugins.communote.tutorial.widget.HelloWorld.button=Show Message
      • messages_de.properties

        plugins.communote.tutorial.widget.HelloWorld.input.label=Deine Nachricht
        plugins.communote.tutorial.widget.HelloWorld.button=Nachricht senden
  4. Now we need an widget factory class TutorialWidgetFactory.java for our plugin in your Java folder. This factory creates an instance of our widget with the given widget group and widget type name:

    public class TutorialWidgetFactory implements WidgetFactory {
        private final String symbolicName;
         * Constructs the widget factory
         * @param bundleContext
         *            the context of the bundle
        public TutorialWidgetFactory(BundleContext bundleContext) {
            // Saving the context of the plugin to stop and start the factory
            this.symbolicName = bundleContext.getBundle().getSymbolicName();
         * Create a new widget instance that should handle the current widget request.
         * @param widgetGroupName
         *            the group of the widget
         * @param widgetName
         *            the name of the widget
         * @param requestParameters
         *            the request parameters of the current request
         * @return the new widget instance
         * @throws WidgetCreationException
         *             in case the widget cannot be created
        public Widget createWidget(String widgetGroupName, String widgetName,
            Map<String, String[]> requestParameters) throws WidgetCreationException {
            // Return the new widget instance
            return new HelloWorldWidget();
         * Plugin start callback to register the factory.
        public void start() {
                       .addWidgetFactory(this.symbolicName, this);
         * Plugin stop callback to remove the factory.
        public void stop() {
  5. Done!

    Figure “Result for Example 2 - Create a view with a widget”

2.1.4 Other Plugin examples

You can download and look through other plugins like:

2.1.5 Platform Extension Points Extend Notes, Tags, Users

What to extendClass/Interface
Note content before stored

This interface allows to edit the content of a note before it is stored at the database.
Note metadata before stored

This interface allows to edit the metadata of a note before it is stored at the database.
Note content rendering

Allows to execute logic after the note is saved. At the point of manipulation the note is already stored at the database. The process is working async. Overwrite the processNoteContent() and manipulate the content with their getter and setter functions of the item object.
Note metadata rendering

Allows to execute logic after the note is saved. At the point of manipulation the note is already stored at the database. The process is working async. Overwrite the processNote() and manipulate the metadata with their getter and setter functions of the item object.
Note user notification (@user)

Allows you to control if the user receiving a notification by overwriting the function preProcess().
Note topic notification (@@all, @@authors and @@managers)

Allows you to control if the users of a topic receiving a notification by overwriting the function preProcess().
Tag suggestions

A TagSuggestionProvider allows searching for tags in arbitrary systems. The results will be shown to the user as suggestions.
Note attachments

Processor to process attachments before they will finally be stored.
New note format

Extension for the ChronologicalPostList widget which provides means to render the HTML of a note with another template. As an example take a look into the ActivityNoteItemTemplateProvider.
Add note meta data

Extension for the {@link ChronologicalPostListWidget} which allows adding meta-data to a note which should be available in the JavaScript frontend, for example in a NoteActionHandler or a note click handler.
Add note functions

Extension for the ChronologicalPostListWidget which allows adding or replacing actions of a note. This extensions just provides the names of the actions. Additionally message keys for the actions which adhere to the following naming conventions must be provided and exposed to JavaScript:
  • widget.chronologicalPostList.note.action.NameOfTheAction.label - the display name of the action
  • widget.chronologicalPostList.note.action.NameOfTheAction.title - to provide a value for the title attribute (optional)
More over a JavaScript action handler has to be registered to the NoteActionHandler of the widget.
Tag store

Add an own tag store
User preferences

Allows to store own user preferences. Implementing class must have an empty constructor to allow instantiation while runtime.
External user repository

It is possible to integrate a own user repository.
Content type

This class allows to add new content types to Communote, for instance "Video", "Image" or "Document". Content types can be used in conjunction with the ContentTypeWidget to allow filtering for content types.
ContentTypeWidgetExtension Administration Page

An own page at the administration is possible via an AdminController. For an example take a look at the AdminController of this plugin: #Todo: Link zum {AdminstrationPluginSample}

Extend the AdministrationViewController and implement the Controller Interface. Events

Implement the interface com.communote.server.events.EventListener<Your Event Type> to extend your Event.

What to extendEvent Type
Topic created

Event to notify about new topics.
Role changed

Event to notify about a change of a blog role of a user. This covers all roles that have been assigned to the user either directly or indirectly through group membership. The reason for the event can be that the role was added, removed or modified.
Discussion changed

Event, which is fired, when a discussion as changed.
Topic Hierarchy Connections changed

Event, which is fired, when a topic is added or removed at the topic hierarchy.
Property changed

Fired if property of any entity is created, updated or deleted. Check available types of properties at com.communote.server.core.property.PropertyType
Status of a user changed

Event that is fired the status of a user was changed.
UserStatusChangedEvent JS Hooks

Initializer Callbacks

Helper to initialize the application. The component provides different hooks to do custom initializations at different points during startup and is exposed in the communote namespace as communote.initializer.

What to extendCallback Function
After initialization complete

Add a callback function that will be called after the initialization completed. If the widget framework should be initialized this callback will be invoked after any callback added with addWidgetFrameworkInitializedCallback.
Before initialization

Add a callback function that will be called before doing any kind of initialization.
After Widget Framework initialization

Add a callback function that will be called after the widget framework was initialized.
After scanning widgets

Add a callback function that will be called after scanning for widgets during page load.
Before scanning widgets

Add a callback function that will be called before scanning for widgets during page load.
After everything

Add a callback function that will be called after everything (initializations and widget scan if required) is ready. If called after initApplication was called the callback will be invoked directly.


What to extendObjects
View registration

New views can be added to the mainPageViewManagerConfig.views object. It is possible to extend a view (via parentViewId as parameter) or create a view from scratch.
Widget registration

New widgets can be added (without the "Widget" suffix) to the mainPageViewManagerConfig.widgets object with the following parameters:
  • widgetType: as identifier and CSS class
  • containerSelector: CSS selector, which says where the Widget takes place
  • settings: object which holds all the widget settings
Navigation registration

Add a link of a view into the navigation (horizontal or vertical).
Set the main view via the parent view ID like for example notesOverview, topicSelected or userEdit and push the view ID of the new view to it.
The localized frontend name of the navigation point can be set via the message key mainpage.horizontal.navigation.{Parent View ID}.{View ID}
.mainPageContexts[Parent View ID].push(View ID)
Default render options for widgets at views

It is possible to change the render options for widgets in a view. You can hide for example the tag cloud in the notes overview per default.
communote.configuration.factory.defaultNoteViewRenderOptions Overwrite Templates

It is possible to overwrite all the templates registered at the core.vm.tiles-mappings.properties (#ToDo: Link) with an own vm.tiles-mapping.properties file but it cannot be recommended. In the worst case the custom template stops working when a core update alters the overwritten template.

Tags: development