/** * TocJS (https://github.com/nghuuphuoc/tocjs) * * Generate a table of contents based on headings * * @author http://twitter.com/nghuuphuoc * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc * @license MIT */ (function($) { var Toc = function(element, options) { this.$element = $(element); this.options = $.extend({}, Toc.DEFAULT_OPTIONS, options); this.headings = []; this.$element.addClass(this.options.elementClass); var that = this; $(this.options.selector).each(function(index, node) { $(node) .data('tagNumber', parseInt(node.tagName.substring(1))) // 1...6 .data('index', 1) .data('numbering', '1'); that.headings.push(node); }); if (this.headings.length > 0) { this.render(); } }; /** * The default options */ Toc.DEFAULT_OPTIONS = { selector: 'h1, h2, h3, h4, h5, h6', elementClass: 'toc', rootUlClass: 'toc-ul-root', ulClass: 'toc-ul', prefixLinkClass: 'toc-link-', heading: null, /** * Define the indexing formats for each heading level * indexingFormats: { * headingLevel: formatter * } * * headingLevel can be 'h1', 'h2', ..., 'h6' * formatter can be: * - 'number', '1': The headings will be prefixed with number (1, 2, 3, ...) * - 'upperAlphabet', 'A': Prefix headings with uppercase alphabetical characters (A, B, C, ...) * - 'lowerAlphabet', 'a': Prefix headings with lowercase alphabetical characters (a, b, c, ...) * - 'upperRoman', 'I': Prefix headings with uppercase Roman numerals (I, II, III, ...) * - 'lowerRoman', 'i': Prefix headings with lowercase Roman numerals (i, ii, iii, ...) * * You can define different formatter for each heading level: * indexingFormats: { * 'h1': 'upperAlphabet', // 'A' * 'h2': 'number', // '1' * 'h3': 'lowerAlphabet' // 'a' * } * * If you want to set indexing formats for levels: * indexingFormats: formatter * * Example: * indexingFormats: 'number' => Prefix all headings by number * indexingFormats: '1AaIi' => Prefix 1st level heading by number * Prefix 2nd level heading by uppercase character, and so forth. */ indexingFormats: {} }; Toc.prototype = { constructor: Toc, /** * Render table of content */ render: function() { var h = {}, headings = this.headings, numHeadings = this.headings.length; for (var i = 0; i < numHeadings; i++) { var currTagNumber = $(headings[i]).data('tagNumber'); if (i == 0) { h[headings[0].tagName] = $(headings[0]); } else { var prevTagNumber = $(headings[i - 1]).data('tagNumber'), prevNumbering = String($(headings[i - 1]).data('numbering')).split('.'); switch (true) { // Case 1: // The current heading is at the same level with previous one // h3___________ <== previous heading // h3___________ <== current heading case (currTagNumber == prevTagNumber): var index = $(headings[i - 1]).data('index') + 1; $(headings[i]).data('index', index); if (prevNumbering.length == 1) { $(headings[i]).data('numbering', parseInt(prevNumbering[0]) + 1); } else { prevNumbering.pop(); prevNumbering.push(index); $(headings[i]).data('numbering', prevNumbering.join('.')); } h[headings[i].tagName] = $(headings[i]); break; // Case 2: // The current heading is child of the previous one // h3____________ <== previous heading // h4________ <== current heading case (currTagNumber > prevTagNumber): prevNumbering.push('1'); $(headings[i]).data('index', 1) .data('numbering', prevNumbering.join('.')); h[headings[i].tagName] = $(headings[i]); break; // Case 3: // h2____________ <== (*) the closest heading that is at the same level with current one // ... // h4________ <== previous heading // h2____________ <== current heading case (currTagNumber < prevTagNumber): // Get the cloest heading (*) var closestHeading = h[headings[i].tagName]; // Now I come back the case 1 var closestNumbering = String($(closestHeading).data('numbering')).split('.'), index = $(closestHeading).data('index') + 1; $(headings[i]).data('index', index); if (closestNumbering.length == 1) { $(headings[i]).data('numbering', parseInt(closestNumbering[0]) + 1); } else { closestNumbering.pop(); closestNumbering.push(index); $(headings[i]).data('numbering', closestNumbering.join('.')); } h[headings[i].tagName] = $(headings[i]); break; default: break; } } } var numberingMap = {}, $toc = $('