A Child Page by Label User Macro

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. 

Motivation

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).

The Macro

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!).

The Options

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.

OptionValuesDescription
Parent Page TitleAnyInclude children of a specific parent page. If no page is defined, the current page will be selected.
LabelsAnyInclude 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 LabelsOn/OffSelect 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
Reverse Nav Order
Alphabetical
Reverse Alphabetical
Create Date
Reverse Create Date

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.

The Code

See the attached file. (apparently the file attachment function doesn't really work. The code is at the bottom).

The Results

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").

Screenshot_2020-10-08 Child Pages by Label Macro - Tech Pubs - Achronix - Confluence.png

Other Possible Modifications

Flat Listing of all Matching Descendants

This macro could be modified to list all of the descendant of the target page. Two changes would need to be made;

  • Strip out support for normal and reverse navigation order support
  • Replace instances of .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.

Hierarchical Listing of all Descendants

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:

  • Restructured to work recursively at each hierarchical level while generating the list.
  • Add support to sort at each level.

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>

 

2 comments

Gonchik Tsymzhitov
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 2, 2021

Thank you for your article!

Do you use the formatting of macro code some IDEs?

Bill Bailey
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 4, 2021

@Gonchik Tsymzhitov  I think for this one, I just updated the space CSS to style it more like the rest of the format I am using.

For writing code, I tend to use Notepad++ as there is support for Velocity. I have tried an Visual Studio before, but that does take time to set up. Depends on the complexity of the project.

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events