Objectif Lune joins Upland Software.Learn more >

Back to all How-tos

Creating a Table of Contents using a Post Pagination script

As of version 2018.2 Connect Designer can now run scripts after the personalization and pagination process (print templates). The Post Pagination scripts run for all sections and comes with commands to fetch page information of elements. When needed a Post Pagination script can re-paginate a section which is handy when changes were made to the content (which may effect the number of pages in the already paginated section). This article describes how a Post Pagination script can be used to generate a multi page and cross section Table Of Contents (TOC).

Introduction

In this sample you will learn how to generate a Table Of Contents based on <h1> and <h2> elements using a Post Pagination script. The template used consists of multiple sections containing these elements. A separate section is used to hold the TOC content. Consider the following structure:

create

 

Creating a Post Pagination script

The Scripts panel in the Designer containers three sections:

  1. Control scripts, these scripts run before the personalization scripts. They are used to control the visibility
    of sections, clone sections, set sheet configuration settings of sections etc. Stuff that needs to take place
    before the personalization scripts.
  2. Standard scripts, these scripts are used to personalize the document. Typically these scripts copy data to
    the document, show hide elements based on data or use data to load content from snippets/remote
    resources.
  3. Post Pagination scripts, these scripts run after the personalization and most importantly after the
    pagination process. The pagination process applies page breaks, adds master pages and sets the media. A
    Post Pagination script can be used to query the rendered document and collect information of elements
    (e.g. the page the reside on, check enabled sections etc).

Our Post Pagination script will collect page information of <h1> and <h2> elements. Subsequently it inserts this information along with text of the respective elements into the TOC section. As our TOC content could be too long for a single page we force an update of the pagination process and update the page numbers in the TOC.

To create the Post Pagination script:

  1. Open the attached 1. sample template.
  2. Right mouse click the Post Pagination folder in the Scripts panel.
  3. Choose New > Post Pagination Script from the contextual menu.
  4. Rename the file to your liking.

Create

Gathering page info

Double click the TOC section in Resources panel, the respective section appears in the main editor. Below the headline you’ll find an <article> element containing the text: {TOC content goes here}. This element has set its ID to #toc-content and is the placeholder for our TOC content.

Our script will perform a few tasks:

  1. Perform a query across sections.
  2. Gather information.
  3. Construct a HTML string with the information.
  4. Add the concatenated string to the document.

The merge.context object is the entry point from where you can query across sections in the current context (e.g. print, web, email). It is not possible to query information across multiple contexts. In this sample the query() command selects all <h1> and <h2> elements and will return a cross-section result set.

merge.context.query("h1, h2");

Adding the each() command with a callback function iterates over the elements in a result set (e.g. each <h1> or <h2> element one by one). The callback is passed the iteration index and the current element. In the scope of the callback, this also refers to the current element.

The following script combines these commands and uses the info() command to fetch the page number of the current element. In addition the text() command is used to fetch the content of the heading element and the tagName() command is used to temporarily store the name of the element (e.g. H1 or H2).

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="' + level + '">' + text + ' <strong>' + pageNo + '</strong></p>';
});
results.html( toc );

After the information is gathered a <p> element is constructed with this data. The tag name is written as a CSS class so we can change the appearance of these lines to mimic levels. The HTML string is appended to the toc variable and the concatenated data is written to our placeholder element (set the selector of the script to #toc-content). The result would look something like this:
Create

Re-paginate

The attached sample document contains multiple sections and many <h1> and <h2> elements. The content for our TOC runs across multiple pages. The content will overflow our TOC page but as we are running a post pagination script no new pages are added.
Create

Use merge.section.paginate(); to re-paginate the content of the current section. All nice, but what about the page numbers in our TOC? Re-paginating the TOC inserts new pages so the page numbers of subsequent sections will shift. Let’s take a look on how to solve this.

Updating the page numbers

In order to update our page numbers we need to do a second pass on the document. First we query for the page numbers in our TOC and store the result in variable called $numbers. Subsequently a cross section query is done for the <h1> and <h2>. This is similar to the initial part of the script. While iterating over the result using each() the page number is fetched and stored in a variable. Now we use the index of the current iteration to update the text of the accompanying page number stored in the $numbers variable. This writes the new page number to our layout.

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

The TOC is updated, the content is paginated and it shows the updated page numbers.

Create

Removing the TOC section title from the TOC

Our TOC contains the title of the TOC section. This is caused by the fact that this text is using an <h1> element. Sounds familiar! You probably encountered this when generating Table Of Contents in tools like MS Word.
Create

This can be fixed by adding a class to the <h1> element of the TOC title and excluding this in the query() entries of our script. How to achieve this:

  1. Assign a class to the <h1> element of the TOC title, for example: ignore-me
  2. Add the :not(.ignore-me) selector to the queries in our script, see the code below.
merge.context.query("h1:not(.ignore-me), h2")

 

Note! Make sure you update both merge.context.query() entries in the script.

Create

Beautify

So we have the content for our TOC in place. Lets take a look on aligning the page number to the right and fill the space between the text and the page number with leader dots. This part is not related to Post Pagination scripts but simply cosmetic. In order to achieve our desired design we will make use of Flexbox in CSS. We will not elaborate on the gory details of display: flex here as there are plenty online resources available (Google is your friend). The desired design:

Create

Here is the recipe: change the script so that each component of the TOC entry is wrapped in a <span> element (e.g. wrap the text, wrap the page number and add an empty element for the leader dots). Assign each element a class as shown below:

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

In this scenario you will need to change the selector to retrieve the page entries from strong to .number in the section of the script that updates the page numbers. The complete script:

// Build the TOC
var toc = '';
merge.context.query("h1:not(.ignore-me), 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:not(.ignore-me), h2").each(function( index ) {
var pageNo = this.info().pageNo;
var entry = $numbers.get( index );
if( entry ) {
entry.text( pageNo );
}
});

The following CSS styles enables flex content for the children of the paragraph (our TOC entry) and populates the .dots element with leader dots.

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;
}

 

Et voila!

Some notes

  1. This article shows how to create a TOC using a Post Pagination script. It is our goal to introduce a
    dedicated TOC feature in a future version of Connect.
  2. It is currently not possible to make TOC entries link to the respective position in the document (for
    example when generating PDF output). This is scheduled for a future version of Connect.

Downloads:

The template used in this sample: scripting-a-toc.zip

Leave a Reply

Your email address will not be published. Required fields are marked *