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.
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:
The extension point used by this plugin is only available in Confluence 5.4 and later.
The Atlassian Plugin SDK provides a number of atlas-create-
application
-plugin
commands that you can use to generate stub code for your plugin.
com.atlassian.confluence.plugins
banner-stats
1.0
com.atlassian.confluence.plugins.banner.stats
pom.xml
file that was generated by the previous step, and update the information in this file to match your organisation and project name.src/main/java
as we will be creating some new ones.pom.xml
(at least 5.4).1 2<confluence.version>5.4</confluence.version> <confluence.data.version>5.4</confluence.data.version>
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"
.
1 2<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:
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.
Don't forget to add the corresponding images and i18n resource (src/main/resources/banner-stats.properties
) to supply the label text.
1 2banner.stats.label=Some statistics
1 2<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.
Create a JavaScript file - src/main/resources/js/banner-stats.js
, with the following content:
1 2AJS.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(); } }); });
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.
1 2<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.
In the src/main/java/
directory, create a Java class in the corresponding package with the data we want to show.
1 2package 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.
Create also the REST Resource to respond to web requests for this information.
1 2package 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.
Don't forget to define the REST resource in src/main/resources/atlassian-plugin.xml.
1 2<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.
In the src/main/resources/
directory, create a new directory for your plugin resources called soy
.
Create a new Soy template - src/main/resources/soy/banner-stats.soy
, with the following content:
1 2{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.
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.
1 2<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>
1 2banner.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
Modify src/main/resources/js/banner-stats.js as follows
.
1 2AJS.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:
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.
1 2.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/ */ }
As always, don't forget to add your new resource to src/main/resources/atlassian-plugin.xml.
1 2<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>
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
atlassian-plugin.xml
for section "page.metadata.banner". Ensure you have installed the plugin in Confluence and that the plugin is enabled.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).
Rate this page: