Automatic Page Contents
read 17 commentsIt'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:
- jqTOC by Dave G.
- Page Contents Menu (.js file) by Joel Birch
But if you want to see how to do this sort of thing on your own, read on...
Let's Begin
Here is what we need to do:
- 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... - Create a new container
<div>for the table of contents and insert a heading and an empty div inside. - Copy the
<h2>elements and the<a>elements inside them. - Change the attributes of the copied links.
- 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:
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:
- if ( $('#content h2').length> 1) {
- // ... etc. ...
- var $this = $(this);
- var thisId = this.id;
- $this
- 'href': '#' + thisId
- })
- });
- }
- });
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:
- if ( $('#content h2').length> 1) {
- $('<div id="page-contents"></div>')
- var $this = $(this);
- var thisId = this.id;
- $this
- 'href': '#' + thisId
- })
- });
- });
- }
- });
If you want to see it in action, just go to the Learning jQuery home page and look in the top-right corner of the page.















Great post, thanks! It's always nice to see how fellow codemen-in-arms use jQuery. :)
Very nice! Keep up the posting of good articles!
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.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 anid. 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>.guess i'm not good with this
hey... thanks for sharing this suff
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.
Real neat! :)
Excellent post ! I'm subscribing to your feed so I can make sure I catch more
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
lielement, 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!
Excellent post, muchas gracias y saludos desde Chile!!
hi,
the exemple is not working, nor using your indications ??
could you please see what is the problem ?
thank you.
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.
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
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:
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
My aplogies for the bad link. My page is here:
http://www.keene.edu/emergency/default2.cfm
Thanks.
This worked exactly how I wanted it to. I just had to change the text then pack it. Thanks for this!