Skip to content

Instantly share code, notes, and snippets.

@paceaux
Created March 16, 2022 16:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paceaux/12ce437e0f7ab1d5a9326d92e68710c0 to your computer and use it in GitHub Desktop.
Save paceaux/12ce437e0f7ab1d5a9326d92e68710c0 to your computer and use it in GitHub Desktop.
A class for generating a table of contents on a page.
class TableOfContents {
static listContainerClass = 'articleTOC__list';
static listItemClass = 'articleTOC__item';
static listLinkClass = 'articleTOC__link';
static modifierSeparator = '--';
/*
* @description Gets a list of all title elements having an ID in a given container
* @param {HTMLElement} container - An element containing title elements (h1, h2, h3)
*
* @returns {Nodelist}
*/
static getTitleElements(container) {
return container.querySelectorAll('h2[id], h3[id], h4[id], h5[id], h6[id]');
}
/*
* @description Returns a <ul>
*
* @returns {HTMLElement} a UL element with the classname articleTOC__list
*/
static getListElement(listElClass = TableOfContents.listContainerClass) {
const listEl = document.createElement('ul');
listEl.classList.add(listElClass);
return listEl;
}
/*
* @description determines the class name that should be applied to a node
* @param {HTMLElement} titleNode - the html element that is an h2-6
* @param {string} [baseName] - the base classname
* @param {string} [modifierSeparator] - the thing that separates the base name and the modified part of the name
*
* @returns {string} the full class to be used on the node as `{baseName}{modifierSeparator}{modifierName}
*/
static getItemModifierClass(titleNode, baseName = TableOfContents.listItemClass, modifierSeparator = TableOfContents.modifierSeparator) {
if (!titleNode) return;
const { tagName} = titleNode;
let modifierName = 'biggest';
switch (tagName) {
case 'H3':
modifierName = 'bigger';
break;
case 'H4':
modifierName = 'big';
break;
case 'H5':
modifierName = 'base';
break;
case 'H6':
modifierName = 'small';
default:
break;
}
return `${baseName}${modifierSeparator}${modifierName}`;
}
/*
* @description generates an <li> with an appropriate class name and a nested <a>
* @param {HTMLElement} titleNode The H1-5 element with an id that needs a link to it
* @param {string} [itemElClass = TableofContents.listItemClass]
* @param {string} [linkElClass]
* @param {modifierSeparator}
*
* @returns {HTMLElement} <li><a>;
*/
static getItemElement(
titleNode,
itemElClass=TableOfContents.listItemClass,
linkElClass=TableOfContents.listLinkClass,
modifierSeparator = TableOfContents.modifierSeparator
) {
const listEl = document.createElement('li');
const linkEl = document.createElement('a');
const href = `#${titleNode.id}`;
const title = titleNode.innerText;
listEl.classList.add(itemElClass);
listEl.classList.add(TableOfContents.getItemModifierClass(titleNode, itemElClass, modifierSeparator))
linkEl.classList.add(linkElClass);
linkEl.setAttribute('href', href);
linkEl.innerText = title;
listEl.appendChild(linkEl);
return listEl;
}
tableOfContents = null;
/*
* @param {HTMLElement} [articleContainer] an HTML element with h1-5 that have ids on them
* @param {HTMLElement} [tocContainer]
* @param {object} [configuration]
*
* @example
* const tocArticle = document.querySelector('.entry.shouldHaveTOC');
* const tocWrapper = document.querySelector('.articleTOC');
*
* const tableofContents = new TableOfContents(tocArticle, tocWrapper, {listContainerClass: 'foo', listItemClass: 'bar', listLinkClass: 'baz'});
*/
constructor(
articleContainer,
tocContainer,
{
listContainerClass = TableOfContents.listContainerClass,
listItemClass = TableOfContents.listItemClass,
listLinkClass = TableOfContents.listLinkClass,
modifierSeparator = TableOfContents.modifierSeparator,
} = {}
) {
this.listContainerClass = listContainerClass;
this.listItemClass = listItemClass;
this.listLinkClass = listLinkClass;
this.modifierSeparator = modifierSeparator;
this.articleContainer = articleContainer;
this.tocContainer = tocContainer;
this.generateTOC(this.articleContainer, this.tocContainer);
}
/*
* @description generates a <ul> element with <li><a> nested inside
* @param {NodeList} titleNodes The H1-5 elements with ids that need
*
* @returns {HTMLElement} <ul><li><a>;
*/
getTOCElement(titleNodes) {
const listEl = TableOfContents.getListElement(this.listContainerClass, this.listContainerClass);
[...titleNodes].forEach((titleNode) => {
const itemEl = TableOfContents.getItemElement(titleNode, this.listItemClass, this.listLinkClass, this.modifierSeparator);
listEl.appendChild(itemEl);
});
return listEl;
}
/*
* @description Generates a table of contents
* @param {HTMLElement} [articleContainer] an HTML element with h1-5 that have ids on them
* @param {HTMLElement} [tocContainer]
*/
generateTOC(articleContainer = this.articleContainer, tocContainer = this.tocContainer) {
if (!articleContainer || !tocContainer) return;
try {
const titles = TableOfContents.getTitleElements(articleContainer);
const tocEl = this.getTOCElement(titles);
console.log(tocEl);
this.tableOfContents = tocEl;
tocContainer.appendChild(tocEl);
} catch (generateTOCError) {
console.error(generateTOCError);
}
}
}
const tocArticle = document.querySelector('.entry.shouldHaveTOC');
const tocWrapper = document.querySelector('.articleTOC');
const tableofContents = new TableOfContents(tocArticle, tocWrapper, {listContainerClass: 'foo', listItemClass: 'bar', listLinkClass: 'baz'});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment