Search results

Building a table of contents with DITA versus Jekyll

Series: Jekyll versus DITA

by Tom Johnson on Apr 2, 2015
categories: dita jekyll

In my ongoing series comparing Jekyll against DITA, I want to touch on how you construct a table of contents.

Creating a TOC with DITA

The ditamap file in DITA is arguably the most important file in a DITA project, and it has a lot of features. Basically, the ditamap defines the table of contents for the project, which is how users navigate all the files (apart from search).

Additionally, any file that you want included in your output must appear in your ditamap file. Otherwise it's excluded from the build.

A project can have multiple ditamap files. You might have separate ditamap file for each output, or you could use the same ditamap file with conditional tags for multiple outputs.

In the ditamap file, you reference each topic you want included like this:

<topichead navtitle="Links">
 <topicref href="keyref_links.dita"/>
 <topicref href="inline_links.dita" audience="field_engineers"/>
 <topicref href="related_links.dita"/>
 <topicref href="relationship_tables.dita"/>

Notice how you can add attributes to the topicref elements. Here, if the build conditions exclude the field engineer audience, the inline_links.dita topic will be excluded.

Additionally, hierarchy is established by nesting the elements. If you want to create sublevels, you nest the topics like this:

<topichead navtitle="Links">
 <topicref href="keyref_links.dita">
   <topicref href="inline_links.dita" audience="field_engineers"/>
 <topicref href="related_links.dita">
   <topicref href="relationship_tables.dita"/>

Here the inline_links.dita topic will appear in a sublevel below keyref_links.dita. Similarly, relationship_tables.dita will appear nested below related_links.dita.

You can combine topics if you use the chunk attribute, like this:

 <topicref href="inserting_links.dita" chunk="to-content">
 <topicref href="inline_links.dita" toc="no"/>
 <topicref href="related_links.dita" toc="no"/>
 <topicref href="keyref_links.dita" toc="no"/>

Now the inserting_links.dita topic will contain inline_links.dita, related_links.dita, and keyref_links.dita files as additional sections in the same inserting_links.dita topic, rather than appearing as separate files.

If you want to include any non-DITA files, you would list them in the ditamap with a special processing-role attribute:

<topicref processing-role="resource-only" href="notes.dita" />

For more details on ditamap files, see my page on ditamap files.

Creating a TOC with Jekyll via categories

Jekyll provides a lot of different ways to create a TOC. Since many Jekyll sites are blogs, more emphasis seems to be given to post sorting than page sorting, but Jekyll also provides an interesting paradigm to handle the TOC for pages.

You have at least two main options for create a TOC. You can add a category and weight as frontmatter in your page, and then run for loops to sort the content based on the category and weight.

If you want to sort pages by category, you would put a category in the frontmatter of a page, like this:

title: My Page
permalink: /mypage/
category: getting_started
weight: 1

You could then use a for loop to get all pages in this category sorted by weight:

{% for page in site.pages | sort: weight %}
{% for tag in page.tags %}
 {% if tag == "getting_started" %}
    <li><a href="{{ page.permalink | prepend: site.baseurl }}">{{page.title}}</a></li>
   {% endif %}
  {% endfor %}
{% endfor %}

This approach may get tedious, though, especially if you want to easily shift and arrange your TOC without opening every single page to change these frontmatter values.

Creating a TOC in Jekyll via data files

Another approach, and the one I'm currently using, is to list your TOC entries in a YML data file. Here is the typical structure of a YML file containing TOC entries. The YML syntax emphasizes human-readable code, so a lot of the formatting is dependent on spacing:

- title: Sidebar
    - title: Overview
        - title: Introduction
          url: /introduction/
          audience: customer</p>
<p>        - title: Release Notes
          url: /release_notes/
          audience: customer, fe</p>
<p>        - title: High-level Summary
          url: /high_level_summary_customer/
          audience: customer</p>
<p>        - title: High-level Summary
          url: /high_level_summary_fe/
          audience: fe</p>
<p>    - title: Configuration
        - title: Configuration overview
          url: /configuration_overview/
        - title: "Configuration Details: First Steps"
          url: /configuration_details/

Now the HTML code in your template uses a for loop to iterate through each of these items and wrap them in the right formatting for your theme. Here's an example of how the template code might look:

{% if site.audience == "fe" or "customer" %}
{% assign sidebar = %}
{% assign buildAudience = "fe" %}</p>
<p>{% elsif site.audience == "operations" %}
{% assign sidebar = %}
{% assign buildAudience = "ops" %}
{% endif %}</p>
<p><ul id="mysidebar" class="nav">
{% for entry in sidebar %}
  {% for subcategory in entry.subcategories %}
    {% if subcategory.audience contains buildAudience %}
       <li><a href="#">{{ subcategory.title }}</a>
           {% for item in subcategory.items %}
              {% if item.audience contains buildAudience %}
                <li><a href="{{item.url | prepend: site.baseurl}}">{{item.title}}</a></li>
              {% endif %}
          {% endfor %}
   {% endif %}
 {% endfor %}
{% endfor %}

When we start the for loop, what sidebar refer and buildAudience refer to is based on the assignments defined. These assignments depend on properties in the particular configuration file that's being used. (This is how the theme is separated from the content.)

The for loop looks in the sidebar list and loops first through the items, and then the subcategories, iterating through each item in the list. Each item is wrapped in a link format. Additionally, only the items containing the buildAudience attributes are included in the list. (This buildAudience element allows us to single source the data file, using the same data file for multiple audiences.)

You can get more fancy with the sidebar formatting, such as showing active links and more. But this shows the basic logic. My intent here is not to explain the code in detail but rather to give you a glimpse of how to build a TOC in Jekyll.

Note also that the logic seems more complex here, but I've taken the explanation one level further than I did with DITA. With the DITA TOC explanation, I didn't get into how the Open Toolkit (OT) processes the ditamap file to render it into HTML and other outputs. I actually have no clue how the OT does the processing -- that's usually a behind-the-scenes process.

With Jekyll, though, once you understand how for loops iterate through items in a YML list, it's fairly accessible to configure how the TOC items get processed.

One important difference with Jekyll is that all content is included by default, whether it appears in the navigation or not. If you want to exclude content, you must list it in the Excludes property in your configuration file.

Multiple types of navigation

The ditamap structure tends to break down when your content resembles more of a knowledge base than a user guide. Because you can't tag pages or add pages to specific categories, about the only way to add DITA files to a knowledge-base-style output is by creating an enormous TOC or by not creating a TOC at all, but instead just relying on search.

In contrast, with a Jekyll project, I could create both a TOC-style navigation for hierarchical content, and a tag-based navigation for knowledge-base style content.

Dynamic assembly of content

One strength of DITA is the ability to dynamically assemble larger topics from smaller ones, but this might also be seen as a weakness. In OxygenXML's webhelp output, if you use the chunk attribute to glue a topic together at build time, you can still find the individual chunks in search (they're only glued together in the TOC, not physically in the file contents).

These individual chunks are problematic -- they don't make much sense out of context from the larger topic they were chunked into. (You might think the chunk attribute permanently glues them together, but that magic only happens in the TOC.)

Additionally, if you have a relationship table in a "chunked" topic (meaning a topic that is dynamically assembled through the chunk attribute), the relationship table appears (oddly enough) after the first chunk, and then the other sections appear (see this post for more detail).

This odd behavior of the Open Toolkit is necessary (I think) so that the relationship table can appear at the bottom of the all the individual chunks that don't appear glued together in the TOC topic but rather surface independently, for example, through search.

If you're going to include a topic in another topic, there's no reason not to use a conref from within the topic itself, or if using Jekyll, to use includes from within the topic.


Overall, I hope to have shown that Jekyll provides some robust options when it comes to creating your table of contents. What's especially important is that the TOC content is not merged into the theme in a tight way. Content is separated from format (stored in a data file) and merged together at build time with your theme's template.

About Tom Johnson

Tom Johnson

I'm an API technical writer based in the Seattle area. On this blog, I write about topics related to technical writing and communication — such as software documentation, API documentation, AI, information architecture, content strategy, writing processes, plain language, tech comm careers, and more. Check out my API documentation course if you're looking for more info about documenting APIs. Or see my posts on AI and AI course section for more on the latest in AI and tech comm.

If you're a technical writer and want to keep on top of the latest trends in the tech comm, be sure to subscribe to email updates below. You can also learn more about me or contact me. Finally, note that the opinions I express on my blog are my own points of view, not that of my employer.