Last updated Oct 3, 2024

Write an advanced blueprint plugin

Applicable:

This tutorial applies to Confluence 5.9.1 and higher.

Level of experience:

Advanced.

In this tutorial, you’ll learn how to create an advanced blueprint plugin for your Confluence instance. Developing for Confluence Cloud? Learn how to create a blueprint for Confluence Cloud.

Overview of the tutorial

If you successfully completed intermediate tutorial you should have a blueprint that is populated using a single-page wizard and some instructional text. In this tutorial, you will create a blueprint that populates its template with data. You can also include an event listener in your blueprint. Event listeners are useful for reacting to user actions in a blueprint plugin. This tutorial shows you a simple event listener.

Before you begin

To complete this tutorial, you should:

Plugin source

If you want to skip ahead or check your work when you finish, you can find the plugin source code on Atlassian Bitbucket. Alternatively, you can download the source as a ZIP archive. To clone the repository, run the following command:

1
2
git clone https://bitbucket.org/atlassian_tutorial/confluence-eventlistener-blueprint.git

About these Instructions

You can use any supported combination of operating system and IDE to create this plugin. These instructions were written using Intellij IDEA 2017.2 on macOS Sierra. If you are using another operating system or IDE combination, you should use the equivalent operations for your specific environment. This tutorial was last tested with Confluence 6.7.0 using the Atlassian SDK 6.3.10.

Step 1. Choose between templates

A plugin may have multiple blueprints and a blueprint may reference multiple templates. For example, you might have a reports blueprint to allow users to choose between creating two types of reports. Each report has a corresponding template.

In this step, you'll add another template to the plugin code you created in the Write an intermediate blueprint plugin. If you don't have that intermediate code, you can download the source as a ZIP archive of the project to start with.  To add a second template, do the following:

  1. Create a templates/mytemplate2.xml file and add the following content:

    1
    2
    <h3><strong>Name</strong>: <at:var at:name="vName" /></h3>
    <h3><strong>Date</strong>: <ac:placeholder>Enter today's date here</ac:placeholder></h3>
    

    This second template has a different look and feel.

  2. Close and save the mytemplate2.xml file.

  3. Add a content-template module in the atlassian-plugin.xml file.

    1
    2
    <!-- Second Template for Blueprint -->
    <content-template key="simplebp-template2" i18n-name-key="my.blueprint.title2">
        <resource name="template" type="download" location="/templates/mytemplate2.xml" />
    </content-template>
    
  4. In the blueprint content module, remove the content-template-key attribute and replace it with two content-template refs.

    1
    2
    <!-- Blueprint -->
    <blueprint key="my-blueprint" index-key="my-index" i18n-name-key="my.blueprint.name">
        <content-template ref="simplebp-template" />
        <content-template ref="simplebp-template2" />
        <dialog-wizard key="simplebp-wizard">
            <dialog-page id="page1Id" template-key="MyPlugin.Blueprints.Simple.page1Form"
                title-key="my.blueprint.wizard.page1.title"
                description-header-key="my.blueprint.wizard.page1.desc.header"
                description-content-key="my.blueprint.wizard.page1.desc.content"
                last="true"/>
        </dialog-wizard>
    </blueprint>
    
  5. Save and close the atlassian-plugin.xml file.

  6. In the simplebp.properties file, add the my.blueprint.title2 value and edit my.blueprint.title value.

    1
    2
    my.blueprint.title=First Template
    my.blueprint.title2=Second Template
    
  7. Add the my.blueprint.form.label.templatekey value. 

    1
    2
    my.blueprint.form.label.templatekey=Choose a template
    

    This value is used in the Soy template in the next step. When you are done the file looks similar to the following:

    1
    2
    my.blueprint.title=First Template
    my.blueprint.title2=Second Template
    my.create-link.title=My Sample Template
    my.create-link.description=Create a new SimpleBB template
    my.blueprint.form.label.title.vName=Name
    my.blueprint.form.label.templatekey=Choose a template
    my.blueprint.wizard.page1.title=Simplebp WIZARD
    my.blueprint.wizard.page1.desc.header=About this blueprint
    my.blueprint.wizard.page1.desc.content=Use this blueprint to create a table with your name and email.
    
  8. Save and close the simplebp.properties file. 

Step 2. Update the Javascript to handle two templates

By default, the blueprint page is created from the first template. Of course, you will want to offer users the choice between your templates. You can set a special property called contentTemplateKey in the wizardData. This is the key of the template to render. You can set this property through a JavaScript hook or implicitly by having a form field with the name contentTemplateKey. This latter method works because pageData automatically loads form field values. When the user submits the wizard, Confluence populates wizardData with all the pageData values.

