/* * Table of Contents jQuery Plugin - jquery.toc * * Copyright 2013 Nikhil Dabas * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations * under the License. */ (function ($) { "use strict"; // Builds a list with the table of contents in the current selector. // options: // content: where to look for headings // headings: string with a comma-separated list of selectors to be used as headings, ordered // by their relative hierarchy level var toc = function (options) { return this.each(function () { var root = $(this), data = root.data(), thisOptions, stack = [root], // The upside-down stack keeps track of list elements listTag = this.tagName, currentLevel = 0, headingSelectors; // Defaults: plugin parameters override data attributes, which override our defaults thisOptions = $.extend( {content: "body", headings: "h1,h2,h3"}, {content: data.toc || undefined, headings: data.tocHeadings || undefined}, options ); headingSelectors = thisOptions.headings.split(","); // Set up some automatic IDs if we do not already have them $(thisOptions.content).find(thisOptions.headings).attr("id", function (index, attr) { // Generate a valid ID: must start with a letter, and contain only letters and // numbers. All other characters are replaced with underscores. return attr || $(this).text().replace(/^[^A-Za-z]*/, "").replace(/[^A-Za-z0-9]+/g, "_"); }).each(function () { // What level is the current heading? var elem = $(this), level = $.map(headingSelectors, function (selector, index) { return elem.is(selector) ? index : undefined; })[0]; if (level > currentLevel) { // If the heading is at a deeper level than where we are, start a new nested // list, but only if we already have some list items in the parent. If we do // not, that means that we're skipping levels, so we can just add new list items // at the current level. // In the upside-down stack, unshift = push, and stack[0] = the top. var parentItem = stack[0].children("li:last")[0]; if (parentItem) { stack.unshift($("<" + listTag + "/>").appendTo(parentItem)); } } else { // Truncate the stack to the current level by chopping off the 'top' of the // stack. We also need to preserve at least one element in the stack - that is // the containing element. stack.splice(0, Math.min(currentLevel - level, Math.max(stack.length - 1, 0))); } // Add the list item $("
").appendTo(stack[0]).append( $("").text(elem.text()).attr("href", "#" + elem.attr("id")) ); currentLevel = level; }); }); }, old = $.fn.toc; $.fn.toc = toc; $.fn.toc.noConflict = function () { $.fn.toc = old; return this; }; // Data API $(function () { toc.call($("[data-toc]")); }); }(window.jQuery));