How to add a Table of Contents automatically (without jQuery)
When I published the Professional Australian Glossary I decided to add a table of contents. It was a large article.
But I thought… there’s got to be a way of automating this. And if there isn’t, I’ll build one!
I pieced this together from around the web, but had to redo it a bit to make it work with updated versions of Ghost.
Table of Contents in Blogs — Summary
Tables of contents are almost mandatory to making a blog post easier to read. There are plugins for WordPress, but no easy way to do it on a Ghost blog.
Sub-heading
This is a lower-level nested block, to make sure it works with h3 and below headings.
How to install
Put this code at the bottom in an HTML block. This looks through the code and finds all the headings (tagged with <h2>
or <h3>
… did you use headings?) and creates the contents.
Here’s the code below. It’s in pure JavaScript, not using jquery.
I wrote this version because I felt like loading extra resources was lazy, and not all themes use it (and I wouldn’t want to rely on whatever version they were using anyway).
Secondly, this is pretty basic DOM manipulation and I thought: why not just do it in pure JS anyway? ES6 is amazing, anyway.
<script>
document.addEventListener('DOMContentLoaded', function () {
var contentsTitle = "Contents";
var ToC = "<h2>" + contentsTitle + "</h2>";
ToC += '<nav role="navigation" class="table-of-contents"><ul>';
// Select the <article> element
var article = document.querySelector('article');
// Find the first <h2> element within the <article>
var firstH2 = article.querySelector('h2');
// Create a container for the table of contents
var tocContainer = document.createElement("div");
tocContainer.id = 'dynamictocnative';
// If there's an <h2> element, insert the ToC container before it
if (firstH2) {
firstH2.parentNode.insertBefore(tocContainer, firstH2);
} else if (article) {
// If there's no <h2>, but an <article>, append the ToC container to it
article.appendChild(tocContainer);
}
// Find all <h2> and <h3> elements within the <article>
var headers = article.querySelectorAll('h2,h3');
headers.forEach(function(el, index) {
var title = el.textContent;
var link = '#' + el.id;
if (el.nodeName === 'H2') {
ToC += '<li><a href="' + link + '">' + title + '</a></li>';
} else if (el.nodeName === "H3") {
ToC += '<li style="margin-left:2em"><a href="' + link + '">' + title + '</a></li>';
}
});
ToC += '</ul></nav>';
ToC += '<style>#dynamictocnative { width: 100%; }</style>';
// Insert the table of contents into the container
var tocDiv = document.getElementById('dynamictocnative');
if (tocDiv) {
tocDiv.innerHTML = ToC;
}
});
</script>
How it works
Here’s how this table of contents script works, in a nutshell.
- We create a new
<div>
element with the id ‘dynamictocnative’ to serve as the container for the table of contents. - We then find the first
<h2>
element within the<article>
element. - If an
<h2>
element is found, we useinsertBefore
to insert the table of contents container before this<h2>
element. If no<h2>
is found but there’s an<article>
element, we append the ToC container to the<article>
element instead. - We then proceed as before to create the table of contents based on the
<h2>
and<h3>
elements within the<article>
. - Finally, we insert the table of contents into the ‘dynamictocnative’ container.
This script assumes that there’s at least one <article>
element and uses the first one it finds. If your page structure is different, you may need to adjust the selectors. But most modern CMS designs use <article>.
Next steps
It might be nice to make this a separate js plugin so you can embed it on a website. But for now, this is just a proof of concept, anyway.
If you like, you can put it in a js file on your local server, and include it.