Community Manager: Customizing the User Interface

The Community Manager product is designed as a consumer-facing portal for our customers. In order to facilitate the need to customize the portal, Akana has provided a number of options to brand, extend and customize the user experience.

This document provides information about the extensibility mechanisms built into the product.

Table of Contents

  1. Architecture
  2. Branding
  3. Adding and Exposing Static Content
  4. Customization Architecture
  5. Adding New Views or Widgets
  6. Altering Existing Widgets
  7. Altering Existing Widgets – Legacy Framework
  8. Editing the Custom Dashboard
  9. Internationalization
  10. Examples
  11. Appendix

Architecture

The Community Manager product is comprised of a backend REST API that encapsulates the business logic and a content management system (CMS) that serves up the user interface. The user interface is built using CanJS and communicates using Ajax with the backend REST API. This document describes how to upload new or altered files into the CMS, allowing customers to customize the user experience with a great deal of flexibility.

The diagram below illustrates the basic architecture:

basic architecture

Back to top

Branding

Branding is done by modifying a dynamic stylesheet (LESS) provided by the system and uploading it into the CMS via the user interface. For more information, please refer to the documentation:

http://docs.akana.com/cm/learnmore/site_admin_config.htm

Back to top

Adding and Exposing Static Content

When customizing the UI, it is important to understand how static content from the CMS interacts with the overall application. The Community Manager UI is a CanJS MVC application that runs in the browser. It has pages (views) that present a variety of dynamic and static content to the user using client-side templates and JavaScript. It is not tightly coupled to the underlying CMS, but has specific pages that are designed to directly serve this static content.

This allows the customer to seamlessly integrate static marketing content and documentation from more dynamic views, such as the DevConsole or Monitoring pages.

content diagram

The default Community Manager theme incorporates static content in several landing areas within the site, namely:

  1. #/home/landing
  2. #/home/support
  3. #/api/*/versions/*/documents

As a customer, you have the freedom to upload any content into the CMS to brand and customize the content. You also have the ability (mentioned later in this document), to add other views that incorporate static content.

For more information, please refer to:

http://docs.akana.com/cm/learnmore/site_admin_config.htm#how_do_i_upload_content_to_the_platform

Back to top

Customization Architecture

The Community Manager UI is built using a javascript framework called CanJS. Modifying the user interface beyond basic branding and static content requires an understanding of CanJS principles and architecture. More info can be found at:

http://canjs.com/

CMS Deployment Package

All user interface artifacts are packaged into a zip file (with the root "/theme/default/") and uploaded via the user interface using the file upload facility (typically Administration -> Config -> Resources -> Resources). The file structure of the package is described in the diagram below:

CMS deployment package

Once uploaded, you should be able to refresh you browser and see you new views in the user interface. Depending on timing, you may also want to refresh your browser cache.

Note: the path "SOA/CM/extensions" cannot be changed, neither can the names of the following files:

  • metadata.json
  • widget_factory.json
metadata.json

Metadata.json is a file that describes the layout of the pages and the corresponding URL for each page. For example, if you want to design a new page at the URL #/foo/bar, you would add an entry within the layoutStructure array in metadata.json as follows:

"layoutStructure": [{
    "objType": "foo", 
    "view": "bar",
    "scriptResources": [
      PATH TO OPTIONAL SCRIPT RESOURCES (e.g. CANJS)
    ],
    "layout":[
      LIST OF WIDGETS
    ]
  }
...
]

Metadata.json can also be used to load other javascript files from the CMS as follows:

"scriptResources": [
    LIST OF JAVASCRIPT FILES
  ]

The "layout" array in the metadata file describes the list of widgets that are used to build a particular page. For example, a page might have header, footer, left nav and content. Each of these is a widget.

Note: In the example below, comments have been added to explain how the file is put together. Comments are not valid in a JSON file. If you use this example, remove the comments and validate the JSON before uploading your file to the developer portal.

