home projects speaker

How to create a table of contents with javascript and prototype.js

Well, if you write long posts every now and then and you want your users to access the information in an easy way instead of splitting up the post in several pages, this is a very nice solution to this issue

Note

You might want to add some styling to the levels in the table of contents, so that the user can differentiate between levels.

Step one: Add html element to the page

html
<div id="toc" style="display:none;">
  <strong>
    Table of Contents
  </strong>
</div>

Step two: Add the javascript

javascript
const createTableOfContents = (postContainerSelector, levels = 4) => {
  const tableOfContentsElement = document.getElementById('toc');

  if (!tableOfContentsElement) {
    return; // Exit early if the element doesn't exist
  }

  const headingLevels = Array.from({ length: levels }, (_, i) => i + 1);
  const selector = headingLevels.map(h => `${postContainerSelector} h${h}`).join(',');
  const headingElements = document.querySelectorAll(selector);

  if (headingElements.length === 0) {
    return; // Exit if no heading elements found
  }

  const createUniqueId = (element, index) => {
    let elementID = element.getAttribute('id');

    if (!elementID) {
      elementID = String(element.textContent)
        .normalize('NFKD')
        .replace(/[\u0300-\u036f]/g, '')
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9 -]/g, '')
        .replace(/\s+/g, '_')
        .replace(/-+/g, '_');

      element.setAttribute('id', `${elementID}_${index + 1}`);
    }

    return elementID;
  };

  const listElement = document.createElement('ol');

  headingElements.forEach((element, index) => {
    const elementID = createUniqueId(element, index);

    const tableOfContentsItemElement = document.createElement('li');
    const tableOfContentsActionElement = document.createElement('a');

    tableOfContentsActionElement.setAttribute('href', `#${elementID}`);
    tableOfContentsActionElement.textContent = element.textContent;

    tableOfContentsItemElement.appendChild(tableOfContentsActionElement);
    listElement.appendChild(tableOfContentsItemElement);
  });
  
  tableOfContentsElement.appendChild(listElement);

  tableOfContentsElement.style.display = 'block';
};

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', () => {createTableOfContents('article')});
} else {
  createTableOfContents('article');
}

That's practically it! Enjoy.


About the author

Hi! My name is Alexander, and I am a creative frontender, specializing in UX, accessibility, universal design, frontend-architecture, node and design systems. I am passionate with open source projects and love to dabble with new emerging technologies related to frontend. With over 24 years of frontend experience, I have earned the right to be called a veteran. I am a lover of life, technologist at heart. If I am not coding, I am cooking and I love whisky and cigars. Oh, and coffee, I LOVE coffee!

If you want to know more about me, here is some links you might want to check out: GitHub, Instagram, Twitter, LinkedIn, CodePen, Slides.com, npm,

Speaker

I am also an avid speaker on several topics! Check out some of the things I speak about, and contact me if you are interested in having me at your next event!