How to use bitmap icons - softwareloop/alfresco-inboxes GitHub Wiki

To start, let's see a screenshot of what we want to achieve:

alfresco-inboxes using the Famfamfam Silk bitmap icons

Step 1: download the Famfamfam Silk icons

Download the Silk icon set. The distribution file is called famfamfam_silk_icons_v013.zip.

In the source code of alfresco-inboxes locate the folder src/main/amp/web/components/softwareloop/inboxes/ and uncompress the zip file there.

To be sure the paths are correct, check that the PNG files are located at src/main/amp/web/components/softwareloop/inboxes/famfamfam_silk_icons_v013/icons/

Step 2: create an html template

Locate the folder src/main/amp/web/js/softwareloop/inboxes/templates/. It contains the html templates for the components of the alfresco-inboxes plugin. The one that defines an inbox is Inbox.html:

<div class="inboxes-inbox"
     data-dojo-attach-event="click:clickHandler">
    <div class="inboxes-inbox-counter" data-dojo-attach-point="counterNode">&nbsp;</div>
    <div class="inboxes-inbox-text">
        <i class="inboxes-inbox-icon ${iconClass}"></i>
        <span class="inboxes-inbox-title">${title}</span>
    </div>
</div>

The structure of the html is simple:

  • a top div container containing:
    • an inboxes-inbox-counter div for the numeric counter on the right side of an inbox
    • an inboxes-inbox-text containing:
      • an icon element
      • a title span

The icon element implemented as an i element with a css class is the typical approach used by icon fonts. To use bitmap icons we're going to replace it with a plain img element. To do so, copy Inbox.html to a new file BitmapInbox.html in the same directory, and populate it with the following content:

<div class="inboxes-inbox"
     data-dojo-attach-event="click:clickHandler">
    <div class="inboxes-inbox-counter" data-dojo-attach-point="counterNode">&nbsp;</div>
    <div class="inboxes-inbox-text">
        <img src="${absoluteIconPath}" style="vertical-align: bottom"/>
        <span class="inboxes-inbox-title">${title}</span>
    </div>
</div>

This file is an html template, so ${absoluteIconPath} is a parameter that will be replaced automatically when the template is instantiated. Instantiated when and replaced with what? This is where we need a new JavaScipt component.

Step 3: extend the Inbox component

The standard component that implements an inbox is called Inbox.js and is located at src/main/amp/web/js/softwareloop/inboxes/. When customising a component, there are two approaches that can be taken: one is to modify the component's code directly, the other is to extend the standard component by creating a new one.

The latter approach is arguably better to increase encapsulation, separation, and re-use.

So create a file called BitmapInbox.js in the same directory as Inbox.js and start with the following content:

define([
  'dojo/_base/declare',
  './Inbox'
], function (declare, Inbox) {
  return declare([Inbox], {
  });
});

These seven lines of code are the bread and butter of many Dojo/Aikau customisations. The global function define() takes two arguments. The first is an array of dependencies, the second is a function that uses those dependencies.

The function returns a class declaration using declare(). The first argument is an array of parent classes that will be inherited. Here we're declaring that BitmapInbox inherits from Inbox.

The second argument of declare() is a "mix-in", an object that will be mixed into the declaration by taking its properties and adding them to the new class, along with those of the inherited classes.

A mix-in is a powerful tool, as its properties can override those of an inherited class. Let's use this feature right away:

define([
  'dojo/_base/declare',
  './Inbox',
  'dojo/text!./templates/BitmapInbox.html'
], function (declare, Inbox, template) {
  return declare([Inbox], {
    templateString: template
  });
});

Here templateString is a property of the mix-in, but it's also a property of the parent class Inbox. The templateString of the mix-in takes precedence, effectively overriding the same property of Inbox.

dojo/text!./templates/BitmapInbox.html is the syntax used to load the html we created in the previous section and make it available through the template dependency. template is simply a string containing the html and its value is assigned to templateString.

Now it's time to look after the absoluteIconPath parameter.

