Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Next challenges

Recent achievements

  • Global
  • Personal

Recognition

  • Give kudos
  • Received
  • Given

Leaderboard

  • Global

Trophy case

Kudos (beta program)

Kudos logo

You've been invited into the Kudos (beta program) private group. Chat with others in the program, or give feedback to Atlassian.

View group

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

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

Thank you for your article!

Do you use the formatting of macro code some IDEs?

@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
Community showcase
Published in Confluence Cloud

Get to know the Confluence team!

Go “behind the screen” to meet some of the Confluence Cloud team. In this video series, we tackle some of the hard-hitting questions you never knew you wanted the answer to!  Meet some of the ...

236 views 0 10
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you