Last updated Oct 3, 2024

Page tree API documentation

This documentation is aimed at developers needing to include page-tree (page reordering) functionality in their Confluence or Plugin code.

Let's start with an example - editing a page deep inside the Confluence Doc space.

This tree is generated by the following markup in listpages-dirview.vm :

1
2
#requireResource("confluence.web.resources:jquery")
#requireResource("confluence.web.resources:page-ordering-tree")
<div id="tree-div"></div>

and the following JavaScript :

1
2
var expandedNodes;
#if ($openNode)
    expandedNodes = [
        {pageId: "$openId"}
    #foreach ($nodeId in $openedNodes)
        ,{pageId: "$nodeId"}
    #end
    ];
#end
jQuery(function ($) {
    tree = $("#tree-div").tree(
        {
            url: contextPath + '/pages/children.action',
            initUrl: contextPath + '/pages/children.action?spaceKey=$space.key&node=root',
            parameters: ["pageId"],
            append: function() {
                recordMove(this.source.pageId, this.target.pageId, "append");
            },
            insertabove: function() {
                recordMove(this.source.pageId, this.target.pageId, "above");
            },
            insertbelow: function() {
                recordMove(this.source.pageId, this.target.pageId, "below");
            },
            onready: function () {
                if (typeof expandedNodes != "undefined") {
                    var doHighlight = function() {
                        tree.findNodeBy("pageId", "$openId").highlight()
                    };
                    tree.expandPath.apply(tree, expandedNodes.reverse().concat(doHighlight));
                }
            }
        }
    );

    ## Callbacks when append/insert events are fired by the tree.
    var recordMove = function (sourceId, targetId, position) {
        $ .ajax({
            url: contextPath + "/pages/movepage.action",
            data: {pageId: sourceId, point: position, targetId: targetId},
            complete: function(xmlhttp) {
                var resultsDiv = document.getElementById("resultsDiv");
                resultsDiv.innerHTML = xmlhttp.responseText;
                if (xmlhttp.getResponseHeader("success") != "true") {
                    tree = tree.reload();
                }
                if ( position == "append") {
                    tree.findNodeBy("pageId", targetId).reload();
                }

            }
        });
    }
});

Once you understand the above code you'll have a good overview of how the tree works.

Stepping through the code

To start, "expandedNodes" is simply a JS array of objects with "pageId" variables. The pageIds are populated using Velocity but any method is okay.

Next, "jQuery(function ($) {" is just a way of enabling $ to be used to gain access to a jQuery object. The page tree code has been written as an extension of the jQuery object, so we call $("#tree-div") to get a jQuery object wrapping the div with id "tree-div" that we added to our HTML markup.

Creating the tree

When the tree() function is called, an object with options is passed. We'll work through each of the options in turn:

1
2
url

This is the location that the tree will load its nodes from as the user navigates it. The alternative is to directly include the tree data in the HTML in nested <ul> or <ol> format.

1
2
initurl

(optional) This is the location that the tree will load its "trunk" (initial nodes) from. If not specified, the "url" will be used and the server would be expected to return something useful. If specified, it can (as in this case) pass extra information to the same server address.

1
2
parameters

(optional but important) This array specifies the key/value pairs that will be sent to the url when making node requests. The nodes returned from the server will be expected to include key/value pairs for each of the parameters in the array, which are stored in the tree internals and sent with any future requests from that node.

1
2
append, insertabove, insertbelow

(optional) These options specify callback functions that should be executed when their respective event occurs:

  • append - means that a tree node (i.e. a page) has been moved inside another node
  • insertabove - means that a tree node has been moved above another node
  • insertbelow - means that a tree node has been moved below another node

For each of these events the most important data is source and target. Source is the node that is being moved and target is the "other" node that the source is interacting with.

While these three events are the most common, you can also hook callbacks to :

  • grab - when the user clicks and holds on a node
  • drag - when the user moves the mouse while a node is grabbed
  • drop - when the user releases the mouse button
  • load - when node data is returned from the server
  • nodeover - when a node is dragged over another node
  • nodeout - when a node is dragged out of a node it was previously over
  • onready - covered next
1
2
onready

(optional) Called when the tree has finished loading, from either its first initUrl call or from hard-coded list data. In this case, if "expandedNodes" exist the tree should be expanded to show them. The way that this is done is worth explaining in more detail.

Finding and Expanding Nodes

Once the first level of tree data has been loaded into the browser, the next step is often to drill into the tree to expose a particular element. This is done by calling :

1
2
tree.expandPath(expandedNodes, callback)

Internally, this function works recursively through the array of expandedNodes, locating the node in the loaded tree and, if present, opening it. In the screenshot above, this is equivalent to passing an array of nodes:
"Confluence Documentation Home", "Confluence Development Hub", "Confluence Architecture", "Confluence Internals". Note that the nodes are referenced by pageId and must be in the correct order - each node to expand must already be loaded. Once each node is expanded the callback function (if present) is executed. In this example, the callback highlights a node inside the expanded nodes - note that the "Bandana caching" node is only loaded from the server when the "Confluence Internals" node is expanded, so the highlighting must occur after this.

Individual nodes are located with this syntax:

1
2
tree.findNodeBy(attribute-name, attribute-value)

Usually the attribute-name will be one of the parameters in the options originally used to create the tree; in this case it is "pageId".

The object returned by findNodeBy has a number of functions that can be called on it :

  • open(callback) - expand this node. If the node has not been opened yet and the tree has a url, child-node JSONs will be requested from the server and appended to the node.
  • close() - closes an opened node
  • getAttribute(attrName) - returns the attribute value for the given name (eg "pageId")
  • setAttribute(attrName, attrValue) - sets an attribute
  • highlight() - adds "highlighted" class to the node
  • makeDraggable() - allows the node to be moved in the tree
  • makeUndraggable() - stops the node from being moved (e.g. when moving a page while editing, other nodes in the tree cannot be moved)
  • setText(text) - updates the node text
  • append(node) - appends a node to this node
  • below(node) - places the passed node after this node
  • above(node) - places the passed node before this node
  • remove() - removes this node from the tree
  • reload() - if this node has children, reload them from the server

Other tree functions

In addition to the functions covered in the example above, the tree object exposes the following variables and functions:

  • options - the options passed in the original tree() function call
  • reload() - clears and rebuilds the tree
  • append(node) - appends a node to the tree root

Rate this page: