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.