I wanted to use an automatic table of content script to generate a TOC if necessary for this blog. I picked a script written by Ates Goral on stackoverflow: stackoverflow.com/a/187946.
When I was writing the Localization Routes post I noticed that some of the table content links weren't working properly. The error was that some headings were called ____________Id____________. So instead of trying to understand why the table of content script wasn't working I decided to write my own for fun.
I made the automatic table of content generation using typescript and the code is in the snippet below.
What the code does is look for a #toc and #content element.
Then it finds all the h1 to h6 elements inside the #content element.
The cool part is that querySelectorAll() returns them in order of appearance so there's no need to sort them.
So it's just a matter of iterating over each heading element and increase/decrease the level of the ToC.
The code can be found on github or in the snippet below: here.
"use strict";
module HF
{
class ToC
{
public m_level: number = 0;
public m_currentLists: HTMLUListElement[] = [];
constructor(a_tocId: string = "toc", a_contentId: string = "contents")
{
var tocElement: HTMLElement = document.getElementById(a_tocId);
var contentElement: HTMLElement = document.getElementById(a_contentId);
// Make sure the 2 elements exists in the DOM
if (tocElement && contentElement)
{
var headerElements: NodeList = contentElement.querySelectorAll("h1, h2, h3, h4, h5, h6");
this.createToC(tocElement, headerElements);
}
}
public createToC(a_tocElement: HTMLElement, a_headerElements: NodeList): void
{
var hlen: number = a_headerElements.length;
var documentFragmentElement: DocumentFragment = document.createDocumentFragment();
for (var i: number = 0; i < hlen; ++i)
{
var headerElement: HTMLHeadingElement = a_headerElements[i] as HTMLHeadingElement;
this.createToCElement(headerElement, documentFragmentElement, a_tocElement.id);
}
// Set the toc element title.
// First I only appended the documentFragment but then in blogger the preview texts it said "Table of Contents:" and no table!
a_tocElement.innerHTML = "Table of Content:";
a_tocElement.appendChild(documentFragmentElement);
// Remove the hide class
a_tocElement.className = "";
}
public createToCElement(a_headerElement: HTMLHeadingElement, a_documentFragment: DocumentFragment, a_tocId: string): void
{
var headingLevel: number = parseInt(a_headerElement.nodeName[1], 10);
this.gotoLevel(headingLevel, a_documentFragment);
var currentListElement: HTMLUListElement = this.m_currentLists[this.m_level];
var toTopLink: HTMLAnchorElement = document.createElement("a") as HTMLAnchorElement;
toTopLink.href = "#" + a_tocId;
var headerCloneElement: HTMLHeadingElement = a_headerElement.cloneNode(true) as HTMLHeadingElement;
toTopLink.appendChild(headerCloneElement);
a_headerElement.parentNode.replaceChild(toTopLink, a_headerElement);
// Because id's can become " id "
var id = a_headerElement.innerHTML.trim();
headerCloneElement.id = id;
headerCloneElement.innerHTML = id;
var listItem = document.createElement("li");
listItem.innerHTML = "<a href='#" + id + "'>" + id + "</a>";
currentListElement.appendChild(listItem);
}
private gotoLevel(a_headingLevel: number, a_documentFragment : DocumentFragment): void
{
// Can skip the if statements and just do the 2 whiles one after another if you want, I like the ifs!
if (this.m_level < a_headingLevel)
{
// This is important otherwise <ul> tags would be missing when jumping up and down in header levels.
while (this.m_level < a_headingLevel)
{
// increase level by 1!
this.m_level++;
// Create the ul for this heading level
this.m_currentLists[this.m_level] = document.createElement("ul");
// If level is 1 then add to the ToC fragment
if (this.m_level == 1)
{
a_documentFragment.appendChild(this.m_currentLists[1]);
}
// Else add it to the parent list
else
{
this.m_currentLists[this.m_level - 1].appendChild(this.m_currentLists[this.m_level]);
}
}
}
// If current level is higher than the new headinglevel
else if (this.m_level > a_headingLevel)
{
while (this.m_level > a_headingLevel)
{
// remove the list at the current level
this.m_currentLists[this.m_level] = undefined;
this.m_level--;
}
}
}
}
// Put the load code last just incase!
var loaded = false;
document.addEventListener("DOMContentLoaded", function ()
{
var toc = new ToC();
loaded = true;
});
window.addEventListener("load", function ()
{
// If the DOMContentLoaded never fired.
if (!loaded)
{
var toc = new ToC();
}
});
}
Now what I learned was that Ates Goral's script worked just fine.
It however was my heading tags that had tabs and many spaces in them which generated the ____________Id____________.
Which is the reason the trim() function is used.
So the point of this whole post is that here is another ToC auto generator but now written in typescript!
Hey there's support for fenced code blocks!
ReplyDelete`console.log("and as always ...");`
```
// multi-line!
console.log("... stay nuusome!");
```