How I made a post contents menu

When I redesigned kristarella.com I implemented a contents menu that you can see in top right corner of some posts and pages; mainly tutorial posts. This was part of my effort to make the site more navigable.

I’ve had a couple of questions about how I did that, so I’m going to answer them today. The menu itself is generated via jQuery. I chose to use javascript to implement the menu because that way the menu would only be generated when it is suitable to do so, and the variety of selectors in jQuery makes it simple to select the content needed.

I thought hard about using javascript in this way, since I generally agree that if it can be done in another language, a server-side language, then it should be. I considered that perhaps a PHP filter on the post contents might be more suitable, and then the menu would be available to those without javascript too. However, in the end I went with javascript because I couldn’t quite conceptualise how I would do it with PHP, and because I thought this feature is an optional extra, not crucial for the navigation of the site, so it is acceptable to do it in javascript.

Headings with IDs

The main pre-req for this menu is that the post or page contain headings with IDs assigned to them. I already had this for quite a few tutorials so that I could link to certain sections of tutorials when I wanted to.

You can cause a browser to jump to a certain ID by adding #id-name to the end of the URL. This applies to any element on the page, not just headings. For example, if I wanted to send you to the footer of my site I could use a link that looks like <a href="#footer">Link to footer</a>.

Load jQuery library

You also need to have the jQuery library loaded on the page. This can be done in the <head> of the webpage, or before the </body> tag at the end of the webpage.

For instructions on how to do this in WordPress see Including jQuery in WordPress (the right way) and Use Google-hosted javascript libraries (… still the right way). If you’re using Thesis, there are options under the Page Options to include various javascript libraries, as well as options for each post or page (on their editing page) to include the libraries. Selecting a library to be included on the home page under Page Options will cause it to be included on all pages.

The code

This code should be included on the webpage, after the jQuery library has been loaded (either in the <head> or at the end of the page, as long as the jQuery library is loaded first). It can be added directly to the page if wrapped in <script type="text/javascript"> and </script> tags, or in an external javascript file with a reference such as <script type="text/javascript" src="menu.js"></script> (recommended).

jQuery(function() {
	var headings = ".format_text h3[id], .format_text h4[id], .format_text h5[id], .format_text h6[id]";
	if (jQuery(headings).length > 0)
		jQuery(".single-post .post_box .format_text, .thesography .post_box .format_text").prepend('<ul id="toc"><h3>Contents</h3></ul>');

	jQuery(headings).each(function() {
		var id = jQuery(this).attr("id");
		var title = jQuery(this).text();
		jQuery(".single-post ul#toc, .thesography ul#toc").append('<li><a href="#' + id + '">' + title + '</a></li>');
	});
});

The first line of code opens the function and will run the function when the whole page has loaded.

var headings creates a variable with a comma separated list of the headings the menu might use — that is, any h3, h4, h5 or h6 headings inside the div with class .format_text (a div containing the main post content in Thesis), and that have IDs (using the jQuery attribute selector).

The next step of the function is subject to a conditional clause, that it should only run if there are some headings with IDs, i.e., if the headings variable contains something. When there are headings with IDs in the post an unordered list with ID toc and a heading of “Contents” is created at the top of the post. In my case, only on pages with a body class of “single-post” or “thesography” (see the code for adding body classes).

For each heading the code collects the ID and the text within that heading, and appends — to the previously created contents list — a list item containing a link to that heading.

Further info

That’s really about it. I hope you’ll be able to bend the code to suit your site if you need to, since this is something I came up with that suits my own blog and posting habits.

I think the biggest things in understanding this chunk of code is getting your head around CSS selectors and jQuery syntax. So, for more info on those check out the jQuery official documentation and HTML Dog or tizag.com CSS tutorials.

Comments

  1. You’ve written many amazing tutorials, but this one may be my favorite. I’d implemented my own version of this on my site, but it was trash compared to this. After only a few moments (took me a second to realize I needed to add the body class, must’ve skimmed over that part), I had it up and running flawlessly.

    You are my hero. But seriously. =)

  2. I am trying to implement it now but I have two questions if you don’t mind. When I put the entire script in the post javascript in Thesis for the individual post the contents show up but when I try making menu.js it and linking to the js file it doesn’t work. The file is uploaded at http://scottwyden.com/js/menu.js

    Second question, is there a way to make the contents drop below the post image?

    Thanks for your help on this. :-)

  3. Thanks Matt & Gregory!

    Scott — What code are you using to add the js file when it doesn’t work?

    I’m sure there is a way to have it “drop below the post image”, but I’d need a specific example to explain it more specifically, other than saying there’s loads of stuff in jQuery that can help you do that. E.g., if you add the toc list to a different element, one after the post image, or just insert it after the post image — I’m sure that’s doable — then that’s a step in the right direction. If you want a slide down effect on hover or something, then that can be done too with the .hover() event and the .slideDown() effect in jQuery. I don’t know jQuery as well as I know CSS and HTML, so I usually find myself going through the different sections of docs to see if the function that I imagine could exist really does, and often it does. When doing that, the “API REFERENCE” section in the sidebar of the docs page is crucial.

  4. The code I’m using is

    I would want it to just appear under the Thesis post image because I am typically using 525px wide post images so having the contents above it looks funny. I’ll have to look into it further.

  5. Sorry I just noticed the script didn’t paste

    script type=”text/javascript” src=”http://scottwyden.com/js/menu.js”></script

  6. Just curious – if you had a “short” page – say only 500 pixels in height, on a 1280×1024 screen, doesn’t anchor based navigation fail, since it can’t take you to that section? Kind of like having a “Go to top” on a short page – since the “#top” is already on the display, clicking it wouldn’t take you there.

  7. Scott — Heh, oh well. Glad it’s working now!

    Joseph — Yes, I think you’re right, the browser won’t take you further than the length of the page, so sometimes anchor based nav isn’t perfect. However, if the page contents were that short, I probably wouldn’t have need of sub-headings and the contents list wouldn’t be generated. I generally use headings to break up these long tutorial posts…

  8. Gary

    You might be able to squeeze a little more optimisation out of it (you’re selecting jQuery(headers) twice), but otherwise, lovely code :)

  9. Gary — How would you optimise it? When I started, it was crucial to the code to have the ul creation before and separate to adding the list items, because of the conditional I was using, but then it morphed into this…

  10. Avi D

    A bit of help, this tut is way too techy for me…

    I’ve added in the menu.js file to my scripts folder and I’ve referenced it from Design Options>Additional Scripts.

    I’m stuck on the first part of headers/sub-headers with #IDS. I looked at your source but that wasn’t much help either…I just saw but no #IDs.

    How do I implement this? What was your code for, say, the subheading “THE CODE”?

  11. Avi — You need to add an ID to the headings within your post that you want to show up in the summary menu.

    Here is a screenshot of the code making up this post and you can see the h3 headings have an ID.

    Those, along with the javascript properly added to the page, is all that is needed.

    If you normally add headings via the Visual editor in WordPress you will need to switch to the code editor to add the IDs to them.

  12. Just stumbled on this. LOL I’m such a hack. I usually do this with custom fields. Custom fields allow for more control, but this is so much more efficient. Awesome stuff.

  13. Avi D

    Thanks for the screenshot Kris…

    I’ve added the IDs to my H2 headings and made corresponding changes to the menu.js script by adding in .format_text h3[id],

    I’ve even copied the CSS of this element off here to see if it works.

    Still no go.

    What am I still missing? I’m guessing something to do with #toc?

    The test post is here…. http://www.avinashdsouza.me/al.....ai-angels/

  14. Dear
    Can you please write this tutorial specially for Thesis Theme. Its quite hard to understand for the newbies. I’d like to request you to write it again for adding it in Thesis Theme.

    Thanks :)

  15. Saif — I prefer for people to learn the basic principles so that they can take the information and convert it to suit their needs. As I said in the post, what I did suits my own blog and content structure, but you need to make it suit yours. Which is why I linked to more elementary tutorials in the last paragraph, and also the Thesis basics tutorials on Tech for Luddites will help in terms of writing functions and hooking them in.

    I know this isn’t just a copy and paste tutorial, and I didn’t intend it to be; you need to read it carefully to understand what is going on.