Introduction
I recently converted this blog from raw Jekyll to Octopress. Octopress uses Jekyll underneath, but it layers a large number of blog-friendly capabilities on top.
One of the features I need to rebuild for Octopress was the ability to generate a table of contents (like the one for this article), for some of the larger articles.
This article describes how I accomplished that goal.
Requirements
I wanted the generated table of contents to meet the following requirements:
- It should be easy to style. In particular, I want it to look different on the screen and printer-friendly versions of a page.
- It should be implemented as an HTML unnumbered list (i.e., a <ul>), for maximum styling flexibility.
- It should be automatic: The code should generate the table of contents from the headings (<H1>, <H2>, etc.) in the document, without my having to mark the headings in some way.
- It should be optional: I should be able to enable or disable it, on a per-article basis.
- If I accidentally enable it on an article that has no heading elements, I don’t want to see an empty table of contents in the document.
Server-side or client-side?
Ideally, since Octopress generates static HTML, I’d like to have a Liquid tag to embed in the appropriate place inside one of my templates. Something like this would be perfect:
1
|
|
Unfortunately, that’s a bit of a pain to implement. Instead, I elected to use Doug Neiner’s jQuery table of contents plugin to generate the table of contents in Javascript, when the browser loads the page.
Implementation Steps
jQuery
You can either download jQuery and install it directly in the source tree for your Octopress blog, or you can use it from one of the public CDNs, like Google. See http://docs.jquery.com/Downloading_jQuery for a list of CDNs.
If you elect to download it and install it locally, copy the appropriate
file (e.g., jquery-1.7.1.min.js
) to your blog’s source/javascripts/
directory.
You’ll also need the jQuery table of contents plugin. Download it and
put it somewhere within your blog’s source. I stored it in
source/javascripts/jquery.tableofcontents.min.js
.
Next, modify source/_includes/custom/head.html
to include <script>
tags for
jQuery and the table of contents plugin. For a local install, use this code:
1 2 |
|
If you’re using the version of jQuery hosted on Google, use this code:
1 2 |
|
You also have to use jQuery.noConflict()
, to prevent conflicts between
jQuery’s use of the ‘$’ alias and the ‘$’ used in ender.js
, which is
automatically included by Octopress. So, regardless of where you source
jQuery, add this code, right after the <script>
tag that pulls jQuery in:
1 2 3 4 5 |
|
Generating the Table of Contents
To generate the table of contents, you’ll need to add some Javascript that fires when each page loads. There are two conditions, however:
- The Javascript should only run if the article is marked as requiring a table of contents.
- The Javascript should not run if Octopress is generating the index page, which can have multiple blog articles on it.
The Javascript
First, let’s take a look at the Javascript itself. I chose to put the bulk
of the logic into its own function, in a separate Javascript file called
generate-toc.js
. (You can see the source for that file in my
blog’s GitHub repo. I’ve also reproduced it, below.)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The insertBefore
parameter is a jQuery string selector for the element to
search for the table of content headings. The optional heading
parameter
specifies the heading to precede the table of contents.
Copy generate-toc.js
to source/javascripts
and put the following line in
source/_includes/custom/head.html
:
1
|
|
Hooking the Javascript In
The next step is to call generateTOC()
at the right time. The following hunk
of code goes at the bottom of source/_includes/custom/after_footer.html
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Things to note:
Octopress sets the index
variable if it’s generating the index page; if that
variable is set, we don’t want to generate a table of contents.
Note, too, that the code only generates the table of contents if the page.toc
variable is set to “true”. page.toc
will be true only if the following line
is in the YAML front matter of an article:
1
|
|
For example:
1 2 3 4 5 6 7 8 |
|
If the toc
line is missing or set to something other than “true”, the
table of contents is skipped.
The Javascript fires when the document has finished loading, using jQuery’s
jQuery(document).ready()
hook. Octopress assigns the .entry-hook
class to
the <div>
element that contains the generated article content. Passing that
selector string to generateTOC()
ensures that we don’t pick up any heading
elements that happen to live somewhere else in the HTML. The second parameter,
the string “Table of Contents”, puts a heading above the generated table of
contents.
Styling
I’m using a few locally-defined mixins within my Sass files. You may wish to use something like Bourbon, instead, since it provides these capabilities, and more. (I may switch to Bourbon myself, at some point.) However, for this article, let’s assume you’re using locally-defined ones.
Store the following definitions in sass/custom/_mixins.scss
. (The source
is available at
https://github.com/bmc/brizzled/blob/master/sass/custom/_mixins.scss.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Then, modify sass/custom/_styles.scss
to import the mixins:
1
|
|
Screen versus Print
generateTOC()
puts the table of contents inside a <ul>
element, which tells
the jQuery Table of Contents plugin to generate a nested list. I chose to
style that list one way for the screen and another way for the printed page.
(You can see the difference by printing this article.)
Screen Styling
Octopress already has a sass/screen.scss
file, but I want to keep my local
screen-specific stylings in a custom file. So, I created
sass/custom/_screen.scss
for my screen-specific rules, and added this line to
sass/screen.scss
:
1
|
|
Then, in sass/custom/_screen.scss
, I put the following rules:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
That styling:
- causes the table of contents to float to the right of the text
- gives it a light gray background
- ensures that the nested lists don’t have too much indentation
- forces all lists to use disc bullets, regardless of nesting level.
Printer-friendly Styling
Octopress does not (yet) ship with a sass/print.scss
file, so I created one.
For consistency with the screen styling (and the rest of the SASS files), that
file just includes a custom sass/custom/_print.scss
file. Here’s
sass/print.scss
:
1
|
|
Then, in sass/custom/_print.scss
, I put the following rules:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
Voilà!
The result of all that work is a table of contents that looks like this:
