Table of Contents (TOC) Generator

Table of contents generator.
Usage
Quick Start
import Contents from 'contents';
const contents = Contents();
document.querySelector('#your-table-of-contents-container').appendChild(contents.list());
contents.eventEmitter().on('change', function () {
console.log('User has navigated to a new section of the page.');
});
const newHeading = document.createElement('h2');
hewHeading.innerHTML = 'Dynamically generated title';
document.body.appendChild(newHeading);
contents.eventEmitter().trigger('resize');
Examples
The code for all of the examples is in the examples folder.
Raise an issue if you are missing an example.
Introduction of ES6 in 4.0.0
Similar Libraries stats have been generated in 22-Nov-14 08:44:41 UTC. Since then Contents has evolved a lot. The source code is written in ES6 and depends on babel-core to run. In projects that already depend on Babel and use webpack to build packages, this is not going to be a problem. Other projects need to consider the relatively heavy weight of the generated package.
Similar Libraries
Last updated: Saturday, 22-Nov-14 08:44:41 UTC.
Required 3rd Party Libraries
There are no 3rd party dependencies. jQuery selectors are used in the examples to make it simple for the reader.
Smooth Scrolling
You can implement smooth scrolling using either of the existing libraries. See Integration Examples.
Window Resize and scroll Event Handling
The library will index offsetTop of all articles. This index is used to reflect the change event. The index is built upon loading the page, and in response to window.onresize and ready events.
Reading offsetTop causes a reflow. Therefore, this should not be done while scrolling.
Table of Contents Array
You can extract the table of contents as a collection of nested objects representing the table of contents.
contents.tree();
Tree is a collection of nodes:
[
{
level: 1,
id: '',
name: '',
element: null,
descendants: [ ]
}
]
Download
Using NPM:
npm install contents
Configuration
articles | NodeList, jQuery | (optional) The default behavior is to index all headings (H1-H6) in the document. See Content Indexing. |
link | function | (optional) Used to represent article in the table of contents and to setup navigation. See Linking. |
| | |
Content Indexing
The default behavior is to index all headings (H1-H6) in the document.
Use articles setting to index content using your own selector:
Contents({
articles: document.querySelectorAll('main h2, main h2')
});
Hierarchy
articles will be used to make the table of contents. articles have level of importance. The level of importance determines list nesting (see Markup). For HTML headings, the level of importance is derived from the tag name (<h[1-6]>). To set your own level of importance, use Contents.level dataset property or jQuery data property with the same name, e.g.
$('main').find('.summary').data('gajus.contents.level', 4);
Contents({
articles: $('main').find('h1, h2, h3, .summary').get()
});
When level of importance cannot be determined, it defaults to 1.
Linking
link method is used to represent article in the table of contents and to setup navigation. This method is called once for each article after the list of the table of contents is generated.
The default implementation:
- Derives ID from the article
- Generates a hyperlink using article ID as the anchor
- Appends the URL to the table of contents
- Wraps the article node in a self-referencing hyperlink.
Contents.link = (guide, article) => {
const guideLink = document.createElement('a'),
const articleLink = document.createElement('a'),
const articleName = article.innerText,
const articleId = article.id || Contents.id(articleName);
article.id = articleId;
articleLink.href = '#' + articleId;
while (article.childNodes.length) {
articleLink.appendChild(article.childNodes[0], articleLink);
}
article.appendChild(articleLink);
guideLink.appendChild(document.createTextNode(articleName));
guideLink.href = '#' + articleId;
guide.insertBefore(guideLink, guide.firstChild);
};
To overwrite the default behavior, you can provide your own link function as part of the configuration:
Contents({
link: (guide, article) => {
var guideLink,
articleName,
articleId;
guide = $(guide);
article = $(article);
guideLink = $('<a>');
articleName = article.text();
articleId = article.attr('id') || Contents.id(articleName);
guideLink
.text(articleName)
.attr('href', '#' + articleId)
.prependTo(guide);
article.attr('id', articleId);
}
});
Article ID
The default implementation relies on each article having an "id" attribute to enable anchor navigation.
If you are overwriting the default link implementation, you can take advantage of the Contents.id function.
Contents.id is responsible for deriving a unique ID from the text of the article, e.g.
<h2>Allow me to reiterate</h2>
<h2>Allow me to reiterate</h2>
<h2>Allow me to reiterate</h2>
The default link implementation will use Contents.id to give each article a unique ID:
<h2 id="allow-me-to-reiterate">Allow me to reiterate</h2>
<h2 id="allow-me-to-reiterate-1">Allow me to reiterate</h2>
<h2 id="allow-me-to-reiterate-2">Allow me to reiterate</h2>
Markup
Table of contents is an ordered list element. List nesting reflects the heading hierarchy. The default behavior is to represent each heading using a hyperlink (See Linking), e.g.
<h1>JavaScript</h1>
<h2>History</h2>
<h2>Trademark</h2>
<h2>Features</h2>
<h3>Imperative and structured</h3>
<h3>Dynamic</h3>
<h3>Functional</h3>
<h2>Syntax</h2>
Contents will generate the following markup for the above content:
<ol>
<li>
<a href="#javascript">JavaScript</a>
<ol>
<li>
<a href="#history">History</a>
</li>
<li>
<a href="#trademark">Trademark</a>
</li>
<li>
<a href="#features">Features</a>
<ol>
<li>
<a href="#imperative-and-structured">Imperative and structured</a>
</li>
<li>
<a href="#dynamic">Dynamic</a>
</li>
<li>
<a href="#functional">Functional</a>
</li>
</ol>
</li>
<li>
<a href="#syntax">Syntax</a>
</li>
</ol>
</li>
</ol>
Events
ready | Fired once after the table of contents has been generated. |
resize | Fired when the page is loaded and in response to "resize" and "orientationchange" window events. |
change | Fired when the page is loaded and when user navigates to a new section of the page. |
Attach event listeners using the eventEmitter.on of the resulting Contents object:
const contents = Contents();
contents.eventEmitter.on('ready', () => {});
contents.eventEmitter.on('resize', () => {});
The change event listener is passed extra parameters: .current.article, .current.guide, and when available, .previous.article, .previous.guide:
contents.eventEmitter.on('change', (data) => {
if (data.previous) {
$(data.previous.article).removeClass('active-article');
$(data.previous.guide).removeClass('active-guide');
}
$(data.current.article).addClass('active-article');
$(data.current.guide).addClass('active-guide');
});
You must trigger "resize" event after programmatically changing the content or the presentation of the content.:
contents.eventEmitter.trigger('resize');
This is required to recalculate the position of the content.