define([
  'dojo/_base/declare',
  'dojo/_base/lang',
  './Inbox',
  'dojo/text!./templates/BitmapInbox.html'
], function (declare, lang, Inbox, template) {
  return declare([Inbox], {
    templateString: template,

    postMixInProperties: function () {
      this.inherited(arguments);
      this.absoluteIconPath = lang.replace(
        "{urlRescontext}components/softwareloop/inboxes/famfamfam_silk_icons_v013/icons/{iconPath}",
        {
          urlRescontext: Alfresco.constants.URL_RESCONTEXT,
          iconPath: this.iconClass
        }
      );
    }

  });
});

The postMixInProperties function is executed automatically when the component is initialised. This is a good place to set the absoluteIconPath property. Its value is calculated using the lang.replace() function that provides a simple string templating mechanism. Notice that the last part of the path is provided by this.iconClass where iconClass is one of the attributes in inboxes.get.config.xml.

Step 4: set the icons

Locate the inboxes.get.config.xml file in src/main/amp/config/alfresco/web-extension/site-webscripts/softwareloop/inboxes/. This is the main web script configuration, as explained in a previous page.

In the file, an inbox definition may look like this:

<inbox id="drafts" iconClass="foundicon-paper-clip">

... where foundicon-paper-clip is a css class specific to the ZURB Foundation Icon font. In the BitmapInbox component we have to use iconClass with a different meaning, i.e. to indicate the name of an icon image. For example:

<inbox id="drafts" iconClass="attach.png">

For the list of available icons, browse the contents of: src/main/amp/web/components/softwareloop/inboxes/famfamfam_silk_icons_v013/icons/

Step 5: reference the new component

The BitmapInbox component is in place but nobody is using it yet. We need to make the alfresco-inboxes plugin aware of the new component so it can be used instead of the default Inbox.

Alfresco-inboxes supports this sort of extension through the inboxClass attribute:

<inbox id="drafts" iconClass="attach.png" inboxClass="softwareloop/inboxes/BitmapInbox">

inboxClass takes the fully qualified name of a dojo/Aikau component to be used as the inbox implementation. Its default value is softwareloop/inboxes/Inbox, i.e., the standard inbox component.

Notice that each inbox has its own iconClass meaning that each inbox can have a different implementation. For example it is possible to have some inboxes use icon fonts through the default Inbox implementation and other inboxes use bitmap icons through the custom BitmapInbox we just created.

Summary of the customised files

BitmapInbox.html:

<div class="inboxes-inbox"
     data-dojo-attach-event="click:clickHandler">
    <div class="inboxes-inbox-counter" data-dojo-attach-point="counterNode">&nbsp;</div>
    <div class="inboxes-inbox-text">
        <img src="${absoluteIconPath}" style="vertical-align: bottom"/>
        <span class="inboxes-inbox-title">${title}</span>
    </div>
</div>

BitmapInbox.js:

define([
    'dojo/_base/declare',
    'dojo/_base/lang',
    './Inbox',
    'dojo/text!./templates/BitmapInbox.html'
], function (declare, lang, Inbox, template) {
    return declare([Inbox], {
        templateString: template,

        postMixInProperties: function () {
            this.inherited(arguments);
            this.absoluteIconPath = lang.replace(
                "{urlRescontext}components/softwareloop/inboxes/famfamfam_silk_icons_v013/icons/{iconPath}",
                {
                    urlRescontext: Alfresco.constants.URL_RESCONTEXT,
                    iconPath: this.iconClass
                }
            );
        }

    });
});

Inbox.html:

<div class="inboxes-inbox"
     data-dojo-attach-event="click:clickHandler">
    <div class="inboxes-inbox-counter" data-dojo-attach-point="counterNode">&nbsp;</div>
    <div class="inboxes-inbox-text">
        <i class="inboxes-inbox-icon ${iconClass}"></i>
        <span class="inboxes-inbox-title">${title}</span>
    </div>
</div>

inboxes.get.config.xml:

<inboxes>
    <group id="my-documents">
        <inbox id="drafts" iconClass="attach.png" inboxClass="softwareloop/inboxes/BitmapInbox">
            <query><![CDATA[
            SELECT d.*, t.* 
            FROM cmis:document AS d
            JOIN cm:titled AS t on d.cmis:objectId = t.cmis:objectId
            WHERE contains(d, 'PATH:"/app:company_home/st:sites/cm:swsdp/cm:documentLibrary/cm:Meeting_x0020_Notes/*"')
            ]]></query>
        </inbox>
        <inbox id="for-my-approval" iconClass="accept.png" inboxClass="softwareloop/inboxes/BitmapInbox">
            <query><![CDATA[
            SELECT d.*, t.*
            FROM cmis:document AS d
            JOIN cm:titled AS t on d.cmis:objectId = t.cmis:objectId
            WHERE contains(d, 'PATH:"/app:company_home/st:sites/cm:swsdp/cm:documentLibrary/cm:Budget_x0020_Files/cm:Invoices/*"')
            ]]></query>
        </inbox>
        <inbox id="overdue" iconClass="clock.png" inboxClass="softwareloop/inboxes/BitmapInbox">
            <query><![CDATA[
            SELECT d.*, t.*
            FROM cmis:document AS d
            JOIN cm:titled AS t on d.cmis:objectId = t.cmis:objectId
            WHERE contains(d, 'PATH:"/app:company_home/st:sites/cm:swsdp/cm:documentLibrary/cm:Agency_x0020_Files/cm:Contracts/*"')
            ]]></query>
        </inbox>
        <inbox id="high-priority" iconClass="flag_red.png" inboxClass="softwareloop/inboxes/BitmapInbox">
            <query><![CDATA[
            SELECT d.*, t.*
            FROM cmis:document AS d
            JOIN cm:titled AS t on d.cmis:objectId = t.cmis:objectId
            WHERE contains(d, 'PATH:"/app:company_home/st:sites/cm:swsdp/cm:documentLibrary/cm:Agency_x0020_Files/cm:Mock-Ups/*"')
            ]]></query>
        </inbox>
    </group>
    <group id="archive">
        <inbox id="invoices" iconClass="page.png" inboxClass="softwareloop/inboxes/BitmapInbox">
            <query><![CDATA[
            SELECT d.*, t.*
            FROM cmis:document AS d
            JOIN cm:titled AS t on d.cmis:objectId = t.cmis:objectId
            WHERE d.cmis:objectTypeId='cmis:document'
            AND d.cmis:createdBy = 'mjackson'
            ]]></query>
        </inbox>
        <inbox id="purchase-orders" iconClass="arrow_left.png" inboxClass="softwareloop/inboxes/BitmapInbox">
            <query><![CDATA[
            SELECT d.*, t.*
            FROM cmis:document AS d
            JOIN cm:titled AS t on d.cmis:objectId = t.cmis:objectId
            WHERE d.cmis:objectTypeId='cmis:document'
            AND d.cmis:createdBy = 'abeecher'
            ]]></query>
        </inbox>
        <inbox id="quotations" iconClass="arrow_right.png" inboxClass="softwareloop/inboxes/BitmapInbox">
            <query><![CDATA[
            SELECT d.*, t.*
            FROM cmis:document AS d
            JOIN cm:titled AS t on d.cmis:objectId = t.cmis:objectId
            WHERE contains(d, 'PATH:"/app:company_home/st:sites/cm:swsdp/cm:documentLibrary/cm:Presentations/*"')
            ]]></query>
        </inbox>
        <inbox id="marketing-documents" iconClass="world.png" inboxClass="softwareloop/inboxes/BitmapInbox">
            <query><![CDATA[
            SELECT d.*, t.*
            FROM cmis:document AS d
            JOIN cm:titled AS t on d.cmis:objectId = t.cmis:objectId
            WHERE contains(d, 'PATH:"/app:company_home/st:sites/cm:swsdp/cm:documentLibrary/cm:Agency_x0020_Files/cm:Images/*"')
            ]]></query>
        </inbox>
    </group>
</inboxes>

Conclusions

Despite the simplicity of the task at hand, this tutorial shows a customisation pattern that has broad applicability:

  • identify the component that needs customisation,
  • create a new component that inherits from the standard one,
  • extend and override in the new component,
  • let the framework know about the new component.

The pattern will prove useful to customise the inbox results on the right side of the plugin's page as discussed in this other page.

References

⚠️ **GitHub.com Fallback** ⚠️