{
  "layoutStructure": [{
              // objType: describes the root url. Typically a resource in the
              // system such as "api", "app", or "user".
    "objType": "foo", 
              // view: describes the specific view for the objType above.
              // Typically something like "details" or "monitor".
    "view": "bar",
              // scriptResources: these are the JavaScript files that need to
              // be loaded; typically the CanJS model and controller files.
    "scriptResources": [
      PATH TO OPTIONAL SCRIPT RESOURCES (e.g. CANJS)        ],
              // layout: an array of widgets with parent/child relationships.
    "layout":[
      {
              // widgetInstance: a unique widget instance name
      "widgetInstance": "Main",
              // widget: the name of the actual widget. In this specific case,
              // "widget.horiz.tiling" is a REQUIRED parent widget describing 
              // a tile layout of all the other widgets on the page.
      "widget": "widget.horiz.tiling",
              // Priority: the order in which the widget is rendered within its
              // parent.
      "priority": "100"
      },
      {
              // The header widget is a predefined widget in the default theme 
              // and is drawn first
      "widgetInstance": "Header",
      "widget": "widget.header",
      "parentWidgetInstance": "Main",
              // Typically, the parent is going to be "widget.horiz.tiling".
      "parent": "widget.horiz.tiling",
      "priority": "1000"
      },
      {
              // Content-Widest is a widget that describes a full-page layout 
              // without left or right nav. (hence: widest). Other options are
              // Content-Wide (to make room for either left or right nav) and
              // Content (to make room for both left and right nav). These
              // correspond to style element in the css, so you can change the
              // size of the columns.
      "widgetInstance": "Content-Widest",
      "widget": "widget.horiz.tiling",
      "parentWidgetInstance": "Main",
      "parent": "widget.horiz.tiling",
      "priority": "2200"
      },
      {
      "widgetInstance": "soa-control-cm-test-form-widget",
              // "widget.iconsole.adapter" is where you will put most of your
              // customizations. It is the container for the actual HTML that
              // will be rendered on the page within its parent. You can have
              // multiple instances of this widget and they will be drawn
              // according to priority within the parent widget.
      "widget": "widget.iconsole.adapter",
      "parentWidgetInstance": "Content-Widest",
      "parent": "widget.horiz.tiling",
      "priority": "2201",
              // "properties" are specific to a widget.
      "properties":{
              // "widgetKey" references the widget in the widget_factory.json
              // file described below. It is specific to the
              // widget.iconsole.adapter.
        "widgetKey": WIDGETKEY
      }
      },
      {
              // The footer widget is a predefined widget in the default theme
              // and is drawn last.
      "widgetInstance": "Footer",
      "widget": "widget.footer",
      "parentWidgetInstance": "Main",
      "parent": "widget.horiz.tiling",
      "priority": "3000"
      }
    ]
  }]
}
widget_factory.json

The widget factory JSON document describes the CanJS view corresponding to each widget in metadata.json.

Note: In the example below, comments have been added to explain how the file is put together. Comments are not valid in a JSON file. If you use this example, remove the comments and validate the JSON before uploading your file to the developer portal.

{
              // The name of the object corresponds to the widgetKey for each of
              // the "widget.iconsole.adapter" widgets in metadata.json.
  "WIDGETKEY": {
    "widgets": [
      {
        "position": "html",
             // view: provides the path to the EJS template for the view.
        "view": PATH TO CANJS VIEW
      }
    ]
  }
} 

Back to top

Adding New Views or Widgets

In the event that branding alone is insufficient to achieve the desired customization a user could either:

  1. Add a new view or widget for a new page
  2. Replace the view or a widget entirely for a page
  3. Alter the existing view

To add a new view or widget, the following process should be followed:

  1. Write the CanJS view, controller and optional model
  2. Modify the metadata.json and widget_factory.json files to add the new view, or to add a new widget to an existing view
  3. Create a zip file with the structure described in the previous section
  4. Upload the zip file into the CMS

The process for replacing a view or widget is similar to creating a new one:

  1. Write the replacement CanJS view, controller and optional model
  2. Modify the metadata.json and widget_factory.json files, adding the replacement view with the same objType and view parameters as the existing view. This essentially overwrites the existing definition in the system
  3. Create a zip file with the structure described in the previous section
  4. Upload the zip file into the CMS

Back to top

Altering Existing Widgets

