If you completed Confluence Blueprints you should have a functional Blueprint plugin. Functional, but basic. A step up from a basic Blueprint is one that populates its template with data, and there are two ways to do this:
These two methods are not exclusive, so you can populate some template variables with Java and others from JavaScript.
Templates for Confluence Blueprint plugins are written in Confluence Storage Format, and may include variables. For example, the following template has the variable myName
:
1 2<p>Hello, reader.</p> <p>My name is: <at:var at:name="myName" /></p>
The variable "myName
" will need to be in the template's render context in order for the <at:var at:name="myName"/>
placeholder to be replaced correctly. Below you'll see how to do this.
As of Confluence 5.1, support for <at:var>
elements inside of other <at:>
namespace elements is incomplete. For example, adding an <at:var>
inside a macro parameter will render correctly for the end-user creating a page, but an admin attempting to edit the template will "lose" the variable in the round-trip to the editor and back.
Fixing this is a priority for the next Confluence release but in the meantime, the recommended workaround is to make the entire macro element an <at:var>
in the template. To correctly insert XHTML from a variable into the template, you'll need to define the variable with an extra "rawxhtml
" attribute, like so:
1 2<at:var at:name="myNameWithSomeXhtmlInIt" at:rawxhtml="true"/>
If the rawxhtml attribute was not present, all XHTML in the attribute value would be escaped when rendering.
context-provider
elements have been used in Atlassian plugins for web-item
s and web-panel
s for some time, and are well-documented - see _Context-Provider Element. In the Blueprint plugin, the context-provider will go inside the content-template
module that it is providing context to, like so:
1 2<content-template key="myplugin-template" i18n-name-key="myplugin.templates.content.name"> <resource name="template" type="download" location="xml/template-body.xml"/> <context-provider class="myplugin.MyContextProvider"/> </content-template>
MyContextProvider
will need to implement com.atlassian.plugin.web.ContextProvider
. A very simple provider that would fill the example template would be:
1 2public class MyContextProvider implements ContextProvider { public void init(Map<String, String> params) throws PluginParseException { } public Map<String, Object> getContextMap(Map<String, Object> context) { context.put("myName", "Sherlock"); return context; } }
Your ContextProvider can be injected with any components available to the Confluence plugin system, so you can fill your templates with almost anything. For now, if you reload your plugin with the above changes and create a new page from your Blueprint, it will include the text "My name is: Sherlock".
The same "myName" example template above could be filled with a name supplied by the user, via a JavaScript-driven wizard. To help reduce boilerplate in your plugin and to provide users with a consistent experience, the Blueprints API has a modular approach to defining and writing wizards.
If you don't already have the "Hello Blueprint" example plugin (see previous tutorial) now might be a good time to get it. It has a detailed example of a Create dialog Wizard configuration.
Let's look at a minimal Wizard:
1 2<blueprint key="myplugin-blueprint" index-key="myplugin-index"> ... <dialog-wizard key="myplugin-wizard"> <dialog-page id="page1Id" template-key="MyPlugin.Blueprints.Hello.page1Form" title-key="myplugin.wizard.page1.title"/> </dialog-wizard> </blueprint>
The dialog-wizard
element can wrap any number of dialog-page
elements, but for now we'll just add one. The key
attribute is required for the Confluence plugin system but is not visible to the user.
A dialog-page
element has three required attributes:
id
- a unique camel-case id (in this dialog-wizard) for use with JavaScript hooks.template-key
- this is a fully-qualified Soy template reference for the content that will be displayed on the Wizard pagetitle-key
- this is the i18n key for the title of the Wizard pageInformation on writing a Soy template can be found at Writing Soy Templates in Your Plugin, but you can skip the steps for creating and building the plugin, plus the step for calling the Soy template from JavaScript. The template will be picked up automatically by Confluence.
In your atlassian-plugin.xml
, add the following web resource:
1 2<web-resource name="Resources" key="myplugin-resources"> <transformation extension="soy"> <transformer key="soyTransformer"> <functions>com.atlassian.confluence.plugins.soy:soy-core-functions</functions> </transformer> </transformation> <resource type="download" name="wizard-templates-soy.js" location="soy/wizard-templates.soy" /> <resource type="download" name="blueprint-wizard.js" location="js/blueprint-wizard.js" /> <dependency>com.atlassian.confluence.plugins.confluence-create-content-plugin:resources</dependency> <context>atl.general</context> <context>atl.admin</context> </web-resource>
The atl.general
and atl.admin
contexts need to be specified so that the JavaScript resources are made available wherever the Create dialog is shown. The blueprint-wizard.js
file is required to register the wizard. The contents look like this:
1 2Confluence.Blueprint.setWizard('com.atlassian.confluence.plugins.myplugin:blueprint-item', function(wizard) {});
The contents of the Soy file:
1 2{namespace MyPlugin.Blueprints.Hello} /** * 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('myplugin.blueprint.form.label.myname')}</label> <input id="myname" class="text" type="text" name="myName"> </div> </fieldset> </form> {/template}
You will need to add an i18n value for myplugin.blueprint.form.label.myname
to your i18n.properties
file.
To give users a consistent user experience, you may find it helpful to have a look at the Atlassian Design Guidelines when designing your templates.
You can now build and install the plugin in your Confluence instance and you should see your rendered Soy template when you select the Blueprint in the dialog. (If you're following all of the tutorial steps in order you'll want to comment out the <context-provider>
element in your <content-template>
so that the "myName" variable comes from the Wizard input and not from the "MyContextProvider" class). Entering a name in the text field and pressing Create should take you to the Editor with the entered name injected into the template.
Although the Wizard form from the previous step is quite simple, a complex or multi-page Wizard will benefit from some explanatory text in the Wizard page. Confluence has a plugin point for this, to make the Wizard consistent across different Blueprints.
1 2<blueprint key="myplugin-blueprint" index-key="myplugin-index"> ... <dialog-wizard key="myplugin-wizard"> <dialog-page id="page1Id" template-key="MyPlugin.Blueprints.Hello.page1Form" title-key="myplugin.wizard.page1.title" description-header-key="myplugin.wizard.page1.desc.title" description-content-key="myplugin.wizard.page1.desc.content"/> </dialog-wizard> </blueprint>
Adding values to your i18n.properties file for myplugin.wizard.page1.desc.title and myplugin.wizard.page1.desc.content will result in the text appearing on the right-hand side of your Wizard page.
You might want to add validation to the form in your Wizard. To do this, you can use the submit
hook provided by the API. Update the setWizard
call in blueprint-wizard.js
to add the hook:
1 2Confluence.Blueprint.setWizard('com.atlassian.confluence.plugins.myplugin:blueprint-item', function(wizard) { wizard.on('submit.page1Id', function(e, state) { var myName = state.pageData.myName; if (myName == 'abc') { alert('That is not a real name!'); return false; } }); });
As you can see from the above code, values from the Wizard form will be present as properties of the state.pageData
object. In addition to the submit
hook, there are 2 other hooks, explained in this table:
Name of hook | Timing | Usage |
---|---|---|
pre-render | Called before the Soy template is rendered. | This can be used to add data to the Soy rendering context. |
post-render | Called after the Soy template is rendered. | This can be used to add JavaScript behaviour to elements rendered by the template. e.g. autocomplete. |
submit | Called when the dialog page is submitted. | This can be used for a number of things:
|
The callback function for each hook gets passed the jQuery event object and a state
object containing several properties. The properties are listed below along with the hooks for which they apply.
Property | Description | pre-render | post-render | submit |
---|---|---|---|---|
wizardData | Contains all data gathered by the Wizard pages. This is contained in the pages property of wizardData . The pages property is a map where the key is the id of the dialog page (e.g. page1Id) and the value is the pageData collected from that page. | |||
$container | The jQuery object wrapping the rendered Soy template for this page. | |||
pageData | Filled with the values from the form on this page, and can have further data added to it. | |||
nextPageId | Blank when the submit callback is called, this value can be set to change which Wizard page should be shown next. If blank, the next page defined in | |||
finalUrl | Blank when the function is called, if set on the last page of the Wizard, this value is where the Wizard will go on completion. If blank, the Wizard will take the user to the Editor page or View page based on the | |||
soyRenderContext | The context Object for adding values to, for use in the Soy render. The context is empty by default. |
Some templates might get enough data from the Wizard that they can skip the Editor and create the page directly. To do this, update your blueprint
module to include a create-result
attribute:
1 2<blueprint key="myplugin-blueprint" ... create-result="view"/>
A result of "view
" will create the page immediately and take the user to the view of that page. The default result is "edit
" to go to the Editor, and can be omitted.
Note that if you choose to skip the editor you must specify a title for the new page; this can be done in two main ways:
These two methods generally correspond to user-specified versus generated page titles.
If the page title is be passed in through the JavaScript Wizard, there are two ways to do it. The easiest is to have a form field with the name "title
" somewhere in your Wizard. This form value will automatically be passed to the server as the page title when the Wizard is submitted. The alternative is to add the "title
" property to the wizardData
state object manually somewhere in your JavaScript Wizard hooks.
An example of a title
field in the Wizard is the File List Blueprint that is bundled with Confluence.
If the title of the page should be generated from back-end logic (e.g. from a call to a remote site, or by adding a prefix/suffix to the Blueprint name), you can use your Context Provider (outlined in a previous section) to add a property with the key "ContentPageTitle"
to the context map. For example,
1 2public Map<String, Object> getContextMap(Map<String, Object> context) { context.put("myName", "Sherlock"); context.put("ContentPageTitle", yourPageTitleProvider.makeTitle()); return context; }
An example of the ContentPageTitle
being set by a ContextProvider is the Meeting Notes Blueprint that is bundled with Confluence.
With or without a Wizard, when the user chooses your Blueprint you might want to show them a page explaining what the Blueprint does and how it can be used. To add a "Let's get started" page, you just add two things: a Soy template and a reference to it in your <blueprint>
element.
1 2<blueprint key="myplugin-blueprint" index-key="myplugin-index" how-to-use-template="MyPlugin.Blueprints.Hello.letsGetStarted"> ... </blueprint>
Assuming that you already have a Soy resource from the "Adding Required Web Resources" section above, you just need to add your new content to wizard-templates.soy
:
1 2/** * Let's-get-started page for the Create dialog. */ {template .letsGetStarted} <h2>{getText('myplugin.blueprint.letsgetstarted.title')}</h2> <p>{getText('myplugin.blueprint.letsgetstarted.content')}</p> {/template}
When you build and install your plugin and choose your Blueprint, you will now see the contents of the template in a new Create dialog page. Note the "Don't show this again" checkbox that users can click to opt out of seeing the page in future.
A plugin may have multiple Blueprints and a Blueprint may have multiple templates. For example a reports Blueprint could create different kind of reports based on user decisions in the wizard. To do this, we add a second <content-template>
to our Blueprint in atlassian-plugin.xml
:
1 2<blueprint key="myplugin-blueprint" index-key="myplugin-index"> <content-template ref="myplugin-template"/> <content-template ref="myplugin-template-2"/> </blueprint> ... <content-template key="myplugin-template-2" i18n-name-key="myplugin.templates.content2.name"> <resource name="template" type="download" location="xml/template2-body.xml"/> </content-template>
By default, the Blueprint page will be created from the first template, but you can set a special property called contentTemplateKey
in the wizardData
, which is the key of the template that should be rendered. The property can be set directly on the wizardData
via a JavaScript hook (see "Adding JavaScript to your wizard page") or implicitly by having a form field with the name contentTemplateKey
. This latter method works because pageData
automatically loads form field values, and on Wizard submission wizardData
gets all values from pageData
. Below is an example of a template-switching form-field, based on the Wizard form we worked on earlier:
1 2{template .page1Form} <form action="#" method="post" class="aui"> <fieldset> <div class="field-group"> <label for="myname">{getText('myplugin.blueprint.form.label.myname')}</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="myplugin-template">{getText('myplugin.template.name')}</option> <option value="myplugin-template-2">{getText('myplugin.template2.name')}</option> </select> </div> </fieldset> </form> {/template}
When you build and install your plugin and choose your Blueprint, you'll now be able to choose the second template and see it rendered in the Editor.
While we've tried to make the API as flexible as possible, there may be certain behaviours that our API doesn't allow for. In that case, you might need more control of the create experience. This can be done by registering a direct callback that is called when the user clicks the create button after selecting your Blueprint.
If you followed the tutorial so far, you can add the following to your blueprint-wizard.js
:
1 2Confluence.Blueprint.setDirectCallback('com.atlassian.confluence.plugins.myplugin:blueprint-item', function(e, state) { state.finalUrl = Confluence.getContextPath() + "/pages/createpage.action?spaceKey=" + encodeURIComponent(state.spaceKey); });
When you build and install your plugin and choose your Blueprint, you'll now be taken to the URL specified above, which is the Editor with a blank page. There are a few interesting things to note here:
state.finalUrl
should be used instead of calling window.location
or window.open
directly. In future versions of Confluence this will open the URL in the correct fashion (new or existing window) based on user preferences. For now this just sets the window.location
after the callback completes.state.spaceKey
contains the space key of the space the user selected in the Create dialog.setWizard
and <dialog-wizard
> defined in your Blueprint.Once you send the user to a different browser location, you'll need to wire up the required struts
actions in your plugin XML, add a custom Action, and so on. This advanced behaviour is common with the Confluence plugins so you'll want to check Struts module.
To provide users with a consistent experience and simplify the development experience for you, we recommend using the Blueprint API as much as possible. If you need to use setDirectCallback
because of a deficiency in the API, please let us know! You can use the "Feedback" link on the right-hand-side of the screen to let us know what you need from the Confluence Blueprints API.
In the next tutorial, you'll learn about listening for the Blueprint created event, creating a custom Blueprint action, and about some more API components. Read on, in Writing a Blueprint - Advanced.
Rate this page: