Accessibility

Contribute Article

Automatic Tables of Contents in Contribute Pages

Michael Matti

Michael Matti

minorepiphanies.net

Usually, you know a site’s needs before you begin the design work—and long before the site is deployed. But sometimes, after you've built and deployed the templates, content authors may see the need for a new, unplanned feature. They may even get creative and add the feature themselves.

Recently, I found that our Macromedia Contribute authors were creating a table of contents for each of their pages. Since the new site was a collection of policies and procedures, some of the pages were unavoidably long and complex, with multiple levels of headings. For an example of a similar document, take a look at this specification from the W3C. It very sensibly starts out with a table of contents, providing links to major headings.

A sample table of contents

Figure 1. A sample table of contents

To create such TOCs, our Contribute users were:

  1. Adding a section anchor to each of the headings
  2. Creating a structured list at the top of the page
  3. Copying the text of each heading to the matching list item
  4. Creating links from each item to the matching content heading

Needless to say, it was a time-consuming, error-prone, inconsistent process. Each user built his or her TOC out of different lists and formatting. And whenever a heading changed, the list went out of synch.

Surely, I thought, there's a better way.

Since the content already had headings, and since Contribute produces clean and consistent markup, why not use JavaScript to scan the content and build the TOC at load time? This approach brings a number of advantages. The new page TOCs are:

  • Perfectly accurate
  • Completely automatic
  • Consistently formatted
  • Inducements to good organization

The technique works for current versions of Internet Explorer and Mozilla/Firebird. It won't produce errors on other browsers, such as early versions of Netscape Navigator.

Prerequisites

System Requirements

The Document Object Model

What we're going to do takes advantage of the Document Object Model (DOM), a set of standards developed by the W3C that provide a way for scripting languages to control the content and appearance of documents. The DOM views a document as a collection of nodes, most of which you can read from and write to. Headings happen to be nodes, very helpfully identified by H1, H2, etc. tags. You'll create a JavaScript function that scans the nodes for these tags, inserts section anchors, and constructs a linked list of the headings. Then, at the top of the page, the script will insert the TOC list. By making the function run as soon as the page loads, the table of contents will always be accurate.

HTML

Let's assume, for the sake of simplicity, that your Contribute site is based on a more-or-less standard Dreamweaver template. It has an area reserved for the main Contribute content. This might be within a DIV element or the TD cell of a layout table, but the point is, there's a place where the primary content goes. If you're using more than one template, repeat as needed:

  1. Open the template in Dreamweaver and switch to Code View.
  2. Add onload="toc()" to the template's BODY tag:

    <body onload="toc()">

    This will make the script function run when the page loads. If you're already using the onload event to run a function, just add semicolon and the call to toc():

    <body onload="somefunc() ;toc() ">
  3. Immediately before the primary TemplateBeginEditable tag, add an empty DIV tag with an ID of toc:
  4. <td id="main">

    <div id="toc"></div>

    <!--TemplateBeginEditable name="content" -->

    This empty DIV-with-ID gives the script a target; once it builds the list of headings, the script needs to know where to place the results.

JavaScript

The JavaScript function isn't terribly long, but don't even think about adding its code to your template, to be written to every page. Instead, follow Drew McLellan's tip and export your JavaScript to a separate file. The idea is to link from each page to single file that holds all the functions. This improves load times, since the file is cached. It also eases maintenance, since you can update one file instead checking out the entire site.

  1. If the template doesn't already link to an external file, add the following somewhere within its HEAD section:
  2. <script src="/dirpath/filename.js" type="text/javascript"></script>

    Change dirpath and filename to your preferred path and name. I usually put scripts.js in the assets directory.

  3. Save the page template.
  4. If you don't already have a scripts file, create one by selecting File > New > General > Basic Page > JavaScript.
  5. Place the following function in your scripts page:

    function toc(){
    if(document.getElementById){
    var nodecol=document.getElementById('main').childNodes;
    var nodelng=nodecol.length;
    var nodecnt='';
    var toc='';
    var nest=tocndx=0;
    for(var i=1;i<nodelng;i++){
    if(nodecol[i].tagName=='H1'||nodecol[i].tagName=='H2'
    ||nodecol[i].tagName=='H3'){
    nest=parseInt(nodecol[i].tagName.substr(1));
    nodecnt=nodecol[i].innerHTML;
    for(var j=1;j<=nest;j++)
    toc+='<ul>';
    toc+='\n<li><a href="#jump'+i+'">'+
    nodecnt.replace(/<\/?a[^>]*>/gi,"")+'</a></li>\n';
    for(var j=1;j<=nest;j++)
    toc+='</ul>';
    nodecol[i].innerHTML=
    '<a name="jump'+i+'"></a>'+nodecnt;
    tocndx++;}}
    if(tocndx>1){
    document.getElementById('toc').innerHTML=
    '<div id="toccnt">\n'+toc+'\n</div>';
    document.getElementById('toc').className='mmhide_toc';}}}
  6. Save the scripts file, using the path and name specified in the template.