A widget is made up of a CanJS view (EJS) and corresponding controller. Altering existing widgets in the product is not a common practice since any significant changes to the view (EJS) files typically means that you need to change the controller as well, but there might be some cases (e.g. adding a link) that justifies modifying the EJS file only.

To modify the EJS file simply upload a new EJS file into the CMS to replace the existing file. For example, if the path of the EJS file is:

/resources/[VER]/SOA/CM/common/notifications/views/notifications.ejs

Then just add a new notifications.ejs file with the same path under theme/default in your CMS Deployment Package.

Back to top

Altering Existing Widgets – Legacy Framework

The Community Manager UI is evolving and still has some widgets rendered using legacy Handlebars and jQuery Templates. A classic example of this is the header widget, which often requires modification.

To modify these templates, simply upload a new template file into the CMS to replace the existing file. For example, if the path of the template file is:

/resources/[VERSION]/widgets/HeaderWidget/header.tmpl.htm  

Then just add a new header.tmpl.htm file with the same path under theme/default in your CMS Deployment Package.

Back to top

Editing the Custom Dashboard

The developer portal includes a custom dashboard available on #/home/customdashboard (version 6.5.0 and later). This dashboard provides customers and their users with a way to create their own dashboard made up of a set of widgets – in a drag and drop way.

The architecture is very similar to the architecture described in the previous sections, with the exception that the metadata.json file has been effectively replaced by the ability to select widgets and drag them to appropriate locations on the page:

custom dashboard

The product ships with a set of prepackaged widgets, including:

  • Top 5 APIs – a graph of the 5 APIs with the most traffic
  • Notifications – a list of the user's notifications
  • Documents – static content stored in the CMS.

The Documents widget is especially powerful because it allows customers to write any static content and present it to the users of the Community Manager product.

In addition to the prepackaged widgets, custom widgets can be built and deployed in the same way as conventional widgets – with the exception of requiring a metadata.json file. To create a custom dashboard widget:

  1. Write the CanJS view, controller and optional model
  2. Modify the widget_factory.json files to add the new widget
  3. Create a zip file with the structure described in the previous section
  4. Upload the zip file into the CMS

The widget_factory.json file will look like the example shown below.

Note: In the example below, comments have been added to explain how the file is put together. Comments are not valid in a JSON file. If you use this example, remove the comments and validate the JSON before uploading your file to the developer portal.

{
  "WIDGETKEY": {
    "widgets": [
      {
        "position": "html",
        "view": PATH TO CANJS VIEW
      }
    ],
    widgetContexts: [
      {
        "context" : "customdashboard",
              // widgetContext provide a list of the views where this widget is
              // available. In this case, it is "customdashboard". The label is
              // the name of the widget that appears in the UI and it must be 
              // resolved to the right key via internationalization.
        "label" : "label.Notifications"
      }  
    }
}  
}

Back to top

Internationalization

There are two places in the product that drive internationalization:

  • /theme/default/i18n/CustomApplicationResources_[LOCALE].properties
  • /locales/custom_[LOCALE].json

CustomApplicationResources.properties

The CustomApplicationResources.properties files are artifacts of the old framework and contain only a limited set of predefined labels:

#com.soa.atmosphere.site.title=Your Developer Portal
#com.soa.atmosphere.footer.legal=Read the legal agreements.
#com.soa.atmosphere.footer.rights= © Your Company. All rights reserved.

Note: No additional values should be added to this file.

Custom.json

The product leverages the jsperanto framework for internationalization – see https://github.com/jpjoyal/jsperanto for more information. All i18n strings in EJS views and widget_factory are defined in these files. The file is as follows:

{
  "errors": {},
  "labels": {
    "key": "value",
    "key": "value"
  },
  "messages": {},
  "validator": {
  "errors": {}
  }
}
Using Internationalization

All language-specific text in the user interface should be externalized in custom_[LOCALE].json files and accessed via the soa.framework wrapper that has been provided in the product. For example:

  • in EJS files:
    <%=soa.framework.globalize("labels.key", replacements)%>
  • in JavaScript files:
    SOA.Framework.Common.I18N.globalize(key, replacements);

Back to top

Examples

Adding a New Static Content View

