- Web development
Table of contents in Javascript
In this post, I will provide you with a javascript code sample that transforms headings in a page into a structured table of contents with anchor links.
When writing a long structured article, it is always handy for your readers having a table of contents that helps them navigate through it. I composed a javascript code sample that creates such a table of contents on the fly for you. It iterates through the article headings and
- creates anchors for each of them and
- renders a structured unordered list with anchor links.
Here is a link to a working demo on Codepen.
How to make it work?
- In your markup, create an empty ul element (i.e.
<ul class="table-of-contents"></ul>
) that will stand for the table of contents. - Copy the Javascript code from the pen or code block below into your project. I wrote the code in ES6 so that you may need to transpile it into ES5 in case you need to use the functionality on the client side. Babel could be a great choice.
- When calling the
tableOfContents
function, pass the settings object with selectors for- the headings you want to be part of the table of contents (
headingsSelector
property) and - the table of contents wrapper you've created in the first step (
wrapperSelector
property).
- the headings you want to be part of the table of contents (
The code
const tableOfContents = (settings) => {
// Helper function that iterates all headings and returns the highest level.
// For example, if array of h2, h3 and h4 is passed, the function returns 2.
const getHighestHIndex = headings => {
let indexes = Array.from(headings).map(item => {
return parseInt(item.tagName.replace('H', ''));
});
return indexes.reduce((a, b) => {
return Math.min(a, b);
});
};
// Iterates passed headings, gets their inner HTML and transforms it into id
const createAnchors = (settings) => {
let headings = document.querySelectorAll(settings.headingsSelector);
headings.forEach(item => {
let anchorName = item.innerHTML.toLowerCase().replace(/(<([^>]+)>)/ig,'').replace(/\W/g,'-');
item.setAttribute('id', anchorName);
});
};
// Iterates passed headings and creates unordered list with anchor links reflecting heading levels
const createTableOfContents = (settings) => {
let headings = document.querySelectorAll(settings.headingsSelector);
let tableOfContentsWrapper = document.querySelector(settings.wrapperSelector);
let tableOfContents = '';
let prevHeadingLevel = getHighestHIndex(headings);
headings.forEach(item => {
let headingLevel = parseInt(item.tagName.replace('H', ''));
if (prevHeadingLevel > headingLevel) {
tableOfContents += '</ul>';
}
if (prevHeadingLevel < headingLevel) {
tableOfContents += '<ul>';
}
tableOfContents += `<li><a href="#${item.getAttribute('id')}">${item.innerHTML}</a></li>`;
prevHeadingLevel = headingLevel;
});
tableOfContentsWrapper.innerHTML = tableOfContents;
};
createAnchors(settings);
createTableOfContents(settings);
};
// Init table of contents
tableOfContents({
headingsSelector: 'h2:not(.do-not-render), h3, h4',
wrapperSelector: '.table-of-contents'
});
About the author
Milan Lund is a Full-Stack Web Developer, Solution Architect, and Consultant for Xperience by Kentico projects, working as both a freelancer and a contractor. He specializes in building and maintaining websites using Xperience by Kentico. Milan writes articles based on his project experiences to assist both his future self and other developers.
Find out more