Create a Dynamic Table of Contents Using MooTools 1.2

By  on  

You've probably noticed that I shy away from writing really long articles. Here are a few reasons why:

  • Most site visitors are coming from Google and just want a straight to the point, bail-me-out ASAP answer to a question.
  • I've noticed that I have a hard time reading long articles. Short attention span, I guess.
  • When the article is long, I tend to see less comments -- both on my blog and others.
  • Quite frankly, if the article bombs, I feel like I wasted a bunch of time.

Say I did put together a lengthy article -- I'd want to automate a table of contents, right? Well, here's how I would do it.

The XHTML

<h1>Article Title</h1><p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.....</p>
<h2>Article Title 2 (1)</h2><p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.....</p>
<h3>Article Title 3 (1)</h3><p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.....</p>
<h3>Article Title 3 (2)</h3><p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.....</p>
<h3>Article Title 3 (3)</h3><p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.....</p>
<h2>Article Title 2 (2)</h2><p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.....</p>
<h2>Article Title 2 (3)</h2><p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.....</p>
<h3>Article Title 3 (4)</h3><p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.....</p>

As you can see, the above content is really just rubbish for our example. Note the order of the "h" tags.

The CSS

#toc		{ float:right; border:1px solid #fc0; background:#fffea1; padding:10px; font-family:tahoma, arial; margin:0 0 20px 20px; }
#toc a 		{ display:block; margin:3px; }
#toc .h1	{ color:#090; }
#toc .h2	{ padding:0 0 0 10px; font-size:11px; }
#toc .h3	{ color:#f00; font-size:10px; padding:0 0 0 30px; }

The above CSS applies only to the table of contents DIV we'll be building. The JavaScript will add the "h{x}" class to the generated link to that section. Note that the TOC will start hidden.

The MooTools JavaScript

//once the dom is set...
window.addEvent('domready', function() {

	//initial vars
	var finders = ['h1','h2','h3','h4','h5','h6'];
	var matches = [];

	//find the h1, which is the article title
	$('article-area').getElements('*').each(function(el,i) {

		//do we want this?
		if(finders.contains(el.get('tag')))
		{
			//create anchor
			var anchor = new Element('a', {
				'class': el.get('tag'),
				'text': el.get('text'),
				'href': 'javascript:;'
			});

			//click event
			anchor.addEvent('click', function() {
				var go = new Fx.Scroll(window).toElement(el);
			});

			//add into our matches array
			matches.include(anchor);
		}

	});

	//should we show the toc?
	if(matches.length)
	{
		//create toc div, inject
		var toc = new Element('div', {
			'id': 'toc',
			'html': '<strong>Table of Contents</strong><br />'
		}).inject('article-area','before');

		//inject the matches
		matches.each(function(el) {
			el.inject(toc);
		});
	}

});

There's a lot going on here so let me break it down. First, I initialize a few vars. The most important is "finders," which allows us to set the tags to be included in the table of contents. Next, we grab all elements inside my designated "article-area" element. If the element is in our "finders" list, we create an anchor for it, attach smooth scrolling to the anchor's "click" event, and store the anchor in our "matches" array. In the end, if we find any matches, we create the DIV and float it to the right. If not, the TOC never displays. Sweet!

Notes

  • You'll likely want to customize this system a bit to suit the structure of your articles.
  • I'm not a huge fan of using lists (<ul>) for the sake of using them, which is why I used anchors and "display:block" CSS
  • My articles only use one H1 and multiple H2's. In this case, I'd customize this TOC to place the H1 value at the top of the TOC (instead of "Table of Contents"). After that heading, the rest of the H2's would follow. I didn't employ this in my example because I wanted it to be as "general" or "basic" as possible.
  • It may suit your blog to make your TOC toggle.

Recent Features

  • By
    Create Namespaced Classes with MooTools

    MooTools has always gotten a bit of grief for not inherently using and standardizing namespaced-based JavaScript classes like the Dojo Toolkit does.  Many developers create their classes as globals which is generally frowned up.  I mostly disagree with that stance, but each to their own.  In any event...

  • By
    9 Mind-Blowing Canvas Demos

    The <canvas> element has been a revelation for the visual experts among our ranks.  Canvas provides the means for incredible and efficient animations with the added bonus of no Flash; these developers can flash their awesome JavaScript skills instead.  Here are nine unbelievable canvas demos that...

Incredible Demos

Discussion

  1. Good article, very straight forward, the layout reminds me of Wikipedia. I don’t think I would ever use this though because I am not a fan of the scroll effect.

  2. Thanks David.
    I am a BIG fan of the scroll effect!
    I imagine using this on a restaurant site. List the dishes as a table of contents and then ping down to their descriptions.
    On the other hand, maybe I’ll use a twist on your FAQ accordion for that restaurant menu.
    On the other hand…
    Seriously, if you have any of your genius thoughts of better ways to display a menu using mootools it would be great to hear them.
    Thanks again

  3. Diego

    cool thanks, i love your blog bro

  4. Ilbrami

    I this this is nice and useful :) for long content
    Thanks

  5. elvisparsley

    I read everywhere telling that “using js should not disturb accessibility”. I don’t take this always but this article violate this 100% unfortunately. If I disable js, I don’t see any content.
    However I love David’s blog and from learning mootools point of view, his blog is the best online.
    thanks David.

  6. @Elic: It is not accessible in that it is built using javascript, but the TOC isn’t necessarily required — I consider it an enhancement.

  7. john

    But in IE7 doesnt seem to work?

  8. marcus

    Hi, IE7 throws out an error, to fix that, remove the last comma on line 38.
    greetings
    marcus

  9. @marcus: Good call! I must have had another parameter in my testing and pulled it out during the article.

  10. marcus

    Hi found again a glitche in IE7 (or better 8beta).
    If I embed Flash content, the (brilliant) JS Debugger by IE8b throws out an error at this line:

    if(finders.contains(el.get('tag')))

    so I change this line

    $('maincontent').getElements('*').each(function(el,i) {
    

    to

    $('maincontent').getElements('h1,h3').each(function(el,i) {
    

    means the DOM is not checked for * any element, but just for the ones I need h1, h3.
    does this still make any sense? (I am not so much into JS, mootools – but I can mess up given scripts :D)
    Currently it works for me in any tested browser (IE7/8beta,FF3,Webkit/GoogleChrome, Opera 9)

    maybe someone can confirm this.
    cheers marcus

  11. charlie

    Hi there David, great job again. First for all, sorry for my poor english. Only one suggestion. The div that contains the toc, should move to the area u are clicking, that’s cause if u have a dynamic navigation but u have to scroll to click another article, what’s the point?

  12. Hi David

    Nice article – I have done the exact same thing as an exercise to create my first MooTools class.

    I came across your artilce since I was Googling for a solution to my problem with MooTools setting attributes on dynamic elements.

    Basically, I am creating my table of contents but also for each ‘target’ element in the body ie. I am also inserting a relative link back to ‘top’ so the user can scroll back to the top of the article as well.

    The problem is that in Internet Explorer both the following snippets do not work:

    var namedAnchor = new Element('a', {'id': 'a1'}); 
    namedAnchor.setProperty('name', 'a1'); 
    namedAnchor.inject(someOtherElement, 'before'); 
    

    OR

    var namedAnchor = new Element('a', {'id': 'a1', 'name': 'a1'}); 
    namedAnchor.inject(someOtherElement, 'before'); 
    

    I get the Element in the DOM fine but in IE the name ‘attribute’ is missing and this is required for the Smooth Scroll to work.

    Any ideas on preserving the name attribute on dynamically created elements in IE?

    Good MooTools information in your articles :)

    Thanks
    Jon

  13. Hi David,

    i like your blog!

    I implemented a jQuery-Version. The (javascript-based) toc will only show as an “qTip”, if you hover over the right symbol in headings or the quick links in text.

    Example: http://www.ardiman.de/datenbanken/grundlagen/formulare.html#SEC4 (see symbol in heading and the dashed Text “Eigenschaften”)

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!