In order to use the table of contents generator, you need to
Don't worry that the javascript might mess up your ids. If there's an existing id on a header tag, then the script uses it. If not it will add one so we have a target for the link.
The memory footprint of the generator is small, and after the table of contents is created, the generator is deleted freeing up even that small amount.
The code works fine in javascript 'strict' mode so you don't have to worry if you (as do I) run all your code in strict mode.
We take the existing toc div and put two things inside of it:
<div> id='toc'>
<span id='toc_toggle_target'> Click to show Table of Contents </span>
<div> id='toc_box'>
<a class='toc_classH2' href='#id of some h2 header'>The text from that same header</a>
<a class='toc_classH3' href='#id of some h3 header'>The text from that same header</a>
<a class='toc_classH3' href='#id of some h3 header'>The text from that same header</a>
Inside the div toc_box we put a horizontal rule to separate the links and then a series of anchor links. Each one of them is built to represent one of the headers in the file, and has a class made up by taking the text toc_class and appending to it the particular header's nodeName, one of H1, H2, H3, H4, H5, or H6. The result are the class names toc_classH1, toc_classH2, toc_classH3, toc_classH4, toc_classH5, and toc_classH6. We use those later to apply styles to them.
The getStyleObj function, given a reference to an object, and the name of the style, will fetch the value of the style for you. I use it to get the min-width and max-width for the table of contents so that the user can choose the minimized and opened widths for the table of contents.
I've made the table of contents generator an object so that none of its variables and methods pollute the global namespace. That means that we have to declare one before you can use it, and we'll see that at the bottom of the script.
In case you don't know, you make an object in javascript by declaring a function. Anything declared with this. a part of the name is a public class variable, and anything declared with var is a private class variable. Making it an object puts everything together in one tidy package.
I set up the strings to be shown to ask the user to expand and contract the table of contents, and also set the default widths that the table of contents will be expanded and contracted to. Later I might very well make the strings and the widths arguments to the class function, but as of yet I haven't had a need. Feel free to change it if you wish. As long as you put in the code that you got it from me, I'm happy for you to use it for any purposes at all.
Here you see that we declare a local variable toc, and use it to hold a reference to the toc div. We also declare two variables, toc_toggle_target and toc_box which we'll use in a minute when we create them.
Here I check to see if someone has applied a min-width or a max-width style to the toc div. Annoyingly enough, if min-width doesn't exist the DOM returns '0px' but if max-width doesn't exist the DOM returns 'none'. The CSS2.1 spec lists the initial value of min-width as 0, and of max-width of 'none' so it's probably portable. If anyone knows more about this please feel free to email me. In any case, if I can tell that they're set, I override the default values with the ones from the style sheet.
This private method is the callback that's called when we click on the table of contents. Remember the two strings that ask the user to click to hide or show the table of contents, as appropriate? Their presence as the html to display in the toc_toggle_target is used as my signal to know whether the table of contents is now displayed or hidden. If toc_toggle_target's innerHTML has the same value as the toc_clicktoshow string, then we're currently hidden and preparing to show. Otherwise, we're currently showing and preparing to hide.
So when we change state, we currently change four things:
We create an Array called headers, fetch all of the headers of each type in turn, and push everything onto the end of the headers array. The problem with this of course is that now we have all of the h1s, then the h2s, through the h6s, surely not the same as the order they occured in the page. We could have iterated through all document elements and inserted the headers in the array as we found them and then they'd be in order. It would be slower though.
So next we need to sort. We're going to call the sort() method on the headers array, which expects a comparison function as its argument. The comparison function which when passed two array elements, a and b, will return:
So the only hard part is coming up with a comparison function for sort that will return what we need. There are two choices, neither one of which works for all browsers.
We look at the first element of the array, (it could have been any) to see if if headers[0].sourceIndex exists. If it does, (IE and Opera), then it would be the index value of the element in the array we would get if we hypothetically called getElementsById('*');.
subtracting the sourceIndex of two elements will do what we want. If, for example, one header had a sourceIndex of 17, and the other 42, then subtracting 42-17 is a number > 0 so we know that 17 preceeds 42. If we were comparing them in the opposite order, we'd have 17-42 is a number < 0 so we know that 42 follows 17.
compareDocumentPosition is a DOM Level 3 method. If it exists (IE>8 or Firefox, Safari, Chrome, Opera), then we'll use it. It returns a bitmask made up of the following values:
Our job is to turn this into something that the sort method will want. First we see that we only care about two values, DOCUMENT_POSITION_PRECEEDING, or DOCUMENT_POSITION_FOLLOWING. There bit values add up to 6, so we take the return value, and do a logical AND with 6 to get rid of anything we don't care about. We're left with either a 2 if the second one preceeds (we want this case to turn into a postitive number) the first, or a 4 if the second one follows the first (we want this one to turn into a negative number). If we subtract the value from 3, i.e. either 3 - 2, or 3 - 4, we get just what we want.
You'll notice that the method for this function is not assigned to a var, but rather to this.genTOC. That means that genTOC is a public method, and users of this object can see and use it. (In point of fact it is the only public method.)
First, if there was no <div id='toc'></div> in the document, then we return right away. This way if the script file containing and instantiating this class is included in a file that doesn't want a table of contents we bail out early, no harm, no foul. As you'll see, after we generate the table of contents (or decide that we aren't going to in this case), we throw away the instantiated toc_generator so that the memory used in the instantiation can be reclaimed by the javascript garbage collector.
We set the click callback for the table of contents, and, assuming that we're going to start off by hiding the table of contents, set the width to the narrow width.
Then we create the span, toc_toggle_target the only part that remains visible when the table of contents is hidden. We set its id so we can style it later, and set its innerHTML to ask the user to click it to show the table of contents.
We create the toc_box div and set its id so we can style it, make it hidden with no display as discussed above in the discussion of the toggleTOCVisibility method, and as a first child of the div, append a horizontal rule.
We loop through all the headers, and for each one we first check via regular expression if its class contain 'notoc'. If not, we'll put it into the table of contents. We create an anchor link and append it to the toc_box div. For each one, we:
At the end we free up the headers since we used new to allocate the Array.
So far we've been declaring the constructor for an object called toc_generator(). Now we want one, so we assign it to toc, call the method to create the table of contents (remember it returns without doing anything if the toc div doesn't exist), and then delete the object. We're done.
If we ran it for this page, without any styling, the result would be an unholy mess. The anchors we'd created would all be on the same line. And the Click to show Table of Contents line wouldn't stand out at all as a target to be clicked on.
We center the text because we want the toc_toggle_target span text to be centered. We can't style the span to do it, because that would just center inside the span, not center the span in the table of contents.
Here's where we can set how narrow and wide the table of contents is with min-width and max-width. We make the background color slightly greyer than the white so it will show up, set the font we want, and for newer browsers add a drop shadow.
We make the font smaller for the part we click on, and set the cursor. Without setting the cursor, we'd get the I-Beam cursor like you would for text, and it wouldn't communicate to the user that they should click on it.
We set display to block so we get one anchor per line, without having to insert <br /> from the script. It seems more elegant. We align left to get rid of the center text-align inherited from the parent div, set the text decoration to none so links are not underlined, and set the color to black for the text.
When people put their cursor over a link we underline them to make it clear that these are links.
If they haven't visited them we turn them blue, and if they have, we turn them purple.
This simply allows us to make higher header numbers be indented more.
I hope this helps you learn more about javascript. Click for the lastest version of my utilities.js script