/* * Project: jquery.responsiveTabs.js * Description: A plugin that creates responsive tabs, optimized for all devices * Author: Jelle Kralt (jelle@jellekralt.nl) * Version: 1.4.0 * License: MIT */ ;(function ( $, window, undefined ) { /** Default settings */ var defaults = { active: null, disabled: [], collapsible: 'accordion', startCollapsed: false, rotate: false, setHash: false, animation: 'default', duration: 500, activate: function(){}, deactivate: function(){}, load: function(){}, activateState: function(){}, classes: { stateDefault: 'r-tabs-state-default', stateActive: 'r-tabs-state-active', stateDisabled: 'r-tabs-state-disabled', stateExcluded: 'r-tabs-state-excluded', tab: 'r-tabs-tab', anchor: 'r-tabs-anchor', panel: 'r-tabs-panel', accordionTitle: 'r-tabs-accordion-title' } }; /** * Responsive Tabs * @constructor * @param {object} element - The HTML element the validator should be bound to * @param {object} options - An option map */ function ResponsiveTabs(element, options) { this.element = element; // Selected DOM element this.$element = $(element); // Selected jQuery element this.tabs = []; // Create tabs array this.state = ''; // Define the plugin state (tabs/accordion) this.rotateInterval = 0; // Define rotate interval this.$queue = $({}); // Extend the defaults with the passed options this.options = $.extend( {}, defaults, options); this.init(); } /** * This function initializes the tab plugin */ ResponsiveTabs.prototype.init = function () { var _this = this; // Load all the elements this.tabs = this._loadElements(); this._loadClasses(); this._loadEvents(); // Window resize bind to check state $(window).on('resize', function(e) { _this._setState(e); }); // Hashchange event $(window).on('hashchange', function(e) { var tabRef = _this._getTabRefBySelector(window.location.hash); var oTab = _this._getTab(tabRef); // Check if a tab is found that matches the hash if(tabRef >= 0 && !oTab._ignoreHashChange && !oTab.disabled) { // If so, open the tab and auto close the current one _this._openTab(e, _this._getTab(tabRef), true); } }); // Start rotate event if rotate option is defined if(this.options.rotate !== false) { this.startRotation(); } // -------------------- // Define plugin events // // Activate: this event is called when a tab is selected this.$element.bind('tabs-activate', function(e, oTab) { _this.options.activate.call(this, e, oTab); }); // Deactivate: this event is called when a tab is closed this.$element.bind('tabs-deactivate', function(e, oTab) { _this.options.deactivate.call(this, e, oTab); }); // Activate State: this event is called when the plugin switches states this.$element.bind('tabs-activate-state', function(e, state) { _this.options.activateState.call(this, e, state); }); // Load: this event is called when the plugin has been loaded this.$element.bind('tabs-load', function(e) { var tabRef = _this._getTabRefBySelector(window.location.hash); var firstTab; _this._setState(e); // Set state // Check if the panel should be collaped on load if(_this.options.startCollapsed !== true && !(_this.options.startCollapsed === 'accordion' && _this.state === 'accordion')) { // Check if the page has a hash set that is linked to a tab if(tabRef >= 0 && !_this._getTab(tabRef).disabled) { // If so, set the current tab to the linked tab firstTab = _this._getTab(tabRef); } else if(_this.options.active > 0 && !_this._getTab(_this.options.active).disabled) { firstTab = _this._getTab(_this.options.active); } else { // If not, just get the first one firstTab = _this._getTab(0); } // Open the initial tab _this._openTab(e, firstTab); // Open first tab // Call the callback function _this.options.load.call(this, e, firstTab); // Call the load callback } }); // Trigger loaded event this.$element.trigger('tabs-load'); }; // // PRIVATE FUNCTIONS // /** * This function loads the tab elements and stores them in an array * @returns {Array} Array of tab elements */ ResponsiveTabs.prototype._loadElements = function() { var _this = this; var $ul = this.$element.children('ul'); var tabs = []; var id = 0; // Add the classes to the basic html elements this.$element.addClass('r-tabs'); // Tab container $ul.addClass('r-tabs-nav'); // List container // Get tab buttons and store their data in an array $('li', $ul).each(function() { var $tab = $(this); var isExcluded = $tab.hasClass(_this.options.classes.stateExcluded); var $anchor, $panel, $accordionTab, $accordionAnchor, panelSelector; // Check if the tab should be excluded if(!isExcluded) { $anchor = $('a', $tab); panelSelector = $anchor.attr('href'); $panel = $(panelSelector); $accordionTab = $('
').insertBefore($panel); $accordionAnchor = $('').attr('href', panelSelector).html($anchor.html()).appendTo($accordionTab); var oTab = { _ignoreHashChange: false, id: id, disabled: ($.inArray(id, _this.options.disabled) !== -1), tab: $(this), anchor: $('a', $tab), panel: $panel, selector: panelSelector, accordionTab: $accordionTab, accordionAnchor: $accordionAnchor, active: false }; // 1up the ID id++; // Add to tab array tabs.push(oTab); } }); return tabs; }; /** * This function adds classes to the tab elements based on the options */ ResponsiveTabs.prototype._loadClasses = function() { for (var i=0; i 0) { this.stopRotation(); } // Set this tab to active oTab.active = true; // Set active classes to the tab button and accordion tab button oTab.tab.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive); oTab.accordionTab.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive); // Run panel transiton _this._doTransition(oTab.panel, _this.options.animation, 'open', function() { // When finished, set active class to the panel oTab.panel.removeClass(_this.options.classes.stateDefault).addClass(_this.options.classes.stateActive); }); this.$element.trigger('tabs-activate', oTab); }; /** * This function closes a tab * @param {Event} e - The event that is triggered when a tab is closed * @param {Object} oTab - The tab object that should be closed */ ResponsiveTabs.prototype._closeTab = function(e, oTab) { var _this = this; if(oTab !== undefined) { // Deactivate tab oTab.active = false; // Set default class to the tab button oTab.tab.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault); // Run panel transition _this._doTransition(oTab.panel, _this.options.animation, 'close', function() { // Set default class to the accordion tab button and tab panel oTab.accordionTab.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault); oTab.panel.removeClass(_this.options.classes.stateActive).addClass(_this.options.classes.stateDefault); }, true); this.$element.trigger('tabs-deactivate', oTab); } }; /** * This function runs an effect on a panel * @param {Element} panel - The HTML element of the tab panel * @param {String} method - The transition method reference * @param {String} state - The state (open/closed) that the panel should transition to * @param {Function} callback - The callback function that is called after the transition * @param {Boolean} dequeue - Defines if the event queue should be dequeued after the transition */ ResponsiveTabs.prototype._doTransition = function(panel, method, state, callback, dequeue) { var effect; var _this = this; // Get effect based on method switch(method) { case 'slide': effect = (state === 'open') ? 'slideDown' : 'slideUp'; break; case 'fade': effect = (state === 'open') ? 'fadeIn' : 'fadeOut'; break; default: effect = (state === 'open') ? 'show' : 'hide'; // When default is used, set the duration to 0 _this.options.duration = 0; break; } // Add the transition to a custom queue this.$queue.queue('responsive-tabs',function(next){ // Run the transition on the panel panel[effect]({ duration: _this.options.duration, complete: function() { // Call the callback function callback.call(panel, method, state); // Run the next function in the queue next(); } }); }); // When the panel is openend, dequeue everything so the animation starts if(state === 'open' || dequeue) { this.$queue.dequeue('responsive-tabs'); } }; /** * This function returns the collapsibility of the tab in this state * @returns {Boolean} The collapsibility of the tab */ ResponsiveTabs.prototype._isCollapisble = function() { return (typeof this.options.collapsible === 'boolean' && this.options.collapsible) || (typeof this.options.collapsible === 'string' && this.options.collapsible === this.getState()); }; /** * This function returns a tab by numeric reference * @param {Integer} numRef - Numeric tab reference * @returns {Object} Tab object */ ResponsiveTabs.prototype._getTab = function(numRef) { return this.tabs[numRef]; }; /** * This function returns the numeric tab reference based on a hash selector * @param {String} selector - Hash selector * @returns {Integer} Numeric tab reference */ ResponsiveTabs.prototype._getTabRefBySelector = function(selector) { // Loop all tabs for (var i=0; i this.options.disabled.length) { this.rotateInterval = setInterval(function(){ var e = jQuery.Event('rotate'); _this._openTab(e, _this._getTab(_this._getNextTabRef()), true); }, speed || (($.isNumeric(_this.options.rotate)) ? _this.options.rotate : 4000) ); } else { throw new Error("Rotation is not possible if all tabs are disabled"); } }; /** * This function stops the rotation of the tabs */ ResponsiveTabs.prototype.stopRotation = function() { window.clearInterval(this.rotateInterval); this.rotateInterval = 0; }; /** jQuery wrapper */ $.fn.responsiveTabs = function ( options ) { var args = arguments; if (options === undefined || typeof options === 'object') { return this.each(function () { if (!$.data(this, 'responsivetabs')) { $.data(this, 'responsivetabs', new ResponsiveTabs( this, options )); } }); } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { return this.each(function () { var instance = $.data(this, 'responsivetabs'); if (instance instanceof ResponsiveTabs && typeof instance[options] === 'function') { instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) ); } // Allow instances to be destroyed via the 'destroy' method if (options === 'destroy') { // TODO: destroy instance classes, etc $.data(this, 'responsivetabs', null); } }); } }; }(jQuery, window));