Creating a Table Of Contents

This topic explains how to create a multi-page, cross-section Table Of Contents (TOC) using a Post Pagination Script.
For information about Post Pagination Scripts in general, see Post Pagination Scripts.
The basics of script-writing in the Designer are explained in the following topic: Writing your own scripts.

Step 1: Opening a Print template

Create or open a Print template.
Make sure to use HTML headings level 1 and level 2 (<h1> and <h2>) in your Print sections, if you want to use the script sample that is given in Step 3. This script collects the text and page numbers of these headings and puts them in the TOC.
To quickly change a paragraph into a Heading, place the cursor inside of it, or select the paragraph (see: Selecting an element). Then select the appropriate element, either on the Format menu, or from the 'Element type' drop-down on the toolbar.
Of course, you could just as well create a table of contents using other heading levels, or even other elements. In that case you'll have to adjust the script accordingly.

Step 2: Creating a placeholder for the TOC

Create one extra Print section to put the Table Of Contents in.
Inside the extra section, insert an Article element (see Inserting an element).
Give the element an ID, for example: toc-content. This element is the placeholder for the TOC.

Step 3: Inserting the Post Pagination script

Insert a Post Pagination script (see Adding a Post Pagination Script).
Double-click on the new script to open it.
Set its selector to the ID that you specified in Step 2, preceded by a #, for example: #toc-content.
Paste the following code in the script editor:

Copy
// Build the TOC
var toc = '';
merge.context.query("h1, h2").each(function() {    
    var pageNo = this.info().pageNo;
    var text = this.text();    
    var level = this.tagName();    
    
    toc += '<p class="toc-entry ' + level + '">';
    toc += '<span class="text">' + text + '</span>';
    toc += '<span class="dots"></span>';
    toc += '<span class="number">' + pageNo + '</span></p>';
});
    
results.html( toc );

// Repaginate the result
merge.section.paginate();

// Update the page numbers
var $numbers = query('.number');
merge.context.query("h1, h2").each(function( index ) {
    var pageNo = this.info().pageNo;
    var entry = $numbers.get( index );
    if( entry ) {        
        entry.text( pageNo );    
    }
});

What the script does

First the script creates a variable to hold the table of contents: toc.
Then it collects all <h1> and <h2> elements - in other words, level 1 and 2 headings. The merge.context.query(selector) function searches across all Print sections (see query(selector)).

The query returns a result set. Each of the elements in the result set goes through the callback function defined in each() (see each()).

The callback function gets the element's page number, text and HTML tag name:

Copy
    var pageNo = this.info().pageNo;    
    var text = this.text();    
    var level = this.tagName();

Note that the info() function can also be used to get an element's sheet number, the section it is located in, and the total page count and sheet count of that section (see PaginationInfo and info()). In this case only the page number is used.

Then the callback function adds an entry to the variable that holds the table of contents, using the retrieved info.

Copy
    toc += '<p class="toc-entry ' + level + '">';    
    toc += '<span class="text">' + text + '</span>';    
    toc += '<span class="dots"></span>';    
    toc += '<span class="number">' + pageNo + '</span></p>';

The HTML tag name is added as a class. This can be used in a CSS file to style the entries in the table of contents according to their level. (See Step 4: Styling the table of contents.)
The empty span between the heading's text and page number has the class dots. This is used to put dots between heading and page number.
The number class (for the page number) is not only used in CSS, but also later on in the script.

The table of contents is inserted in the section with: results.html( toc ); (see html()).

The table of contents may get too long for a single page and affect the page numbers in other sections. In that case it is necessary to re-paginate the content; merge.section.paginate(); does the trick.

Note: Whether page numbering restarts in each section, depends on the settings for page numbering; see Configuring page numbers. By default, page numbering starts with page 1 for each section.

If the pagination process has changed the page numbers, the TOC needs to be updated as well.
To do that, the script first has to collect the page numbers from the table of contents. This is where the number class comes in handy: var $numbers = query('.number');.
Note that this query() function, as opposed to merge.context.query(), only searches the current section (see query()).
Then, the level 1 and 2 headings are collected from all Print sections again using merge.context.query("h1, h2").
The callback function in each() retrieves the heading's new page number. It then uses the index number of the heading in the result set to get the corresponding entry in the TOC: var entry = $numbers.get( index ); (see get(index)), and replaces it with the new page number.

Excluding headings

Often there are certain headings that you don't want to appear in the table of contents. The title of the table of contents itself, for example.
To exclude these headings from the table of contents, do the following:

  1. Give all the headings that you want to be ignored the same class (see ID and class), for example ignore-me.

  2. Add the:notselector to the queries in the script, specifying that class. Remember to put a dot before the name of the class. For example: merge.context.query("h1:not(.ignore-me), h2").

Adding internal hyperlinks

It is possible to create links in a table of contents that point to the respective position in the document (for example when generating PDF output). An internal hyperlink points to an element with a specified ID. (See: Hyperlink and mailto link.)

Here's how to change the each() function in the script in order to add internal hyperlinks to the table of contents:

  1. Add the following variable: var linknumber = 1;. This variable is used to generate unique IDs for elements that don't have an ID.

  2. Before the first line that starts with 'toc ', add the following:

    Copy
    var anchorId = '';
    if (this.attr("id")) {
    anchorId = this.attr("id");
    }
    else {
    anchorId = 'generatedID-' + linknumber;
    linknumber++;
    this.attr("id", anchorId);
    }

    This code takes the element's ID. If an element doesn't have an ID, the script generates a new, unique ID for it.

  3. Replace the line:
    toc += '<span class="text">' + text + '</span>';
    with this line:

    Copy
    toc += '<a class="text" href="#' + anchorId + '">' + text + '</a>';
  4. The new line adds a hyperlink: <a ... href> with the element's ID to the table of contents.

Step 4: Styling the table of contents

Each component of the table of contents inserted in Step 3 is wrapped in a <span> element that has a class, for example: "dots" or even multiple classes: "toc-entry h1". (Class names are separated by a space.)
These classes can be used to style the table of contents with CSS. The principles of styling with CSS are explained in another topic: Styling templates with CSS files.

Add the following styles to the style sheet for the Print context (see Step 1: edit CSS) to align the page number to the right and fill the space between the text and the page number with leader dots.

Copy
p.toc-entry {
display: flex;
margin: 0 0 0.25em 0;
}

p.toc-entry .text {
flex-shrink: 0;
}

p.toc-entry .dots {
flex-shrink: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: clip;
}

p.toc-entry .dots::after {
font-weight: normal;
content: ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ";
}

p.toc-entry .number {
flex-shrink: 1;
text-align: right;
font-weight: bold;
}

These styles make use of the CSS Flexbox Layout Module. For more information about that, see MDN Web Docs: Flexbox.