I have headers in <h1>
through <h6>
tags. Is there a way that I can use JavaScript to generate a table of contents for the contents that serves as anchor tags as well?
I would like the output to be something like:
<ol>
<li>Header 1</li>
<li>Header 1</li>
<li>Header 2</li>
<li>Header 3</li>
</ol>
I am not currently using a JavaScript framework, but I don't see why I couldn't use one.
I am also looking for something done, since I'm guessing this is a common problem, but if not, a starting point to roll my own would be good.
The HTML source code for the TOC (table of contents) will be inside a <template> tag. The code inside <template> doesn't get rendered until it's added to the document by JavaScript. Our TOC will have placeholders, held in <slot> tags, for all the headings and subheadings found in the document.
Here is the code that generates the table of contents: This code iterates through all h2 and h3 elements using the jQuery call $ ("h2, h3").each (...) . For each h2 or h3 element, an <a name="index"></a> is inserted in the HTML document just before it.
Just add the code below to your scripts and call TableOfContents (container, output); on load, where container is the class or id of your content element and output is the class or id of the TOC element. Default values are '#contents' and '#toc', respectively.
You just want the table of contents to contain the <h2>, <h3> elements in the page. Here is an example of an HTML page like the one mentioned above: Notice the div with the id tocDiv, and the ul inside with the id tocList .
The first thing we do is to get the <div>element that we are going to use for the table of contents from its ID, “Toc”. We then create a header, an <h2>, give it the text “Table of Contents”and append it to the ToC <div>.
I couldn't resist putting together a quick implementation.
Add the following script anywhere on your page:
window.onload = function () { var toc = ""; var level = 0; document.getElementById("contents").innerHTML = document.getElementById("contents").innerHTML.replace( /<h([\d])>([^<]+)<\/h([\d])>/gi, function (str, openLevel, titleText, closeLevel) { if (openLevel != closeLevel) { return str; } if (openLevel > level) { toc += (new Array(openLevel - level + 1)).join("<ul>"); } else if (openLevel < level) { toc += (new Array(level - openLevel + 1)).join("</ul>"); } level = parseInt(openLevel); var anchor = titleText.replace(/ /g, "_"); toc += "<li><a href=\"#" + anchor + "\">" + titleText + "</a></li>"; return "<h" + openLevel + "><a name=\"" + anchor + "\">" + titleText + "</a></h" + closeLevel + ">"; } ); if (level) { toc += (new Array(level + 1)).join("</ul>"); } document.getElementById("toc").innerHTML += toc; };
Your page should be structured something like this:
<body> <div id="toc"> <h3>Table of Contents</h3> </div> <hr/> <div id="contents"> <h1>Fruits</h1> <h2>Red Fruits</h2> <h3>Apple</h3> <h3>Raspberry</h3> <h2>Orange Fruits</h2> <h3>Orange</h3> <h3>Tangerine</h3> <h1>Vegetables</h1> <h2>Vegetables Which Are Actually Fruits</h2> <h3>Tomato</h3> <h3>Eggplant</h3> </div> </body>
You can see it in action at https://codepen.io/scheinercc/pen/KEowRK (old link: http://magnetiq.com/exports/toc.htm (Works in IE, FF, Safari, Opera))
Here's a great script to do this:
https://github.com/matthewkastor/html-table-of-contents/wiki
To use it:
Add this tag:
<script src="./node_modules/html-table-of-contents/src/html-table-of-contents.js" type="text/javascript">
Call the function, such as in your body's onload attribute:
<body onload="htmlTableOfContents();">
Here is the definition of the method that does the generation:
/**
* Generates a table of contents for your document based on the headings
* present. Anchors are injected into the document and the
* entries in the table of contents are linked to them. The table of
* contents will be generated inside of the first element with the id `toc`.
* @param {HTMLDOMDocument} documentRef Optional A reference to the document
* object. Defaults to `document`.
* @author Matthew Christopher Kastor-Inare III
* @version 20130726
* @example
* // call this after the page has loaded
* htmlTableOfContents();
*/
function htmlTableOfContents (documentRef) {
var documentRef = documentRef || document;
var toc = documentRef.getElementById('toc');
var headings = [].slice.call(documentRef.body.querySelectorAll('h1, h2, h3, h4, h5, h6'));
headings.forEach(function (heading, index) {
var anchor = documentRef.createElement('a');
anchor.setAttribute('name', 'toc' + index);
anchor.setAttribute('id', 'toc' + index);
var link = documentRef.createElement('a');
link.setAttribute('href', '#toc' + index);
link.textContent = heading.textContent;
var div = documentRef.createElement('div');
div.setAttribute('class', heading.tagName.toLowerCase());
div.appendChild(link);
toc.appendChild(div);
heading.parentNode.insertBefore(anchor, heading);
});
}
try {
module.exports = htmlTableOfContents;
} catch (e) {
// module.exports is not defined
}
JQuery comes to mind as a fast and easy solution. A quick google search for jquery table of contents yields two promising results:
I've modified the function in AtesGoral's accepted answer to output correctly nested lists and valid HTML5.
Just add the code below to your scripts and call TableOfContents(container, output);
on load, where container is the class or id of your content element and output is the class or id of the TOC element. Default values are '#contents' and '#toc', respectively.
See http://codepen.io/aufmkolk/pen/RWKLzr for a working demo.
function TableOfContents(container, output) {
var toc = "";
var level = 0;
var container = document.querySelector(container) || document.querySelector('#contents');
var output = output || '#toc';
container.innerHTML =
container.innerHTML.replace(
/<h([\d])>([^<]+)<\/h([\d])>/gi,
function (str, openLevel, titleText, closeLevel) {
if (openLevel != closeLevel) {
return str;
}
if (openLevel > level) {
toc += (new Array(openLevel - level + 1)).join('<ul>');
} else if (openLevel < level) {
toc += (new Array(level - openLevel + 1)).join('</li></ul>');
} else {
toc += (new Array(level+ 1)).join('</li>');
}
level = parseInt(openLevel);
var anchor = titleText.replace(/ /g, "_");
toc += '<li><a href="#' + anchor + '">' + titleText
+ '</a>';
return '<h' + openLevel + '><a href="#' + anchor + '" id="' + anchor + '">'
+ titleText + '</a></h' + closeLevel + '>';
}
);
if (level) {
toc += (new Array(level + 1)).join('</ul>');
}
document.querySelector(output).innerHTML += toc;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With