There's no need to detail every line of the function, but a few points are worth mentioning. Also, there are two places to consider for edits:

  • The first line is a test condition, if(document.getElementById), that allows only DOM-savvy browsers to continue.
  • The next line, referring to document.getElementById('main').childNodes, scoops up all the nodes in the Contribute editable region. The script assumes that the editable region is contained in an element with the ID main. Your own template likely uses a different ID for the containing element—or might not use an ID at all. Change main to match whatever ID you've used; if the content container doesn't have an ID, change document.getElementById('main').childNodes to document.childNodes.
  • After collecting the nodes in the content region, the script starts a FOR loop to search for the heading tags H1, H2, and H3. HTML offers six levels of headings, but three is plenty for a page TOC, and might be too much. If you want to limit it to two levels, delete ||nodecol[i].tagName=='H3'.
  • The numeric level of a heading (H1 , H2 , H3) determines how many UL tags will be nested in the TOC to represent it.
  • The FOR loop gives each node a unique number. The script concatenates the string jump and the number to create names for the section anchors.
  • There's a bit of anti-absurdity logic at the end: a TOC is written to the page only if two or more headings are found.

When script processing is complete, you have a list built from nested UL tags. If you could see its HTML, an entry would look like:

<ul><ul>
<li><a href="#jump5">Vertebrates</a></li>
</ul></ul>		

Meanwhile, down in the content, the matching heading would look like:

<h2><a name="jump5"></a>Vertebrates</h2> 

Of course, none of these changes are written to the page itself. Modifications are confined to browser memory, in a virtual page that's rendered onscreen.

Style Sheet

If at this point you eagerly load a page, you'll be in for a rude surprise. Because we're nesting UL tags to maintain the heading hierarchy, the TOC will start out as a sprawling mess. We need CSS definitions to tame its appearance.

The Table of Contents before setting up the style sheet

Figure 2. The Table of Contents before setting up the style sheet

  1. If you haven't already done so, follow the instructions in TechNote 12922 to set up an external style sheet.
  2. Add the following definitions to your style sheet:

    div.mmhide_toc{
       background-color:#fff;
       padding:1ex;
       width:33%;
       float:right;}
    #toccnt{
       background-color:#eed;
       padding:0 0 2px .5ex;
       border:1px solid #ccc;}
    #toccnt ul{
       padding:0;
       margin:.5ex .1ex 0 1.25em;}
    #toccnt ul li{
       color:#666;
       font-size:x-small;
       line-height:1.2em;
       list-style-type:disc;}
    #toccnt ul li a{
       color:#333;
       text-decoration:none;}
    #toccnt ul li a:hover{
       text-decoration:underline;}
    
  3. Edit to suit your site's appearance. For example, if you don't like the bullets, change list-style-type:disc to list-style-type:none in the #toccnt ul li definition. The reason for having mmhide_ in a class name is to keep it off the Contribute Styles menu; it's unlikely you'd want authors to see these. (Styles assigned by ID don't appear in the menu.)
  4. Save the style sheet.
  5. Check in the site.
The Table of Contents after applying the style sheet

Figure 3. The Table of Contents after applying the style sheet

Once this feature is added, it's a good idea to explain the new page behavior to your Contribute authors and editors. Encourage them to use headings consistently; users and authors will reap the benefits.


About the author

Michael C. Matti is a usability analyst at SAS, where he polishes business intelligence applications and prototypes components and sites. He is also the creator of minorepiphanies.net. You can reach him at mimatt@sas.com.