Here we propose a small piece of JavaScript code you can use in your documents to display an embedded table of contents.
This example is a simple document, with a hyperlink that, once clicked, displays the table of contents in an <aside> element in the main <section>. Just look at the source code and copy/paste the link into your own HTML documents.
HTML:
<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>Embedding a TOC</title>
</head>
<body>
<h1>This is an example of embedded table of content
</h1>
<section>
<header>
<h1>First section of the document (this is a h1)</h1>
This is a subheading...
</header>
<h2>First subsection of the first section (a h2)</h2>
<p>Blah Blah...</p>
</section>
<section>
<h1>Second section of the document (a h1)</h1>
<h2>First subsection (a h2)</h2>
</section>
<aside>
<h3>Table of content</h3>
<a href="javascript:(function(){var i=function(a){this.sections=[];this.startingNode=a};i.prototype={heading:false,append:function(a){a.container=this;this.sections.push(a)},asHTML:function(a){var b=u(this.heading);if(a)b='<a href="#'+v(this.startingNode)+'">'+b+"</a>";return b+q(this.sections,a)}};var q=function(a,b){for(var f="",c=0;c<a.length;c++)f+="<li>"+a[c].asHTML(b)+"</li>";return f==""?f:"<ol>"+f+"</ol>"},r=function(a){a=a.heading;return h(a)?j(a):1},u=function(a){if(h(a)){if(k(a)=="HGROUP")a=a.getElementsByTagName("h"+
-j(a))[0];return a.textContent||a.innerText||"<i>No text content inside "+a.nodeName+"</i>"}return""+a},v=function(a){var b=a.getAttribute("id");if(b)return b;do b="h5o-"+ ++s;while(t.getElementById(b));a.setAttribute("id",b);return b},e,d,g,s,t,w=function(a,b,f){var c=a;a:for(;c;){b(c);if(c.firstChild){c=c.firstChild;continue a}for(;c;){f(c);if(c.nextSibling){c=c.nextSibling;continue a}c=c==a?null:c.parentNode}}},x=function(a){if(!h(o(g)))if(l(a)||m(a)){e!=null&&g.push(e);e=a;d=new i(a);e.outline=
{sections:[d],startingNode:a,asHTML:function(c){return q(this.sections,c)}}}else if(e!=null)if(h(a)){if(d.heading)if(j(a)>=r(n(e.outline))){var b=new i(a);e.outline.sections.push(b);d=b;d.heading=a}else{b=false;var f=d;do{if(j(a)<r(f)){b=new i(a);f.append(b);d=b;d.heading=a;b=true}f=f.container}while(!b)}else d.heading=a;g.push(a)}},y=function(a){var b=o(g);if(h(b))b==a&&g.pop();else{if((l(a)||m(a))&&!d.heading)d.heading="<i>Untitled "+k(a)+"</i>";if(l(a)&&g.length>0){e=g.pop();d=n(e.outline);for(b=
0;b<a.outline.sections.length;b++)d.append(a.outline.sections[b])}else if(m(a)&&g.length>0){e=g.pop();for(d=n(e.outline);d.sections.length>0;)d=n(d)}else if(l(a)||m(a))d=e.outline.sections[0]}},k=function(a){return a.tagName.toUpperCase()},p=function(a){return function(b){return z(b)&&(new RegExp(a,"i")).test(k(b))}},m=p("^BLOCKQUOTE|BODY|DETAILS|FIELDSET|FIGURE|TD$"),l=p("^ARTICLE|ASIDE|NAV|SECTION$"),h=p("^H[1-6]|HGROUP$"),z=function(a){return a&&a.tagName},j=function(a){var b=k(a);if(b=="HGROUP")for(b=
1;b<=6;b++){if(a.getElementsByTagName("H"+b).length>0)return-b}else return-parseInt(b.substr(1))},n=function(a){return o(a.sections)},o=function(a){return a[a.length-1]};HTML5Outline=function(a){s=0;t=a.ownerDocument||window.document;d=e=null;g=[];w(a,x,y);return e!=null?e.outline:null}})();
;(function(){var b=function(e,f){for(var d=0;d<e.length;d++)e[d].setAttribute("style",f)},g=HTML5Outline(document.body).asHTML(true),a=document.createElement("div");b([a],"position:fixed;top:10px;right:10px;border:2px solid #000;background:rgba(255,255,255,.9);padding:15px;z-index:999;max-height:400px;overflow:auto;font-size:11px;font-family:Verdana, sans-serif;");a.innerHTML=g;b(a.getElementsByTagName("li"),"list-style:decimal outside;margin-left:20px;font-size:11px;font-family:Verdana, sans-serif;");
b(a.getElementsByTagName("ol"),"margin: 0;padding:0;font-size:11px;font-family:Verdana, sans-serif;");b(a.getElementsByTagName("a"),"color:#008;text-decoration:underline;font-size:11px;font-family:Verdana, sans-serif;");var c=a.insertBefore(document.createElement("a"),a.firstChild);b([c],"float: right; margin: 0 0 5px 5px; padding: 5px; border: 1px #008 solid;color:#00f;background-color:#ccf;");c.innerHTML="Close";c.href="#";c.onclick=function(){document.body.removeChild(a);a=c=null};document.body.appendChild(a)})();
;" title="TableDeMatiere"><h1>Click here for displaying the table of contents!</h1></a>
</aside>
</body>
</html>
CSS:
aside {
float: right;
position:relative;
}
Extract of source code:
<body>
<h1>This is an example of embedded table of content</h1>
<section>
<header>
<h1>First section of the document (this is a h1)</h1>
This is a subheading...
</header>
<h2>First subsection of the first section (a h2)</h2>
<p>Blah Blah...</p>
</section>
<section>
<h1>Second section of the document (a h1)</h1>
<h2>First subsection (a h2)</h2>
</section>
<aside>
<h3>Table of contents</h3>
<a href="javascript:(function(){...})();"
title="TableDeMatiere">
Click here to display the table of contents!
</a>
</aside>
</body>
Best practice: visualizing the table of contents is useful for debugging the structure of your page, and checking the presence of headings after sectioning content.
Indeed, tools that generate the table of contents are a good way to debug the structure of your page. Is the hierarchy correct? Is it what I wanted when I designed my page?
They are also useful for checking the presence of headings in each sectioning content. If some headings are missing, the table of contents will display some "untitled entries". Remember that having a heading after each sectioning content is a best practice in terms of accessibility.