PageSourceSearch

https://www.asteronlife.co.nz/sites/default/files/cohesion/scripts…/jquery.responsiveTabs.js?tmgibf

js asteronlife.co.nz collected 2026-10-10 00:57:57 UTC 35,279 bytes, 1,025 lines download raw bytes

1/**
2 * @sitestudioexcludesonar
3 * *************************************************
4 * ************ WARNING WARNING WARNING ************
5 * *************************************************
6 * This code has been modified from the original
7 * there are several pull requests which should
8 * include these modifications
9 * See ...
10 * *************************************************
11 */
12
13(function ($, once, window, undefined) {
14
15  /** Default settings */
16  var defaults = {
17    active: null,
18    event: 'click',
19    disabled: [],
20    collapsible: 'accordion',
21    startCollapsed: false,
22    rotate: false,
23    setHash: false,
24    animation: 'default',
25    animationQueue: false,
26    duration: 500,
27    fluidHeight: true,
28    scrollToAccordion: false,
29    scrollToAccordionOnLoad: false,
30    scrollToAccordionOffset: 0,
31    accordionTabElement: '<div></div>',
32    accordionTriggerHTML: '<a></a>',
33    click: function () { },
34    activate: function () { },
35    activateStart: function () { },
36    activateFinished: function () { },
37    deactivate: function () { },
38    load: function () { },
39    activateState: function () { },
40    classes: {
41      stateDefault: 'r-tabs-state-default',
42      stateActive: 'r-tabs-state-active',
43      stateDisabled: 'r-tabs-state-disabled',
44      stateExcluded: 'r-tabs-state-excluded',
45      stateTypePrefix: 'r-tabs-state',
46      container: 'r-tabs',
47      ul: 'r-tabs-nav',
48      tab: 'r-tabs-tab',
49      anchor: 'r-tabs-anchor',
50      panel: 'r-tabs-panel',
51      accordionTitle: 'r-tabs-accordion-title'
52    }
53  };
54
55  var events = [
56    'tabs-click',
57    'tabs-activate',
58    'tabs-active-start',
59    'tabs-activate-finished',
60    'tabs-deactivate',
61    'tabs-activate-state',
62    'tabs-load',
63    'tabs-refresh'
64  ];
65
66  /**
67   * Responsive Tabs
68   * @constructor
69   * @param {object} element - The HTML element the validator should be bound to
70   * @param {object} options - An option map
71   */
72  function ResponsiveTabs(element, options) {
73    this.element = element; // Selected DOM element
74    this.$element = $(element); // Selected jQuery element
75
76    this.tabs = []; // Create tabs array
77    this.panels = []; // Create panels array
78    this.tabItems = []; // Create tabbed navigation items array
79    this.tabItemAnchors = []; // Create tabbed naviation anchors array
80    this.accordionItems = []; // Create accordion items array
81    this.accordionItemAnchors = []; // Create accordion item anchor
82    this.state = ''; // Define the plugin state (tabs/accordion)
83    this.rotateInterval = 0; // Define rotate interval
84    this.$queue = $({});
85
86    // Extend the defaults with the passed options
87    this.options = $.extend({}, defaults, options);
88
89    this.init();
90  }
91
92
93  /**
94   * This function initializes the tab plugin
95   */
96  ResponsiveTabs.prototype.init = function () {
97    var _this = this;
98
99    // Load all the elements
100    this.tabs = this._loadElements();
101    this._loadClasses();
102    this._loadEvents();
103    this._loadAria();
104
105    // Window resize bind to check state
106    $(window).on('resize', function (e) {
107      _this._setState(e);
108      if (_this.options.fluidHeight !== true) {
109        _this._equaliseHeights();
110      }
111    });
112
113    // Hashchange event
114    $(window).on('hashchange', function (e) {
115      var tabRef = _this._getTabRefBySelector(window.location.hash);
116      var oTab = _this._getTab(tabRef);
117
118      // Check if a tab is found that matches the hash
119      if (tabRef >= 0 && !oTab._ignoreHashChange && !oTab.disabled) {
120        // If so, open the tab and auto close the current one
121        _this._openTab(e, _this._getTab(tabRef), true);
122      }
123    });
124
125    // Start rotate event if rotate option is defined
126    if (this.options.rotate !== false) {
127      this.startRotation();
128    }
129
130    // Set fluid height
131    if (this.options.fluidHeight !== true) {
132      _this._equaliseHeights();
133    }
134
135    // --------------------
136    // Define plugin events
137    //
138
139    // Activate: this event is called when a tab is selected
140    this.$element.bind('tabs-click', function (e, oTab) {
141      _this.options.click.call(this, e, oTab);
142    });
143
144    // Activate: this event is called when a tab is selected
145    this.$element.bind('tabs-activate', function (e, oTab) {
146      _this.options.activate.call(this, e, oTab);
147    });
148    // Activate start: this event is called when a tab is selected and before the animation has completed
149    this.$element.bind('tabs-activate-start', function (e, oTab) {
150      _this.options.activateFinished.call(this, e, oTab);
151    });
152    // Activate finished: this event is called when a tab is selected and the animation has completed
153    this.$element.bind('tabs-activate-finished', function (e, oTab) {
154      _this.options.activateFinished.call(this, e, oTab);
155    });
156    // Deactivate: this event is called when a tab is closed
157    this.$element.bind('tabs-deactivate', function (e, oTab) {
158      _this.options.deactivate.call(this, e, oTab);
159    });
160    // Activate State: this event is called when the plugin switches states
161    this.$element.bind('tabs-activate-state', function (e, state) {
162      _this.options.activateState.call(this, e, state);
163    });
164
165    // Load: this event is called when the plugin has been loaded
166    this.$element.bind('tabs-load', function (e) {
167      e.stopPropagation();
168      var startTab;
169
170      if(_this.options.startCollapsed === 'true') {
171          _this.options.startCollapsed = true;
172      } else if (_this.options.startCollapsed === 'false') {
173          _this.options.startCollapsed = false;
174      }
175
176      _this._setState(e); // Set state
177
178      // Check if the panel should be collapsed on load
179      if ((_this.options.startCollapsed !== true) && !(_this.options.startCollapsed === 'accordion' && _this.state === 'accordion')) {
180
181        startTab = _this._getStartTab();
182
183        // disable animation on initial page load
184        var cacheAnimationType = _this.options.animation;
185        _this.options.animation = 'default';
186
187        // Open the initial tab
188        _this._openTab(e, startTab); // Open first tab
189
190        // restore animation after initial page load
191        _this.options.animation = cacheAnimationType;
192
193        // Call the callback function
194        _this.options.load.call(this, e, startTab); // Call the load callback
195      }
196    });
197    // Trigger loaded event
198    this.$element.trigger('tabs-load', _this);
199  };
200
201  //
202  // PRIVATE FUNCTIONS
203  //
204
205  /**
206   * This function loads the tab elements and stores them in an array
207   * @returns {Array} Array of tab elements
208   */
209  ResponsiveTabs.prototype._loadElements = function () {
210    var _this = this;
211    var $ul = this.$element.children('ul:first');
212    var tabs = [];
213    var id = 0;
214
215    // Add the classes to the basic html elements
216    this.$element.addClass(_this.options.classes.container); // Tab container
217    $ul.addClass(_this.options.classes.ul); // List container
218
219    var wrapper = $('.coh-accordion-tabs-content-wrapper:first', this.$element);
220
221    // use .not to ensure we dont select child tab items from any nested accordions
222    var tabButtons = $('.' + _this.options.classes.accordionTitle, wrapper)
223      .not(wrapper.find('.coh-accordion-tabs-content-wrapper .'+ _this.options.classes.accordionTitle));
224
225    once('tab-init', $(tabButtons)).forEach(function (e, i) {
226
227      var $accordionTab = $(e);
228
229      var $anchor = $('a, button', $accordionTab);
230
231      var isExcluded = $accordionTab.hasClass(_this.options.classes.stateExcluded);
232      var $panel, panelSelector, $tab, $tabAnchor, tabSettings;
233
234      // Check if the tab should be excluded
235      if (!isExcluded) {
236
237        panelSelector = $anchor.attr('href') || $anchor.data('href');
238        $panel = $(panelSelector);
239        $panel.hide();
240
241        tabSettings = $accordionTab.data('cohTabSettings');
242
243        $tab = $('<li />').appendTo($ul);
244
245        $tab.addClass(tabSettings.customStyle);
246
247        $tabAnchor = $('<a />', {
248          href: panelSelector,
249        }).html($anchor.html()).appendTo($tab);
250
251        var oTab = {
252          _ignoreHashChange: false,
253          id: id,
254          disabled: typeof tabSettings.disabled !== 'undefined' ? tabSettings.disabled : false,
255          tab: $tab,
256          anchor: $tabAnchor,
257          panel: $panel,
258          selector: panelSelector,
259          accordionTab: $accordionTab,
260          accordionAnchor: $anchor,
261          active: false,
262          linkUrl: typeof tabSettings.linkUrl !== 'undefined' ? tabSettings.linkUrl : false,
263          linkTarget: typeof tabSettings.linkTarget !== 'undefined' ? tabSettings.linkTarget : false,
264          hide: typeof tabSettings.hide !== 'undefined' ? tabSettings.hide : false
265        };
266
267        // 1up the ID
268        id++;
269        // Add to tab array
270        tabs.push(oTab);
271
272        // Add to panels array
273        _this.panels.push(oTab.panel);
274
275        // Add to tab items array
276        _this.tabItems.push(oTab.tab);
277        _this.tabItemAnchors.push(oTab.anchor);
278
279        // Add to accordion items array
280        _this.accordionItems.push(oTab.accordionTab);
281        _this.accordionItemAnchors.push(oTab.accordionAnchor);
282      }
283    });
284
285    return tabs;
286  };
287
288  /**
289   * Load the initial aria attributes
290   * @private
291   */
292  ResponsiveTabs.prototype._loadAria = function () {
293    for (var i = 0; i < this.tabs.length; i++) {
294
295      this.tabs[i].accordionAnchor.attr('aria-expanded', this.tabs[i].active);
296
297      if (this.tabs[i].disabled) {
298        this.tabs[i].accordionAnchor.attr('aria-disabled', this.tabs[i].disabled);
299      }
300    }
301  };
302
303  /**
304   * Update the state of the aria attributes
305   * @param tab | obj - the tab object
306   * @private
307   */
308  ResponsiveTabs.prototype._updateAria = function (tab) {
309
310    tab.accordionAnchor.attr('aria-expanded', tab.active);
311    tab.accordionAnchor.removeAttr('aria-disabled');
312
313    if (tab.disabled || (!this.options.collapsible && tab.active)) {
314      tab.accordionAnchor.attr('aria-disabled', true);
315    }
316
317    // Update aria-selected on tab anchor when in tab mode.
318    if (this.state === 'tabs' && tab.linkUrl === false) {
319      tab.anchor.attr('aria-selected', tab.active ? 'true' : 'false');
320    }
321  };
322
323  /**
324   * This function adds classes to the tab elements based on the options
325   */
326  ResponsiveTabs.prototype._loadClasses = function () {
327    for (var i = 0; i < this.tabs.length; i++) { // Change this to a $.each with once
328      this.tabs[i].tab.addClass(this.options.classes.stateDefault).addClass(this.options.classes.tab);
329      this.tabs[i].anchor.addClass(this.options.classes.anchor);
330      this.tabs[i].panel.addClass(this.options.classes.stateDefault).addClass(this.options.classes.panel);
331      this.tabs[i].accordionTab.addClass(this.options.classes.accordionTitle);
332      this.tabs[i].accordionAnchor.addClass(this.options.classes.anchor);
333      if (this.tabs[i].disabled) {
334        this.tabs[i].tab.removeClass(this.options.classes.stateDefault).addClass(this.options.classes.stateDisabled);
335        this.tabs[i].accordionTab.removeClass(this.options.classes.stateDefault).addClass(this.options.classes.stateDisabled);
336      }
337    }
338  };
339
340  /**
341   * This function adds events to the tab elements
342   */
343  ResponsiveTabs.prototype._loadEvents = function () {
344    var _this = this;
345
346    // Define activate event on a tab element
347    var fActivate = function (e) {
348      var current = _this._getCurrentTab(); // Fetch current tab
349      var activatedTab = e.data.tab;
350
351      e.preventDefault();
352
353      // If the tab is a link
354      if (activatedTab.linkUrl !== false) {
355        window.open(activatedTab.linkUrl, activatedTab.linkTarget);
356        return;
357      }
358
359      // Trigger click event for whenever a tab is clicked/touched even if the tab is disabled
360      activatedTab.tab.trigger('tabs-click', activatedTab);
361
362      // Make sure this tab isn't disabled
363      if (!(activatedTab.disabled || activatedTab.linkUrl)) {
364
365        // Check if hash has to be set in the URL location
366        if (_this.options.setHash) {
367          // Set the hash using the history api if available to tackle Chromes repaint bug on hash change
368          if (history.pushState) {
369            // Fix for missing window.location.origin in IE
370            if (!window.location.origin) {
371              window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
372            }
373
374            history.pushState(null, null, window.location.origin + window.location.pathname + window.location.search + activatedTab.selector);
375          } else {
376            // Otherwise fallback to the hash update for sites that don't support the history api
377            window.location.hash = activatedTab.selector;
378          }
379        }
380
381        e.data.tab._ignoreHashChange = true;
382
383        // Check if the activated tab isnt the current one or if its collapsible. If not, do nothing
384        if (current !== activatedTab || _this._isCollapisble()) {
385          // The activated tab is either another tab of the current one. If it's the current tab it is collapsible
386          // Either way, the current tab can be closed
387          _this._closeTab(e, current);
388
389          // Check if the activated tab isnt the current one or if it isnt collapsible
390          if (current !== activatedTab || !_this._isCollapisble()) {
391            _this._openTab(e, activatedTab, false, true);
392          }
393        }
394      }
395    };
396
397    // Loop tabs
398    for (var i = 0; i < this.tabs.length; i++) {
399      // Add activate function to the tab and accordion selection element
400      $(once('loadEvent', this.tabs[i].anchor.get(0))).on(_this.options.event, { tab: _this.tabs[i] }, fActivate);
401      $(once('loadEvent', this.tabs[i].accordionAnchor.get(0))).on(_this.options.event, { tab: _this.tabs[i] }, fActivate);
402    }
403  };
404
405  /**
406   * This function gets the tab that should be opened at start
407   * @returns {Object} Tab object
408   */
409  ResponsiveTabs.prototype._getStartTab = function () {
410    var tabRef = this._getTabRefBySelector(window.location.hash);
411    var startTab;
412
413    // Check if the page has a hash set that is linked to a tab
414    if (tabRef >= 0 && !this._getTab(tabRef).disabled) {
415      // If so, set the current tab to the linked tab
416      startTab = this._getTab(tabRef);
417    } else if (this.options.active > 0 && !this._getTab(this.options.active).disabled) {
418      startTab = this._getTab(this.options.active);
419    } else {
420      // If not, just get the first one
421      startTab = this._getTab(0);
422    }
423
424    return startTab;
425  };
426
427  /**
428   * This function sets the current state of the plugin
429   * @param {Event} e - The event that triggers the state change
430   */
431  ResponsiveTabs.prototype._setState = function (e) {
432    var $ul = $('ul:first', this.$element);
433    var oldState = this.state;
434    var startCollapsedIsState = (typeof this.options.startCollapsed === 'string');
435    var startTab;
436
437    var visible = $ul.is(':visible');
438
439    // The state is based on the visibility of the tabs list
440    if (visible) {
441      // Tab list is visible, so the state is 'tabs'
442      this.state = 'tabs';
443    } else {
444      // Tab list is invisible, so the state is 'accordion'
445      this.state = 'accordion';
446    }
447
448    // If the new state is different from the old state
449    if (this.state !== oldState) {
450
451      // Add the state class to the Container
452      this.$element.toggleClass(this.options.classes.stateTypePrefix + '-' + 'tabs', visible);
453      this.$element.toggleClass(this.options.classes.stateTypePrefix + '-' + 'accordion', !visible);
454
455      // If so, the state activate trigger must be called
456      this.$element.trigger('tabs-activate-state', { oldState: oldState, newState: this.state, tabs: this });
457
458      // Check if the state switch should open a tab
459      if (oldState && startCollapsedIsState && this.options.startCollapsed !== this.state && this._getCurrentTab() === undefined) {
460        // Get initial tab
461        startTab = this._getStartTab(e);
462        // Open the initial tab
463        this._openTab(e, startTab); // Open first tab
464      }
465
466      // Apply or remove role attributes based on mode.
467      if (this.state === 'tabs') {
468        this._applyTabRoles();
469      } else {
470        this._removeTabRoles();
471      }
472    }
473  };
474
475  /**
476   * This function opens a tab
477   * @param {Event} e - The event that triggers the tab opening
478   * @param {Object} oTab - The tab object that should be opened
479   * @param {Boolean} closeCurrent - Defines if the current tab should be closed
480   * @param {Boolean} stopRotation - Defines if the tab rotation loop should be stopped
481   */
482  ResponsiveTabs.prototype._openTab = function (e, oTab, closeCurrent, stopRotation) {
483    var _this = this;
484    var scrollOffset;
485
486    // If there is no tab (generally when tabs are empty) just return
487    if (typeof oTab === 'undefined') {
488      return;
489    }
490
491    // Check if the current tab has to be closed
492    if (closeCurrent) {
493      this._closeTab(e, this._getCurrentTab());
494    }
495
496    // Check if the rotation has to be stopped when activated
497    if (stopRotation && this.rotateInterval > 0) {
498      this.stopRotation();
499    }
500
501    // Set this tab to active
502    oTab.active = true;
503    // Set active classes to the tab button and accordion tab button
504    oTab.tab.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive);
505    oTab.accordionTab.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive);
506    // Run panel transition
507    _this._doTransition(oTab, _this.options.animation, 'open', function () {
508      // Determine if we should scroll:
509      // 1. On page load (tabs-load): only if hash matches THIS accordion's tab
510      // 2. On user interaction: always scroll (if enabled)
511      var hashMatchesThisTab = window.location.hash && oTab.selector === window.location.hash;
512      var isPageLoad = e.type === 'tabs-load';
513      var shouldScrollOnLoad = isPageLoad ? (_this.options.scrollToAccordionOnLoad && hashMatchesThisTab) : true;
514
515      // When finished, set active class to the panel
516      oTab.panel.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive);
517
518      // Update the aria
519      _this._updateAria(oTab);
520
521      // Scroll if enabled, in correct state (accordion or tabs), not in view, and should scroll
522      var isCorrectState = (_this.getState() === 'accordion' || _this.getState() === 'tabs');
523
524      // For tabs mode, scroll to the container; for accordion mode, scroll to the specific accordion tab
525      var scrollTarget = _this.getState() === 'tabs' ? _this.$element : oTab.accordionTab;
526
527      var shouldScroll = isCorrectState &&
528                        _this.options.scrollToAccordion &&
529                        (!_this._isInView(scrollTarget) || _this.options.animation !== 'default') &&
530                        shouldScrollOnLoad;
531
532      if (shouldScroll) {
533        // Add offset element's height to scroll position
534        scrollOffset = scrollTarget.offset().top - _this.options.scrollToAccordionOffset;
535
536        // Use consistent delay for both accordion and tabs to ensure content is rendered
537        var scrollDelay = 0;
538        if (isPageLoad) {
539          scrollDelay = 150;
540        } else {
541          // For user interactions (clicks), add small delay to ensure content is rendered
542          scrollDelay = 0;
543        }
544
545        setTimeout(function() {
546          // Check if the animation option is enabled, and if the duration isn't 0
547          if (_this.options.animation !== 'default' && _this.options.duration > 0) {
548            // If so, set scrollTop with animate and use the 'animation' duration
549            $('html, body').animate({
550              scrollTop: scrollOffset
551            }, _this.options.duration);
552          } else {
553            //  If not, just set scrollTop
554            $('html, body').animate({
555              scrollTop: scrollOffset
556            }, 1);
557          }
558        }, scrollDelay);
559      }
560    });
561
562    this.$element.trigger('tabs-activate', oTab);
563  };
564
565  /**
566   * This function closes a tab
567   * @param {Event} e - The event that is triggered when a tab is closed
568   * @param {Object} oTab - The tab object that should be closed
569   */
570  ResponsiveTabs.prototype._closeTab = function (e, oTab) {
571    var _this = this;
572    var doQueueOnState = typeof _this.options.animationQueue === 'string';
573    var doQueue;
574
575    if (oTab !== undefined) {
576      if (doQueueOnState && _this.getState() === _this.options.animationQueue) {
577        doQueue = true;
578      } else if (doQueueOnState) {
579        doQueue = false;
580      } else {
581        doQueue = _this.options.animationQueue;
582      }
583
584      // Deactivate tab
585      oTab.active = false;
586      // Set default class to the tab button
587      oTab.tab.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault);
588
589      // Update the aria
590      _this._updateAria(oTab);
591
592      // Run panel transition
593      _this._doTransition(oTab, _this.options.animation, 'close', function () {
594        // Set default class to the accordion tab button and tab panel
595        oTab.accordionTab.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault);
596        oTab.panel.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault);
597      }, !doQueue);
598
599      this.$element.trigger('tabs-deactivate', oTab);
600    }
601  };
602
603  /**
604   * This function runs an effect on a panel
605   * @param {Object} oTab - The object for the panel
606   * @param {String} method - The transition method reference
607   * @param {String} state - The state (open/closed) that the panel should transition to
608   * @param {Function} callback - The callback function that is called after the transition
609   * @param {Boolean} dequeue - Defines if the event queue should be dequeued after the transition
610   */
611  ResponsiveTabs.prototype._doTransition = function (oTab, method, state, callback, dequeue) {
612    var effect;
613    var _this = this;
614    var duration = _this.options.duration;
615
616    // Get effect based on method
617    switch (method) {
618      case 'slide':
619        effect = (state === 'open') ? 'slideDown' : 'slideUp';
620        duration = _this.options.duration;
621        break;
622      case 'fade':
623        effect = (state === 'open') ? 'fadeIn' : 'fadeOut';
624        duration = _this.options.duration;
625        break;
626      default:
627        effect = (state === 'open') ? 'show' : 'hide';
628        // When default is used, set the duration to 0
629        //_this.options.duration = 0;
630        duration = 0;
631        break;
632    }
633
634    // Prevent new tab content stacking underneath current tab content by removing fade animation duration
635    if (_this.options.animation === 'fade' && _this.state === 'tabs') {
636      effect = state === 'open' ? effect : 'hide';
637      duration = state === 'open' ? duration : 0;
638      oTab.panel.css('opacity', '');
639    }
640    // Run the transition on the panel
641    oTab.panel[effect]({
642      duration: duration,
643      queue: 'responsive-tabs-' + state,
644      complete: function () {
645        // Call the callback function
646        callback.call(oTab.panel, method, state);
647        _this.$element.trigger('tabs-activate-finished', oTab);
648      }
649    }).dequeue('responsive-tabs-' + state);
650  };
651
652  /**
653   * This function returns the collapsibility of the tab in this state
654   * @returns {Boolean} The collapsibility of the tab
655   */
656  ResponsiveTabs.prototype._isCollapisble = function () {
657    return (typeof this.options.collapsible === 'boolean' && this.options.collapsible) || (typeof this.options.collapsible === 'string' && this.options.collapsible === this.getState());
658  };
659
660  /**
661   * This function returns a tab by numeric reference
662   * @param {Integer} numRef - Numeric tab reference
663   * @returns {Object} Tab object
664   */
665  ResponsiveTabs.prototype._getTab = function (numRef) {
666    return this.tabs[numRef];
667  };
668
669  /**
670   * This function returns the numeric tab reference based on a hash selector
671   * @param {String} selector - Hash selector
672   * @returns {Integer} Numeric tab reference
673   */
674  ResponsiveTabs.prototype._getTabRefBySelector = function (selector) {
675    // Loop all tabs
676    for (var i = 0; i < this.tabs.length; i++) {
677      // Check if the hash selector is equal to the tab selector
678      if (this.tabs[i].selector === selector) {
679        return i;
680      }
681    }
682    // If none is found return a negative index
683    return -1;
684  };
685
686  /**
687   * This function returns the current tab element
688   * @returns {Object} Current tab element
689   */
690  ResponsiveTabs.prototype._getCurrentTab = function () {
691    return this._getTab(this._getCurrentTabRef());
692  };
693
694  /**
695   * This function returns the next tab's numeric reference
696   * @param {Integer} currentTabRef - Current numeric tab reference
697   * @returns {Integer} Numeric tab reference
698   */
699  ResponsiveTabs.prototype._getNextTabRef = function (currentTabRef) {
700    var tabRef = (currentTabRef || this._getCurrentTabRef());
701    var nextTabRef = (tabRef === this.tabs.length - 1) ? 0 : tabRef + 1;
702    return (this._getTab(nextTabRef).disabled) ? this._getNextTabRef(nextTabRef) : nextTabRef;
703  };
704
705  /**
706   * This function returns the previous tab's numeric reference
707   * @returns {Integer} Numeric tab reference
708   */
709  ResponsiveTabs.prototype._getPreviousTabRef = function () {
710    return (this._getCurrentTabRef() === 0) ? this.tabs.length - 1 : this._getCurrentTabRef() - 1;
711  };
712
713  /**
714   * This function returns the current tab's numeric reference
715   * @returns {Integer} Numeric tab reference
716   */
717  ResponsiveTabs.prototype._getCurrentTabRef = function () {
718    // Loop all tabs
719    for (var i = 0; i < this.tabs.length; i++) {
720      // If this tab is active, return it
721      if (this.tabs[i].active) {
722        return i;
723      }
724    }
725    // No tabs have been found, return negative index
726    return -1;
727  };
728
729  /**
730   * This function gets the tallest tab and applied the height to all tabs
731   */
732  ResponsiveTabs.prototype._equaliseHeights = function () {
733    var maxHeight = 0;
734
735    $.each($.map(this.tabs, function (tab) {
736      maxHeight = Math.max(maxHeight, tab.panel.css('minHeight', '').height());
737      return tab.panel;
738    }), function () {
739      this.css('minHeight', maxHeight);
740    });
741  };
742
743  //
744  // HELPER FUNCTIONS
745  //
746
747  ResponsiveTabs.prototype._isInView = function ($element) {
748    var docViewTop = $(window).scrollTop(),
749      docViewBottom = docViewTop + $(window).height(),
750      elemTop = $element.offset().top,
751      elemBottom = elemTop + $element.height();
752    return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
753  };
754
755  //
756  // PUBLIC FUNCTIONS
757  //
758
759
760  /**
761   * This function returns the current tab's numeric reference
762   * @returns {Integer} Numeric tab reference
763   */
764  ResponsiveTabs.prototype.getCurrentTab = function () {
765    return this._getCurrentTabRef();
766  };
767
768  /**
769   * This function returns the previous tab's numeric reference
770   * @returns {Integer} Numeric tab reference
771   */
772  ResponsiveTabs.prototype.getPreviousTab = function () {
773    return this._getPreviousTabRef();
774  };
775
776  /**
777   * This function activates a tab
778   * @param {Integer} tabRef - Numeric tab reference
779   * @param {Boolean} stopRotation - Defines if the tab rotation should stop after activation
780   */
781  ResponsiveTabs.prototype.activate = function (tabRef, stopRotation) {
782    var e = jQuery.Event('tabs-activate');
783    var oTab = this._getTab(tabRef);
784    if (!oTab.disabled) {
785      this._openTab(e, oTab, true, stopRotation || true);
786    }
787  };
788
789  /**
790   * This function deactivates a tab
791   * @param {Integer} tabRef - Numeric tab reference
792   */
793  ResponsiveTabs.prototype.deactivate = function (tabRef) {
794    var e = jQuery.Event('tabs-dectivate');
795    var oTab = this._getTab(tabRef);
796    if (!oTab.disabled) {
797      this._closeTab(e, oTab);
798    }
799  };
800
801  /**
802   * This function enables a tab
803   * @param {Integer} tabRef - Numeric tab reference
804   */
805  ResponsiveTabs.prototype.enable = function (tabRef) {
806    var oTab = this._getTab(tabRef);
807    if (oTab) {
808      oTab.disabled = false;
809      oTab.tab.addClass(this.options.classes.stateDefault).removeClass(this.options.classes.stateDisabled);
810      oTab.accordionTab.addClass(this.options.classes.stateDefault).removeClass(this.options.classes.stateDisabled);
811      if (this.state === 'tabs') {
812        oTab.anchor.removeAttr('aria-disabled');
813      }
814    }
815  };
816
817  /**
818   * This function disable a tab
819   * @param {Integer} tabRef - Numeric tab reference
820   */
821  ResponsiveTabs.prototype.disable = function (tabRef) {
822    var oTab = this._getTab(tabRef);
823    if (oTab) {
824      oTab.disabled = true;
825      oTab.tab.removeClass(this.options.classes.stateDefault).addClass(this.options.classes.stateDisabled);
826      oTab.accordionTab.removeClass(this.options.classes.stateDefault).addClass(this.options.classes.stateDisabled);
827      if (this.state === 'tabs') {
828        oTab.anchor.attr('aria-disabled', 'true');
829      }
830    }
831  };
832
833  /**
834   * This function gets the current state of the plugin
835   * @returns {String} State of the plugin
836   */
837  ResponsiveTabs.prototype.getState = function () {
838    return this.state;
839  };
840
841  /**
842   * This function starts the rotation of the tabs
843   * @param {Integer} speed - The speed of the rotation
844   */
845  ResponsiveTabs.prototype.startRotation = function (speed) {
846    var _this = this;
847    // Make sure not all tabs are disabled
848    if (this.tabs.length > this.options.disabled.length) {
849      this.rotateInterval = setInterval(function () {
850        var e = jQuery.Event('rotate');
851        _this._openTab(e, _this._getTab(_this._getNextTabRef()), true);
852      }, speed || ((typeof (_this.options.rotate) === 'number') ? _this.options.rotate : 4000));
853    } else {
854      throw new Error("Rotation is not possible if all tabs are disabled");
855    }
856  };
857
858  /**
859   * This function stops the rotation of the tabs
860   */
861  ResponsiveTabs.prototype.stopRotation = function () {
862    window.clearInterval(this.rotateInterval);
863    this.rotateInterval = 0;
864  };
865
866  /**
867   * This function can be used to get/set options
868   * @return {any} Option value
869   */
870  ResponsiveTabs.prototype.option = function (key, value) {
871    if (typeof value !== 'undefined') {
872      this.options[key] = value;
873    }
874    return this.options[key];
875  };
876
877  /**
878   * This function refreshes current list of tabs - particularly useful for adding tabs with AJAX
879   * @returns {undefined}
880   */
881  ResponsiveTabs.prototype.refresh = function () {
882
883    this.tabs = this.tabs.concat(this._loadElements());
884
885    this._loadClasses();
886    this._loadEvents();
887
888    // Set fluid height
889    if (this.options.fluidHeight !== true) {
890      this._equaliseHeights();
891    }
892
893    this.$element.trigger('tabs-refresh', this);
894    this._setState();
895
896    // Re-apply roles for any newly added tabs.
897    if (this.state === 'tabs') {
898      this._applyTabRoles();
899    }
900
901    return this;
902  };
903
904  /**
905   * This function adds specific role attributes when in tab mode.
906   * @returns {undefined}
907   */
908  ResponsiveTabs.prototype._applyTabRoles = function () {
909    var hasTabItems = false;
910
911    // Add tab & tabpanels attributes only for items that activate panels.
912    for (var i = 0; i < this.tabs.length; i++) {
913      var tab = this.tabs[i];
914      if (tab.linkUrl === false) {
915        hasTabItems = true;
916        var panelId = tab.panel.attr('id');
917        var tabId = 'tab-' + panelId;
918        tab.tab.attr('role', 'presentation');
919        tab.anchor.attr('role', 'tab');
920        tab.anchor.attr('id', tabId);
921        tab.anchor.attr('aria-selected', tab.active ? 'true' : 'false');
922        tab.anchor.attr('aria-controls', panelId);
923        if (tab.disabled) {
924          tab.anchor.attr('aria-disabled', 'true');
925        } else {
926          tab.anchor.removeAttr('aria-disabled');
927        }
928        tab.panel.attr('role', 'tabpanel');
929        tab.panel.attr('aria-labelledby', tabId);
930      } else {
931        tab.tab.attr('role', 'presentation');
932        tab.anchor.removeAttr('role id aria-selected aria-controls aria-disabled');
933        tab.panel.removeAttr('role aria-labelledby');
934      }
935    }
936
937    // Only add tablist role when the list contains tab items.
938    if (hasTabItems) {
939      this.$element.children('ul:first').attr('role', 'tablist');
940    } else {
941      this.$element.children('ul:first').removeAttr('role');
942    }
943  };
944
945  /**
946   * This function removes role attributes when not in tab mode.
947   * @returns {undefined}
948   */
949  ResponsiveTabs.prototype._removeTabRoles = function () {
950    this.$element.children('ul:first').removeAttr('role');
951
952    for (var i = 0; i < this.tabs.length; i++) {
953      this.tabs[i].tab.removeAttr('role');
954      this.tabs[i].anchor.removeAttr('role id aria-selected aria-controls aria-disabled');
955      this.tabs[i].panel.removeAttr('role aria-labelledby');
956    }
957  };
958
959  /** jQuery wrapper */
960  $.fn.responsiveTabs = function (options) {
961    var args = arguments;
962    var instance;
963
964    var classes = [
965      'stateActive',
966      'stateDisabled',
967      'stateExcluded'
968    ];
969
970    if (options === undefined || typeof options === 'object') {
971      return this.each(function () {
972
973        // If responsiveTabs doesn't exist init
974        if (!$.data(this, 'responsivetabs')) {
975          $.data(this, 'responsivetabs', new ResponsiveTabs(this, options));
976        } else {
977          // Otherwise just update the settings
978          $.extend($.data(this, 'responsivetabs').options, options);
979        }
980      });
981    } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
982      instance = $.data(this[0], 'responsivetabs');
983
984      // Allow instances to be destroyed via the 'destroy' method
985      if (options === 'destroy') {
986        // TODO: destroy instance classes, etc
987
988        // Clean up the tabs etc
989        if (typeof instance !== 'undefined') {
990          for (var i = 0; i < instance.tabs.length; i++) {
991            $.each($([instance.tabs[i].accordionTab, instance.tabs[i].panel, instance.tabs[i].tab]), function () {
992              var $this = $(this);
993              $this.removeAttr('style');
994              $this.removeClass(instance.options.classes.stateActive);
995              $this.removeClass(instance.options.classes.stateDisabled);
996              $this.removeClass(instance.options.classes.stateExcluded);
997            });
998          }
999
1000          // Remove any bound event handlers on the container
1001          for (var i = 0; i < events.length; i++) {
1002            instance.$element.unbind(events[i]);
1003          }
1004
1005          // Loop tabs to remove any 'event' bindings
1006          for (var i = 0; i < instance.tabs.length; i++) {
1007            // Add activate function to the tab and accordion selection element
1008            instance.tabs[i].anchor.off(instance.options.event);
1009            instance.tabs[i].accordionAnchor.off(instance.options.event);
1010          }
1011
1012          // Remove data from the DOM element
1013          $.removeData(this[0], 'responsivetabs');
1014        }
1015      }
1016
1017      if (instance instanceof ResponsiveTabs && typeof instance[options] === 'function') {
1018        return instance[options].apply(instance, Array.prototype.slice.call(args, 1));
1019      } else {
1020        return this;
1021      }
1022    }
1023  };
1024
1025}(jQuery, once, window));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.