Automatic Page Contents

read 17 comments

It's been so long since I last posted a tutorial here that I'm afraid everyone might have forgotten about the place. For the past few months, there has been a little "Page Contents" menu at the top-right corner of some of the pages on this site — actually, any page that has more than one <h2> elements in the main content area. In this entry, I'd like to demonstrate how to create an automatic page contents list using jQuery.

The Plugin Option

Before we begin, I should let you know that there are a number of plugins that do what I'm about to show you. If you want to go the plug-in route, take a look at one of these:

But if you want to see how to do this sort of thing on your own, read on...

Continue Reading Below

Let's Begin

Here is what we need to do:

  1. Find out if there is more than one <h2> in the main content area of the page (on this site, the home page and most listing pages will have more than one). If so, we start building the table of contents...
  2. Create a new container <div> for the table of contents and insert a heading and an empty div inside.
  3. Copy the <h2> elements and the <a> elements inside them.
  4. Change the attributes of the copied links.
  5. Insert each copied <h2> along with its contained link into the page-contents <div>.

When we're finished, we should have something that looks like the image to the right:

Create the container

Okay, so our first step is to create the container. We'll wrap our code in a $(document).ready(), as we do nearly every time, so that the script loads when the DOM (but not necessarily image, etc.) has loaded. Then we make sure that we're dealing with more than one <h2> and, if so, start building:

JavaScript:
  1. $(document).ready(function() {
  2.   if ( $('#content h2').length> 1) {
  3.     $('<div id="page-contents"></div>')
  4.       .prepend('<h3>Page Contents</h3>')
  5.       .append('<div></div>')
  6.       .prependTo('body');
  7.   }
  8. });

Okay, maybe a little explanation is warranted here. The first line inside our if condition (line 3) creates a new <div> element with an id of "page-contents." Line 4 inserts an <h3> at the beginning of that new <div> and line 5 inserts an empty <div> at the end of the page-contents <div>. Finally, line 5 inserts the whole group of newly created elements at the beginning of the <body>.

Copy the headings

The next part involves copying all of the <h2> elements, and while we're at it, changing the href of the copied <a> elements to point to the original <h2>s. How do we know where to point them? Each of the original <h2>s has an id associated with it. We just take the id, prepend a "#", and attach it to the href:

JavaScript:
  1. $(document).ready(function() {
  2.   if ( $('#content h2').length> 1) {
  3.     // ... etc. ...
  4.     $('#content h2').each(function(index) {
  5.       var $this = $(this);
  6.       var thisId = this.id;
  7.       $this
  8.         .clone()
  9.         .find('a')
  10.           .attr({
  11.             'title': 'jump to ' + $this.text(),
  12.             'href': '#' + thisId
  13.           })
  14.         .end()
  15.         .attr('id', 'pc-' + index)
  16.         .appendTo('#page-contents div');
  17.     });  
  18.   }
  19. });

We use a .each() method here so that we can apply a number of things to each <h2> in turn. Inside this method, we start by declaring a couple variables—one for $(this), because we use it twice, and one for each id, because we use it to set the hrefs.

So, for each <h2>, we make a copy using .clone() and find the <a> inside of it. Then we set each link's title and href attribute.

When we're finished with the links, we back up to the cloned <h2> elements by using .end(). We want to ensure that we don't duplicate the id along with the element, so we prepend "pc-" to it, keeping each one unique.

When all is ready, we append each <h2> to the <div>inside the page-contents container.

A Little Extra Flair

Now, for an optional addition to our script, we can hide the page-contents <div> in a style sheet (by setting it to display: none). Then we attach a click handler to the page-contents heading with a .toggleClass() to change the background image and a .slideToggle() to show and hide the same-page links.

Here is the complete script, with the additional click handler at the end:

JavaScript:
  1. $(document).ready(function() {
  2.   if ( $('#content h2').length> 1) {
  3.     $('<div id="page-contents"></div>')
  4.       .prepend('<h3>Page Contents</h3>')
  5.       .append('<div></div>')
  6.       .prependTo('body');  
  7.     $('#content h2').each(function(index) {
  8.       var $this = $(this);
  9.       var thisId = this.id;
  10.       $this
  11.         .clone()
  12.         .find('a')
  13.           .attr({
  14.             'title': 'jump to ' + $this.text(),
  15.             'href': '#' + thisId
  16.           })
  17.         .end()
  18.         .attr('id', 'pc-' + index)
  19.         .appendTo('#page-contents div');
  20.     });
  21.     $('#page-contents h3').click(function() {
  22.       $(this).toggleClass('arrow-down')
  23.         .next().slideToggle('fast');
  24.     });
  25.   }
  26. });


