/* * jQuery EasyTabs plugin 2.2.0 * * Copyright (c) 2010-2011 Steve Schwartz (JangoSteve) * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * * Date: Sat Mar 30 17:00:00 2011 -0500 */ (function($) { // Triggers an event on an element and returns the event result function fire(obj, name, data) { var event = $.Event(name); obj.trigger(event, data); return event.result !== false; } $.fn.easyTabs = function(){ $.error("easyTabs() is no longer used. Now use easytabs() -- no capitalization."); } $.fn.easytabs = function(options) { var args = arguments; return this.each(function() { var $container = $(this), data = $container.data("easytabs"); // Initialization was called with $(el).easytabs( { options } ); if ( ! data ) { $.fn.easytabs.methods.init.apply($container,[options]); $.fn.easytabs.methods.initHashChange.apply($container); $.fn.easytabs.methods.initCycle.apply($container); } // User called public method if ( $.fn.easytabs.publicMethods[options] ){ return $.fn.easytabs.publicMethods[ options ].apply( $container, Array.prototype.slice.call( args, 1 )); } }); } $.fn.easytabs.defaults = { animate: true, panelActiveClass: "active", tabActiveClass: "active", defaultTab: "li:first-child", animationSpeed: "normal", tabs: "> ul > li", updateHash: true, cycle: false, collapsible: false, collapsedClass: "collapsed", collapsedByDefault: true, uiTabs: false, transitionIn: 'fadeIn', transitionOut: 'fadeOut', transitionCollapse: 'slideUp', transitionUncollapse: 'slideDown' } $.fn.easytabs.methods = { init: function(options){ var $container = this, opts, $tabs, $panels = $(), $defaultTab, $defaultTabLink, animationSpeeds = { fast: 200, normal: 400, slow: 600 }; if ( options && options['uiTabs'] ) { $container.addClass('ui-tabs'); $.extend($.fn.easytabs.defaults, { tabActiveClass: 'ui-tabs-selected' }); } // If collapsible is true and defaultTab specified, assume user wants defaultTab showing (not collapsed) if ( options && options.collapsible && options.defaultTab ) $.fn.easytabs.defaults.collapsedByDefault = false; opts = $.extend({}, $.fn.easytabs.defaults, options); // Convert 'normal', 'fast', and 'slow' animation speed settings to their respective speed in milliseconds if( typeof(opts.animationSpeed) == 'string' ) opts.animationSpeed = animationSpeeds[opts.animationSpeed]; $tabs = $container.find(opts.tabs); $tabs.each(function(){ targetId = $(this).children("a").attr("href").match(/#([^\?]+)/)[0].substr(1); $matchingPanel = $container.find("div[id=" + targetId + "]"); if ( $matchingPanel.size() > 0 ) { // Store panel height before hiding $matchingPanel.data('easytabs', {height: $matchingPanel.height() }); $panels = $panels.add($matchingPanel.hide()); } else { $tabs = $tabs.not($(this)); // excludes tabs from set that don't have a target div } }); $('a.anchor').remove().prependTo('body'); $container.data("easytabs", { opts: opts, skipUpdateToHash: false, tabs: $tabs, panels: $panels }); $.fn.easytabs.methods.setDefaultTab.apply($container); $tabs.children("a").bind("click.easytabs", function(e) { $container.data("easytabs").opts.cycle = false; $container.data("easytabs").skipUpdateToHash = false; $clicked = $(this); $.fn.easytabs.methods.selectTab.apply($clicked, [$container]); e.preventDefault(); }); }, loadFromData: function(){ return this.data("easytabs"); }, setDefaultTab: function(){ var $container = this, data = $.fn.easytabs.methods.loadFromData.apply($container), opts = data.opts, $tabs = data.tabs, $panels = data.panels, hash = window.location.hash.match(/^[^\?]*/)[0], $selectedTab = $tabs.find("a[href='" + hash + "']").parent(), $defaultTab, $defaultTabLink; if( $selectedTab.size() == 1 ){ $defaultTab = $selectedTab; $container.data("easytabs").opts.cycle = false; } else { $defaultTab = $tabs.parent().find(opts.defaultTab); if ( $defaultTab.size() == 0 ) { $.error("The specified default tab ('" + opts.defaultTab + "') could not be found in the tab set."); } } $defaultTabLink = $defaultTab.children("a").first(); $container.data("easytabs").defaultTab = $defaultTab; $container.data("easytabs").defaultTabLink = $defaultTabLink; if( opts.collapsible && $selectedTab.size() == 0 && opts.collapsedByDefault ){ $defaultTab.addClass(opts.collapsedClass).children().addClass(opts.collapsedClass); } else { $panels.filter("#" + $defaultTabLink.attr("href").match(/#([^\?]+)/)[0].substr(1)).show().addClass(opts.panelActiveClass); $defaultTab.addClass(opts.tabActiveClass).children().addClass(opts.tabActiveClass); } }, selectTab: function($container,callback){ var $clicked = this, url = window.location, hash = url.hash.match(/^[^\?]*/)[0], data = $.fn.easytabs.methods.loadFromData.apply($container), opts = data.opts, skipUpdateToHash = data.skipUpdateToHash, $tabs = data.tabs, $panels = data.panels, $targetPanel = $panels.filter( $clicked.attr("href").match(/#([^\?]+)/)[0] ), $defaultTabLink = data.defaultTabLink, transitions = ( opts.animate ) ? { show: opts.transitionIn, hide: opts.transitionOut, speed: opts.animationSpeed, collapse: opts.transitionCollapse, uncollapse: opts.transitionUncollapse, halfSpeed: opts.animationSpeed / 2 } : { show: "show", hide: "hide", speed: 0, collapse: "hide", uncollapse: "show", halfSpeed: 0 }; if( opts.collapsible && ! skipUpdateToHash && ($clicked.hasClass(opts.tabActiveClass) || $clicked.hasClass(opts.collapsedClass)) ) { $panels.stop(true,true); if( fire($container,"easytabs:before", [$clicked, $targetPanel, data]) ){ $tabs.filter("." + opts.tabActiveClass).removeClass(opts.tabActiveClass).children().removeClass(opts.tabActiveClass); if( $clicked.hasClass(opts.collapsedClass) ){ $clicked.parent() .removeClass(opts.collapsedClass) .addClass(opts.tabActiveClass) .children() .removeClass(opts.collapsedClass) .addClass(opts.tabActiveClass); $targetPanel .addClass(opts.panelActiveClass) [transitions.uncollapse](transitions.speed, function(){ $container.trigger('easytabs:midTransition', [$clicked, $targetPanel, data]); if(typeof callback == 'function') callback(); }); } else { $clicked.parent().addClass(opts.collapsedClass).children().addClass(opts.collapsedClass); $targetPanel .removeClass(opts.panelActiveClass) [transitions.collapse](transitions.speed, function(){ $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, data]); if(typeof callback == 'function') callback(); }); } } } else if( ! $clicked.hasClass(opts.tabActiveClass) || ! $targetPanel.hasClass(opts.panelActiveClass) ){ $panels.stop(true,true); if( fire($container,"easytabs:before", [$clicked, $targetPanel, data]) ){ var $visiblePanel = $panels.filter(":visible"), $panelContainer = $targetPanel.parent(), heightDifference = $targetPanel.data('easytabs').height - $visiblePanel.data('easytabs').height, showPanel = function(){ // At this point, the previous panel is hidden, and the new one will be selected $container.trigger("easytabs:midTransition", [$clicked, $targetPanel, data]); // Gracefully animate between panels of differing heights, start height change animation *after* panel change if panel needs to contract, // so that there is no chance of making the visible panel overflowing the height of the target panel if( opts.animate && heightDifference < 0 ) $panelContainer.animate({ height: $panelContainer.height() + heightDifference },{ duration: ( transitions.halfSpeed ) }); if ( opts.updateHash && ! skipUpdateToHash ) { //window.location = url.toString().replace((url.pathname + hash), (url.pathname + $clicked.attr("href"))); // Not sure why this behaves so differently, but it's more straight forward and seems to have less side-effects window.location.hash = $clicked.attr("href"); } else { $container.data("easytabs").skipUpdateToHash = false; } $targetPanel [transitions.show](transitions.speed, function(){ // Save the new tabs and panels to the container data (with new active tab/panel) $container.data("easytabs").tabs = $tabs; $container.data("easytabs").panels = $panels; $container.trigger("easytabs:after", [$clicked, $targetPanel, data]); // callback only gets called if selectTab actually does something, since it's inside the if block if(typeof callback == 'function'){ callback(); } }); }; // Gracefully animate between panels of differing heights, start height change animation *before* panel change if panel needs to expand, // so that there is no chance of making the target panel overflowing the height of the visible panel if( opts.animate ) { if( heightDifference > 0 ) { $panelContainer.animate({ height: ( $panelContainer.height() + heightDifference ) },{ duration: ( transitions.halfSpeed ) }); } else { // Prevent height jumping before height transition is triggered at midTransition $panelContainer.css({ height: $panelContainer.height() }); } } // Change the active tab *first* to provide immediate feedback when the user clicks $tabs.filter("." + opts.tabActiveClass).removeClass(opts.tabActiveClass).children().removeClass(opts.tabActiveClass); $tabs.filter("." + opts.collapsedClass).removeClass(opts.collapsedClass).children().removeClass(opts.collapsedClass); $clicked.parent().addClass(opts.tabActiveClass).children().addClass(opts.tabActiveClass); $panels.filter("." + opts.panelActiveClass).removeClass(opts.panelActiveClass); $targetPanel.addClass(opts.panelActiveClass); if( $visiblePanel.size() > 0 ) { $visiblePanel [transitions.hide](transitions.speed, showPanel); } else { $targetPanel [transitions.uncollapse](transitions.speed, showPanel); } } } }, selectTabFromHashChange: function(){ var $container = this, data = $.fn.easytabs.methods.loadFromData.apply($container), opts = data.opts, $tabs = data.tabs, $defaultTab = data.defaultTab, $defaultTabLink = data.defaultTabLink, hash = window.location.hash.match(/^[^\?]*/)[0], $tab = $tabs.find("a[href='" + hash + "']"); if ( opts.updateHash ) { if( $tab.size() > 0 ){ $container.data("easytabs").skipUpdateToHash = true; $.fn.easytabs.methods.selectTab.apply( $tab, [$container] ); } else if ( hash == '' && ! $defaultTab.hasClass(opts.tabActiveClass) && ! opts.cycle ) { $container.data("easytabs").skipUpdateToHash = true; $.fn.easytabs.methods.selectTab.apply( $defaultTabLink, [$container] ); } } }, cycleTabs: function(tabNumber){ var $container = this, data = $.fn.easytabs.methods.loadFromData.apply($container), opts = data.opts, $tabs = data.tabs; if(opts.cycle){ tabNumber = tabNumber % $tabs.size(); $tab = $($tabs[tabNumber]).children("a").first(); $container.data("easytabs").skipUpdateToHash = true; $.fn.easytabs.methods.selectTab.apply($tab, [$container, function(){ setTimeout(function(){ $.fn.easytabs.methods.cycleTabs.apply($container,[tabNumber + 1]);}, opts.cycle); }]); } }, initHashChange: function(){ var $container = this; // enabling back-button with jquery.hashchange plugin // http://benalman.com/projects/jquery-hashchange-plugin/ if(typeof $(window).hashchange == 'function'){ $(window).hashchange( function(){ $.fn.easytabs.methods.selectTabFromHashChange.apply($container); }); }else if($.address && typeof $.address.change == 'function'){ // back-button with jquery.address plugin http://www.asual.com/jquery/address/docs/ $.address.change( function(){ $.fn.easytabs.methods.selectTabFromHashChange.apply($container); }); } }, initCycle: function(){ var $container = this, data = $.fn.easytabs.methods.loadFromData.apply($container), opts = data.opts, $tabs = data.tabs, $defaultTab = data.defaultTab, tabNumber; if (opts.cycle) { tabNumber = $tabs.index($defaultTab); setTimeout( function(){ $.fn.easytabs.methods.cycleTabs.apply($container, [tabNumber + 1]); }, opts.cycle); } } } $.fn.easytabs.publicMethods = { select: function(tabSelector){ var $container = this, data = $.fn.easytabs.methods.loadFromData.apply($container), $tabs = data.tabs, $tab; if ( ($tab = $tabs.filter(tabSelector)).size() == 0 ) { // Find tab container that matches selector (like 'li#tab-one' which contains tab link) if ( ($tab = $tabs.find("a[href='" + tabSelector + "']")).size() == 0 ) { // Find direct tab link that matches href (like 'a[href="#panel-1"]') if ( ($tab = $tabs.find("a" + tabSelector)).size() == 0 ) { // Find direct tab link that matches selector (like 'a#tab-1') $.error('Tab \'' + tabSelector + '\' does not exist in tab set'); } } } else { $tab = $tab.children("a").first(); // Select the child tab link, since the first option finds the tab container (like