Automatic Tables of Contents in Contribute Pages
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.
Figure 1. A sample table of contents
To create such TOCs, our Contribute users were:
- Adding a section anchor to each of the headings
- Creating a structured list at the top of the page
- Copying the text of each heading to the matching list item
- 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
- An overview of Cascading Style Sheets (TechNote 15230)
- How to create an external Cascading Style Sheet(TechNote 12922)
- Drew McLellan 's Five steps to more professional pages with Dreamweaver MX
System Requirements
- Dreamweaver MX Try | Buy
- Contribute Try | Buy
- Internet Explorer 5 or later, Netscape 6 or later, Mozilla/Firebird
- JavaScript enabled
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:
- Open the template in Dreamweaver and switch to Code View.
-
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
onloadevent to run a function, just add semicolon and the call totoc():<body onload="somefunc() ;toc() ">
- Immediately before the primary
TemplateBeginEditabletag, add an empty DIV tag with an ID oftoc:
<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.
- If the template doesn't already link to an external file, add the following somewhere within its HEAD section:
- Save the page template.
- If you don't already have a scripts file, create one by selecting File > New > General > Basic Page > JavaScript.
-
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';}}} - Save the scripts file, using the path and name specified in the template.
<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.
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 IDmain. Your own template likely uses a different ID for the containing element—or might not use an ID at all. Changemainto match whatever ID you've used; if the content container doesn't have an ID, changedocument.getElementById('main').childNodestodocument.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
jumpand 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.
Figure 2. The Table of Contents before setting up the style sheet
- If you haven't already done so, follow the instructions in TechNote 12922 to set up an external style sheet.
-
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;} - Edit to suit your site's appearance. For example, if you don't like
the bullets, change
list-style-type:disctolist-style-type:nonein the#toccnt ul lidefinition. The reason for havingmmhide_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.) - Save the style sheet.
- Check in the site.
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.