The Holy Grail of TOC Scripts

by @jehiah on 2004-09-25 13:02
Filed under: All, HTML, Javascript

I have attempted to improve upon Peter-Paul Koch's Table Of Contents script which (thankfully) he does a good job of describing how it works so I don't need to do that here. My TOC source code is also available.

Read on for an overview of my changes, important notes, and the code you need.

TOC Code


    <script language="javascript">
    function createTOC()
        // to do : gracefully handle if h2 is top level id and not h1

        // configuration options
        var page_block_id = 'mainContent'; // this is the id which contains our h1's etc
        var toc_page_position =-1; // used later to remember where in the page to put the final TOC
        var top_level ="H1";// default top level.. shouldn't matter what is here it is set at line 50 anyway
        var skip_first = true;

        var w = document.getElementById(page_block_id);
        var x = w.childNodes;
        //build our table tbody tr td - structure
        y = document.createElement('table');'toc';
        mytablebody = document.createElement('TBODY');
        myrow = document.createElement('TR');
        mycell = document.createElement('TD');

        // create the two title strings so we can switch between the two later via the id
        var a = mycell.appendChild(document.createElement('span')); = 'toc_hide';
        a.innerHTML = '<b>Contents</b> <small>[<a href="" onclick="javascript:showhideTOC();return false;">hide</a>]</small>';'center';
        var a = mycell.appendChild(document.createElement('span')); = 'toc_show';'none'

        a.innerHTML = '<b>Contents</b> <small>[<a href="" onclick="javascript:showhideTOC();return false;">show</a>]</small>';'center';

        var z = mycell.appendChild(document.createElement('div'));

        // set the id so we can show/hide this div block later ='toc_contents';

        var toBeTOCced = new Array();
        for (var i=0;i<x.length;i++)
            if (x[i].nodeName.indexOf('H') != -1 && x[i].nodeName != "HR") // added check for hr tags
                if (toc_page_position == -1)
                    // get the first one.. don't care which level it is
                    toc_page_position = 0; 
                    // we should also remember which level is top of the page
                    top_level = x[i].nodeName;
                else if (toc_page_position == 0)
                    toc_page_position = i-1; // we want the toc before the first subheading
        // array to store numeric toc prefixes
        var counterArray = new Array();
        for (var i=0;i<=7;i++)

        // quit if it is a small toc
        if (toBeTOCced.length <= 2) return;

        for (var i=0;i<toBeTOCced.length;i++)
            // put the link item in the toc
            var tmp_indent =0;
            // tmp is link in toc
            var tmp = document.createElement('a');
            // tmp2 is name link for this heading ancor
            var tmp2 = document.createElement('a');

            // we need to prefix with a number
            var level = toBeTOCced[i].nodeName.charAt(1);
            // we need to put in the upper numbers ie: 4.2 etc.

            tmp.href = '#header_' + i;
   = 'header_' + i;

            for (var j=2;j<=level;j++)
                if (counterArray[j] > 0)
                    tmp.innerHTML += counterArray[j]+'.' // add numbering before this toc entry
                    tmp_indent +=10;
            tmp.innerHTML +=  ' ' + toBeTOCced[i].innerHTML;

            // if counterArray[+1] != 1 .. reset it and all the above
            level++; // counterArray[level+1] was giving me issues... stupid javascript
            if (counterArray[level] > 0) // if we dropped back down, clear out the upper numbers
                for (var j=level; j < 7; j++)

            if (tmp_indent > 10)

            // if NOT h1 tag, add to toc
            if (!skip_first)
                // put in a br tag after the link
                var tmp_br = document.createElement('br');
            else // else, act as if this item was never created.
                // this is so the toc prefixes stay proper if the page starts with a h2 instead of a h1... we just reset the first heading to 0

            if (toBeTOCced[i].nodeName == 'H1')
                tmp.innerHTML = 'Top';
                tmp.href = '#top';
       = 'top';
            // put the a name tag right before the heading
        w.insertBefore(y,w.childNodes[toc_page_position+2]); // why is this +2 and not +1?

    var TOCstate = 'block';

    function showhideTOC()
        TOCstate = (TOCstate == 'none') ? 'block' : 'none';
        // flip the toc contents
        document.getElementById('toc_contents').style.display = TOCstate;
        // now flip the headings
        if (TOCstate == 'none')
            document.getElementById('toc_show').style.display = 'inline';
            document.getElementById('toc_hide').style.display = 'none';
            document.getElementById('toc_show').style.display = 'none';
            document.getElementById('toc_hide').style.display = 'inline';

    // now attache the createTOC() to the onload
    window.onload = createTOC;



Wikipedia Styling

Many kudos to Wikipedia for their styling of contents boxes. While they are not dynamically generated by the browser, I believe they are still dynamically generated on page edit. There are a few changes I made to the script to allow it to follow wikipedia styling in a fairly cross-browser implementation.

The biggest change involves using a table structure instead of a div tag so that it does nto bump out to the full width of the containing element (which is only needed because Internet Explorer does not understand the display:table; property for div tags).

/* table of contents stuff */
#toc {
    border:1px solid #aaaaaa;
#toc div {text-align:left;}


Auto Numbering

I believe it is simply easier to follow a table of contents that is auto-numbered. I may eventually automagically add the numbering to the actuall page headings, but for now this works.

Headings from div tag

Peter-Paul's script made a table of contents for all headings the fell under the root <body> tag of the page, however very few sites have actuall page content directly under the root. Most sites follow a html structure closer to this:

|-- contentdiv
|   |-- page
|   |-- paragraph
|   |-- paragraph2
|   `-- subheading
|-- headerdiv
|   |-- links
|   `-- title
`-- leftnavdiv
    |-- link
    |-- link2
    `-- section title

In fact, the only case I believe this doesn't really happen is when your site is using frames (like Peter-Paul's). So I changed the script so it pulls all the headings from a specific div instead of the root <html> tag.

Onload Event Handler

I added a slice of code to automatically run the toc generator by onload event.


the friggin anchor links don't work when you open them from a bookmark etc.. only when they are pulled up from within the page. This might be a good thing though because it adds the usability of a visitor always landing at the top of the page where they have your site navigation (ie: they don't automatically get lost somewhere towards the bottom of the page). The anchors don't work because they are not added to the page till after the onload event fires. If someone wants to write some code to run in the onload event that would check to see if a visitor is trying to go to a specific part of the page, and take them there.. I'd be happy to accept it =)

Subscribe via RSS ı Email
© 2016 - Jehiah Czebotar