A common customization task is to add a new view in the developer portal to support static content from the CMS. For example, a site may benefit from a "Showcases" page that lists all the best apps that have been built and provides links to them.

To set this up you'd have to create and upload two files:

  1. Create metadata.json file and upload it to the platform in Resources:
    1. Create the metadata.json file along the lines of the example shown below (Sample metadata.json file for adding a new static content view).
    2. Upload the new file to the Resources folder structure (create folders as needed, be sure case is exact on folder names). In the developer portal: Administration > Config > Resources. At top of page, under Resources, click File Manager.
    3. In the resources folder, create additional folders to create this path: resources/default/SOA/CM/extensions
    4. In the extensions folder, upload the metadata.json file.
  2. Create the new content page and upload it to the platform in Content:
    1. Offline, develop your index.htm content file. Note the file extension should be htm, not html.
    2. In the developer portal: Administration > Config > Resources. At bottom of page, under Content, click File Manager.
    3. In the content folder, create additional folders to create this path: content/home/showcases.
      Note: the path is defined, in the sample file below, by the value of objType, which is home, and the value of view, which is showcases.
    4. In the showcases folder, upload the index.htm file.
  3. Test. Go to the landing page, then change the URL from home/landing to home/showcases to view the new page.
    Note: You might need to refresh the page.
Sample metadata.json file for adding a new static content view

The following metadata.json file shows how a "widget.document" widget can be added to a new view.

Note: Line 27 of the file below references widget.document which is a standard widget that renders the index.htm file from the CMS system at the path corresponding to the browser URL. For example, "#/home/showcases" will automatically load "/home/showcases/index.htm" from the CMS.

{
  "layoutStructure": [{
    "objType": "home", 
    "view": "showcases",
    "layout":[
      {
        "widgetInstance": "Main",
        "widget": "widget.horiz.tiling",
        "priority": "100"
      },
      {
        "widgetInstance": "Header",
        "widget": "widget.header",
        "parentWidgetInstance": "Main",
        "parent": "widget.horiz.tiling",
        "priority": "1000"
      },
      {
        "widgetInstance": "Content-Widest",
        "widget": "widget.horiz.tiling",
        "parentWidgetInstance": "Main",
        "parent": "widget.horiz.tiling",
        "priority": "2200"
      },
      {
        "widgetInstance": "Landing",
        "widget": "widget.document",
        "parentWidgetInstance": "Content-Widest",
        "parent": "widget.horiz.tiling",
        "priority": "2202"
      },
      {
        "widgetInstance": "Footer",
        "widget": "widget.footer",
        "parentWidgetInstance": "Main",
        "parent": "widget.horiz.tiling",
        "priority": "3000"
      }
    ]
  }]
}

Back to top

Appendix

Sample View

The following file is a very basic view template. These files are Mustache templates and are rendered by the controller, which is referenced at the top of the template as follows:

<div id="soa-control-test-custom-form-container" 
<%= soa.framework.control("TEST.Home.Test.TestForm", this) %>>
  Test Succeeded!
</div>

Sample Controller

The following controller is just a tiny sample of the logic that is typically in a controller. Typically the controllers extend SOA.Console.BaseControl and there are two key functions, namely "display" and "postDisplay". Display is where the data is typically retrieved asynchronously for the form. Once the data is retrieved, a call is made to "SOA.Framework.Common.WidgetFactory.draw()" to actually render the EJS template above.

steal("iconsole/SOA/console").then(function () {
  can.getObject("TEST.Home.Test.TestForm", window, true);
  TEST.Home.Test.TestForm = SOA.Console.BaseControl(
  /* @Static */
  {
    "defaults" : {
    }
  },
  /* @Prototype */
  {
    "display" : function () {
      var thisControl = this;
      var somethingDeferred = ..;
      somethingDeferred.then(function () {
        SOA.Framework.Common.WidgetFactory.draw("[WIDGETKEY]", {
          "data" : [DATA TO BE PASSED TO THE FORM]
        }, thisControl.element);
        thisControl.postDisplay();
      });
    },
    "postDisplay" : function () {
  /* All the actions on the form that are done after redering */
    }
  });
});

Back to top