Building a Table of Contents in JavaScript
Aug 19, 2014
4 min read
Javascript
All-Topics

by Qwertie
Contributor
So you're publishing a long document online and don't have an easy mechanism to automatically add a table of contents on the server side? Well with JavaScript, you enslave the web browser to do it instead! This TOC generator...
- needs no jQuery or other third-party library
- creates links to each heading, adding an
id
attribute to each heading that doesn't already have one - can support pages that contain multiple content areas with multiple unrelated tables of contents
Usage
After installing the code below, call addTOC(contentElement, before, tocClass)
, where
contentElement
is an element that contains the document or blog post for which you want a table of contents, andbefore
is an immediate child ofcontentElement
; the table of contents will be inserted as a child ofcontentElement
, just beforebefore
.before
is optional and if unspecified,contentElement.firstChild
is used.tocClass
is the CSS class of the table-of-contents blog (default value: "sidebox
")
See example below.
The Code
Insert this code at the bottom of any page in which you want to add a TOC (before </body>
).
<script>// Add table of contents! <![CDATA[ function $get(selector) { return document.querySelector(selector); }; function $all(selector) { return Array.prototype.slice.call(document.querySelectorAll(selector)); } function buildTOC_ul(selector) { var levels=[document.createElement("ul"),null,null]; levels[0].style var lvl=0, c=0; if (!selector) selector = "h2, h3, h4"; $all(selector).forEach(function(el) { if (!el.id) el.id='section_'+ ++c; var newLvl=(el.tagName=="H2"?0:el.tagName=="H3"?1:2); for (;lvl<newLvl;lvl++) levels[lvl].appendChild(levels[lvl+1]=document.createElement("ul")); lvl=newLvl; var li=document.createElement('li'); li.innerHTML="<a href='#"+el.id+"'></a>"; li.firstChild.innerHTML=el.innerHTML; levels[lvl].appendChild(li); }); return levels[0]; } function addTOC(contentElement, before, tocClass) { if (before===undefined) before=contentElement.firstChild; var prefix = ""; var c = contentElement.className; if (c) prefix="." + (c.indexOf(" ")==-1?c:c.substr(0,c.indexOf(" "))) + " "; var selector = prefix+"h2, "+prefix+"h3, "+prefix+"h4"; var toc=document.createElement("div"); toc.className=tocClass||"sidebox"; toc.appendChild(document.createTextNode("Contents")); toc.appendChild(buildTOC_ul(selector)); contentElement.insertBefore(toc, before); } // ========================= // TODO: CALL addTOC() HERE! // ========================= //]]> </script>
Example
The simplest thing you can do is to call addTOC(document.body)
, but that only works on very simple pages. Generally the page has a main content area; if you assign an id="content"
to this content area, you can add the table of contents at the top like this:
addTOC($get("#content"));
(See definition of $get
in the code above.) Here's how I'm calling addToc()
on my blog:
var _post_ = $get("#post") || $get("#content"); addTOC(_post_, _post_.firstChild.nextSibling.nextSibling);
This allows the content area to have _post_.firstChild.nextSibling.nextSibling
skip past the initial heading so that the table of contents is inserted underneath the heading.
Sidebar Style
The default class is sidebox
, and if you insert the following CSS on your page, you will get a table of contents that floats beside the content, just as you see on this page.
<style> .sidebox { border: 1px dotted rgb(127, 127, 127); padding: 4px 3px 4px 6px; /* top right bottom left */ min-width: 100px ! important; float: right ! important; font-size: 90%; margin-top: 1px; margin-bottom: 1px; margin-left: 6px; visibility: visible; max-width: 50%; width: 35%; } .sidebox ul { padding:0em 0em 0em 1.3em; margin:0em; }//TRBL </style>
Usage with GitHub Pages / Jekyll
If you're publishing with Jekyll, I suggest adding the above code to the bottom of /_layouts/default.html (and any other layouts you might use that might need a TOC, as long as they do not import default.html using a layout: default
option), just before </body>
, surrounded by a test like this:
{% if page.toc %} <script> ... ... var _post_ = $get("#post")||$get("#content"); addTOC(_post_, _post_.firstChild.nextSibling.nextSibling); </script> {% endif %}
This way, Jekyll only adds the TOC code to a page if the front-matter contains a toc: true
option.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)