Dynamic Section Numbers and Table of Contents with jQuery

Posted by Tom on 2009-10-25 16:18

At work I'm thinking of moving our specs and other documentation over to HTML. We are, after all, well acquianted with the Tubular Interwebs.

One of the things that I miss whenever it's not present in these kinds of docs are section numbers and a table of contents, but maintaining them can be a pain in the arse. You add a new section and then all your subsequent sections needs to shuffle down to accomodate it. What we need is the equivalent of BBC Basic's RENUMBER command, and I never thought I would say that ever again.

But why not generate it dynamically? We have the tools, and we have the talent. Also, we have jQuery.

function NumberHeadings(list, parent, prefix)
    if (list.length == 0)
        return '';

    var menu = '';
    var level = parseInt(list[0].tagName.substr(1));
    var index = 0;
    menu += '<ul>\n';
    list.each(function() {
            var current = $(this);

            if (parent != '' && current.prevAll('h' + (level - 1)).eq(0).html() != parent)
                return '';
            var number = prefix  + (++ index) + '.';
            var content = number + ' ' + current.html();
            current.before('<a name="' + number + '"></a>');
            menu += '<li><a href="#' + number + '">' + current.html() + '</a></li>\n';
            menu += NumberHeadings(current.nextAll('h' + (level + 1)), content, number);
    menu += '</ul>\n';
    return menu;

This function takes as it's input a list of h tags (h2, h3, whatever), the contents of its 'parent' heading tag and the prefix of the numerical prefix of the new section. But you can safely ignore most of that. The easiest way to use it is like this:

$(document).ready(function() {
        $('#Menu').html(NumberHeadings($('h2'), '', ''));

Now as long as you make sure your headings use H tags and are nested correctly like you should be doing anyway, you slacker then that script will number all of your headings and provide a table of contents with anchor links to each section. The only other caveat is that the prevAll function must return items in the reverse of the order that they appear in the DOM (as it does currently) or things will break.

And here it is working.

In a largely irrelevant asside, I'm sure the first version of this that I wrote was neater, but since that version only existed on my laptop, which I left on the train last week and has never been handed in, we'll never know. Also there's some tossbag wandering around London with my laptop and, while I take some solace in the fact that it had Vista on it, I hope their nipples drop off.