In this step, you create a select field to allow users to choose a template.

  1. Open the soy/simplebp.soy template.

  2. Add a second field group below the first that contains a select input.

    1
    2
    <div class="field-group">
        <label for="template-key">{getText('my.blueprint.form.label.templatekey')}</label>
        <select id="template-key" class="select" name="contentTemplateKey">
            <option value="simplebp-template">{getText('my.blueprint.title')}</option>
            <option value="simplebp-template2">{getText('my.blueprint.title2')}</option>
        </select>
     </div>
    

    Each option in the select is a template whose value corresponds to a content-template key. When the form is submitted, the wizardData.contentTemplateKey contains the selection value.  
    When you are done, the Soy template looks like the following:

    1
    2
    /**
    *  A form that accepts a person's name
    */
    {template .page1Form}
        <form action="#" method="post" class="aui">
            <fieldset>
                <div class="field-group">
                    <label for="myname">{getText('my.blueprint.form.label.title.vName')}</label>
                    <input id="myname" class="text" type="text" name="vName">
                </div>
                <div class="field-group">
                        <label for="template-key">{getText('my.blueprint.form.label.templatekey')}</label>
                        <select id="template-key" class="select" name="contentTemplateKey">
                            <option value="simplebp-template">{getText('my.blueprint.title')}</option>
                            <option value="simplebp-template2">{getText('my.blueprint.title2')}</option>
                        </select>
                 </div>
            </fieldset>
        </form>
        {/template}
    
  3. Save and close the simplebp.soy file.

  4. Build your plugin with the atlas-run command.

  5. Log in and click the Create button.

  6. After clicking My Sample Template you'll see the following:
     

Step 3. Add a "Let's get started" page

You may want to kick off your blueprint with a "Let's get started" page. This page allows you to explain what the blueprint is and how to use it. Adding a "Let's get started" page requires you to define the page content in your Soy template and a reference to your blueprint.

  1. In the resource/soy/simplebp.soy file, add a new template description.

    1
    2
    /**
    * Let's-get-started page for the Create dialog
    */
    {template .letsGetStarted}
        <h2>{getText('my.blueprint.letsgetstarted.title')}</h2>
        <p>{getText('my.blueprint.letsgetstarted.content')}</p>
    {/template}
    

    Here is full file, including earlier example.

    1
    2
    {namespace MyPlugin.Blueprints.Simple}
    /**
    *  A form that accepts a person's name
    */
    {template .page1Form}
        <form action="#" method="post" class="aui">
          <fieldset>
            <div class="field-group">
               <label for="myname">{getText('my.blueprint.form.label.title.vName')}</label>
               <input id="myname" class="text" type="text" name="vName">
            </div>
            <div class="field-group">
                <label for="template-key">{getText('my.blueprint.form.label.templatekey')}</label>
                <select id="template-key" class="select" name="contentTemplateKey">
                    <option value="simplebp-template">{getText('my.blueprint.title')}</option>
                    <option value="simplebp-template2">{getText('my.blueprint.title2')}</option>
                </select>
            </div>
          </fieldset>
        </form>
    {/template}
    /**
     * Let's-get-started page for the Create dialog.
     */
    {template .letsGetStarted}
        <h2>{getText('my.blueprint.letsgetstarted.title')}</h2>
        <p>{getText('my.blueprint.letsgetstarted.content')}</p>
    {/template}
    
  2. Save and close the simplebp.soy file.

  3. In the simplebp.properties file, add strings for the title and content.

    1
    2
    my.blueprint.letsgetstarted.title=About this blueprint
    my.blueprint.letsgetstarted.content=You can choose between two reports. One uses a table; one does not.
    

    The following code snippet shows the full file, including earlier example.

    1
    2
    my.blueprint.title=First Template
    my.blueprint.title2=Second Template
    my.create-link.title=My Sample Template
    my.create-link.description=Create a new SimpleBB template
    my.blueprint.form.label.title.vName=Name
    my.blueprint.form.label.templatekey=Choose a template
    my.blueprint.wizard.page1.title=Simplebp WIZARD
    my.blueprint.wizard.page1.desc.header=About this blueprint
    my.blueprint.wizard.page1.desc.content=Use this blueprint to create a table with your name and email.
    my.blueprint.letsgetstarted.title=About this blueprint
    my.blueprint.letsgetstarted.content=You can choose between two reports. One uses a table; one does not.
    
  4. Save and close the simplebp.properties file.

  5. In the atlassian-plugin.xml file, add a how-to-use-template attribute to your blueprint module.

    1
    2
    <blueprint key="my-blueprint" index-key="my-index" i18n-name-key="my.blueprint.name"
               how-to-use-template="MyPlugin.Blueprints.Simple.letsGetStarted" >
        <content-template ref="simplebp-template" />
        <content-template ref="simplebp-template2" />
        <dialog-wizard key="simplebp-wizard">
            <dialog-page id="page1Id" template-key="MyPlugin.Blueprints.Simple.page1Form"
                title-key="my.blueprint.wizard.page1.title"
                description-header-key="my.blueprint.wizard.page1.desc.header"
                description-content-key="my.blueprint.wizard.page1.desc.content"
                last="true"/>
        </dialog-wizard>
    </blueprint>
    

    Here is an example of full file, including the previous attribute.

    1
    2
    <atlassian-plugin key="${atlassian.plugin.key}"
        name="${project.name}" plugins-version="2">
        <plugin-info>
            <description>${project.description}</description>
            <version>${project.version}</version>
            <vendor name="${project.organization.name}" url="${project.organization.url}" />
            <param name="plugin-icon">images/pluginIcon.png</param>
            <param name="plugin-logo">images/pluginLogo.png</param>
        </plugin-info>
        <!-- add our i18n resource -->
        <resource type="i18n" name="i18n" location="simplebp" />
        <!-- add our web resources -->
        <web-resource key="simplebp-resources" name="simplebp Web Resources">
            <dependency>com.atlassian.auiplugin:ajs</dependency>
            <dependency>com.atlassian.confluence.plugins.confluence-create-content-plugin:resources</dependency>
            <transformation extension="soy">
                <transformer key="soyTransformer">
                    <functions>com.atlassian.confluence.plugins.soy:soy-core-functions
                    </functions>
                </transformer>
            </transformation>
            <resource type="download" name="simplebp.css" location="/css/simplebp.css" />
            <resource type="download" name="simplebp.js" location="/js/simplebp.js" />
            <resource type="download" name="simplebp.soy.js" location="/soy/simplebp.soy" />
            <resource type="download" name="images/" location="/images" />
            <context>create-content</context>
        </web-resource>
        <!-- Blueprint -->
        <blueprint key="my-blueprint" index-key="my-index" i18n-name-key="my.blueprint.name"
                   how-to-use-template="MyPlugin.Blueprints.Simple.letsGetStarted" >
            <content-template ref="simplebp-template" />
            <content-template ref="simplebp-template2" />
            <dialog-wizard key="simplebp-wizard">
                <dialog-page id="page1Id" template-key="MyPlugin.Blueprints.Simple.page1Form"
                    title-key="my.blueprint.wizard.page1.title"
                    description-header-key="my.blueprint.wizard.page1.desc.header"
                    description-content-key="my.blueprint.wizard.page1.desc.content"
                    last="true"/>
            </dialog-wizard>
        </blueprint>
        <!-- Add to the Create Menu -->
        <web-item key="create-by-sample-template" i18n-name-key="my.create-link.title"
            section="system.create.dialog/content">
            <description key="my.create-link.description" />
            <resource name="icon" type="download" location="/images/myblueprint.png" />
            <param name="blueprintKey" value="my-blueprint" />
        </web-item>
    
        <!-- Template for Blueprint -->
        <content-template key="simplebp-template" i18n-name-key="my.blueprint.title">
            <resource name="template" type="download" location="/templates/mytemplate.xml" />
    <!--         <context-provider -->
    <!--             class="com.example.plugins.tutorial.confluence.simplebp.MyContextProvider" /> -->
        </content-template>
    
        <!-- Second Template for Blueprint -->
        <content-template key="simplebp-template2" i18n-name-key="my.blueprint.title2">
            <resource name="template" type="download" location="/templates/mytemplate2.xml" />
        </content-template>    
    
    </atlassian-plugin>
    
  6. Save and close the atlassian-plugin.xml file.

  7. Rebuild your plugin using atlas-package from the command line, QuickReload automatically reloads your plugin for you.

  8. Now, when you click Create, the first page is your Let's get started page.

Users can choose to not see this page the next time they choose your blueprint.

Step 4. Add an event listener

You may want to take some action after the blueprint is created. You can listen for the BlueprintPageCreateEvent by implementing a listener class in your plugin. In this step, you will implement an annotation-based listener that sends an event to the console when a plugin is created.

  1. Create a com/example/plugins/tutorials/confluence/simplebp/MyBlueprintListener.java file.
1
2
package com.example.plugins.tutorial.confluence.simplebp;

import com.atlassian.confluence.plugins.createcontent.api.events.BlueprintPageCreateEvent;

@Named
public class MyBlueprintListener {

}
  1. Add imports for the EventPublisher, EventPublisher, and ModuleCompleteKey.
1
2
import com.atlassian.event.api.EventListener;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.plugin.ModuleCompleteKey;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import com.atlassian.plugin.spring.scanner.annotation.imports.ConfluenceImport;
import javax.inject.Inject;
import javax.inject.Named;

You'll inject the EventPublisher into your class and use the EventListener to handle the event. The ModuleCompleteKey helps the listener to recognize the appropriate events.

  1. Import the logger files:
1
2
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  1. Declare MY_BLUEPRINT_KEY as an instance of ModuleCompleteKey and get an instance of the logger.

    1
    2
    private static final ModuleCompleteKey MY_BLUEPRINT_KEY = new ModuleCompleteKey("com.example.plugins.tutorial.confluence.simplebp.simplebp", "my-blueprint");
    private static final Logger log = LoggerFactory.getLogger(MyBlueprintListener.class);
    
  2. Create a constructor that injects the EventPublisher and sets its value to the private field:

1
2
@ConfluenceImport
private EventPublisher eventPublisher;

@Inject
public MyBlueprintListener(EventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
}
1
2
 <div class="sc-gLdKKF bwagGt"><section class="sc-SFOxd gpanBo"><div class="sc-hkaZBZ gWhWHQ"><style data-emotion="css snhnyn">.css-snhnyn{display:inline-block;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;line-height:1;}.css-snhnyn >svg{overflow:hidden;pointer-events:none;max-width:100%;max-height:100%;color:var(--icon-primary-color);fill:var(--icon-secondary-color);vertical-align:bottom;}.css-snhnyn >svg stop{stop-color:currentColor;}@media screen and (forced-colors: active){.css-snhnyn >svg{-webkit-filter:grayscale(1);filter:grayscale(1);--icon-primary-color:CanvasText;--icon-secondary-color:Canvas;}}</style><span aria-hidden="true" style="--icon-primary-color:#0747A6;--icon-secondary-color:#DEEBFF" class="css-snhnyn"><svg width="24" height="24" viewBox="0 0 24 24" role="presentation"><g fill-rule="evenodd"><path d="M2 12c0 5.523 4.477 10 10 10s10-4.477 10-10S17.523 2 12 2 2 6.477 2 12z" fill="currentColor"/><rect fill="inherit" x="11" y="10" width="2" height="7" rx="1"/><circle fill="inherit" cx="12" cy="8" r="1"/></g></svg></span></div><div class="sc-jOBXIr jErMvQ"><div class="sc-kIWQTW nnCfa">

Note that we use standard JSR annotations @Named and @Inject to inject EventPublisher, you can use Spring annotation @org.springframework.stereotype.Component and @Autowired but then you need to add Spring dependencies. Also, we use @ConfluenceImport to mark it as OSGi component we want to import

1
2
 </div></div></section></div>

To start listening to Confluence events we need to register our class with EventPublisher. To make this, we will implement InitializingBean, and DisposableBean interfaces

1
2
public class MyBlueprintListener implements InitializingBean, DisposableBean
    ```
    Implement `afterPropertiesSet()` and `destroy` methods :

    ``` java
    @Override
    public void afterPropertiesSet() throws Exception {
       eventPublisher.register(this);
    }

    @Override
    public void destroy() throws Exception {
       eventPublisher.unregister(this);
    }
  1. To catch create blueprint page events, add an onBlueprintCreateEvent() method.
    You should use the @EventListener annotation to indicate this method is called for a specific event type.  

    1
    2
    @EventListener
    public void onBlueprintCreateEvent(BlueprintPageCreateEvent event) {
        String moduleCompleteKey = event.getBlueprint().getModuleCompleteKey();
        if (MY_BLUEPRINT_KEY.getCompleteKey().equals(moduleCompleteKey)) {
            //take some action
            log.warn("WARN: Created a blueprint.");
        }
    }
    

    This method checks if the event comes from this plugin and, if it does, logs a WARN event. Typically, you would log an INFO event if any. However, in this sample, you just want to see the event log to the console.

  2. Save and close the MyBlueprintListener.java file. 

  3. Rebuild your plugin using atlas-package from the command line, QuickReload automatically reloads your plugin for you.

Now, after you click Create and save your new page, you'll see output similar to the following in your console:

1
2
[INFO] [talledLocalContainer]  -- referer: http://localhost:1990/confluence/plugins/createcontent/createpage.action | url: /confluence/plugins/createcontent/docreatepage.action | userName: admin | action: docreatepage
[INFO] [talledLocalContainer] 2013-07-29 14:36:11,993 WARN [http-1990-6] [tutorial.confluence.simplebp.MyBlueprintListener] onBlueprintCreateEvent WARN: Created a blueprint.
[INFO] [talledLocalContainer]  -- referer: http://localhost:1990/confluence/plugins/createcontent/createpage.action | url: /confluence/plugins/createcontent/docreatepage.action | userName: admin | action: docreatepage

Rate this page: