1/* 2 * Project: jquery.responsiveTabs.js 3 * Description: A plugin that creates responsive tabs, optimized for all devices 4 * Author: Jelle Kralt ([email protected]) 5 * Version: 1.4.2 6 * License: MIT 7 */ 8 9;(function ( $, window, undefined ) { 10 11 /** Default settings */ 12 var defaults = { 13 active: null, 14 event: 'click', 15 disabled: [], 16 collapsible: 'accordion', 17 startCollapsed: false, 18 rotate: false, 19 setHash: false, 20 animation: 'default', 21 duration: 500, 22 scrollToAccordion: false, 23 activate: function(){}, 24 deactivate: function(){}, 25 load: function(){}, 26 activateState: function(){}, 27 classes: { 28 stateDefault: 'r-tabs-state-default', 29 stateActive: 'r-tabs-state-active', 30 stateDisabled: 'r-tabs-state-disabled', 31 stateExcluded: 'r-tabs-state-excluded', 32 tab: 'r-tabs-tab', 33 anchor: 'r-tabs-anchor', 34 panel: 'r-tabs-panel', 35 accordionTitle: 'r-tabs-accordion-title' 36 } 37 }; 38 39 /** 40 * Responsive Tabs 41 * @constructor 42 * @param {object} element - The HTML element the validator should be bound to 43 * @param {object} options - An option map 44 */ 45 function ResponsiveTabs(element, options) { 46 this.element = element; // Selected DOM element 47 this.$element = $(element); // Selected jQuery element 48 49 this.tabs = []; // Create tabs array 50 this.state = ''; // Define the plugin state (tabs/accordion) 51 this.rotateInterval = 0; // Define rotate interval 52 this.$queue = $({}); 53 54 // Extend the defaults with the passed options 55 this.options = $.extend( {}, defaults, options); 56 57 this.init(); 58 } 59 60 61 /** 62 * This function initializes the tab plugin 63 */ 64 ResponsiveTabs.prototype.init = function () { 65 var _this = this; 66 67 // Load all the elements 68 this.tabs = this._loadElements(); 69 this._loadClasses(); 70 this._loadEvents(); 71 72 // Window resize bind to check state 73 $(window).on('resize', function(e) { 74 _this._setState(e); 75 }); 76 77 // Hashchange event 78 $(window).on('hashchange', function(e) { 79 var tabRef = _this._getTabRefBySelector(window.location.hash); 80 var oTab = _this._getTab(tabRef); 81 82 // Check if a tab is found that matches the hash 83 if(tabRef >= 0 && !oTab._ignoreHashChange && !oTab.disabled) { 84 // If so, open the tab and auto close the current one 85 _this._openTab(e, _this._getTab(tabRef), true); 86 } 87 }); 88 89 // Start rotate event if rotate option is defined 90 if(this.options.rotate !== false) { 91 this.startRotation(); 92 } 93 94 // -------------------- 95 // Define plugin events 96 // 97 98 // Activate: this event is called when a tab is selected 99 this.$element.bind('tabs-activate', function(e, oTab) { 100 _this.options.activate.call(this, e, oTab); 101 }); 102 // Deactivate: this event is called when a tab is closed 103 this.$element.bind('tabs-deactivate', function(e, oTab) { 104 _this.options.deactivate.call(this, e, oTab); 105 }); 106 // Activate State: this event is called when the plugin switches states 107 this.$element.bind('tabs-activate-state', function(e, state) { 108 _this.options.activateState.call(this, e, state); 109 }); 110 111 // Load: this event is called when the plugin has been loaded 112 this.$element.bind('tabs-load', function(e) { 113 var startTab; 114 115 _this._setState(e); // Set state 116 117 // Check if the panel should be collaped on load 118 if(_this.options.startCollapsed !== true && !(_this.options.startCollapsed === 'accordion' && _this.state === 'accordion')) { 119 120 startTab = _this._getStartTab(); 121 122 // Open the initial tab 123 _this._openTab(e, startTab); // Open first tab 124 125 // Call the callback function 126 _this.options.load.call(this, e, startTab); // Call the load callback 127 } 128 }); 129 // Trigger loaded event 130 this.$element.trigger('tabs-load'); 131 }; 132 133 // 134 // PRIVATE FUNCTIONS 135 // 136 137 /** 138 * This function loads the tab elements and stores them in an array 139 * @returns {Array} Array of tab elements 140 */ 141 ResponsiveTabs.prototype._loadElements = function() { 142 var _this = this; 143 var $ul = this.$element.children('ul'); 144 var tabs = []; 145 var id = 0; 146 147 // Add the classes to the basic html elements 148 this.$element.addClass('r-tabs'); // Tab container 149 $ul.addClass('r-tabs-nav'); // List container 150 151 // Get tab buttons and store their data in an array 152 $('li', $ul).each(function() { 153 var $tab = $(this);
154 var isExcluded = $tab.hasClass(_this.options.classes.stateExcluded); 155 var $anchor, $panel, $accordionTab, $accordionAnchor, panelSelector; 156 157 // Check if the tab should be excluded 158 if(!isExcluded) { 159 160 $anchor = $('a', $tab); 161 panelSelector = $anchor.attr('href'); 162 $panel = $(panelSelector); 163 $accordionTab = $('<div></div>').insertBefore($panel); 164 $accordionAnchor = $('<a></a>').attr('href', panelSelector).html($anchor.html()).appendTo($accordionTab); 165 166 var oTab = { 167 _ignoreHashChange: false, 168 id: id, 169 disabled: ($.inArray(id, _this.options.disabled) !== -1), 170 tab: $(this), 171 anchor: $('a', $tab), 172 panel: $panel, 173 selector: panelSelector, 174 accordionTab: $accordionTab, 175 accordionAnchor: $accordionAnchor, 176 active: false 177 }; 178 179 // 1up the ID 180 id++; 181 // Add to tab array 182 tabs.push(oTab); 183 } 184 }); 185 return tabs; 186 }; 187 188 189 /** 190 * This function adds classes to the tab elements based on the options 191 */ 192 ResponsiveTabs.prototype._loadClasses = function() { 193 for (var i=0; i<this.tabs.length; i++) { 194 this.tabs[i].tab.addClass(this.options.classes.stateDefault).addClass(this.options.classes.tab); 195 this.tabs[i].anchor.addClass(this.options.classes.anchor); 196 this.tabs[i].panel.addClass(this.options.classes.stateDefault).addClass(this.options.classes.panel); 197 this.tabs[i].accordionTab.addClass(this.options.classes.accordionTitle); 198 this.tabs[i].accordionAnchor.addClass(this.options.classes.anchor); 199 if(this.tabs[i].disabled) { 200 this.tabs[i].tab.removeClass(this.options.classes.stateDefault).addClass(this.options.classes.stateDisabled); 201 this.tabs[i].accordionTab.removeClass(this.options.classes.stateDefault).addClass(this.options.classes.stateDisabled); 202 } 203 } 204 }; 205 206 /** 207 * This function adds events to the tab elements 208 */ 209 ResponsiveTabs.prototype._loadEvents = function() { 210 var _this = this; 211 212 // Define activate event on a tab element 213 var fActivate = function(e) { 214 var current = _this._getCurrentTab(); // Fetch current tab 215 var activatedTab = e.data.tab; 216 217 e.preventDefault(); 218 219 // Make sure this tab isn't disabled 220 if(!activatedTab.disabled) { 221 222 // Check if hash has to be set in the URL location 223 if(_this.options.setHash) { 224 window.location.hash = activatedTab.selector; 225 } 226 227 e.data.tab._ignoreHashChange = true; 228 229 // Check if the activated tab isnt the current one or if its collapsible. If not, do nothing 230 if(current !== activatedTab || _this._isCollapisble()) { 231 // The activated tab is either another tab of the current one. If it's the current tab it is collapsible 232 // Either way, the current tab can be closed 233 _this._closeTab(e, current); 234 235 // Check if the activated tab isnt the current one or if it isnt collapsible 236 if(current !== activatedTab || !_this._isCollapisble()) { 237 _this._openTab(e, activatedTab, false, true); 238 } 239 } 240 } 241 }; 242 243 // Loop tabs 244 for (var i=0; i<this.tabs.length; i++) { 245 // Add activate function to the tab and accordion selection element 246 this.tabs[i].anchor.on(_this.options.event, {tab: _this.tabs[i]}, fActivate); 247 this.tabs[i].accordionAnchor.on(_this.options.event, {tab: _this.tabs[i]}, fActivate); 248 } 249 }; 250 251 /** 252 * This function gets the tab that should be opened at start 253 * @returns {Object} Tab object 254 */ 255 ResponsiveTabs.prototype._getStartTab = function() { 256 var tabRef = this._getTabRefBySelector(window.location.hash); 257 var startTab; 258 259 // Check if the page has a hash set that is linked to a tab 260 if(tabRef >= 0 && !this._getTab(tabRef).disabled) { 261 // If so, set the current tab to the linked tab 262 startTab = this._getTab(tabRef); 263 } else if(this.options.active > 0 && !this._getTab(this.options.active).disabled) { 264 startTab = this._getTab(this.options.active); 265 } else { 266 // If not, just get the first one 267 startTab = this._getTab(0); 268 } 269 270 return startTab; 271 }; 272 273 /** 274 * This function sets the current state of the plugin 275 * @param {Event} e - The event that triggers the state change 276 */ 277 ResponsiveTabs.prototype._setState = function(e) { 278 var $ul = $('ul', this.$element); 279 var oldState = this.state; 280 var startCollapsedIsState = (typeof this.options.startCollapsed === 'string'); 281 var startTab; 282 283 // The state is based on the visibility of the tabs list 284 if($ul.is(':visible')){ 285 // Tab list is visible, so the state is 'tabs' 286 this.state = 'tabs'; 287 } else { 288 // Tab list is invisible, so the state is 'accordion' 289 this.state = 'accordion'; 290 } 291 292 // If the new state is different from the old state 293 if(this.state !== oldState) { 294 // If so, the state activate trigger must be called 295 this.$element.trigger('tabs-activate-state', {oldState: oldState, newState: this.state}); 296 297 // Check if the state switch should open a tab 298 if(oldState && startCollapsedIsState && this.options.startCollapsed !== this.state && this._getCurrentTab() === undefined) { 299 // Get initial tab 300 startTab = this._getStartTab(e); 301 // Open the initial tab 302 this._openTab(e, startTab); // Open first tab 303 } 304 305 306 } 307 }; 308 309 /** 310 * This function opens a tab 311 * @param {Event} e - The event that triggers the tab opening 312 * @param {Object} oTab - The tab object that should be opened 313 * @param {Boolean} closeCurrent - Defines if the current tab should be closed 314 * @param {Boolean} stopRotation - Defines if the tab rotation loop should be stopped 315 */ 316 ResponsiveTabs.prototype._openTab = function(e, oTab, closeCurrent, stopRotation) { 317 var _this = this; 318 319 // Check if the current tab has to be closed 320 if(closeCurrent) { 321 this._closeTab(e, this._getCurrentTab()); 322 } 323 324 // Check if the rotation has to be stopped when activated 325 if(stopRotation && this.rotateInterval > 0) { 326 this.stopRotation(); 327 } 328 329 // Set this tab to active 330 oTab.active = true; 331 // Set active classes to the tab button and accordion tab button 332 oTab.tab.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive); 333 oTab.accordionTab.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive); 334 335 // Run panel transiton 336 _this._doTransition(oTab.panel, _this.options.animation, 'open', function() { 337 // When finished, set active class to the panel 338 oTab.panel.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive); 339 340 // And if enabled and state is accordion, scroll to the accordion tab
341 if(_this.getState() === 'accordion' && _this.options.scrollToAccordion && (!_this._isInView(oTab.accordionTab) || _this.options.animation !== 'default')) { 342 // Check if the animation option is enabled, and if the duration isn't 0 343 if(_this.options.animation !== 'default' && _this.options.duration > 0) { 344 // If so, set scrollTop with animate and use the 'animation' duration 345 $('html, body').animate({ 346 scrollTop: oTab.accordionTab.offset().top 347 }, _this.options.duration); 348 } else { 349 // If not, just set scrollTop 350 $('html, body').scrollTop(oTab.accordionTab.offset().top); 351 } 352 } 353 }); 354 355 356 357 this.$element.trigger('tabs-activate', oTab); 358 }; 359 360 /** 361 * This function closes a tab 362 * @param {Event} e - The event that is triggered when a tab is closed 363 * @param {Object} oTab - The tab object that should be closed 364 */ 365 ResponsiveTabs.prototype._closeTab = function(e, oTab) { 366 var _this = this; 367 368 if(oTab !== undefined) { 369 370 // Deactivate tab 371 oTab.active = false; 372 // Set default class to the tab button 373 oTab.tab.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault); 374 375 // Run panel transition 376 _this._doTransition(oTab.panel, _this.options.animation, 'close', function() { 377 // Set default class to the accordion tab button and tab panel 378 oTab.accordionTab.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault); 379 oTab.panel.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault); 380 }, true); 381 382 this.$element.trigger('tabs-deactivate', oTab); 383 } 384 }; 385 386 /** 387 * This function runs an effect on a panel 388 * @param {Element} panel - The HTML element of the tab panel 389 * @param {String} method - The transition method reference 390 * @param {String} state - The state (open/closed) that the panel should transition to 391 * @param {Function} callback - The callback function that is called after the transition 392 * @param {Boolean} dequeue - Defines if the event queue should be dequeued after the transition 393 */ 394 ResponsiveTabs.prototype._doTransition = function(panel, method, state, callback, dequeue) { 395 var effect; 396 var _this = this; 397 398 // Get effect based on method 399 switch(method) { 400 case 'slide': 401 effect = (state === 'open') ? 'slideDown' : 'slideUp'; 402 break; 403 case 'fade': 404 effect = (state === 'open') ? 'fadeIn' : 'fadeOut'; 405 break; 406 default: 407 effect = (state === 'open') ? 'show' : 'hide'; 408 // When default is used, set the duration to 0 409 _this.options.duration = 0; 410 break; 411 } 412 413 // Add the transition to a custom queue 414 this.$queue.queue('responsive-tabs',function(next){ 415 // Run the transition on the panel 416 panel[effect]({ 417 duration: _this.options.duration, 418 complete: function() { 419 // Call the callback function 420 callback.call(panel, method, state); 421 // Run the next function in the queue 422 next(); 423 } 424 }); 425 }); 426 427 // When the panel is openend, dequeue everything so the animation starts 428 if(state === 'open' || dequeue) { 429 this.$queue.dequeue('responsive-tabs'); 430 } 431 432 }; 433 434 /** 435 * This function returns the collapsibility of the tab in this state 436 * @returns {Boolean} The collapsibility of the tab 437 */ 438 ResponsiveTabs.prototype._isCollapisble = function() { 439 return (typeof this.options.collapsible === 'boolean' && this.options.collapsible) || (typeof this.options.collapsible === 'string' && this.options.collapsible === this.get
439State()); 440 }; 441 442 /** 443 * This function returns a tab by numeric reference 444 * @param {Integer} numRef - Numeric tab reference 445 * @returns {Object} Tab object 446 */ 447 ResponsiveTabs.prototype._getTab = function(numRef) { 448 return this.tabs[numRef]; 449 }; 450 451 /** 452 * This function returns the numeric tab reference based on a hash selector 453 * @param {String} selector - Hash selector 454 * @returns {Integer} Numeric tab reference 455 */ 456 ResponsiveTabs.prototype._getTabRefBySelector = function(selector) { 457 // Loop all tabs 458 for (var i=0; i<this.tabs.length; i++) { 459 // Check if the hash selector is equal to the tab selector 460 if(this.tabs[i].selector === selector) { 461 return i; 462 } 463 } 464 // If none is found return a negative index 465 return -1; 466 }; 467 468 /** 469 * This function returns the current tab element 470 * @returns {Object} Current tab element 471 */ 472 ResponsiveTabs.prototype._getCurrentTab = function() { 473 return this._getTab(this._getCurrentTabRef()); 474 }; 475 476 /** 477 * This function returns the next tab's numeric reference 478 * @param {Integer} currentTabRef - Current numeric tab reference 479 * @returns {Integer} Numeric tab reference 480 */ 481 ResponsiveTabs.prototype._getNextTabRef = function(currentTabRef) { 482 var tabRef = (currentTabRef || this._getCurrentTabRef()); 483 var nextTabRef = (tabRef === this.tabs.length - 1) ? 0 : tabRef + 1; 484 return (this._getTab(nextTabRef).disabled) ? this._getNextTabRef(nextTabRef) : nextTabRef; 485 }; 486 487 /** 488 * This function returns the previous tab's numeric reference 489 * @returns {Integer} Numeric tab reference 490 */ 491 ResponsiveTabs.prototype._getPreviousTabRef = function() { 492 return (this._getCurrentTabRef() === 0) ? this.tabs.length - 1 : this._getCurrentTabRef() - 1; 493 }; 494 495 /** 496 * This function returns the current tab's numeric reference 497 * @returns {Integer} Numeric tab reference 498 */ 499 ResponsiveTabs.prototype._getCurrentTabRef = function() { 500 // Loop all tabs 501 for (var i=0; i<this.tabs.length; i++) { 502 // If this tab is active, return it 503 if(this.tabs[i].active) { 504 return i; 505 } 506 } 507 // No tabs have been found, return negative index 508 return -1; 509 }; 510 511 // 512 // HELPER FUNCTIONS 513 // 514 515 ResponsiveTabs.prototype._isInView = function($element) { 516 var docViewTop = $(window).scrollTop(), 517 docViewBottom = docViewTop + $(window).height(), 518 elemTop = $element.offset().top, 519 elemBottom = elemTop + $element.height(); 520 return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); 521 }; 522 523 // 524 // PUBLIC FUNCTIONS 525 // 526 527 /** 528 * This function activates a tab 529 * @param {Integer} tabRef - Numeric tab reference 530 * @param {Boolean} stopRotation - Defines if the tab rotation should stop after activation 531 */ 532 ResponsiveTabs.prototype.activate = function(tabRef, stopRotation) { 533 var e = jQuery.Event('tabs-activate'); 534 var oTab = this._getTab(tabRef); 535 if(!oTab.disabled) { 536 this._openTab(e, oTab, true, stopRotation || true); 537 } 538 }; 539 540 /** 541 * This function deactivates a tab 542 * @param {Integer} tabRef - Numeric tab reference 543 */ 544 ResponsiveTabs.prototype.deactivate = function(tabRef) { 545 var e = jQuery.Event('tabs-dectivate'); 546 var oTab = this._getTab(tabRef); 547 if(!oTab.disabled) { 548 this._closeTab(e, oTab); 549 } 550 }; 551 552 /** 553 * This function gets the current state of the plugin 554 * @returns {String} State of the plugin 555 */ 556 ResponsiveTabs.prototype.getState = function() { 557 return this.state; 558 }; 559 560 /** 561 * This function starts the rotation of the tabs 562 * @param {Integer} speed - The speed of the rotation 563 */ 564 ResponsiveTabs.prototype.startRotation = function(speed) { 565 var _this = this; 566 // Make sure not all tabs are disabled 567 if(this.tabs.length > this.options.disabled.length) { 568 this.rotateInterval = setInterval(function(){ 569 var e = jQuery.Event('rotate'); 570 _this._openTab(e, _this._getTab(_this._getNextTabRef()), true); 571 }, speed || (($.isNumeric(_this.options.rotate)) ? _this.options.rotate : 4000) ); 572 } else { 573 throw new Error("Rotation is not possible if all tabs are disabled"); 574 } 575 }; 576 577 /** 578 * This function stops the rotation of the tabs 579 */ 580 ResponsiveTabs.prototype.stopRotation = function() { 581 window.clearInterval(this.rotateInterval); 582 this.rotateInterval = 0; 583 }; 584 585 /** jQuery wrapper */ 586 $.fn.responsiveTabs = function ( options ) { 587 var args = arguments; 588 if (options === undefined || typeof options === 'object') { 589 return this.each(function () { 590 if (!$.data(this, 'responsivetabs')) { 591 $.data(this, 'responsivetabs', new ResponsiveTabs( this, options )); 592 } 593 }); 594 } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { 595 return this.each(function () { 596 var instance = $.data(this, 'responsivetabs'); 597 598 if (instance instanceof ResponsiveTabs && typeof instance[options] === 'function') { 599 instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) ); 600 } 601 602 // Allow instances to be destroyed via the 'destroy' method 603 if (options === 'destroy') { 604 // TODO: destroy instance classes, etc 605 $.data(this, 'responsivetabs', null); 606 } 607 }); 608 } 609 }; 610 611}(jQuery, window)); 612
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.