Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Building a table of contents in Javascript

, 21 Aug 2014 LGPL3
Rate this:
Please Sign up or sign in to vote.
So you're publishing a long document online and don't have an easy mechanism to automatically add a table of contents on the server side? Well with Javascript, you enslave the web browser to do it instead! This TOC generator...needs no jQuery or other third-party librarycreates links to each heading

Editorial Note

This article appears in the Third Party Product Reviews section. Articles in this section are for the members only and must not be used by tool vendors to promote or advertise products in any way, shape or form. Please report any spam or advertising.

So you're publishing a long document online and don't have an easy mechanism to automatically add a table of contents on the server side? Well with Javascript, you enslave the web browser to do it instead! This TOC generator...

  • needs no jQuery or other third-party library
  • creates links to each heading, adding an id attribute to each heading that doesn't already have one
  • can support pages that contain multiple content areas with multiple unrelated tables of contents

Usage

After installing the code below, call addTOC(contentElement, before, tocClass), where

  • contentElement is an element that contains the document or blog post for which you want a table of contents, and
  • before is an immediate child of contentElement; the table of contents will be inserted as a child of contentElement, just before before. before is optional and if unspecified, contentElement.firstChild is used.
  • tocClass is the CSS class of the table-of-contents blog (default value: "sidebox")

See example below.

The code

Insert this code at the bottom of any page in which you want to add a TOC (before </body>).

Note: Code blocks in this post are garbled by CodeProject. See the original article for correct code.