comment feed

17 comments

  1. Great post, thanks! It's always nice to see how fellow codemen-in-arms use jQuery. :)

  2. etnt

    Very nice! Keep up the posting of good articles!

  3. Hi Karl,

    well done. While reading your post I realized that it's possible for a link to directly jump to an id (which makes perfectly sense). I was still in the good old <a name="myAnchor"> mode ...

    Modification is needed if your <h2>s just don't have an id. In that case we have to generate + add them as well.

  4. Hi Ralf,

    Yeah, I think IDs are definitely the way to go. There might be an older browser (NN4?) that doesn't support jumping to IDs, so be sure to test it first if you need to support any dinosaurs. ;)

    Thanks for pointing out the need to modify things if the <h2>s don't already have an id. Also, if the <h2>s don't already have an <a> inside of them, then the .clone() method is not really the best choice. For that situation, it makes sense to create a new <a> for each <h2>. So, inside the .each() we'd do something like this: $('<a></a>').attr('href',$(this).text()) and append it to the page content's child <div>.

  5. rookie

    guess i'm not good with this

  6. sigue

    hey... thanks for sharing this suff

  7. siddhesh

    Hi karl,
    Nice stuff! But i have one q. How can i get the content - in the sense suppose I have top topics in my site & that content I want to get on other page to publish some where else. Is there anything for this?
    Please help me out.
    Thanks for listening me.

  8. Excellent post ! I'm subscribing to your feed so I can make sure I catch more

  9. Thanks for posting this. It may be very useful. But there's one thing I don't like - just cloning the h2 elements into the page contents div.
    I think it might be much better to use ordered list, and put each link to li element, to keep it more semantic. I modified the code and posted it on my blog, with a description (and a link back to your article, of course. don't bother about the language, it's Polish ;)) and prepared a working example.

    btw. Great blog, I've learned quite much here, thanks!

  10. Excellent post, muchas gracias y saludos desde Chile!!

  11. alberto

    hi,

    the exemple is not working, nor using your indications ??
    could you please see what is the problem ?

    thank you.

  12. Alberto,

    I'm sorry it's not working for you. Can you please provide me with some more information so I can troubleshoot the problem? It's working fine for me on the Learning jQuery home page in Firefox 2 and Safari 3.1. Which browser are you using? Is it giving you a JavaScript error?

    thanks.

  13. Eyveneena

    Hi Karl,

    I was thankful to run across this article on element delegation since I am doing a content menu for a specific page in a website. This will enable an additional menu specifically for that page where the options before this article were the appending "li's" to the already intact "ul". My question is with the a href's tags that would be appended to the child divider of the page-contents divider. How would I exactly include it into the each statement?

    Thank You
    Eyveneena

  14. Russ

    Thanks so much for posting this. I think I almost have it but I am a little confused.

    I have the full script inside the proper JavaScript tags on my page, and then the following HTML:

    
    <div id="page-contents">
    
    <h2>
    <h3>Page Contents</h3>
    <a href="">Link</a>
    <a href="">Link</a>
    <a href="">Link</a>
    <a href="">Link</a>
    <a href="">Link</a>
    <a href="">Link</a>
    <a href="">Link</a>
    </h2>
    </div>
    

    The page I'm working on is here. Can you tell me what I'm doing wrong? It looks as though I'm missing some styling.

    Thanks again.
    R

  15. Russ

    My aplogies for the bad link. My page is here:

    http://www.keene.edu/emergency/default2.cfm

    Thanks.

  16. This worked exactly how I wanted it to. I just had to change the text then pack it. Thanks for this!

4 Pings

  1. [...] Automatic Page Contents In this entry, I’d like to demonstrate how to create an automatic page contents list using jQuery. (tags: jquery) [...]

  2. 最佳jQuery教程和实例 | jquery ajax中文资料博客

    [...] Automatic Page Contents [...]

  3. 51+ JQuery Tutorials and Examples at Expertz

    [...] Automatic Page Contents [...]

Sorry, but comments for this entry are now closed.