• documentation
  • examples
  • about
  • FAQ
  • journal
  • Gitlab

Build a Table of Contents from your HTML

written by Julie Blanc on Jan 1, 2020

You got some HTML content you want to make a book with, but it would takes too much time to set the ids and hyperlinks? We got you covered!
table of content target-counter guide

In this part of the doc, we’ll show you how to build a table of contents using a custom script and the css function target-counter() for the content property.

Build the table of contents from your html

In HTML, a table of contents is a <nav> elements that contain a list of the reveleant titles of your document with a link to the unique identifier of each. This part can be done with your own tool/generator but here is an exemple of a script to generate a table of contents in vanilla javascript:

function createToc(config) {
const content = config.content;
const tocElement = config.tocElement;
const titleElements = config.titleElements;

let tocElementDiv = content.querySelector(tocElement);
let tocUl = document.createElement("ul");
tocUl.id = "list-toc-generated";
tocElementDiv.appendChild(tocUl);

// add class to all title elements
let tocElementNbr = 0;
for (var i = 0; i < titleElements.length; i++) {
let titleHierarchy = i + 1;
let titleElement = content.querySelectorAll(titleElements[i]);

titleElement.forEach(function (element) {
// add classes to the element
element.classList.add("title-element");
element.setAttribute("data-title-level", titleHierarchy);

// add id if doesn't exist
tocElementNbr++;
idElement = element.id;
if (idElement == "") {
element.id = "title-element-" + tocElementNbr;
}
let newIdElement = element.id;
});
}

// create toc list
let tocElements = content.querySelectorAll(".title-element");

for (var i = 0; i < tocElements.length; i++) {
let tocElement = tocElements[i];

let tocNewLi = document.createElement("li");

// Add class for the hierarcy of toc
tocNewLi.classList.add("toc-element");
tocNewLi.classList.add(
"toc-element-level-" + tocElement.dataset.titleLevel
);

// Keep class of title elements
let classTocElement = tocElement.classList;
for (var n = 0; n < classTocElement.length; n++) {
if (classTocElement[n] != "title-element") {
tocNewLi.classList.add(classTocElement[n]);
}
}

// Create the element
tocNewLi.innerHTML =
'<a href="#' + tocElement.id + '">' + tocElement.innerHTML + "</a>";
tocUl.appendChild(tocNewLi);
}
}

Copy this script to a .js and link this file to your document.

The table of contents need to be generated before paged.js fragment the content into pages. Therefore, you need to register the handler beforeParsed() and call the table of contents script inside.

Add this code in the head of you html document after the call for paged.js script:

<script>
class handlers extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
}

beforeParsed(content) {
createToc({
content: content,
tocElement: "#my-toc-content",
titleElements: [".mw-content-ltr h2", "h3"],
});
}
}
Paged.registerHandlers(handlers);
</script>

Configuring the script

tocElement: define the id element where the toc list will be created

titleElements: array of the title element you want in the your table of contents. You can add as many as you want and the elements can be selected like any css, for example: .title-1 or .my-content h1

Generate page numbers

Thanks to the previous script, your content is now structured using id for the heading, and the table of contents we created use hyperlinks to those #id:

<!-- the headings in the text-->
<h1 id="pre-digital_era" class="title-element" data-title-level="h1">
Pre-digital era
</h1>
<p>Content...</p>
<h1 id="digital_era" class="title-element" data-title-level="h1">
Digital era
</h1>
<p>Content...</p>
<!-- the table of contents-->
<ul id="toc">
<li><a href="#pre-digital_era">Pre-digital era</a></li>
<li><a href="#digital_era">Digital era</a></li>
</ul>

In the CSS, the target-counter property is used within ::before and ::after pseudo-elements, using the content property. It can be translated as: find the counter named page that appears where you find the element we’re targetting with the attribute href:

#toc li a::after {
content: target-counter(attr(href), page);
float: right;
}

Add styles to the table of contents

If you need to add counters or leaders to your table of contents generated above, here is an exemple of CSS you can use:

/* set the style for the list numbering to none */
#list-toc-generated {
list-style: none;
}

#list-toc-generated .toc-element {
break-inside: avoid;
}

#list-toc-generated .toc-element a::after {
content: " p. " target-counter(attr(href), page);
float: right;
}

#list-toc-generated .toc-element-level-1 {
margin-top: 25px;
font-weight: bold;
}

#list-toc-generated .toc-element-level-2 {
margin-left: 25px;
}

/* counters */

#list-toc-generated {
counter-reset: counterTocLevel1;
}

#list-toc-generated .toc-element-level-1 {
counter-increment: counterTocLevel1;
counter-reset: counterTocLevel2;
}

#list-toc-generated .toc-element-level-1::before {
content: counter(counterTocLevel1) ". ";
padding-right: 5px;
}

#list-toc-generated .toc-element-level-2 {
counter-increment: counterTocLevel2;
}

#list-toc-generated .toc-element-level-2::before {
content: counter(counterTocLevel1) ". " counter(counterTocLevel2) ". ";
padding-right: 5px;
}

/* hack for leaders */

#list-toc-generated {
overflow-x: hidden;
}

/* fake leading */
#list-toc-generated .toc-element::after {
content: ".............................................."
".............................................."
".............................................." "........";
float: left;
width: 0;
padding-left: 5px;
letter-spacing: 2px;
}

#list-toc-generated .toc-element {
display: flex;
}

#list-toc-generated .toc-element a::after {
position: absolute;
right: 0;
background-color: white;
padding-left: 6px;
}

#list-toc-generated .toc-element a {
right: 0;
}

And voilà! All the files (scripts, CSS, HTML exemples) to create a table of contents are available on gitlab: https://gitlab.coko.foundation/pagedjs/experiments/tree/master/table-of-content.

Paged Media approaches : page floats

Paged.js is maintained by the nice folks at Coko.