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.
[js]
<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');
y.id='toc';
mytablebody = document.createElement('TBODY');
myrow = document.createElement('TR');
mycell = document.createElement('TD');
myrow.appendChild(mycell);
mytablebody.appendChild(myrow);
y.appendChild(mytablebody);
// create the two title strings so we can switch between the two later via the id
var a = mycell.appendChild(document.createElement('span'));
a.id = 'toc_hide';
a.innerHTML = '<b>Contents</b> <small>[<a href="" onclick="javascript:showhideTOC();return false;">hide</a>]</small>';
a.style.textAlign='center';
var a = mycell.appendChild(document.createElement('span'));
a.id = 'toc_show';
a.style.display='none'
a.innerHTML = '<b>Contents</b> <small>[<a href="" onclick="javascript:showhideTOC();return false;">show</a>]</small>';
a.style.textAlign='center';
var z = mycell.appendChild(document.createElement('div'));
// set the id so we can show/hide this div block later
z.id ='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
{
toBeTOCced.push(x[i])
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++)
{counterArray[i]=0;}
// 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.
++counterArray[level];
tmp.href = '#header_' + i;
tmp2.id = '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++)
{counterArray[j]=0;}
}
if (tmp_indent > 10)
tmp.style.paddingLeft=tmp_indent -10+'px';
// if NOT h1 tag, add to toc
if (!skip_first)
{
z.appendChild(tmp);
// put in a br tag after the link
var tmp_br = document.createElement('br');
z.appendChild(tmp_br);
}
else // else, act as if this item was never created.
{
skip_first=false;
// 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
--level;
--counterArray[level];
}
if (toBeTOCced[i].nodeName == 'H1')
{
tmp.innerHTML = 'Top';
tmp.href = '#top';
tmp2.id = 'top';
}
// put the a name tag right before the heading
toBeTOCced[i].parentNode.insertBefore(tmp2,toBeTOCced[i]);
}
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';
}
else
{
document.getElementById('toc_show').style.display = 'none';
document.getElementById('toc_hide').style.display = 'inline';
}
}
// now attache the createTOC() to the onload
window.onload = createTOC;
</script>
[/js]
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).
<style>
/* table of contents stuff */
#toc {
padding:5px;
background-color:#f0f0f0;
border:1px solid #aaaaaa;
margin-right:auto;
text-align:center;
}
#toc div {text-align:left;}
</style>
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.
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:
root
|-- 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.
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 =)