Adding items to the Info Banner

Applicable:

This tutorial applies to Confluence 5.4.

Level of experience:

Intermediate. Our tutorials are classified as 'beginner', 'intermediate' and 'advanced'. This one is at 'intermediate' level, so you will need to have developed a few plugins before to follow it.

Time estimate:

It will take you approximately 1 hour to complete this tutorial.

The source code of the plugin used in this tutorial is hosted on Bitbucket: confluence-banner-tutorial.

On this page:

Overview

The Info Banner was added in Confluence 5.4 and allows plugin developers to add items that display additional information about a page or blogpost. The JIRA Links button, also released with Confluence 5.4, is a good example of how the Info Banner can be used to display information about a page. See new integration with JIRA.

The plugin in this tutorial inserts a new item called "Some statistics" to the Info Banner. Clicking this item launches a dialog, which contains some basic information about the page (number of likes, comments and versions). From this starting point, it is possible to create more advanced plugins to show any type of customised information.

Screenshot: The demo 'Some statistics' item and dialog.

In order to do this, you will create a Confluence plugin, which will consist of the following components:

  • A Google Closure "Soy" template for rendering the dialog's HTML markup.
  • A JavaScript file to initialise and provide the logic for the item and dialog.
  • A CSS file to make everything look nice.
  • An internationalisation (i18n) properties file, to provide default English text for the panel.
  • A REST Module to provide the information for the dialog to render when the item is clicked.
  • A plugin XML descriptor file which registers all the plugin modules with Confluence.

The extension point used by this plugin is only available in Confluence 5.4 and later.

Step 1. Create the Plugin Project

The Atlassian Plugin SDK provides a number of atlas-create-application-plugin commands that you can use to generate stub code for your plugin.

  1. Run the atlas-create-confluence-plugin command and enter the following information when prompted:
    • group-id: com.atlassian.confluence.plugins
    • artifact-id: banner-stats
    • version: 1.0
    • package: com.atlassian.confluence.plugins.banner.stats
  2. Open the pom.xml file that was generated by the previous step, and update the information in this file to match your organisation and project name.
  3. You can remove the generated Java classes in src/main/java as we will be creating some new ones.
  4. Make sure the correct Confluence version is specified in pom.xml (at least 5.4).
<confluence.version>5.4</confluence.version>
<confluence.data.version>5.4</confluence.data.version>

Step 2. Add a web item to the banner

  1. In the plugin XML descriptor file, src/main/resources/atlassian-plugin.xml, we need to register a web-item for the banner in the section "page.metadata.banner".

    <web-item key="banner-stats" name="Banner Stats" section="page.metadata.banner" weight="80">
        <icon height="16" width="16">
            <link>/download/resources/${project.groupId}.${project.artifactId}:banner-stats-resources/images/pluginIcon.png</link>
        </icon>
        <label key="banner.stats.label"/>
    	<tooltip key="banner.stats.label"/>
        <link linkId="banner-stats" absolute="true"/>
        <styleClass>aui-button aui-button-subtle</styleClass>
    </web-item>

    It is important to note:

    • We are using an optional icon.
    • As the link is a required element, we will add an absolute empty link and use it to render a dialog when it is clicked via Javascript.
    • Atlassian CSS classes for the basic styling. See Atlassian Design Guidelines for more information.
    See the Web Item documentation for other options like conditions (for example you may only want to show the item if some requirements are met) or rendering the label dynamically. 
  2. Don't forget to add the corresponding images and i18n resource (src/main/resources/banner-stats.properties) to supply the label text. 

    banner.stats.label=Some statistics
    <resource type="i18n" name="i18n" location="banner-stats"/>
     
    <web-resource key="banner-stats-resources" name="banner-stats Web Resources">
        <resource type="download" name="images/" location="/images"/>
        <context>viewcontent</context>
    </web-resource>

    Make sure the context tag is "viewcontent" as that is the context where the banner gets loaded, so it is also the one where you want to make your resources available.

Step 3. Create a JavaScript file to bind the dialog to the item

  1. Create a JavaScript file - src/main/resources/js/banner-stats.js, with the following content:

    AJS.toInit(function ($) {
        var dataLoaded = false; // only load inline dialog contents once
        var dialogId = "banner-stats-dialog";
        var $webItem = $('#banner-stats');
        var dialog = AJS.InlineDialog($webItem, dialogId,
                    function(content, trigger, showPopup) {
                        if(!dataLoaded) {
                            content.html("Content goes here");
                        }
                        showPopup();
                        return false;
                    }
                );
    
        // Workaround to dismiss the inline dialog when clicking on web-item again until https://ecosystem.atlassian.net/browse/AUI-1175 is done
        $webItem.click(function() {
            if($('#inline-dialog-' + dialogId).is(':visible')) {
                dialog.hide();
            }
        });
    });
  2. Don't forget to add the corresponding resource definition and it's dependencies in src/main/resources/atlassian-plugin.xml, so that the javascript file is loaded.

    <web-resource key="banner-stats-resources" name="banner-stats Web Resources">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
        <transformation extension="js">
            <transformer key="jsI18n"/>
        </transformation>
    
        <resource type="download" name="banner-stats.js" location="/js/banner-stats.js"/>
        <resource type="download" name="images/" location="/images"/>
        <context>viewcontent</context>
    </web-resource>

    Now let's add some content in there.

