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