Making of a table of contents script

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!

1 comment:

  1. Hey there's support for fenced code blocks!
    `console.log("and as always ...");`
    ```
    // multi-line!
    console.log("... stay nuusome!");
    ```

    ReplyDelete