I am a huge (yooge) fan of user macros. They fill in the holes in Confluence functionality, or create usable variants of existing macros. This article provides a solution for generating a child page listing that filters based on labels.
For PDF exports (using the v3.x Scroll PDF Exporter), I needed a list of child pages filtered by labels (including excluding labels), but still had the same appearance as the Children Display Macro. You could use the Content by Label Macro, but it adds page icons and has more setting to enable. And I wanted to get rid of the extra space above these macros (see image later). Moreover, I needed this macro to list page in navigation order (which the Content by Label Macro cannot do).
Because I am an amateur coder, I often look to work done by others to kick start my project. In this case, I started with the Include Child Pages User Macro developed by Gregor Mirai and Ronald van Rij. As is often the case, I ended up make a lot of modifications to the original code to get what I needed, but the kick-start was very valuable. And of course, I may not have found the most elegant solution as time was of the essence (apologies!).
I structured the macro to limit the number of user options to make it both quick to insert, and easy to use. I did tie off the sort order for my use case, but left the code in place.
Option | Values | Description |
Parent Page Title | Any | Include children of a specific parent page. If no page is defined, the current page will be selected. |
Labels | Any | Include only subpages with a delimited list of label(s). The macro accepts any non-word character (as defined by RegEx) as a separator. Using a hyphen before a label indicates exclusion. No label will cause the macro to list all child pages (same as Children Display macro). |
Display Labels | On/Off | Select to display page labels at the end of the page title. Label names are hyperlinked to display all matching pages within the same space Parent Page. This option is useful for debug. Default setting is off. |
Sort Order† | Nav Order | Selects the sort order the page display. Nav order sorts the pages as they appear in the left-hand ToC display. It is also the default option. |
Table Note:
† Macro currently hard codes and hides this setting from the GUI, but the code is left in place.
See the attached file. (apparently the file attachment function doesn't really work. The code is at the bottom).
The figure below show how this macro compares to the other options (notice less space with this user macro above the listing as well as the order of results). Of course, now that it is a user macro, you can add classes to the HTML to change the styling as you see fit (in this case I added the class "child-by-label").
This macro could be modified to list all of the descendant of the target page. Two changes would need to be made;
.getChildren()
with .getDescendants()
Note: I don't know what order the descendants will be returned in, so I assume the bubble sort will be needed.
This macro could also be modified to produce a hierarchical listing of pages — a sort of Page Tree Macro that allows label filtering, sorting and supported an unordered listing display. But that modification would be more complex. The macro would need to be modified similar to the above, but also:
This work is left to the industrious reader!
## Macro title: Child Pages by Label
## Developed by: Bill Bailey, MarketCom, LLC
## Date created: 2018.02.22
## Version: 0.125
## Inspired by the Include Child Pages Macro
##
## Source: https://github.com/unidwell/confluence-include-child-pages-macro
## @param ParentPage:title=Parent Page Title|type=confluence-content|desc=Include children of a specific parent page. If no page is defined, current page will be selected.
## @param inputLabels:title=Labels|type=string|desc=Include only subpages with a delimited list of label(s). Hyphen before label indicates exclusion.
## @param displayLabels:title= Display Labels|type=boolean|default=false|desc=Select to display page labels (good for debug)
#* Removed support to handle situation of just inserting via keyboard
@param Order:title=Sort Order|type=enum|enumValues=Nav Order,Reverse Nav Order,Alphabetical,Reverse Alphabetical,Create Date,Reverse Create Date|default=nav order|desc=In what order should the child pages be displayed? Nav Order refers to the order of the child pages in the page tree. Reverse Nav Order simply reverses that.
*#
## @param Limit:title=Limit|type=int|default=|desc=The number of pages to include. If number is not specified all pages will be included.
#set( $containerManagerClass=$content.class.forName('com.atlassian.spring.container.ContainerManager') )
#set( $getInstanceMethod=$containerManagerClass.getDeclaredMethod('getInstance',null) )
#set( $containerManager=$getInstanceMethod.invoke(null,null) )
#set( $containerContext=$containerManager.containerContext )
#set( $pageManager=$containerContext.getComponent('pageManager') )
#set( $Integer = 0 )
## Initialize $childrenMap
#set($childrenMap = {})
## Initialize default sort order as work around, renamed $paramOrder to $navOrder
#set($navOrder = "Nav Order")
## Get page title and space key from the parent page parameter
#set( $currentSpaceKey = $space.getKey() )
#set( $pageColonPos = $paramParentPage.indexOf(":", 0) )
#if( $pageColonPos >= 0 )
#set( $currentSpaceKey = $paramParentPage.substring(0, $pageColonPos) )
#set( $pageColonPos = $pageColonPos + 1 )
#set( $paramParentPage = $paramParentPage.substring($pageColonPos) )
#end
#set( $currentPage = $pageManager.getPage($content.id) )
#if( $paramParentPage )
#set( $currentPage = $pageManager.getPage($currentSpaceKey, $paramParentPage) )
#end
#set($inputLabelsMap = {})
#set($FilterLabelexists=false)
#set($inputLabelList=[])
#set($_ = $inputLabelsMap.put(" ", true)) ## set to dummy value to avoid major code restructuring
#if($paraminputLabels)
#set($FilterLabelexists=true)
#set($inputLabelsMap = {}) ## Reset map to clear dummy value
## encode not setting
#set($paraminputLabels=$paraminputLabels.replaceAll("-", "_"))
## Parse labels and convert to a lits
#set($paraminputLabels=$paraminputLabels.replaceAll("\W", " "))
#set($paraminputLabels=$paraminputLabels.replaceAll("\s+", ","))
#set($inputLabelList=$paraminputLabels.split(","))
## convert array to map to hold the label and logical not modifier
## Java .add() method for the list type returns a boolean value, when you add an element to the list, Velocity will print "true" or "false" based on the result of the "add" function.
#foreach( $inputLabel in $inputLabelList )
#end
#foreach( $inputLabel in $inputLabelList )
#if($inputLabel.startsWith("_"))
#set($inputLabel=$inputLabel.replaceFirst("_", ""))
#set( $_ = $inputLabelsMap.put($inputLabel.toString(), false) )
#else
#set( $_ = $inputLabelsMap.put($inputLabel.toString(), true) )
#end
#end
#end
## Find the array of (sorted) child pages.
#set( $data = "" )
#if( $navOrder == "Nav Order" || $navOrder == "Reverse Nav Order" )
#set( $children = $currentPage.getSortedChildren() )
#elseif( $navOrder == "Alphabetical" || $navOrder == "Reverse Alphabetical" )
#set( $array = $currentPage.getChildren() )
#################
## Bubble sort ##
#################
#set( $size = $array.size() )
#foreach( $junk in $array )
#set( $count = -1 )
#foreach( $line in $array )
#set( $count = $count + 1 )
#if( $velocityCount < $size )
#if( $line.getTitle().compareToIgnoreCase($array.get($velocityCount).getTitle()) > 0 )
#set( $tmp = $array.get($velocityCount) )
#set( $junk = $array.set($velocityCount, $line) )
#set( $junk = $array.set($count, $tmp) )
#end
#end
#end
#end
#set( $children = $array )
#elseif( $navOrder == "Create Date" || $navOrder == "Reverse Create Date" )
#set( $children = $currentPage.getChildren() )
#end
## Define sorting order.
#set( $inOrder = true )
#if($navOrder.contains("Reverse"))
#set( $inOrder = false )
#end
#foreach( $child in $children )
## Set key for $childrenMap and set value to null for tristate logic
#set( $_ = $childrenMap.put($child, null) )
#end
#foreach ($labelEntry in $inputLabelsMap.entrySet())
#if($FilterLabelexists==true && $labelEntry.key)
#foreach( $child in $children )
## Check if current user can view the child page.
#if($permissionHelper.canViewPage($action.getAuthenticatedUser(), $child.id))
## Create the map of labels for the page.
#set( $labelsMap = {} )
#foreach( $label in $child.getLabels() )
#set( $_ = $labelsMap.put($label.toString(), 1) )
#end
#if ($labelEntry.value == true)
#if(!$childrenMap.get($child))
#set( $_ = $childrenMap.put($child, false) ) ## normal logic works for finding matching labels
#end
#if($labelsMap.containsKey($labelEntry.key))
#set( $_ = $childrenMap.put($child, true) ) ## Found labels set to true
#end
#elseif ($labelEntry.value == false)
## value left to null to allow for excluding a page with more then one label.
#if ($labelsMap.containsKey($labelEntry.key))
#set( $_ = $childrenMap.put($child, false) ) ## set to false to exclude a page
#end
#end
#end
#end
#end
#end
#foreach( $child in $children )
#if($childrenMap.get($child)!= false)
#set( $_ = $childrenMap.put($child, true) ) ## Set nulls to true to include pages for excluded pages.
#end
#end
#set( $include = "" )
#set( $count = 1 )
#set( $pageLabels ="")
#if($paramLimit=="")
#set($paramLimit = $childrenMap.size())
#else
#set($paramLimit = $Integer.parseInt($paramLimit))
#end
#foreach( $child in $children )
#if($childrenMap.get($child) == true && $count <= $paramLimit)
#if($paramdisplayLabels==true)
#set( $pageLabels =": ")
#foreach( $label in $child.getLabels() )
#set( $pageLabels = $pageLabels + ' <a class="aui-label-split-main aui-label" href="' + $label.getUrlPath($currentSpaceKey) + '">' + $label.name + '</a>' )
#end
#end
#set( $include = '<li class="child-by-label-page-title"><a href="' + $child.getUrlPath() + '">' + $child.getTitle() + '</a>' + $!pageLabels + '</li>' )
#set( $count = $count + 1 )
## Add page content in order or in reverse order.
#if( $inOrder == true )
#set( $data = $data + $include )
#else
#set( $data = $include + $data)
#end
#end
#end
<ul class="child-by-label">$data</ul>
Bill Bailey
Owner/ Head Honcho
MarketCom, LLC
Texas
218 accepted answers
2 comments