First a note about the intent of this series of articles (this is the first —hopefully). While there are resources that talk about writing user macros, they typically talk about all that surrounds the macro, syntax, and give a simple "hello world" example. The problem with user macros is that they are coded using Velocity Tempting Language (VTL), which make use of Java constructs. VTL is obscure enough (and hard to Google as the name is a common word), that there are not many online tutorials. This series is intended to help those wanting to write user macros in getting started with some more complex (and useful) examples. On to the article!
Often with Confluence, there is a Confluence macro you love, but really need it to work differently than planned, or need a way to control its use (especially if you are an admin, or tech writer, for example). One solution is to create a wrapper macro – a user macro that wraps a Confluence macro, providing a way to control the parameters and possibly perform additional processing. Motivations for writing a macro are:
A good candidate for a wrapper macro is the status macro. The status macro allows you to set label text and color to create a nice colored lozenge. For example:
This macro is good for creating dashboards for reporting on status. Atlassian provides a good sample application in their article, How to build a release planning page in Confluence. The problems with the existing status macro for this use-model are:
One use of the a status macro is for indicating priority, for example, critical, high, medium, low – with associated color coding (management loves colors!). Rather than requiring/hoping that users only use these four labels and the correct colors associated with each, our macro will just have a pull-down with the four statuses. No typing; no color selection — quick, easy and consistent.
Key to starting a wrapper macro is obtaining the storage view of a macro. The easiest way to obtain the storage view is to create a test page, insert the macro set to one of the desired configurations, then select View Storage Format from the page menu. For example, the storage view of the status macro configured for a critical status is as follows:
<ac:structured-macro ac:macro-id="4e9a977c-c6cd-48d1-8381-e4f22e73e215" ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">Red</ac:parameter>
<ac:parameter ac:name="title">Critical</ac:parameter>
</ac:structured-macro>
For our use, we need to set two parameters: colour and title.
Note: The macro attribute ac:macro-id
can (and may need to) be removed. It is a unique identifier for a macro instance.
Since the goal of this wrapper macro is to restrict the options to a defined set, we want the user to only be able to select from one of the defined priorities (the associated color will be set in macro code). The needed macro code for creating a pull-down with a set number of conditions is:
## @param Priority:title=Priority|type=enum|enumValues=Critical,High,Medium,Low
Note: There should be no spaces in the list of enumerated values. In other words, "Critical" is different from "Critical ". For details on user macro syntax, see User Macro Template Syntax.
With a defined parameter, we can now substitute this parameter in our status macro.
<ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">Red</ac:parameter>
<ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>
Note: The exclamation point in the variable is Velocity quiet notation.
Our macro will now update the status macro lozenge text, based on the parameter selected by the user. But the colo(u)r value is still static, so a parameter is needed for this setting, for example:
<ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
<ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro
But we need to set the value of $priorityColor based on the value of $paramPriority and not force the user to select it.
We could use an if-elseif tree to set the color value for our wrapper macro, but a more compact and less error-prone way to set this value is via lookup. Velocity supports Java methods and properties, allowing for the creation of a key-value map via the Java Map class. In essence, we create a list of key-value pairs that then can be used as a look-up table later. For our priority macro, the key-value map that sets the various macro colors based on the priority value is:
#set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue"})
Then setting the value for $priorityColor is then done by looking up (via the get method) the corresponding value:
#set($priorityColor = $priorityvalues.get($paramPriority))
Putting the entire macro together, along with adding headers (best practices):
## Macro title: Priority
## Developed by: Bill Bailey, MarketCom, LLC
## Date created: 2019.01.12
## Version: 1.0
## @param Priority:title=Priority|type=enum|enumValues=Critical,High,Medium,Low
## Set value pairs for status macro instances
#set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue"})
#set($priorityColor = $priorityvalues.get($paramPriority))
<ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
<ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>
Follow the instruction spelled out in Writing User Macros for installing the macro code. Be sure to select No Macro Body in the settings. Voilá! You have a priority macro.
I deployed this macro inside a Page Properties Macro table within a page template. As users created pages using this template, a summary report (dashboard) was created using the Page Properties Report Macro. This macro creates a table pulling the properties from all the child pages, creating a management dashboard.
Within a few days of deploying this macro to my user base, they asked if there was a way to have a custom sort order. The table created by Page Properties Report Macro can be sorted alphanumerically, but that results in the priority being sorted as Critical, High, Low, then Medium — as situation deemed unacceptable by management! The easiest solution would have been to just add numbers to all the priorities, for example 1-Critical, 2-High, 3-Medium, 4-Low — a result that is cosmetically ugly. But we have our wrapper macro which allows us to add additional code.
While it might be possible to add Javascript to a page to create a custom sort order, I am both lazy and not Javascript literate enough to generate and deploy such code. The solution I settled on was to take the simple solution a step further and add the numbering outside of the status macro (and then hide that number using CSS trickery).
Again, using a key-value-map construct to associate a numbering with a priority:
#set($priorityorder = {"Critical" : "1", "High" : "2", "Medium" : "3", "Low" : "4"})
Then wrapping the status macro with a span tag plus $priorityorder:
<span>$!priorityorder.get($paramPriority)
<ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
<ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>
</span>
Now our sorting problem is fixed, but the result is not really what we want:
Adding some inline styling to the span tag to apply the white-tape approach (make the number white to render it invisible), plus dropping the font size down and setting negative margin to get better alignment, our final code now appears as:
## Macro title: Priority
## Developed by: Bill Bailey, MarketCom, LLC
## Date created: 2019.01.12
## Version: 1.1
## @param Priority:title=Priority|type=enum|enumValues=Critical,High,Medium,Low
## Set value pairs for status macro instances
#set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue"})
#set($priorityorder = {"Critical" : "1", "High" : "2", "Medium" : "3", "Low" : "4"})
#set($priorityColor = $priorityvalues.get($paramPriority))
<span style="color:#FFFFFF;font-size:10px;margin-right:-5px;">$!priorityorder.get($paramPriority)
<ac:structured-macro ac:name="status" ac:schema-version="1">
<ac:parameter ac:name="colour">$!priorityColor</ac:parameter>
<ac:parameter ac:name="title">$!paramPriority</ac:parameter>
</ac:structured-macro>
</span>
The resulting macro renders as:
Now end users are happy and so is management (and we can get back to surfing the web).
So your macro has been working fine now for some time, but your boss comes in and wants you to add two new statuses to the listing, "On Hold" and "Cancelled". But the macro only supports five colors. However, there is the Use lighter lozenge color option in the macro that uses a lighter lozenge background color with colored text. This option is controlled via a new macro parameter subtle with the values of true/false:
<ac:parameter ac:name="subtle">true</ac:parameter> |
Now with this new option, we can use normal gray for Hold and subtle gray for Cancelled:
First we need to update the exiisting macro paramater selections as well as the key map pairs to add these two new statuses:
## @param Priority:title=Priority|type=enum|enumValues=Critical,High,Medium,Low,On Hold,Cancelled ## Set value pairs for status macro instances #set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue", "On Hold" : "Grey", "Cancelled" : "Grey"}) #set($priorityorder = {"Critical" : "1", "High" : "2", "Medium" : "3", "Low" : "4", "On Hold" : "5", "Cancelled" : "6"}) |
Next we need to create a new key map to handle the subtle parameter settings, plus logic to select the setting:
#set($priorityoutline = {"Critical" : "false", "High" : "false", "Medium" : "false", "Low" : "false", "On Hold" : "false", "Cancelled" : "true"}) #set($prioritySubtle = $priorityoutline.get($paramPriority)) |
Then put it all together, adding the logic for the sublte parameter:
## Macro title: Priority ## Developed by: Bill Bailey, MarketCom, LLC ## Date created: 2021.12.02 ## Version: 2.0 ## @param Priority:title=Priority|type=enum|enumValues=Critical,High,Medium,Low,On Hold,Cancelled ## Set value pairs for status macro instances #set($priorityvalues = {"Critical" : "Red", "High" : "Yellow", "Medium" : "Green", "Low" : "Blue", "On Hold" : "Grey", "Cancelled" : "Grey"}) #set($priorityorder = {"Critical" : "1", "High" : "2", "Medium" : "3", "Low" : "4", "On Hold" : "5", "Cancelled" : "6"}) #set($priorityoutline = {"Critical" : "false", "High" : "false", "Medium" : "false", "Low" : "false", "On Hold" : "false", "Cancelled" : "true"}) #set($priorityColor = $priorityvalues.get($paramPriority)) #set($prioritySubtle = $priorityoutline.get($paramPriority)) <span style="color:#FFFFFF;font-size:10px;margin-right:-5px;">$!priorityorder.get($paramPriority) <ac:structured-macro ac:name="status" ac:schema-version="1"> <ac:parameter ac:name="subtle">$!prioritySubtle</ac:parameter> <ac:parameter ac:name="colour">$!priorityColor</ac:parameter> <ac:parameter ac:name="title">$!paramPriority</ac:parameter> </ac:structured-macro> </span> |
Now we have a way of handling up to 10 total priority values with our macro, and can go back to online shopping!
Bill Bailey
Owner/ Head Honcho
MarketCom, LLC
Texas
218 accepted answers
19 comments