Step 4. Create a REST resource to retrieve data

  1. In the src/main/java/ directory, create a Java class in the corresponding package with the data we want to show.

    package com.atlassian.confluence.plugins.banner.stats;
    
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
     
    @XmlRootElement
    public class BannerStats
    {
        @XmlElement
        private final int likes;
        @XmlElement
        private final int comments;
        @XmlElement
        private final int versions;
    
        public BannerStats(final int likes, final int comments, final int versions)
        {
            this.likes = likes;
            this.comments = comments;
            this.versions = versions;
        }
     
        public int getLikes()
        {
            return likes;
        }
     
        public int getComments()
        {
            return comments;
        }
        public int getVersions()
        {
            return versions;
        }
    }

    The annotations in this class allow the server to return the information contained in the instances of this class as either XML or JSON depending on the client request or other configuration.

  2. Create also the REST Resource to respond to web requests for this information.

    package com.atlassian.confluence.plugins.banner.stats;
    
    import com.atlassian.confluence.like.LikeManager;
    import com.atlassian.confluence.pages.AbstractPage;
    import com.atlassian.confluence.pages.PageManager;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.QueryParam;
    import javax.ws.rs.core.MediaType;
    import javax.ws.rs.core.Response;
    
    @Path ("banner-stats")
    @Produces ({ MediaType.APPLICATION_JSON})
    public class BannerStatsResource
    {
        private LikeManager likeManager;
        private PageManager pageManager;
    
        public BannerStatsResource(final LikeManager likeManager, final PageManager pageManager)
        {
            this.likeManager = likeManager;
            this.pageManager = pageManager;
        }
    
        @GET
        public Response getMetadata(@QueryParam ("pageId") long pageId)
        {
            try
            {
                return Response.ok(getStats(pageId)).build();
            }
            catch (Exception e)
            {
                return Response.serverError().build();
            }
        }
        private BannerStats getStats(final long pageId)
        {
            AbstractPage page = pageManager.getAbstractPage(pageId);
            int likes = likeManager.countLikes(page);
            int comments = pageManager.getCommentCountOnPage(pageId);
            int versions = page.getLatestVersion().getVersion();
            return new BannerStats(likes, comments, versions);
        }
    }

    This class basically will, given a pageId, build and return the data we want to use as a JSON object.

  3. Don't forget to define the REST resource in src/main/resources/atlassian-plugin.xml.

    <rest key="banner-stats-rest" path="/banner-stats" version="1.0">
        <description>Provides services for retrieving stats</description>
    </rest>

    Check the REST Module page for more detailed information about how REST resources work.

Step 5. Create Soy templates to render the data in the dialog

  1. In the src/main/resources/ directory, create a new directory for your plugin resources called soy.
  2. Create a new Soy template - src/main/resources/soy/banner-stats.soy, with the following content:

    {namespace Confluence.Templates.Plugins.Banner.Stats}
    
    /**
     * Renders the contents of the drop-down dialog.
     * @param pageId
     * @param likes
     * @param comments
     * @param versions
     */
    {template .stats}
        <div id="banner-stats-content">
        <ul>
            <li><a href="#likes-and-labels-container">{getText('banner.stats.likes', $likes)}</a></li>
            <li><a href="#comments-section">{getText('banner.stats.comments', $comments)}</a></li>
            <li><a href="{contextPath()}/pages/viewpreviousversions.action?pageId={$pageId}">{getText('banner.stats.versions', $versions)}</a></li>
         </ul>
        </div>
    {/template}
    
    /**
     * Renders loading state
     */
    {template .loading}
        <div class="banner-stats-spinner-container">
            <div class="spinner"></div>
        </div>
    {/template}
    
    /**
     * Renders warning message
     */
    {template .error}
        {call aui.message.warning}
            {param content}
                <p>{getText('banner.stats.error')}</p>
            {/param}
        {/call}
    {/template}


     The template should contain the markup you want to appear in the dialog. In this example we have templates for displaying a spinner while loading the information, the content the dialog, and an error.

  3. Don't forget to add the soy file and it's dependencies to src/main/resources/atlassian-plugin.xml, as well as the new messages to the properties file.

    <web-resource key="banner-stats-resources" name="banner-stats Web Resources">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
    
        <transformation extension="js">
            <transformer key="jsI18n"/>
        </transformation>
        <transformation extension="soy">
            <transformer key="soyTransformer"/>
        </transformation>
    
        <resource type="download" name="jira-metadata-soy.js" location="soy/banner-stats.soy"/>
        <resource type="download" name="banner-stats.js" location="/js/banner-stats.js"/>
        <resource type="download" name="images/" location="/images"/>
        <context>viewcontent</context>
    </web-resource>
    banner.stats.label=Some statistics
    
    banner.stats.likes={0} likes
    banner.stats.comments={0} comments
    banner.stats.versions={0} versions
    banner.stats.error=Error loading data

Step 6. Modify the JavaScript file to get the data from the REST resource and render it using the Soy templates

  1. Modify src/main/resources/js/banner-stats.js as follows.

    AJS.toInit(function ($) {
        var dataLoaded = false; // only load inline dialog contents once
        var dialogId = "banner-stats-dialog";
        var $webItem = $('#banner-stats');
        var dialog = AJS.InlineDialog($webItem, dialogId,
                    function(content, trigger, showPopup) {
                        if(!dataLoaded) {
                            content.html(Confluence.Templates.Plugins.Banner.Stats.loading());
                            content.find(".spinner").spin("medium");
                            $.ajax({
                                url: AJS.contextPath() + "/rest/banner-stats/1.0/banner-stats?pageId=" + AJS.Meta.get("page-id"),
                                type: "GET",
                                dataType: "json",
                                contentType: "application/json",
                                error:function () {
                                    content.html(Confluence.Templates.Plugins.Banner.Stats.error());
                                },
                                success: function (response) {
                                    response.pageId = AJS.Meta.get("page-id");
                                    content.html(Confluence.Templates.Plugins.Banner.Stats.stats(response));
                                    dataLoaded = true;
                                }
                            });
                        }
                        showPopup();
                        return false;
                    }
                );
        // Workaround to dismiss the inline dialog when clicking on web-item again until https://ecosystem.atlassian.net/browse/AUI-1175 is done
        $webItem.click(function() {
            if($('#inline-dialog-' + dialogId).is(':visible')) {
                dialog.hide();
            }
        });
    }); 

    What we do here is:

    • If the data is not already loaded, set the content of the dialog with the spinner template.
    • Call the REST resource to get the data using a URL according to its configuration and passing the necessary parameters.
    • Show the dialog.
    • Once the server returns a response, we render the error or the information template accordingly.

Step 7. Create CSS to provide styling for your components

  1. Create src/main/resources/css/banner-stats.css containing the following CSS to apply styling to the spinner and the button. You could add styles that apply to the dialog too.

    .banner-stats-spinner-container .spinner {
        margin: auto;
        padding-top: 40px;
    }
    
    /* BANNER OVERRIDES */
    #page-metadata-banner #banner-stats-wrapper {
        padding: 0 10px 0 0;
    }
    
    /* AUI OVERRIDES */
    #page-metadata-banner .banner #banner-stats:hover {
        border-color: #999;
    }
    #page-metadata-banner .banner #banner-stats.active {
        border: 1px solid #ccc;
        border-radius: 3.01px; /* http://blogs.atlassian.com/2012/11/rounded-corners-and-gradients-in-ie10-the-4-missing-pixels/ */
    }
  2. As always, don't forget to add your new resource to src/main/resources/atlassian-plugin.xml.

    <web-resource key="banner-stats-resources" name="banner-stats Web Resources">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
    
        <transformation extension="js">
            <transformer key="jsI18n"/>
        </transformation>
        <transformation extension="soy">
            <transformer key="soyTransformer"/>
        </transformation>
    
        <resource type="download" name="jira-metadata-soy.js" location="soy/banner-stats.soy"/>
        <resource type="download" name="banner-stats.css" location="/css/banner-stats.css"/>
        <resource type="download" name="banner-stats.js" location="/js/banner-stats.js"/>
        <resource type="download" name="images/" location="/images"/>
        <context>viewcontent</context>
    </web-resource>

Step 8. Install into Confluence

You can start an instance of Confluence with atlas-run, or build the plugin with atlas-package and install it into your existing Confluence instance.

The result when opening a page and clicking the banner item we created should look like the screenshot below.

Screenshot: The demo 'Some statistics' item and dialog

Troubleshooting

  • If the new item does not appear on the banner, ensure you have the web-item correctly registered in atlassian-plugin.xml for section "page.metadata.banner". Ensure you have installed the plugin in Confluence and that the plugin is enabled.
  • If the item or dialog are not working properly, check for JavaScript errors in the debugging console in your browser. Firebug or the WebKit inspector may help you debug your code and track down the problem.
  • If the dialog is not displaying information check that you are using the proper URL or check for errors in the application log or by debugging the server side code.

Other important considerations

Currently, the render of the banner items blocks the page render so if getting the information you need to render the item properly can take too much time, you should consider caching it if possible and retrieve it asynchronously when it's not cached (you could place a hidden web item and make it visible if appropriate when retrieving the information needed with an AJAX call after the page is loaded).

Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport