Home > Articles > Open Source > Ajax & JavaScript

  • Print
  • + Share This
Like this article? We recommend

Making the Table of Content Collapsible

In the next phase of our progressive enhancement, we’ll make the TOC more dynamic. If you would click on a category link in the original page, the browser would bring you to the start of that category (thanks to links to fragment identifiers in the TOC). After we expand the TOC, the links to categories are redundant (because we can jump to each individual question), and the first-level TOC entries would work better if they would expand or collapse the second-level sections. To implement this new functionality, we need to modify the following attributes:

  • Attach an onclick handler to all A elements in the first-level LI elements in our TOC. The onclick handler should call a generic ExpandOrCollapse routine.
  • Define two new CSS classes (collapsed and expanded) that will modify the formatting of the first-level TOC entries (for example, replacing the bullet with a small + or – sign).

The following JavaScript code modifies the onclick handlers of the A elements:

function makeTocCollapsible() {
 // Get the top TOC UL element
 var tocdiv = xGetElementById("toc"); if (!tocdiv) return;
 var toc = GetFirstElementByTagName("ul",tocdiv); if (!toc) return;
 toc.id = "toctop";

 // scan only the child nodes of top UL element, not all LI elements
 for (var i = 0; i < toc.childNodes.length; i++) {
  var ulElem = toc.childNodes[i];
  if (ulElem.nodeName.toLowerCase() != "li") continue;
  
  // get the first link in the level-1 bullet line and modify it
  var aElem = GetFirstElementByTagName("a",ulElem);
  if (!aElem) continue;
  aElem.onclick = ExpandOrCollapse; aElem.onclick();
 }
}

The makeTocCollapsible function finds the TOC top-level UL element and sets its id name to "toctop" for easier identification. It then scans the child nodes of the top-level UL element (thus avoiding the second-level LI elements that would be returned by the getElementsByTagName DOM call). If a child node is a bullet line (as identified by its nodeName being equal to LI), the first A element within the LI element is found and its onclick handler modified. The onclick handler is called immediately after that to collapse the TOC entry.

The ExpandOrCollapse function is very simple. It’s called within the context of the A element, so its first task is to identify parent first-level TOC entries and corresponding list of second-level TOC entries. It then checks whether the second-level entries are currently visible (the style.display attribute of the UL element is empty) and changes the state of their visibility together with the CSS class name of the first-level LI element:

function ExpandOrCollapse() {
 // get first-level LI element
 var tocElem = this.parentNode; 
 if (tocElem.nodeName.toLowerCase() != "li") return;

 // get second-level UL element
 var tocLev2 = tocElem.nextSibling; 
 if (tocLev2.nodeName.toLowerCase() != "ul") return;

 var collapse = tocLev2.style.display == "";
 tocElem.className = collapse ? "collapsed" : "expanded";
 tocLev2.style.display = collapse ? "none" : "";
 return false;
}

The CSS formatting needed to implement the collapsible TOC is also quite straightforward: the list style images are defined for expanded and collapsed TOC entries, and the margins and padding are slightly adjusted. To make the top-level TOC entries stand out, their background color is changed, and thus the list style image has to be moved to the inside of the paragraph to ensure that the background color stretches behind it as well.

#toc li.collapsed, #toc li.expanded { 
 list-style-position: inside; background-color: #DDDDDD; 
 margin: 0.5em 0 0.3em 0; padding: 0.1em 0 0.1em 0.5em;}

#toc li.collapsed { list-style-image: url(plus.gif); }
#toc li.expanded { 
 list-style-image: url(minus.gif); margin-bottom: 0; }

#toctop { margin-left: 0; padding-left: 0; }

As before, the makeTocCollapsible function shall be called after the FAQ page is completely loaded; we just have to make sure that it’s called after the buildToc function. Traditionally, we would solve this by modifying the onload attribute of the BODY tag, as shown in the next code fragment, but this would violate the requirement that the source HTML code should not be changed.

<body onload="buildToc(); makeTocCollapsible();">...

It’s thus better if we attach the code that calls both functions in sequence to the window load event:

function ProgressiveEnhancements() {
 buildToc();
 makeTocCollapsible();
}

xAddEventListener(window,"load",ProgressiveEnhancements);
  • + Share This
  • 🔖 Save To Your Account