<code class="language-html" data-lang="html"><span class="nt"><script></span><span class="c1">// Add table of contents! <![CDATA[</span>
<span class="kd">function</span> <span class="nx">$get</span><span class="p">(</span><span class="nx">selector</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="nx">selector</span><span class="p">);</span> <span class="p">};</span>
<span class="kd">function</span> <span class="nx">$all</span><span class="p">(</span><span class="nx">selector</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">slice</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="nx">selector</span><span class="p">));</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">buildTOC_ul</span><span class="p">(</span><span class="nx">selector</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">var</span> <span class="nx">levels</span><span class="o">=</span><span class="p">[</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"ul"</span><span class="p">),</span><span class="kc">null</span><span class="p">,</span><span class="kc">null</span><span class="p">];</span>
      <span class="nx">levels</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">style</span>
      <span class="kd">var</span> <span class="nx">lvl</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="nx">c</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">selector</span><span class="p">)</span> <span class="nx">selector</span> <span class="o">=</span> <span class="s2">"h2, h3, h4"</span><span class="p">;</span>
      <span class="nx">$all</span><span class="p">(</span><span class="nx">selector</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">el</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">el</span><span class="p">.</span><span class="nx">id</span><span class="p">)</span> <span class="nx">el</span><span class="p">.</span><span class="nx">id</span><span class="o">=</span><span class="s1">'section_'</span><span class="o">+</span> <span class="o">++</span><span class="nx">c</span><span class="p">;</span>
            <span class="kd">var</span> <span class="nx">newLvl</span><span class="o">=</span><span class="p">(</span><span class="nx">el</span><span class="p">.</span><span class="nx">tagName</span><span class="o">==</span><span class="s2">"H2"</span><span class="o">?</span><span class="mi">0</span><span class="o">:</span><span class="nx">el</span><span class="p">.</span><span class="nx">tagName</span><span class="o">==</span><span class="s2">"H3"</span><span class="o">?</span><span class="mi">1</span><span class="o">:</span><span class="mi">2</span><span class="p">);</span>
            <span class="k">for</span> <span class="p">(;</span><span class="nx">lvl</span><span class="o"><</span><span class="nx">newLvl</span><span class="p">;</span><span class="nx">lvl</span><span class="o">++</span><span class="p">)</span>
                 <span class="nx">levels</span><span class="p">[</span><span class="nx">lvl</span><span class="p">].</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">levels</span><span class="p">[</span><span class="nx">lvl</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"ul"</span><span class="p">));</span>
            <span class="nx">lvl</span><span class="o">=</span><span class="nx">newLvl</span><span class="p">;</span>

            <span class="kd">var</span> <span class="nx">li</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">'li'</span><span class="p">);</span>
            <span class="nx">li</span><span class="p">.</span><span class="nx">innerHTML</span><span class="o">=</span><span class="s2">"<a href='#"</span><span class="o">+</span><span class="nx">el</span><span class="p">.</span><span class="nx">id</span><span class="o">+</span><span class="s2">"'></a>"</span><span class="p">;</span>
            <span class="nx">li</span><span class="p">.</span><span class="nx">firstChild</span><span class="p">.</span><span class="nx">innerHTML</span><span class="o">=</span><span class="nx">el</span><span class="p">.</span><span class="nx">innerHTML</span><span class="p">;</span>
            <span class="nx">levels</span><span class="p">[</span><span class="nx">lvl</span><span class="p">].</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">li</span><span class="p">);</span>
      <span class="p">});</span>
      <span class="k">return</span> <span class="nx">levels</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">addTOC</span><span class="p">(</span><span class="nx">contentElement</span><span class="p">,</span> <span class="nx">before</span><span class="p">,</span> <span class="nx">tocClass</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">before</span><span class="o">===</span><span class="kc">undefined</span><span class="p">)</span> <span class="nx">before</span><span class="o">=</span><span class="nx">contentElement</span><span class="p">.</span><span class="nx">firstChild</span><span class="p">;</span>
      <span class="kd">var</span> <span class="nx">prefix</span> <span class="o">=</span> <span class="s2">""</span><span class="p">;</span>
        <span class="kd">var</span> <span class="nx">c</span> <span class="o">=</span> <span class="nx">contentElement</span><span class="p">.</span><span class="nx">className</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">c</span><span class="p">)</span> <span class="nx">prefix</span><span class="o">=</span><span class="s2">"."</span> <span class="o">+</span> <span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span><span class="o">==-</span><span class="mi">1</span><span class="o">?</span><span class="nx">c</span><span class="o">:</span><span class="nx">c</span><span class="p">.</span><span class="nx">substr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="nx">c</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s2">" "</span><span class="p">)))</span> <span class="o">+</span> <span class="s2">" "</span><span class="p">;</span>
      <span class="kd">var</span> <span class="nx">selector</span> <span class="o">=</span> <span class="nx">prefix</span><span class="o">+</span><span class="s2">"h2, "</span><span class="o">+</span><span class="nx">prefix</span><span class="o">+</span><span class="s2">"h3, "</span><span class="o">+</span><span class="nx">prefix</span><span class="o">+</span><span class="s2">"h4"</span><span class="p">;</span>
      <span class="kd">var</span> <span class="nx">toc</span><span class="o">=</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"div"</span><span class="p">);</span>
      <span class="nx">toc</span><span class="p">.</span><span class="nx">className</span><span class="o">=</span><span class="nx">tocClass</span><span class="o">||</span><span class="s2">"sidebox"</span><span class="p">;</span>
      <span class="nx">toc</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">createTextNode</span><span class="p">(</span><span class="s2">"Contents"</span><span class="p">));</span>
      <span class="nx">toc</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">buildTOC_ul</span><span class="p">(</span><span class="nx">selector</span><span class="p">));</span>
      <span class="nx">contentElement</span><span class="p">.</span><span class="nx">insertBefore</span><span class="p">(</span><span class="nx">toc</span><span class="p">,</span> <span class="nx">before</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// =========================</span>
<span class="c1">// TODO: CALL addTOC() HERE!</span>
<span class="c1">// =========================</span>
<span class="c1">//]]></span>
<span class="nt"></script></span>

Example

The simplest thing you can do is to to call addTOC(document.body), but that only works on very simple pages. Generally the page has a main content area; if you assign an id="content" to this content area, you can add the table of contents at the top like this:

<code class="language-js" data-lang="js"><span class="nx">addTOC</span><span class="p">(</span><span class="nx">$get</span><span class="p">(</span><span class="s2">"#content"</span><span class="p">));</span>

(See definition of $get in the code above.) Here's how I'm calling addToc() on my blog:

<code class="language-js" data-lang="js"><span class="kd">var</span> <span class="nx">_post_</span> <span class="o">=</span> <span class="nx">$get</span><span class="p">(</span><span class="s2">"#post"</span><span class="p">)</span> <span class="o">||</span> <span class="nx">$get</span><span class="p">(</span><span class="s2">"#content"</span><span class="p">);</span>
<span class="nx">addTOC</span><span class="p">(</span><span class="nx">_post_</span><span class="p">,</span> <span class="nx">_post_</span><span class="p">.</span><span class="nx">firstChild</span><span class="p">.</span><span class="nx">nextSibling</span><span class="p">.</span><span class="nx">nextSibling</span><span class="p">);</span>

This allows the content area to have id="post" or id="content". _post_.firstChild.nextSibling.nextSibling skips past the initial heading so that the table of contents is inserted underneath the heading.

Sidebar style

The default class is sidebox, and if you insert the following CSS on your page, you will get a table of contents that floats beside the content, just as you see on this page.

<code class="language-html" data-lang="html"><span class="nt"><style></span>
<span class="nc">.sidebox</span> <span class="p">{</span>
  <span class="k">border</span><span class="o">:</span> <span class="m">1px</span> <span class="k">dotted</span> <span class="k">rgb</span><span class="p">(</span><span class="m">127</span><span class="o">,</span> <span class="m">127</span><span class="o">,</span> <span class="m">127</span><span class="p">);</span>
  <span class="k">padding</span><span class="o">:</span> <span class="m">4px</span> <span class="m">3px</span> <span class="m">4px</span> <span class="m">6px</span><span class="p">;</span> <span class="c">/* top right bottom left */</span>
  <span class="k">min-width</span><span class="o">:</span> <span class="m">100px</span> <span class="o">!</span> <span class="n">important</span><span class="p">;</span>
  <span class="k">float</span><span class="o">:</span> <span class="k">right</span> <span class="o">!</span> <span class="n">important</span><span class="p">;</span>
  <span class="k">font-size</span><span class="o">:</span> <span class="m">90%</span><span class="p">;</span>
  <span class="k">margin-top</span><span class="o">:</span> <span class="m">1px</span><span class="p">;</span>
  <span class="k">margin-bottom</span><span class="o">:</span> <span class="m">1px</span><span class="p">;</span>
  <span class="k">margin-left</span><span class="o">:</span> <span class="m">6px</span><span class="p">;</span>
  <span class="k">visibility</span><span class="o">:</span> <span class="k">visible</span><span class="p">;</span>
  <span class="k">max-width</span><span class="o">:</span> <span class="m">50%</span><span class="p">;</span>
  <span class="k">width</span><span class="o">:</span> <span class="m">35%</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.sidebox</span> <span class="nt">ul</span> <span class="p">{</span> <span class="k">padding</span><span class="o">:</span><span class="m">0em</span> <span class="m">0em</span> <span class="m">0em</span> <span class="m">1.3em</span><span class="p">;</span> <span class="k">margin</span><span class="o">:</span><span class="m">0em</span><span class="p">;</span> <span class="p">}</span><span class="o">//</span><span class="nt">TRBL</span>
<span class="nt"></style></span>

Usage with GitHub Pages / Jekyll

If you're publishing with Jekyll, I suggest adding the above code to the bottom of /_layouts/default.html (and any other layouts you might use that might need a TOC, as long as they do not import default.html using a layout: default option), just before </body>, surrounded by a test like this:

<code class="language-text" data-lang="text">{% if page.toc %}
<script>
...
...
var _post_ = $get("#post")||$get("#content");
addTOC(_post_, _post_.firstChild.nextSibling.nextSibling);
</script>
{% endif %}

This way, Jekyll only adds the TOC code to a page if the front-matter contains a toc: true option.

Published on . Comments? Leave them there.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

Share

About the Author

Qwertie
Software Developer Trapeze Software, Inc.
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, and LLLPG, among other things. Now I'm old.
 
In my spare time I'm developing a system called Loyc (Language of your choice), which will include an enhanced C# compiler. Many programs have an add-in architecture; why not your programming language? I'm also looking for a life partner. Oh hi future wife! Wazzap.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141220.1 | Last Updated 21 Aug 2014
Article Copyright 2014 by Qwertie
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid