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