2012-09-27

Creating a table of contents from headings in a page with JavaScript

Here is a HOW-TO for dynamically creating a linked table of contents from the headings of a webpage. This really owes the core code to Janko's blog.

Introduction

Writing a table of contents to jump to each heading on a longer page manually is excise, boring work. Solution: have the page create its own table of contents, by using JavaScript to inspect the document structure.

This is especially important on Blogger, because it messes up your HTML if you accidentially or for more convenient editing switch the editor mode from "HTML" to "Compose". It will turn all your paragraph tags into hard line breaks and vomit div tags all over the place. It'll massacre HTML or JavaScript code displays such as the ones on this page. The worst effect however is on name anchors, where it actually destroys their functionality, by inserting href attributes. Strong recommendation: if you are on Blogger, stick to HTML mode.

Setting up

First, we insert an element into the page that acts as the placeholder for the table of contents. This might also be done dynamically, if all the pages have the same structure, but for now I'll just do this manually, so I can put the table where I want it:
<div id="toc"></div>
Second, because blogger uses heading tags also in gadgets and other places, we wrap the actual page content in a second div element, so we can limit ourselves to headings that are part of the actual post. Otherwise, all kinds of things will be listed. If you are not on Blogger, you can omit this step. The wireframe for this page looks as follows
<div id="content">
    <h2>Setting Up</h2>
        <h3>Creating the table</h3>
            <h4>jQuery</h4>
            <h4>toContent.js</h4>
        <h3>Formating the table</h3>
</div>
Third, we have to include the JavaScript that transforms this into a table of contents. Put this code at the bottom of the page body, so all the elements it refers to have already loaded. Also, it does not further delay the loading of the page. I put the script into a file, to make it possible to centrally change the code for many pages. There are also ways to do more dynamic loading of scripts, see for example here.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
<script src="http://www.schacherer.de/js/tocontent.js" type="text/javascript"></script>
To be straight, the pages on Blogger are already crammed with dynamically loaded scripts to the hilt by Blogger itself, so where you put this should not make much of a difference in speed, and the way the script is written it will execute only once the entire document is ready, anyways, so you could put this even in the header. But to put it into the header on Blogger would mean to modify the fundamental template code, and it then would show up on all pages, not just those were you need it.

Creating the Table

Now lets talk about the scripts that actually do the work.

jQuery

The first script just loads the jquery API, which allows you to manipulate and search page elements much more concisely than plain Javascript.

tocontent.js

This script expands your toc div into a hyperlinked table.
$(document).ready(function() {
    $("#toc").append('<p>Contents');        
    $("#content").find("h1, h2, h3, h4").each(function(i) {
        var current = $(this);
        current.attr("id", "title" + i);
        $("#toc").append("<a id='link" + i + 
                         " href='#title" + i + 
                         "' title='" + current.attr("tagName") + "'>" +    
                         current.html() + "<\/a><\/br>");
    });
    $("#toc").append("<\/p>");
});
You can modify this script to include other elements, or for example not display the level four headings, by modifying the element list in the find clause. If you do not need to encapsulate your tags in a content section, just delete ("#content").find. The title attribute is used later in formatting the table with CSS.

A feature, or rather bug about JavaScript is that it may insert semicolons into your code when you have broken long statements across multiple lines. You have to close statements with a semicolon if the following one starts with an opening parenthesis, square bracket or one of the arithmetic operator tokens. This is not an issue if you close your statements with semicolons anyways. What is an issue is that you may not insert a line break to format code for clarity if your statement continues, before or after: return, break, continue, throw, ++ or --, as JavaScript will insert a semicolon and break up your statement.

Formating the table

To format the table, you have many options with CSS, I use the following to get an indented list that is compact:
#toc a { display:inline-block; }
#toc a[title=H1] { text-indent:0em; font-size:12pt;}
#toc a[title=H2] { text-indent:1em; font-size:11pt;}
#toc a[title=H3] { text-indent:2em; font-size:10pt;}
#toc a[title=H4] { text-indent:3em; font-size:9pt;}
You can put this into a CSS file to load in the header, or in Blogger, you can go to the advanced layout options, and select "Custom CSS" and paste this in to be included with every page. That's it. Have fun with dynamical TOCs.

No comments:

Post a Comment