Write an advanced blueprint plugin

Applicable:

This tutorial applies to Confluence Server 5.1 and higher.

Level of experience:

Advanced.

This tutorial will create a plugin that can only be installed on Confluence Server. To create a blueprint for Confluence Cloud head to Multi-page Blueprints with Confluence Connect.

Overview of the tutorial

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

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. Bitbucket serves a public Git repository containing the tutorial's code. Alternatively, you can download the source as a ZIP archive To clone the repository, issue the following command:

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

About these Instructions

You can use any supported combination of OS and IDE to create this plugin. These instructions were written using Eclipse Classic Version 3.7.1 on Mac OS X. If you are using another OS or IDE combination, you should use the equivalent operations for your specific environment. This tutorial was last tested with Confluence 5.10.1 using the Atlassian SDK 6.2.6.

Step 1. Choosing 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 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.
  2. Edit the file and add the following content to it:
     This second template has a different look and feel.

    <h3><strong>Name</strong>: <at:var at:name="vName" /></h3>
    <h3><strong>Date</strong>: <ac:placeholder>Enter today's date here</ac:placeholder></h3>
  3. Close and save the mytemplate2.xml file.
  4. Edit the atlassian-plugin.xml file.
  5. Add a content-template module for the second template file.

    <!-- Second Template for Blueprint -->
    <content-template key="simplebp-template2" template-title-key="my.blueprint.title2">
    	<resource name="template" type="download" location="/templates/mytemplate2.xml" />
    </content-template>
  6. Edit the blueprint content module.
  7. Remove the content-template-key attribute and replace it with two content-template refs.

    <!-- Blueprint -->
    <blueprint key="my-blueprint" index-key="my-index" >
    	<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>
  8. Save and close the atlassian-plugin.xml file.
  9. Edit the simplebp.properties file and add the my.blueprint.title2 value.

    my.blueprint.title2=Second Template
  10. Add the my.blueprint.form.label.templatekey value. 

    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:

    #put any key/value pairs here
    my.plugin.name=MyPlugin
    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.
  11. 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'll 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 the 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.

    <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:

     

    {template .page1Form}
        <form action="#" method="post" class="aui">
            <fieldset>
                <div class="field-group">
                    <label for="myname">{getText('my.blueprint.form.label.templatekey')}</label>
                    <input id="myname" class="text" type="text" name="myName">
                </div>
                <div class="field-group">
                    <label for="template-key">{getText('myplugin.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. Login and choose the Create button.
  6. After choosing My Sample Template you should see the following:
     

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

You may want to kick off your blue print 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. Edit the resource/soy/simplebp.soy file.
  2. Add a new template description to the file.

    /**
     * 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}
      Click here to view the entire file at this point...
    {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="vname">{getText('my.blueprint.form.label.title.vName')}</label>
     	        <input id="vname" 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}
  3. Save and close the simplebp.soy file.
  4. Edit the simplebp.properties file and add strings for the title and content.

    my.blueprint.letsgetstarted.title=About this blueprint
    my.blueprint.letsgetstarted.content=You can choose between two reports. One uses a table; one does not. 
    
      Click here to view the entire file at this point...
    #put any key/value pairs here
    my.plugin.name=MyPlugin
    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.
  5. Save and close the simplebp.properties file.
  6. Edit the atlassian-plugin.xml file.
  7. Add a how-to-use-template attribute to your blueprint module.

    <blueprint key="my-blueprint" index-key="my-index" 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>
    
      Click here to view the entire file at this point...
    <atlassian-plugin key="${project.groupId}.${project.artifactId}"
    	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>simplebp</context>
    		<context>atl.general</context>
    		<context>atl.admin</context>
    	</web-resource>
    	<!-- Blueprint -->
    	<blueprint key="my-blueprint" index-key="my-index" 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" template-title-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" template-title-key="my.blueprint.title2">
    		<resource name="template" type="download" location="/templates/mytemplate2.xml" />
    	</content-template>
    
    	<!-- import from the product container -->
    	<component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties" />
    	
    	
    </atlassian-plugin>
  8. Save and close the atlassian-plugin.xml file.
  9. Rebuild your plugin from the command line, QuickReload will automatically reload your plugin for you.
  10. Now, when you press 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 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

    package com.example.plugins.tutorial.confluence.simplebp;
    
    import com.atlassian.confluence.plugins.createcontent.api.events.BlueprintPageCreateEvent;
    
    public class MyBlueprintListener {
    	
    }

     

  2. Add imports for the EventPublisher, EventPublisher, and ModuleCompleteKey.

    import com.atlassian.event.api.EventListener;
    import com.atlassian.event.api.EventPublisher;
    import com.atlassian.plugin.ModuleCompleteKey;

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

  3. Import the logger files also:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
  4. Declare MY_BLUEPRINT_KEY as an instance of ModuleCompleteKey and get an instance of the logger.

    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);
  5. Create a constructor that injects the EventPublisher:

    public MyBlueprintListener(EventPublisher eventPublisher) {
    		eventPublisher.register(this); //demonstration only
    }
  6. Add an onBlueprintCreateEvent() method to trap create blueprint events.
     You use the @EventListener annotation to indicate this method is called for a specific event type.  

    @EventListener
    public void onBlueprintCreateEvent(BlueprintPageCreateEvent event){
    
    	ModuleCompleteKey moduleCompleteKey = event.getBlueprintKey();
    
    	if (MY_BLUEPRINT_KEY.equals(moduleCompleteKey)){
    			//Take some action when 
    			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.

  7. Save and close the MyBlueprintListener.java file 
  8. Edit the atlassian-plugin.xml file.
  9. Add a component-import for the EventPublisher.

    <component-import key="eventPublisher" interface="com.atlassian.event.api.EventPublisher"/>
  10. Add a component to register your new listener class.

    <component key="eventListener" class="com.example.plugins.tutorial.confluence.simplebp.MyBlueprintListener"/>
  11. Save and close the atlassian-plugin.xml file.
  12. Rebuild your plugin from the command line, QuickReload will automatically reload your plugin for you.
  13. Now, after you press Create and save your new page, you should see output similar to the following in your console:

    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
Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport