jquery.tocify.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987
  1. /* jquery Tocify - v1.9.0 - 2013-10-01
  2. * http://www.gregfranko.com/jquery.tocify.js/
  3. * Copyright (c) 2013 Greg Franko; Licensed MIT */
  4. // Immediately-Invoked Function Expression (IIFE) [Ben Alman Blog Post](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) that calls another IIFE that contains all of the plugin logic. I used this pattern so that anyone viewing this code would not have to scroll to the bottom of the page to view the local parameters that were passed to the main IIFE.
  5. (function(tocify) {
  6. // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
  7. "use strict";
  8. // Calls the second IIFE and locally passes in the global jQuery, window, and document objects
  9. tocify(window.jQuery, window, document);
  10. }
  11. // Locally passes in `jQuery`, the `window` object, the `document` object, and an `undefined` variable. The `jQuery`, `window` and `document` objects are passed in locally, to improve performance, since javascript first searches for a variable match within the local variables set before searching the global variables set. All of the global variables are also passed in locally to be minifier friendly. `undefined` can be passed in locally, because it is not a reserved word in JavaScript.
  12. (function($, window, document, undefined) {
  13. // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
  14. "use strict";
  15. var tocClassName = "tocify",
  16. tocClass = "." + tocClassName,
  17. tocFocusClassName = "tocify-focus",
  18. tocHoverClassName = "tocify-hover",
  19. hideTocClassName = "tocify-hide",
  20. hideTocClass = "." + hideTocClassName,
  21. headerClassName = "tocify-header",
  22. headerClass = "." + headerClassName,
  23. subheaderClassName = "tocify-subheader",
  24. subheaderClass = "." + subheaderClassName,
  25. itemClassName = "tocify-item",
  26. itemClass = "." + itemClassName,
  27. extendPageClassName = "tocify-extend-page",
  28. extendPageClass = "." + extendPageClassName;
  29. // Calling the jQueryUI Widget Factory Method
  30. $.widget("toc.tocify", {
  31. //Plugin version
  32. version: "1.9.0",
  33. // These options will be used as defaults
  34. options: {
  35. // **context**: Accepts String: Any jQuery selector
  36. // The container element that holds all of the elements used to generate the table of contents
  37. context: "body",
  38. // **ignoreSelector**: Accepts String: Any jQuery selector
  39. // A selector to any element that would be matched by selectors that you wish to be ignored
  40. ignoreSelector: null,
  41. // **selectors**: Accepts an Array of Strings: Any jQuery selectors
  42. // The element's used to generate the table of contents. The order is very important since it will determine the table of content's nesting structure
  43. selectors: "h1, h2, h3",
  44. // **showAndHide**: Accepts a boolean: true or false
  45. // Used to determine if elements should be shown and hidden
  46. showAndHide: true,
  47. // **showEffect**: Accepts String: "none", "fadeIn", "show", or "slideDown"
  48. // Used to display any of the table of contents nested items
  49. showEffect: "slideDown",
  50. // **showEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
  51. // The time duration of the show animation
  52. showEffectSpeed: "medium",
  53. // **hideEffect**: Accepts String: "none", "fadeOut", "hide", or "slideUp"
  54. // Used to hide any of the table of contents nested items
  55. hideEffect: "slideUp",
  56. // **hideEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
  57. // The time duration of the hide animation
  58. hideEffectSpeed: "medium",
  59. // **smoothScroll**: Accepts a boolean: true or false
  60. // Determines if a jQuery animation should be used to scroll to specific table of contents items on the page
  61. smoothScroll: true,
  62. // **smoothScrollSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
  63. // The time duration of the smoothScroll animation
  64. smoothScrollSpeed: "medium",
  65. // **scrollTo**: Accepts Number (pixels)
  66. // The amount of space between the top of page and the selected table of contents item after the page has been scrolled
  67. scrollTo: 0,
  68. // **showAndHideOnScroll**: Accepts a boolean: true or false
  69. // Determines if table of contents nested items should be shown and hidden while scrolling
  70. showAndHideOnScroll: true,
  71. // **highlightOnScroll**: Accepts a boolean: true or false
  72. // Determines if table of contents nested items should be highlighted (set to a different color) while scrolling
  73. highlightOnScroll: true,
  74. // **highlightOffset**: Accepts a number
  75. // The offset distance in pixels to trigger the next active table of contents item
  76. highlightOffset: 40,
  77. // **theme**: Accepts a string: "bootstrap", "jqueryui", or "none"
  78. // Determines if Twitter Bootstrap, jQueryUI, or Tocify classes should be added to the table of contents
  79. theme: "bootstrap",
  80. // **extendPage**: Accepts a boolean: true or false
  81. // If a user scrolls to the bottom of the page and the page is not tall enough to scroll to the last table of contents item, then the page height is increased
  82. extendPage: true,
  83. // **extendPageOffset**: Accepts a number: pixels
  84. // How close to the bottom of the page a user must scroll before the page is extended
  85. extendPageOffset: 100,
  86. // **history**: Accepts a boolean: true or false
  87. // Adds a hash to the page url to maintain history
  88. history: true,
  89. // **scrollHistory**: Accepts a boolean: true or false
  90. // Adds a hash to the page url, to maintain history, when scrolling to a TOC item
  91. scrollHistory: false,
  92. // **hashGenerator**: How the hash value (the anchor segment of the URL, following the
  93. // # character) will be generated.
  94. //
  95. // "compact" (default) - #CompressesEverythingTogether
  96. // "pretty" - #looks-like-a-nice-url-and-is-easily-readable
  97. // function(text, element){} - Your own hash generation function that accepts the text as an
  98. // argument, and returns the hash value.
  99. hashGenerator: "compact",
  100. // **highlightDefault**: Accepts a boolean: true or false
  101. // Set's the first TOC item as active if no other TOC item is active.
  102. highlightDefault: true
  103. },
  104. // _Create
  105. // -------
  106. // Constructs the plugin. Only called once.
  107. _create: function() {
  108. var self = this;
  109. self.extendPageScroll = true;
  110. // Internal array that keeps track of all TOC items (Helps to recognize if there are duplicate TOC item strings)
  111. self.items = [];
  112. // Generates the HTML for the dynamic table of contents
  113. self._generateToc();
  114. // Adds CSS classes to the newly generated table of contents HTML
  115. self._addCSSClasses();
  116. self.webkit = (function() {
  117. for(var prop in window) {
  118. if(prop) {
  119. if(prop.toLowerCase().indexOf("webkit") !== -1) {
  120. return true;
  121. }
  122. }
  123. }
  124. return false;
  125. }());
  126. // Adds jQuery event handlers to the newly generated table of contents
  127. self._setEventHandlers();
  128. // Binding to the Window load event to make sure the correct scrollTop is calculated
  129. $(window).load(function() {
  130. // Sets the active TOC item
  131. self._setActiveElement(true);
  132. // Once all animations on the page are complete, this callback function will be called
  133. $("html, body").promise().done(function() {
  134. setTimeout(function() {
  135. self.extendPageScroll = false;
  136. },0);
  137. });
  138. });
  139. },
  140. // _generateToc
  141. // ------------
  142. // Generates the HTML for the dynamic table of contents
  143. _generateToc: function() {
  144. // _Local variables_
  145. // Stores the plugin context in the self variable
  146. var self = this,
  147. // All of the HTML tags found within the context provided (i.e. body) that match the top level jQuery selector above
  148. firstElem,
  149. // Instantiated variable that will store the top level newly created unordered list DOM element
  150. ul,
  151. ignoreSelector = self.options.ignoreSelector;
  152. // If the selectors option has a comma within the string
  153. if(this.options.selectors.indexOf(",") !== -1) {
  154. // Grabs the first selector from the string
  155. firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,"").substr(0, this.options.selectors.indexOf(",")));
  156. }
  157. // If the selectors option does not have a comman within the string
  158. else {
  159. // Grabs the first selector from the string and makes sure there are no spaces
  160. firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,""));
  161. }
  162. if(!firstElem.length) {
  163. self.element.addClass(hideTocClassName);
  164. return;
  165. }
  166. self.element.addClass(tocClassName);
  167. // Loops through each top level selector
  168. firstElem.each(function(index) {
  169. //If the element matches the ignoreSelector then we skip it
  170. if($(this).is(ignoreSelector)) {
  171. return;
  172. }
  173. // Creates an unordered list HTML element and adds a dynamic ID and standard class name
  174. ul = $("<ul/>", {
  175. "id": headerClassName + index,
  176. "class": headerClassName
  177. }).
  178. // Appends a top level list item HTML element to the previously created HTML header
  179. append(self._nestElements($(this), index));
  180. // Add the created unordered list element to the HTML element calling the plugin
  181. self.element.append(ul);
  182. // Finds all of the HTML tags between the header and subheader elements
  183. $(this).nextUntil(this.nodeName.toLowerCase()).each(function() {
  184. // If there are no nested subheader elemements
  185. if($(this).find(self.options.selectors).length === 0) {
  186. // Loops through all of the subheader elements
  187. $(this).filter(self.options.selectors).each(function() {
  188. //If the element matches the ignoreSelector then we skip it
  189. if($(this).is(ignoreSelector)) {
  190. return;
  191. }
  192. self._appendSubheaders.call(this, self, ul);
  193. });
  194. }
  195. // If there are nested subheader elements
  196. else {
  197. // Loops through all of the subheader elements
  198. $(this).find(self.options.selectors).each(function() {
  199. //If the element matches the ignoreSelector then we skip it
  200. if($(this).is(ignoreSelector)) {
  201. return;
  202. }
  203. self._appendSubheaders.call(this, self, ul);
  204. });
  205. }
  206. });
  207. });
  208. },
  209. _setActiveElement: function(pageload) {
  210. var self = this,
  211. hash = window.location.hash.substring(1),
  212. elem = self.element.find('li[data-unique="' + hash + '"]');
  213. if(hash.length) {
  214. // Removes highlighting from all of the list item's
  215. self.element.find("." + self.focusClass).removeClass(self.focusClass);
  216. // Highlights the current list item that was clicked
  217. elem.addClass(self.focusClass);
  218. // If the showAndHide option is true
  219. if(self.options.showAndHide) {
  220. // Triggers the click event on the currently focused TOC item
  221. elem.click();
  222. }
  223. }
  224. else {
  225. // Removes highlighting from all of the list item's
  226. self.element.find("." + self.focusClass).removeClass(self.focusClass);
  227. if(!hash.length && pageload && self.options.highlightDefault) {
  228. // Highlights the first TOC item if no other items are highlighted
  229. self.element.find(itemClass).first().addClass(self.focusClass);
  230. }
  231. }
  232. return self;
  233. },
  234. // _nestElements
  235. // -------------
  236. // Helps create the table of contents list by appending nested list items
  237. _nestElements: function(self, index) {
  238. var arr, item, hashValue;
  239. arr = $.grep(this.items, function (item) {
  240. return item === self.text();
  241. });
  242. // If there is already a duplicate TOC item
  243. if(arr.length) {
  244. // Adds the current TOC item text and index (for slight randomization) to the internal array
  245. this.items.push(self.text() + index);
  246. }
  247. // If there not a duplicate TOC item
  248. else {
  249. // Adds the current TOC item text to the internal array
  250. this.items.push(self.text());
  251. }
  252. hashValue = this._generateHashValue(arr, self, index);
  253. // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
  254. item = $("<li/>", {
  255. // Sets a common class name to the list item
  256. "class": itemClassName,
  257. "data-unique": hashValue
  258. }).append($("<a/>", {
  259. "text": self.text()
  260. }));
  261. // Adds an HTML anchor tag before the currently traversed HTML element
  262. self.before($("<div/>", {
  263. // Sets a name attribute on the anchor tag to the text of the currently traversed HTML element (also making sure that all whitespace is replaced with an underscore)
  264. "name": hashValue,
  265. "data-unique": hashValue
  266. }));
  267. return item;
  268. },
  269. // _generateHashValue
  270. // ------------------
  271. // Generates the hash value that will be used to refer to each item.
  272. _generateHashValue: function(arr, self, index) {
  273. var hashValue = "",
  274. hashGeneratorOption = this.options.hashGenerator;
  275. if (hashGeneratorOption === "pretty") {
  276. // prettify the text
  277. hashValue = self.text().toLowerCase().replace(/\s/g, "-");
  278. // fix double hyphens
  279. while (hashValue.indexOf("--") > -1) {
  280. hashValue = hashValue.replace(/--/g, "-");
  281. }
  282. // fix colon-space instances
  283. while (hashValue.indexOf(":-") > -1) {
  284. hashValue = hashValue.replace(/:-/g, "-");
  285. }
  286. } else if (typeof hashGeneratorOption === "function") {
  287. // call the function
  288. hashValue = hashGeneratorOption(self.text(), self);
  289. } else {
  290. // compact - the default
  291. hashValue = self.text().replace(/\s/g, "");
  292. }
  293. // add the index if we need to
  294. if (arr.length) { hashValue += ""+index; }
  295. // return the value
  296. return hashValue;
  297. },
  298. // _appendElements
  299. // ---------------
  300. // Helps create the table of contents list by appending subheader elements
  301. _appendSubheaders: function(self, ul) {
  302. // The current element index
  303. var index = $(this).index(self.options.selectors),
  304. // Finds the previous header DOM element
  305. previousHeader = $(self.options.selectors).eq(index - 1),
  306. currentTagName = +$(this).prop("tagName").charAt(1),
  307. previousTagName = +previousHeader.prop("tagName").charAt(1),
  308. lastSubheader;
  309. // If the current header DOM element is smaller than the previous header DOM element or the first subheader
  310. if(currentTagName < previousTagName) {
  311. // Selects the last unordered list HTML found within the HTML element calling the plugin
  312. self.element.find(subheaderClass + "[data-tag=" + currentTagName + "]").last().append(self._nestElements($(this), index));
  313. }
  314. // If the current header DOM element is the same type of header(eg. h4) as the previous header DOM element
  315. else if(currentTagName === previousTagName) {
  316. ul.find(itemClass).last().after(self._nestElements($(this), index));
  317. }
  318. else {
  319. // Selects the last unordered list HTML found within the HTML element calling the plugin
  320. ul.find(itemClass).last().
  321. // Appends an unorderedList HTML element to the dynamic `unorderedList` variable and sets a common class name
  322. after($("<ul/>", {
  323. "class": subheaderClassName,
  324. "data-tag": currentTagName
  325. })).next(subheaderClass).
  326. // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
  327. append(self._nestElements($(this), index));
  328. }
  329. },
  330. // _setEventHandlers
  331. // ----------------
  332. // Adds jQuery event handlers to the newly generated table of contents
  333. _setEventHandlers: function() {
  334. // _Local variables_
  335. // Stores the plugin context in the self variable
  336. var self = this,
  337. // Instantiates a new variable that will be used to hold a specific element's context
  338. $self,
  339. // Instantiates a new variable that will be used to determine the smoothScroll animation time duration
  340. duration;
  341. // Event delegation that looks for any clicks on list item elements inside of the HTML element calling the plugin
  342. this.element.on("click.tocify", "li", function(event) {
  343. if(self.options.history) {
  344. window.location.hash = $(this).attr("data-unique");
  345. }
  346. // Removes highlighting from all of the list item's
  347. self.element.find("." + self.focusClass).removeClass(self.focusClass);
  348. // Highlights the current list item that was clicked
  349. $(this).addClass(self.focusClass);
  350. // If the showAndHide option is true
  351. if(self.options.showAndHide) {
  352. var elem = $('li[data-unique="' + $(this).attr("data-unique") + '"]');
  353. self._triggerShow(elem);
  354. }
  355. self._scrollTo($(this));
  356. });
  357. // Mouseenter and Mouseleave event handlers for the list item's within the HTML element calling the plugin
  358. this.element.find("li").on({
  359. // Mouseenter event handler
  360. "mouseenter.tocify": function() {
  361. // Adds a hover CSS class to the current list item
  362. $(this).addClass(self.hoverClass);
  363. // Makes sure the cursor is set to the pointer icon
  364. $(this).css("cursor", "pointer");
  365. },
  366. // Mouseleave event handler
  367. "mouseleave.tocify": function() {
  368. if(self.options.theme !== "bootstrap") {
  369. // Removes the hover CSS class from the current list item
  370. $(this).removeClass(self.hoverClass);
  371. }
  372. }
  373. });
  374. // only attach handler if needed (expensive in IE)
  375. if (self.options.extendPage || self.options.highlightOnScroll || self.options.scrollHistory || self.options.showAndHideOnScroll)
  376. {
  377. // Window scroll event handler
  378. $(window).on("scroll.tocify", function() {
  379. // Once all animations on the page are complete, this callback function will be called
  380. $("html, body").promise().done(function() {
  381. // Local variables
  382. // Stores how far the user has scrolled
  383. var winScrollTop = $(window).scrollTop(),
  384. // Stores the height of the window
  385. winHeight = $(window).height(),
  386. // Stores the height of the document
  387. docHeight = $(document).height(),
  388. scrollHeight = $("body")[0].scrollHeight,
  389. // Instantiates a variable that will be used to hold a selected HTML element
  390. elem,
  391. lastElem,
  392. lastElemOffset,
  393. currentElem;
  394. if(self.options.extendPage) {
  395. // If the user has scrolled to the bottom of the page and the last toc item is not focused
  396. if((self.webkit && winScrollTop >= scrollHeight - winHeight - self.options.extendPageOffset) || (!self.webkit && winHeight + winScrollTop > docHeight - self.options.extendPageOffset)) {
  397. if(!$(extendPageClass).length) {
  398. lastElem = $('div[data-unique="' + $(itemClass).last().attr("data-unique") + '"]');
  399. if(!lastElem.length) return;
  400. // Gets the top offset of the page header that is linked to the last toc item
  401. lastElemOffset = lastElem.offset().top;
  402. // Appends a div to the bottom of the page and sets the height to the difference of the window scrollTop and the last element's position top offset
  403. $(self.options.context).append($("<div />", {
  404. "class": extendPageClassName,
  405. "height": Math.abs(lastElemOffset - winScrollTop) + "px",
  406. "data-unique": extendPageClassName
  407. }));
  408. if(self.extendPageScroll) {
  409. currentElem = self.element.find('li.active');
  410. self._scrollTo($('div[data-unique="' + currentElem.attr("data-unique") + '"]'));
  411. }
  412. }
  413. }
  414. }
  415. // The zero timeout ensures the following code is run after the scroll events
  416. setTimeout(function() {
  417. // _Local variables_
  418. // Stores the distance to the closest anchor
  419. var closestAnchorDistance = null,
  420. // Stores the index of the closest anchor
  421. closestAnchorIdx = null,
  422. // Keeps a reference to all anchors
  423. anchors = $(self.options.context).find("div[data-unique]"),
  424. anchorText;
  425. // Determines the index of the closest anchor
  426. anchors.each(function(idx) {
  427. var distance = Math.abs(($(this).next().length ? $(this).next() : $(this)).offset().top - winScrollTop - self.options.highlightOffset);
  428. if (closestAnchorDistance == null || distance < closestAnchorDistance) {
  429. closestAnchorDistance = distance;
  430. closestAnchorIdx = idx;
  431. } else {
  432. return false;
  433. }
  434. });
  435. anchorText = $(anchors[closestAnchorIdx]).attr("data-unique");
  436. // Stores the list item HTML element that corresponds to the currently traversed anchor tag
  437. elem = $('li[data-unique="' + anchorText + '"]');
  438. // If the `highlightOnScroll` option is true and a next element is found
  439. if(self.options.highlightOnScroll && elem.length) {
  440. // Removes highlighting from all of the list item's
  441. self.element.find("." + self.focusClass).removeClass(self.focusClass);
  442. // Highlights the corresponding list item
  443. elem.addClass(self.focusClass);
  444. }
  445. if(self.options.scrollHistory) {
  446. if(window.location.hash !== "#" + anchorText) {
  447. window.location.replace("#" + anchorText);
  448. }
  449. }
  450. // If the `showAndHideOnScroll` option is true
  451. if(self.options.showAndHideOnScroll && self.options.showAndHide) {
  452. self._triggerShow(elem, true);
  453. }
  454. }, 0);
  455. });
  456. });
  457. }
  458. },
  459. // Show
  460. // ----
  461. // Opens the current sub-header
  462. show: function(elem, scroll) {
  463. // Stores the plugin context in the `self` variable
  464. var self = this,
  465. element = elem;
  466. // If the sub-header is not already visible
  467. if (!elem.is(":visible")) {
  468. // If the current element does not have any nested subheaders, is not a header, and its parent is not visible
  469. if(!elem.find(subheaderClass).length && !elem.parent().is(headerClass) && !elem.parent().is(":visible")) {
  470. // Sets the current element to all of the subheaders within the current header
  471. elem = elem.parents(subheaderClass).add(elem);
  472. }
  473. // If the current element does not have any nested subheaders and is not a header
  474. else if(!elem.children(subheaderClass).length && !elem.parent().is(headerClass)) {
  475. // Sets the current element to the closest subheader
  476. elem = elem.closest(subheaderClass);
  477. }
  478. //Determines what jQuery effect to use
  479. switch (self.options.showEffect) {
  480. //Uses `no effect`
  481. case "none":
  482. elem.show();
  483. break;
  484. //Uses the jQuery `show` special effect
  485. case "show":
  486. elem.show(self.options.showEffectSpeed);
  487. break;
  488. //Uses the jQuery `slideDown` special effect
  489. case "slideDown":
  490. elem.slideDown(self.options.showEffectSpeed);
  491. break;
  492. //Uses the jQuery `fadeIn` special effect
  493. case "fadeIn":
  494. elem.fadeIn(self.options.showEffectSpeed);
  495. break;
  496. //If none of the above options were passed, then a `jQueryUI show effect` is expected
  497. default:
  498. elem.show();
  499. break;
  500. }
  501. }
  502. // If the current subheader parent element is a header
  503. if(elem.parent().is(headerClass)) {
  504. // Hides all non-active sub-headers
  505. self.hide($(subheaderClass).not(elem));
  506. }
  507. // If the current subheader parent element is not a header
  508. else {
  509. // Hides all non-active sub-headers
  510. self.hide($(subheaderClass).not(elem.closest(headerClass).find(subheaderClass).not(elem.siblings())));
  511. }
  512. // Maintains chainablity
  513. return self;
  514. },
  515. // Hide
  516. // ----
  517. // Closes the current sub-header
  518. hide: function(elem) {
  519. // Stores the plugin context in the `self` variable
  520. var self = this;
  521. //Determines what jQuery effect to use
  522. switch (self.options.hideEffect) {
  523. // Uses `no effect`
  524. case "none":
  525. elem.hide();
  526. break;
  527. // Uses the jQuery `hide` special effect
  528. case "hide":
  529. elem.hide(self.options.hideEffectSpeed);
  530. break;
  531. // Uses the jQuery `slideUp` special effect
  532. case "slideUp":
  533. elem.slideUp(self.options.hideEffectSpeed);
  534. break;
  535. // Uses the jQuery `fadeOut` special effect
  536. case "fadeOut":
  537. elem.fadeOut(self.options.hideEffectSpeed);
  538. break;
  539. // If none of the above options were passed, then a `jqueryUI hide effect` is expected
  540. default:
  541. elem.hide();
  542. break;
  543. }
  544. // Maintains chainablity
  545. return self;
  546. },
  547. // _triggerShow
  548. // ------------
  549. // Determines what elements get shown on scroll and click
  550. _triggerShow: function(elem, scroll) {
  551. var self = this;
  552. // If the current element's parent is a header element or the next element is a nested subheader element
  553. if(elem.parent().is(headerClass) || elem.next().is(subheaderClass)) {
  554. // Shows the next sub-header element
  555. self.show(elem.next(subheaderClass), scroll);
  556. }
  557. // If the current element's parent is a subheader element
  558. else if(elem.parent().is(subheaderClass)) {
  559. // Shows the parent sub-header element
  560. self.show(elem.parent(), scroll);
  561. }
  562. // Maintains chainability
  563. return self;
  564. },
  565. // _addCSSClasses
  566. // --------------
  567. // Adds CSS classes to the newly generated table of contents HTML
  568. _addCSSClasses: function() {
  569. // If the user wants a jqueryUI theme
  570. if(this.options.theme === "jqueryui") {
  571. this.focusClass = "ui-state-default";
  572. this.hoverClass = "ui-state-hover";
  573. //Adds the default styling to the dropdown list
  574. this.element.addClass("ui-widget").find(".toc-title").addClass("ui-widget-header").end().find("li").addClass("ui-widget-content");
  575. }
  576. // If the user wants a twitterBootstrap theme
  577. else if(this.options.theme === "bootstrap") {
  578. this.element.find(headerClass + "," + subheaderClass).addClass("nav nav-list");
  579. this.focusClass = "active";
  580. }
  581. // If a user does not want a prebuilt theme
  582. else {
  583. // Adds more neutral classes (instead of jqueryui)
  584. this.focusClass = tocFocusClassName;
  585. this.hoverClass = tocHoverClassName;
  586. }
  587. //Maintains chainability
  588. return this;
  589. },
  590. // setOption
  591. // ---------
  592. // Sets a single Tocify option after the plugin is invoked
  593. setOption: function() {
  594. // Calls the jQueryUI Widget Factory setOption method
  595. $.Widget.prototype._setOption.apply(this, arguments);
  596. },
  597. // setOptions
  598. // ----------
  599. // Sets a single or multiple Tocify options after the plugin is invoked
  600. setOptions: function() {
  601. // Calls the jQueryUI Widget Factory setOptions method
  602. $.Widget.prototype._setOptions.apply(this, arguments);
  603. },
  604. // _scrollTo
  605. // ---------
  606. // Scrolls to a specific element
  607. _scrollTo: function(elem) {
  608. var self = this,
  609. duration = self.options.smoothScroll || 0,
  610. scrollTo = self.options.scrollTo,
  611. currentDiv = $('div[data-unique="' + elem.attr("data-unique") + '"]');
  612. if(!currentDiv.length) {
  613. return self;
  614. }
  615. // Once all animations on the page are complete, this callback function will be called
  616. $("html, body").promise().done(function() {
  617. // Animates the html and body element scrolltops
  618. $("html, body").animate({
  619. // Sets the jQuery `scrollTop` to the top offset of the HTML div tag that matches the current list item's `data-unique` tag
  620. "scrollTop": currentDiv.offset().top - ($.isFunction(scrollTo) ? scrollTo.call() : scrollTo) + "px"
  621. }, {
  622. // Sets the smoothScroll animation time duration to the smoothScrollSpeed option
  623. "duration": duration
  624. });
  625. });
  626. // Maintains chainability
  627. return self;
  628. }
  629. });
  630. })); //end of plugin