diff options
Diffstat (limited to 'themes/default/js/ui/jquery.ui.tabs.js')
-rw-r--r-- | themes/default/js/ui/jquery.ui.tabs.js | 1768 |
1 files changed, 1184 insertions, 584 deletions
diff --git a/themes/default/js/ui/jquery.ui.tabs.js b/themes/default/js/ui/jquery.ui.tabs.js index 4e5165a0f..8e866947b 100644 --- a/themes/default/js/ui/jquery.ui.tabs.js +++ b/themes/default/js/ui/jquery.ui.tabs.js @@ -1,11 +1,12 @@ -/* - * jQuery UI Tabs 1.8.16 +/*! + * jQuery UI Tabs 1.9.0 + * http://jqueryui.com * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. * http://jquery.org/license * - * http://docs.jquery.com/UI/Tabs + * http://api.jqueryui.com/tabs/ * * Depends: * jquery.ui.core.js @@ -14,745 +15,1344 @@ (function( $, undefined ) { var tabId = 0, - listId = 0; + rhash = /#.*$/; function getNextTabId() { return ++tabId; } -function getNextListId() { - return ++listId; +function isLocal( anchor ) { + // clone the node to work around IE 6 not normalizing the href property + // if it's manually set, i.e., a.href = "#foo" kills the normalization + anchor = anchor.cloneNode( false ); + return anchor.hash.length > 1 && + anchor.href.replace( rhash, "" ) === location.href.replace( rhash, "" ); } $.widget( "ui.tabs", { + version: "1.9.0", + delay: 300, options: { - add: null, - ajaxOptions: null, - cache: false, - cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true } + active: null, collapsible: false, - disable: null, - disabled: [], - enable: null, event: "click", - fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 } - idPrefix: "ui-tabs-", - load: null, - panelTemplate: "<div></div>", - remove: null, - select: null, + heightStyle: "content", + hide: null, show: null, - spinner: "<em>Loading…</em>", - tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>" + + // callbacks + activate: null, + beforeActivate: null, + beforeLoad: null, + load: null }, _create: function() { - this._tabify( true ); - }, + var panel, + that = this, + options = this.options, + active = options.active; - _setOption: function( key, value ) { - if ( key == "selected" ) { - if (this.options.collapsible && value == this.options.selected ) { - return; + this.running = false; + + this.element + .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ) + .toggleClass( "ui-tabs-collapsible", options.collapsible ) + // Prevent users from focusing disabled tabs via click + .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) { + if ( $( this ).is( ".ui-state-disabled" ) ) { + event.preventDefault(); + } + }) + // support: IE <9 + // Preventing the default action in mousedown doesn't prevent IE + // from focusing the element, so if the anchor gets focused, blur. + // We don't have to worry about focusing the previously focused + // element since clicking on a non-focusable element should focus + // the body anyway. + .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() { + if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) { + this.blur(); + } + }); + + this._processTabs(); + + if ( active === null ) { + // check the fragment identifier in the URL + if ( location.hash ) { + this.anchors.each(function( i, anchor ) { + if ( anchor.hash === location.hash ) { + active = i; + return false; + } + }); + } + + // check for a tab marked active via a class + if ( active === null ) { + active = this.tabs.filter( ".ui-tabs-active" ).index(); + } + + // no active tab, set to false + if ( active === null || active === -1 ) { + active = this.tabs.length ? 0 : false; } - this.select( value ); - } else { - this.options[ key ] = value; - this._tabify(); } - }, - _tabId: function( a ) { - return a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF-]/g, "" ) || - this.options.idPrefix + getNextTabId(); - }, + // handle numbers: negative, out of range + if ( active !== false ) { + active = this.tabs.index( this.tabs.eq( active ) ); + if ( active === -1 ) { + active = options.collapsible ? false : 0; + } + } + options.active = active; - _sanitizeSelector: function( hash ) { - // we need this because an id may contain a ":" - return hash.replace( /:/g, "\\:" ); - }, + // don't allow collapsible: false and active: false + if ( !options.collapsible && options.active === false && this.anchors.length ) { + options.active = 0; + } + + // Take disabling tabs via class attribute from HTML + // into account and update option properly. + if ( $.isArray( options.disabled ) ) { + options.disabled = $.unique( options.disabled.concat( + $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { + return that.tabs.index( li ); + }) + ) ).sort(); + } + + // check for length avoids error when initializing empty list + if ( this.options.active !== false && this.anchors.length ) { + this.active = this._findActive( this.options.active ); + } else { + this.active = $(); + } + + this._refresh(); - _cookie: function() { - var cookie = this.cookie || - ( this.cookie = this.options.cookie.name || "ui-tabs-" + getNextListId() ); - return $.cookie.apply( null, [ cookie ].concat( $.makeArray( arguments ) ) ); + if ( this.active.length ) { + this.load( options.active ); + } }, - _ui: function( tab, panel ) { + _getCreateEventData: function() { return { - tab: tab, - panel: panel, - index: this.anchors.index( tab ) + tab: this.active, + panel: !this.active.length ? $() : this._getPanelForTab( this.active ) }; }, - _cleanup: function() { - // restore all former loading tabs labels - this.lis.filter( ".ui-state-processing" ) - .removeClass( "ui-state-processing" ) - .find( "span:data(label.tabs)" ) - .each(function() { - var el = $( this ); - el.html( el.data( "label.tabs" ) ).removeData( "label.tabs" ); - }); + _tabKeydown: function( event ) { + var focusedTab = $( this.document[0].activeElement ).closest( "li" ), + selectedIndex = this.tabs.index( focusedTab ), + goingForward = true; + + if ( this._handlePageNav( event ) ) { + return; + } + + switch ( event.keyCode ) { + case $.ui.keyCode.RIGHT: + case $.ui.keyCode.DOWN: + selectedIndex++; + break; + case $.ui.keyCode.UP: + case $.ui.keyCode.LEFT: + goingForward = false; + selectedIndex--; + break; + case $.ui.keyCode.END: + selectedIndex = this.anchors.length - 1; + break; + case $.ui.keyCode.HOME: + selectedIndex = 0; + break; + case $.ui.keyCode.SPACE: + // Activate only, no collapsing + event.preventDefault(); + clearTimeout( this.activating ); + this._activate( selectedIndex ); + return; + case $.ui.keyCode.ENTER: + // Toggle (cancel delayed activation, allow collapsing) + event.preventDefault(); + clearTimeout( this.activating ); + // Determine if we should collapse or activate + this._activate( selectedIndex === this.options.active ? false : selectedIndex ); + return; + default: + return; + } + + // Focus the appropriate tab, based on which key was pressed + event.preventDefault(); + clearTimeout( this.activating ); + selectedIndex = this._focusNextTab( selectedIndex, goingForward ); + + // Navigating with control key will prevent automatic activation + if ( !event.ctrlKey ) { + // Update aria-selected immediately so that AT think the tab is already selected. + // Otherwise AT may confuse the user by stating that they need to activate the tab, + // but the tab will already be activated by the time the announcement finishes. + focusedTab.attr( "aria-selected", "false" ); + this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" ); + + this.activating = this._delay(function() { + this.option( "active", selectedIndex ); + }, this.delay ); + } }, - _tabify: function( init ) { - var self = this, - o = this.options, - fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash + _panelKeydown: function( event ) { + if ( this._handlePageNav( event ) ) { + return; + } - this.list = this.element.find( "ol,ul" ).eq( 0 ); - this.lis = $( " > li:has(a[href])", this.list ); - this.anchors = this.lis.map(function() { - return $( "a", this )[ 0 ]; - }); - this.panels = $( [] ); - - this.anchors.each(function( i, a ) { - var href = $( a ).attr( "href" ); - // For dynamically created HTML that contains a hash as href IE < 8 expands - // such href to the full page url with hash and then misinterprets tab as ajax. - // Same consideration applies for an added tab with a fragment identifier - // since a[href=#fragment-identifier] does unexpectedly not match. - // Thus normalize href attribute... - var hrefBase = href.split( "#" )[ 0 ], - baseEl; - if ( hrefBase && ( hrefBase === location.toString().split( "#" )[ 0 ] || - ( baseEl = $( "base" )[ 0 ]) && hrefBase === baseEl.href ) ) { - href = a.hash; - a.href = href; - } + // Ctrl+up moves focus to the current tab + if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) { + event.preventDefault(); + this.active.focus(); + } + }, - // inline tab - if ( fragmentId.test( href ) ) { - self.panels = self.panels.add( self.element.find( self._sanitizeSelector( href ) ) ); - // remote tab - // prevent loading the page itself if href is just "#" - } else if ( href && href !== "#" ) { - // required for restore on destroy - $.data( a, "href.tabs", href ); - - // TODO until #3808 is fixed strip fragment identifier from url - // (IE fails to load from such url) - $.data( a, "load.tabs", href.replace( /#.*$/, "" ) ); - - var id = self._tabId( a ); - a.href = "#" + id; - var $panel = self.element.find( "#" + id ); - if ( !$panel.length ) { - $panel = $( o.panelTemplate ) - .attr( "id", id ) - .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) - .insertAfter( self.panels[ i - 1 ] || self.list ); - $panel.data( "destroy.tabs", true ); - } - self.panels = self.panels.add( $panel ); - // invalid tab href - } else { - o.disabled.push( i ); - } - }); + // Alt+page up/down moves focus to the previous/next tab (and activates) + _handlePageNav: function( event ) { + if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) { + this._activate( this._focusNextTab( this.options.active - 1, false ) ); + return true; + } + if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) { + this._activate( this._focusNextTab( this.options.active + 1, true ) ); + return true; + } + }, - // initialization from scratch - if ( init ) { - // attach necessary classes for styling - this.element.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" ); - this.list.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ); - this.lis.addClass( "ui-state-default ui-corner-top" ); - this.panels.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ); - - // Selected tab - // use "selected" option or try to retrieve: - // 1. from fragment identifier in url - // 2. from cookie - // 3. from selected class attribute on <li> - if ( o.selected === undefined ) { - if ( location.hash ) { - this.anchors.each(function( i, a ) { - if ( a.hash == location.hash ) { - o.selected = i; - return false; - } - }); - } - if ( typeof o.selected !== "number" && o.cookie ) { - o.selected = parseInt( self._cookie(), 10 ); - } - if ( typeof o.selected !== "number" && this.lis.filter( ".ui-tabs-selected" ).length ) { - o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) ); - } - o.selected = o.selected || ( this.lis.length ? 0 : -1 ); - } else if ( o.selected === null ) { // usage of null is deprecated, TODO remove in next release - o.selected = -1; - } - - // sanity check - default to first tab... - o.selected = ( ( o.selected >= 0 && this.anchors[ o.selected ] ) || o.selected < 0 ) - ? o.selected - : 0; - - // Take disabling tabs via class attribute from HTML - // into account and update option properly. - // A selected tab cannot become disabled. - o.disabled = $.unique( o.disabled.concat( - $.map( this.lis.filter( ".ui-state-disabled" ), function( n, i ) { - return self.lis.index( n ); - }) - ) ).sort(); + _findNextTab: function( index, goingForward ) { + var lastTabIndex = this.tabs.length - 1; - if ( $.inArray( o.selected, o.disabled ) != -1 ) { - o.disabled.splice( $.inArray( o.selected, o.disabled ), 1 ); + function constrain() { + if ( index > lastTabIndex ) { + index = 0; } + if ( index < 0 ) { + index = lastTabIndex; + } + return index; + } - // highlight selected tab - this.panels.addClass( "ui-tabs-hide" ); - this.lis.removeClass( "ui-tabs-selected ui-state-active" ); - // check for length avoids error when initializing empty list - if ( o.selected >= 0 && this.anchors.length ) { - self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) ).removeClass( "ui-tabs-hide" ); - this.lis.eq( o.selected ).addClass( "ui-tabs-selected ui-state-active" ); + while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) { + index = goingForward ? index + 1 : index - 1; + } - // seems to be expected behavior that the show callback is fired - self.element.queue( "tabs", function() { - self._trigger( "show", null, - self._ui( self.anchors[ o.selected ], self.element.find( self._sanitizeSelector( self.anchors[ o.selected ].hash ) )[ 0 ] ) ); - }); + return index; + }, - this.load( o.selected ); - } + _focusNextTab: function( index, goingForward ) { + index = this._findNextTab( index, goingForward ); + this.tabs.eq( index ).focus(); + return index; + }, - // clean up to avoid memory leaks in certain versions of IE 6 - // TODO: namespace this event - $( window ).bind( "unload", function() { - self.lis.add( self.anchors ).unbind( ".tabs" ); - self.lis = self.anchors = self.panels = null; - }); - // update selected after add/remove - } else { - o.selected = this.lis.index( this.lis.filter( ".ui-tabs-selected" ) ); + _setOption: function( key, value ) { + if ( key === "active" ) { + // _activate() will handle invalid values and update this.options + this._activate( value ); + return; } - // update collapsible - // TODO: use .toggleClass() - this.element[ o.collapsible ? "addClass" : "removeClass" ]( "ui-tabs-collapsible" ); + if ( key === "disabled" ) { + // don't use the widget factory's disabled handling + this._setupDisabled( value ); + return; + } + + this._super( key, value); - // set or update cookie after init and add/remove respectively - if ( o.cookie ) { - this._cookie( o.selected, o.cookie ); + if ( key === "collapsible" ) { + this.element.toggleClass( "ui-tabs-collapsible", value ); + // Setting collapsible: false while collapsed; open first panel + if ( !value && this.options.active === false ) { + this._activate( 0 ); + } } - // disable tabs - for ( var i = 0, li; ( li = this.lis[ i ] ); i++ ) { - $( li )[ $.inArray( i, o.disabled ) != -1 && - // TODO: use .toggleClass() - !$( li ).hasClass( "ui-tabs-selected" ) ? "addClass" : "removeClass" ]( "ui-state-disabled" ); + if ( key === "event" ) { + this._setupEvents( value ); } - // reset cache if switching from cached to not cached - if ( o.cache === false ) { - this.anchors.removeData( "cache.tabs" ); + if ( key === "heightStyle" ) { + this._setupHeightStyle( value ); } + }, - // remove all handlers before, tabify may run on existing tabs after add or option change - this.lis.add( this.anchors ).unbind( ".tabs" ); + _tabId: function( tab ) { + return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId(); + }, - if ( o.event !== "mouseover" ) { - var addState = function( state, el ) { - if ( el.is( ":not(.ui-state-disabled)" ) ) { - el.addClass( "ui-state-" + state ); - } - }; - var removeState = function( state, el ) { - el.removeClass( "ui-state-" + state ); - }; - this.lis.bind( "mouseover.tabs" , function() { - addState( "hover", $( this ) ); + _sanitizeSelector: function( hash ) { + return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : ""; + }, + + refresh: function() { + var next, + options = this.options, + lis = this.tablist.children( ":has(a[href])" ); + + // get disabled tabs from class attribute from HTML + // this will get converted to a boolean if needed in _refresh() + options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) { + return lis.index( tab ); + }); + + this._processTabs(); + + // was collapsed or no tabs + if ( options.active === false || !this.anchors.length ) { + options.active = false; + this.active = $(); + // was active, but active tab is gone + } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) { + // all remaining tabs are disabled + if ( this.tabs.length === options.disabled.length ) { + options.active = false; + this.active = $(); + // activate previous tab + } else { + this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) ); + } + // was active, active tab still exists + } else { + // make sure active index is correct + options.active = this.tabs.index( this.active ); + } + + this._refresh(); + }, + + _refresh: function() { + this._setupDisabled( this.options.disabled ); + this._setupEvents( this.options.event ); + this._setupHeightStyle( this.options.heightStyle ); + + this.tabs.not( this.active ).attr({ + "aria-selected": "false", + tabIndex: -1 + }); + this.panels.not( this._getPanelForTab( this.active ) ) + .hide() + .attr({ + "aria-expanded": "false", + "aria-hidden": "true" }); - this.lis.bind( "mouseout.tabs", function() { - removeState( "hover", $( this ) ); + + // Make sure one tab is in the tab order + if ( !this.active.length ) { + this.tabs.eq( 0 ).attr( "tabIndex", 0 ); + } else { + this.active + .addClass( "ui-tabs-active ui-state-active" ) + .attr({ + "aria-selected": "true", + tabIndex: 0 + }); + this._getPanelForTab( this.active ) + .show() + .attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }); + } + }, + + _processTabs: function() { + var that = this; + + this.tablist = this._getList() + .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) + .attr( "role", "tablist" ); + + this.tabs = this.tablist.find( "> li:has(a[href])" ) + .addClass( "ui-state-default ui-corner-top" ) + .attr({ + role: "tab", + tabIndex: -1 }); - this.anchors.bind( "focus.tabs", function() { - addState( "focus", $( this ).closest( "li" ) ); + + this.anchors = this.tabs.map(function() { + return $( "a", this )[ 0 ]; + }) + .addClass( "ui-tabs-anchor" ) + .attr({ + role: "presentation", + tabIndex: -1 }); - this.anchors.bind( "blur.tabs", function() { - removeState( "focus", $( this ).closest( "li" ) ); + + this.panels = $(); + + this.anchors.each(function( i, anchor ) { + var selector, panel, panelId, + anchorId = $( anchor ).uniqueId().attr( "id" ), + tab = $( anchor ).closest( "li" ), + originalAriaControls = tab.attr( "aria-controls" ); + + // inline tab + if ( isLocal( anchor ) ) { + selector = anchor.hash; + panel = that.element.find( that._sanitizeSelector( selector ) ); + // remote tab + } else { + panelId = that._tabId( tab ); + selector = "#" + panelId; + panel = that.element.find( selector ); + if ( !panel.length ) { + panel = that._createPanel( panelId ); + panel.insertAfter( that.panels[ i - 1 ] || that.tablist ); + } + panel.attr( "aria-live", "polite" ); + } + + if ( panel.length) { + that.panels = that.panels.add( panel ); + } + if ( originalAriaControls ) { + tab.data( "ui-tabs-aria-controls", originalAriaControls ); + } + tab.attr({ + "aria-controls": selector.substring( 1 ), + "aria-labelledby": anchorId }); + panel.attr( "aria-labelledby", anchorId ); + }); + + this.panels + .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) + .attr( "role", "tabpanel" ); + }, + + // allow overriding how to find the list for rare usage scenarios (#7715) + _getList: function() { + return this.element.find( "ol,ul" ).eq( 0 ); + }, + + _createPanel: function( id ) { + return $( "<div>" ) + .attr( "id", id ) + .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) + .data( "ui-tabs-destroy", true ); + }, + + _setupDisabled: function( disabled ) { + if ( $.isArray( disabled ) ) { + if ( !disabled.length ) { + disabled = false; + } else if ( disabled.length === this.anchors.length ) { + disabled = true; + } } - // set up animations - var hideFx, showFx; - if ( o.fx ) { - if ( $.isArray( o.fx ) ) { - hideFx = o.fx[ 0 ]; - showFx = o.fx[ 1 ]; + // disable tabs + for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) { + if ( disabled === true || $.inArray( i, disabled ) !== -1 ) { + $( li ) + .addClass( "ui-state-disabled" ) + .attr( "aria-disabled", "true" ); } else { - hideFx = showFx = o.fx; + $( li ) + .removeClass( "ui-state-disabled" ) + .removeAttr( "aria-disabled" ); } } - // Reset certain styles left over from animation - // and prevent IE's ClearType bug... - function resetStyle( $el, fx ) { - $el.css( "display", "" ); - if ( !$.support.opacity && fx.opacity ) { - $el[ 0 ].style.removeAttribute( "filter" ); + this.options.disabled = disabled; + }, + + _setupEvents: function( event ) { + var events = { + click: function( event ) { + event.preventDefault(); } + }; + if ( event ) { + $.each( event.split(" "), function( index, eventName ) { + events[ eventName ] = "_eventHandler"; + }); } - // Show a tab... - var showTab = showFx - ? function( clicked, $show ) { - $( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" ); - $show.hide().removeClass( "ui-tabs-hide" ) // avoid flicker that way - .animate( showFx, showFx.duration || "normal", function() { - resetStyle( $show, showFx ); - self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) ); - }); - } - : function( clicked, $show ) { - $( clicked ).closest( "li" ).addClass( "ui-tabs-selected ui-state-active" ); - $show.removeClass( "ui-tabs-hide" ); - self._trigger( "show", null, self._ui( clicked, $show[ 0 ] ) ); - }; + this._off( this.anchors.add( this.tabs ).add( this.panels ) ); + this._on( this.anchors, events ); + this._on( this.tabs, { keydown: "_tabKeydown" } ); + this._on( this.panels, { keydown: "_panelKeydown" } ); - // Hide a tab, $show is optional... - var hideTab = hideFx - ? function( clicked, $hide ) { - $hide.animate( hideFx, hideFx.duration || "normal", function() { - self.lis.removeClass( "ui-tabs-selected ui-state-active" ); - $hide.addClass( "ui-tabs-hide" ); - resetStyle( $hide, hideFx ); - self.element.dequeue( "tabs" ); - }); + this._focusable( this.tabs ); + this._hoverable( this.tabs ); + }, + + _setupHeightStyle: function( heightStyle ) { + var maxHeight, overflow, + parent = this.element.parent(); + + if ( heightStyle === "fill" ) { + // IE 6 treats height like minHeight, so we need to turn off overflow + // in order to get a reliable height + // we use the minHeight support test because we assume that only + // browsers that don't support minHeight will treat height as minHeight + if ( !$.support.minHeight ) { + overflow = parent.css( "overflow" ); + parent.css( "overflow", "hidden"); } - : function( clicked, $hide, $show ) { - self.lis.removeClass( "ui-tabs-selected ui-state-active" ); - $hide.addClass( "ui-tabs-hide" ); - self.element.dequeue( "tabs" ); - }; + maxHeight = parent.height(); + this.element.siblings( ":visible" ).each(function() { + var elem = $( this ), + position = elem.css( "position" ); - // attach tab event handler, unbind to avoid duplicates from former tabifying... - this.anchors.bind( o.event + ".tabs", function() { - var el = this, - $li = $(el).closest( "li" ), - $hide = self.panels.filter( ":not(.ui-tabs-hide)" ), - $show = self.element.find( self._sanitizeSelector( el.hash ) ); - - // If tab is already selected and not collapsible or tab disabled or - // or is already loading or click callback returns false stop here. - // Check if click handler returns false last so that it is not executed - // for a disabled or loading tab! - if ( ( $li.hasClass( "ui-tabs-selected" ) && !o.collapsible) || - $li.hasClass( "ui-state-disabled" ) || - $li.hasClass( "ui-state-processing" ) || - self.panels.filter( ":animated" ).length || - self._trigger( "select", null, self._ui( this, $show[ 0 ] ) ) === false ) { - this.blur(); - return false; + if ( position === "absolute" || position === "fixed" ) { + return; + } + maxHeight -= elem.outerHeight( true ); + }); + if ( overflow ) { + parent.css( "overflow", overflow ); } - o.selected = self.anchors.index( this ); + this.element.children().not( this.panels ).each(function() { + maxHeight -= $( this ).outerHeight( true ); + }); - self.abort(); + this.panels.each(function() { + $( this ).height( Math.max( 0, maxHeight - + $( this ).innerHeight() + $( this ).height() ) ); + }) + .css( "overflow", "auto" ); + } else if ( heightStyle === "auto" ) { + maxHeight = 0; + this.panels.each(function() { + maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); + }).height( maxHeight ); + } + }, - // if tab may be closed - if ( o.collapsible ) { - if ( $li.hasClass( "ui-tabs-selected" ) ) { - o.selected = -1; + _eventHandler: function( event ) { + var options = this.options, + active = this.active, + anchor = $( event.currentTarget ), + tab = anchor.closest( "li" ), + clickedIsActive = tab[ 0 ] === active[ 0 ], + collapsing = clickedIsActive && options.collapsible, + toShow = collapsing ? $() : this._getPanelForTab( tab ), + toHide = !active.length ? $() : this._getPanelForTab( active ), + eventData = { + oldTab: active, + oldPanel: toHide, + newTab: collapsing ? $() : tab, + newPanel: toShow + }; - if ( o.cookie ) { - self._cookie( o.selected, o.cookie ); - } + event.preventDefault(); + + if ( tab.hasClass( "ui-state-disabled" ) || + // tab is already loading + tab.hasClass( "ui-tabs-loading" ) || + // can't switch durning an animation + this.running || + // click on active header, but not collapsible + ( clickedIsActive && !options.collapsible ) || + // allow canceling activation + ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { + return; + } - self.element.queue( "tabs", function() { - hideTab( el, $hide ); - }).dequeue( "tabs" ); + options.active = collapsing ? false : this.tabs.index( tab ); - this.blur(); - return false; - } else if ( !$hide.length ) { - if ( o.cookie ) { - self._cookie( o.selected, o.cookie ); - } + this.active = clickedIsActive ? $() : tab; + if ( this.xhr ) { + this.xhr.abort(); + } - self.element.queue( "tabs", function() { - showTab( el, $show ); - }); + if ( !toHide.length && !toShow.length ) { + $.error( "jQuery UI Tabs: Mismatching fragment identifier." ); + } - // TODO make passing in node possible, see also http://dev.jqueryui.com/ticket/3171 - self.load( self.anchors.index( this ) ); + if ( toShow.length ) { + this.load( this.tabs.index( tab ), event ); + } + this._toggle( event, eventData ); + }, - this.blur(); - return false; - } - } + // handles show/hide for selecting tabs + _toggle: function( event, eventData ) { + var that = this, + toShow = eventData.newPanel, + toHide = eventData.oldPanel; - if ( o.cookie ) { - self._cookie( o.selected, o.cookie ); - } + this.running = true; - // show new tab - if ( $show.length ) { - if ( $hide.length ) { - self.element.queue( "tabs", function() { - hideTab( el, $hide ); - }); - } - self.element.queue( "tabs", function() { - showTab( el, $show ); - }); + function complete() { + that.running = false; + that._trigger( "activate", event, eventData ); + } - self.load( self.anchors.index( this ) ); + function show() { + eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); + + if ( toShow.length && that.options.show ) { + that._show( toShow, that.options.show, complete ); } else { - throw "jQuery UI Tabs: Mismatching fragment identifier."; + toShow.show(); + complete(); } + } - // Prevent IE from keeping other link focussed when using the back button - // and remove dotted border from clicked link. This is controlled via CSS - // in modern browsers; blur() removes focus from address bar in Firefox - // which can become a usability and annoying problem with tabs('rotate'). - if ( $.browser.msie ) { - this.blur(); - } + // start out by hiding, then showing, then completing + if ( toHide.length && this.options.hide ) { + this._hide( toHide, this.options.hide, function() { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + show(); + }); + } else { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + toHide.hide(); + show(); + } + + toHide.attr({ + "aria-expanded": "false", + "aria-hidden": "true" }); + eventData.oldTab.attr( "aria-selected", "false" ); + // If we're switching tabs, remove the old tab from the tab order. + // If we're opening from collapsed state, remove the previous tab from the tab order. + // If we're collapsing, then keep the collapsing tab in the tab order. + if ( toShow.length && toHide.length ) { + eventData.oldTab.attr( "tabIndex", -1 ); + } else if ( toShow.length ) { + this.tabs.filter(function() { + return $( this ).attr( "tabIndex" ) === 0; + }) + .attr( "tabIndex", -1 ); + } - // disable click in any case - this.anchors.bind( "click.tabs", function(){ - return false; + toShow.attr({ + "aria-expanded": "true", + "aria-hidden": "false" + }); + eventData.newTab.attr({ + "aria-selected": "true", + tabIndex: 0 }); }, - _getIndex: function( index ) { + _activate: function( index ) { + var anchor, + active = this._findActive( index ); + + // trying to activate the already active panel + if ( active[ 0 ] === this.active[ 0 ] ) { + return; + } + + // trying to collapse, simulate a click on the current active header + if ( !active.length ) { + active = this.active; + } + + anchor = active.find( ".ui-tabs-anchor" )[ 0 ]; + this._eventHandler({ + target: anchor, + currentTarget: anchor, + preventDefault: $.noop + }); + }, + + _findActive: function( index ) { + return index === false ? $() : this.tabs.eq( index ); + }, + + _getIndex: function( index ) { // meta-function to give users option to provide a href string instead of a numerical index. - // also sanitizes numerical indexes to valid values. - if ( typeof index == "string" ) { - index = this.anchors.index( this.anchors.filter( "[href$=" + index + "]" ) ); + if ( typeof index === "string" ) { + index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) ); } return index; }, - destroy: function() { - var o = this.options; + _destroy: function() { + if ( this.xhr ) { + this.xhr.abort(); + } - this.abort(); + this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" ); - this.element - .unbind( ".tabs" ) - .removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" ) - .removeData( "tabs" ); + this.tablist + .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ) + .removeAttr( "role" ); - this.list.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" ); + this.anchors + .removeClass( "ui-tabs-anchor" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeData( "href.tabs" ) + .removeData( "load.tabs" ) + .removeUniqueId(); - this.anchors.each(function() { - var href = $.data( this, "href.tabs" ); - if ( href ) { - this.href = href; + this.tabs.add( this.panels ).each(function() { + if ( $.data( this, "ui-tabs-destroy" ) ) { + $( this ).remove(); + } else { + $( this ) + .removeClass( "ui-state-default ui-state-active ui-state-disabled " + + "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-live" ) + .removeAttr( "aria-busy" ) + .removeAttr( "aria-selected" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "role" ); } - var $this = $( this ).unbind( ".tabs" ); - $.each( [ "href", "load", "cache" ], function( i, prefix ) { - $this.removeData( prefix + ".tabs" ); - }); }); - this.lis.unbind( ".tabs" ).add( this.panels ).each(function() { - if ( $.data( this, "destroy.tabs" ) ) { - $( this ).remove(); + this.tabs.each(function() { + var li = $( this ), + prev = li.data( "ui-tabs-aria-controls" ); + if ( prev ) { + li.attr( "aria-controls", prev ); } else { - $( this ).removeClass([ - "ui-state-default", - "ui-corner-top", - "ui-tabs-selected", - "ui-state-active", - "ui-state-hover", - "ui-state-focus", - "ui-state-disabled", - "ui-tabs-panel", - "ui-widget-content", - "ui-corner-bottom", - "ui-tabs-hide" - ].join( " " ) ); + li.removeAttr( "aria-controls" ); } }); - if ( o.cookie ) { - this._cookie( null, o.cookie ); + if ( this.options.heightStyle !== "content" ) { + this.panels.css( "height", "" ); } - - return this; }, - add: function( url, label, index ) { - if ( index === undefined ) { - index = this.anchors.length; + enable: function( index ) { + var disabled = this.options.disabled; + if ( disabled === false ) { + return; } - var self = this, - o = this.options, - $li = $( o.tabTemplate.replace( /#\{href\}/g, url ).replace( /#\{label\}/g, label ) ), - id = !url.indexOf( "#" ) ? url.replace( "#", "" ) : this._tabId( $( "a", $li )[ 0 ] ); - - $li.addClass( "ui-state-default ui-corner-top" ).data( "destroy.tabs", true ); + if ( index === undefined ) { + disabled = false; + } else { + index = this._getIndex( index ); + if ( $.isArray( disabled ) ) { + disabled = $.map( disabled, function( num ) { + return num !== index ? num : null; + }); + } else { + disabled = $.map( this.tabs, function( li, num ) { + return num !== index ? num : null; + }); + } + } + this._setupDisabled( disabled ); + }, - // try to find an existing element before creating a new one - var $panel = self.element.find( "#" + id ); - if ( !$panel.length ) { - $panel = $( o.panelTemplate ) - .attr( "id", id ) - .data( "destroy.tabs", true ); + disable: function( index ) { + var disabled = this.options.disabled; + if ( disabled === true ) { + return; } - $panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide" ); - if ( index >= this.lis.length ) { - $li.appendTo( this.list ); - $panel.appendTo( this.list[ 0 ].parentNode ); + if ( index === undefined ) { + disabled = true; } else { - $li.insertBefore( this.lis[ index ] ); - $panel.insertBefore( this.panels[ index ] ); + index = this._getIndex( index ); + if ( $.inArray( index, disabled ) !== -1 ) { + return; + } + if ( $.isArray( disabled ) ) { + disabled = $.merge( [ index ], disabled ).sort(); + } else { + disabled = [ index ]; + } } + this._setupDisabled( disabled ); + }, - o.disabled = $.map( o.disabled, function( n, i ) { - return n >= index ? ++n : n; - }); + load: function( index, event ) { + index = this._getIndex( index ); + var that = this, + tab = this.tabs.eq( index ), + anchor = tab.find( ".ui-tabs-anchor" ), + panel = this._getPanelForTab( tab ), + eventData = { + tab: tab, + panel: panel + }; - this._tabify(); + // not remote + if ( isLocal( anchor[ 0 ] ) ) { + return; + } - if ( this.anchors.length == 1 ) { - o.selected = 0; - $li.addClass( "ui-tabs-selected ui-state-active" ); - $panel.removeClass( "ui-tabs-hide" ); - this.element.queue( "tabs", function() { - self._trigger( "show", null, self._ui( self.anchors[ 0 ], self.panels[ 0 ] ) ); - }); + this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) ); + + // support: jQuery <1.8 + // jQuery <1.8 returns false if the request is canceled in beforeSend, + // but as of 1.8, $.ajax() always returns a jqXHR object. + if ( this.xhr && this.xhr.statusText !== "canceled" ) { + tab.addClass( "ui-tabs-loading" ); + panel.attr( "aria-busy", "true" ); + + this.xhr + .success(function( response ) { + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout(function() { + panel.html( response ); + that._trigger( "load", event, eventData ); + }, 1 ); + }) + .complete(function( jqXHR, status ) { + // support: jQuery <1.8 + // http://bugs.jquery.com/ticket/11778 + setTimeout(function() { + if ( status === "abort" ) { + that.panels.stop( false, true ); + } + + tab.removeClass( "ui-tabs-loading" ); + panel.removeAttr( "aria-busy" ); - this.load( 0 ); + if ( jqXHR === that.xhr ) { + delete that.xhr; + } + }, 1 ); + }); } + }, - this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); - return this; + // TODO: Remove this function in 1.10 when ajaxOptions is removed + _ajaxSettings: function( anchor, event, eventData ) { + var that = this; + return { + url: anchor.attr( "href" ), + beforeSend: function( jqXHR, settings ) { + return that._trigger( "beforeLoad", event, + $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) ); + } + }; }, - remove: function( index ) { - index = this._getIndex( index ); - var o = this.options, - $li = this.lis.eq( index ).remove(), - $panel = this.panels.eq( index ).remove(); - - // If selected tab was removed focus tab to the right or - // in case the last tab was removed the tab to the left. - if ( $li.hasClass( "ui-tabs-selected" ) && this.anchors.length > 1) { - this.select( index + ( index + 1 < this.anchors.length ? 1 : -1 ) ); - } - - o.disabled = $.map( - $.grep( o.disabled, function(n, i) { - return n != index; - }), - function( n, i ) { - return n >= index ? --n : n; - }); + _getPanelForTab: function( tab ) { + var id = $( tab ).attr( "aria-controls" ); + return this.element.find( this._sanitizeSelector( "#" + id ) ); + } +}); - this._tabify(); +// DEPRECATED +if ( $.uiBackCompat !== false ) { - this._trigger( "remove", null, this._ui( $li.find( "a" )[ 0 ], $panel[ 0 ] ) ); - return this; - }, + // helper method for a lot of the back compat extensions + $.ui.tabs.prototype._ui = function( tab, panel ) { + return { + tab: tab, + panel: panel, + index: this.anchors.index( tab ) + }; + }; - enable: function( index ) { - index = this._getIndex( index ); - var o = this.options; - if ( $.inArray( index, o.disabled ) == -1 ) { - return; + // url method + $.widget( "ui.tabs", $.ui.tabs, { + url: function( index, url ) { + this.anchors.eq( index ).attr( "href", url ); } + }); + + // TODO: Remove _ajaxSettings() method when removing this extension + // ajaxOptions and cache options + $.widget( "ui.tabs", $.ui.tabs, { + options: { + ajaxOptions: null, + cache: false + }, + + _create: function() { + this._super(); + + var that = this; + + this._on({ tabsbeforeload: function( event, ui ) { + // tab is already cached + if ( $.data( ui.tab[ 0 ], "cache.tabs" ) ) { + event.preventDefault(); + return; + } - this.lis.eq( index ).removeClass( "ui-state-disabled" ); - o.disabled = $.grep( o.disabled, function( n, i ) { - return n != index; - }); - - this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); - return this; - }, + ui.jqXHR.success(function() { + if ( that.options.cache ) { + $.data( ui.tab[ 0 ], "cache.tabs", true ); + } + }); + }}); + }, + + _ajaxSettings: function( anchor, event, ui ) { + var ajaxOptions = this.options.ajaxOptions; + return $.extend( {}, ajaxOptions, { + error: function( xhr, s, e ) { + try { + // Passing index avoid a race condition when this method is + // called after the user has selected another tab. + // Pass the anchor that initiated this request allows + // loadError to manipulate the tab content panel via $(a.hash) + ajaxOptions.error( + xhr, s, ui.tab.closest( "li" ).index(), ui.tab[ 0 ] ); + } + catch ( e ) {} + } + }, this._superApply( arguments ) ); + }, - disable: function( index ) { - index = this._getIndex( index ); - var self = this, o = this.options; - // cannot disable already selected tab - if ( index != o.selected ) { - this.lis.eq( index ).addClass( "ui-state-disabled" ); + _setOption: function( key, value ) { + // reset cache if switching from cached to not cached + if ( key === "cache" && value === false ) { + this.anchors.removeData( "cache.tabs" ); + } + this._super( key, value ); + }, - o.disabled.push( index ); - o.disabled.sort(); + _destroy: function() { + this.anchors.removeData( "cache.tabs" ); + this._super(); + }, - this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); + url: function( index, url ){ + this.anchors.eq( index ).removeData( "cache.tabs" ); + this._superApply( arguments ); } + }); - return this; - }, - - select: function( index ) { - index = this._getIndex( index ); - if ( index == -1 ) { - if ( this.options.collapsible && this.options.selected != -1 ) { - index = this.options.selected; - } else { - return this; + // abort method + $.widget( "ui.tabs", $.ui.tabs, { + abort: function() { + if ( this.xhr ) { + this.xhr.abort(); } } - this.anchors.eq( index ).trigger( this.options.event + ".tabs" ); - return this; - }, + }); + + // spinner + $.widget( "ui.tabs", $.ui.tabs, { + options: { + spinner: "<em>Loading…</em>" + }, + _create: function() { + this._super(); + this._on({ + tabsbeforeload: function( event, ui ) { + // Don't react to nested tabs or tabs that don't use a spinner + if ( event.target !== this.element[ 0 ] || + !this.options.spinner ) { + return; + } - load: function( index ) { - index = this._getIndex( index ); - var self = this, - o = this.options, - a = this.anchors.eq( index )[ 0 ], - url = $.data( a, "load.tabs" ); + var span = ui.tab.find( "span" ), + html = span.html(); + span.html( this.options.spinner ); + ui.jqXHR.complete(function() { + span.html( html ); + }); + } + }); + } + }); + + // enable/disable events + $.widget( "ui.tabs", $.ui.tabs, { + options: { + enable: null, + disable: null + }, + + enable: function( index ) { + var options = this.options, + trigger; + + if ( index && options.disabled === true || + ( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) !== -1 ) ) { + trigger = true; + } - this.abort(); + this._superApply( arguments ); - // not remote or from cache - if ( !url || this.element.queue( "tabs" ).length !== 0 && $.data( a, "cache.tabs" ) ) { - this.element.dequeue( "tabs" ); - return; - } + if ( trigger ) { + this._trigger( "enable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); + } + }, - // load remote from here on - this.lis.eq( index ).addClass( "ui-state-processing" ); + disable: function( index ) { + var options = this.options, + trigger; - if ( o.spinner ) { - var span = $( "span", a ); - span.data( "label.tabs", span.html() ).html( o.spinner ); - } + if ( index && options.disabled === false || + ( $.isArray( options.disabled ) && $.inArray( index, options.disabled ) === -1 ) ) { + trigger = true; + } - this.xhr = $.ajax( $.extend( {}, o.ajaxOptions, { - url: url, - success: function( r, s ) { - self.element.find( self._sanitizeSelector( a.hash ) ).html( r ); + this._superApply( arguments ); - // take care of tab labels - self._cleanup(); + if ( trigger ) { + this._trigger( "disable", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); + } + } + }); + + // add/remove methods and events + $.widget( "ui.tabs", $.ui.tabs, { + options: { + add: null, + remove: null, + tabTemplate: "<li><a href='#{href}'><span>#{label}</span></a></li>" + }, + + add: function( url, label, index ) { + if ( index === undefined ) { + index = this.anchors.length; + } - if ( o.cache ) { - $.data( a, "cache.tabs", true ); + var doInsertAfter, panel, + options = this.options, + li = $( options.tabTemplate + .replace( /#\{href\}/g, url ) + .replace( /#\{label\}/g, label ) ), + id = !url.indexOf( "#" ) ? + url.replace( "#", "" ) : + this._tabId( li ); + + li.addClass( "ui-state-default ui-corner-top" ).data( "ui-tabs-destroy", true ); + li.attr( "aria-controls", id ); + + doInsertAfter = index >= this.tabs.length; + + // try to find an existing element before creating a new one + panel = this.element.find( "#" + id ); + if ( !panel.length ) { + panel = this._createPanel( id ); + if ( doInsertAfter ) { + if ( index > 0 ) { + panel.insertAfter( this.panels.eq( -1 ) ); + } else { + panel.appendTo( this.element ); + } + } else { + panel.insertBefore( this.panels[ index ] ); } + } + panel.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ).hide(); - self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) ); - try { - o.ajaxOptions.success( r, s ); - } - catch ( e ) {} - }, - error: function( xhr, s, e ) { - // take care of tab labels - self._cleanup(); - - self._trigger( "load", null, self._ui( self.anchors[ index ], self.panels[ index ] ) ); - try { - // Passing index avoid a race condition when this method is - // called after the user has selected another tab. - // Pass the anchor that initiated this request allows - // loadError to manipulate the tab content panel via $(a.hash) - o.ajaxOptions.error( xhr, s, index, a ); - } - catch ( e ) {} + if ( doInsertAfter ) { + li.appendTo( this.tablist ); + } else { + li.insertBefore( this.tabs[ index ] ); } - } ) ); - // last, so that load event is fired before show... - self.element.dequeue( "tabs" ); + options.disabled = $.map( options.disabled, function( n ) { + return n >= index ? ++n : n; + }); - return this; - }, + this.refresh(); + if ( this.tabs.length === 1 && options.active === false ) { + this.option( "active", 0 ); + } - abort: function() { - // stop possibly running animations - this.element.queue( [] ); - this.panels.stop( false, true ); + this._trigger( "add", null, this._ui( this.anchors[ index ], this.panels[ index ] ) ); + return this; + }, + + remove: function( index ) { + index = this._getIndex( index ); + var options = this.options, + tab = this.tabs.eq( index ).remove(), + panel = this._getPanelForTab( tab ).remove(); + + // If selected tab was removed focus tab to the right or + // in case the last tab was removed the tab to the left. + // We check for more than 2 tabs, because if there are only 2, + // then when we remove this tab, there will only be one tab left + // so we don't need to detect which tab to activate. + if ( tab.hasClass( "ui-tabs-active" ) && this.anchors.length > 2 ) { + this._activate( index + ( index + 1 < this.anchors.length ? 1 : -1 ) ); + } - // "tabs" queue must not contain more than two elements, - // which are the callbacks for the latest clicked tab... - this.element.queue( "tabs", this.element.queue( "tabs" ).splice( -2, 2 ) ); + options.disabled = $.map( + $.grep( options.disabled, function( n ) { + return n !== index; + }), + function( n ) { + return n >= index ? --n : n; + }); - // terminate pending requests from other tabs - if ( this.xhr ) { - this.xhr.abort(); - delete this.xhr; - } + this.refresh(); - // take care of tab labels - this._cleanup(); - return this; - }, + this._trigger( "remove", null, this._ui( tab.find( "a" )[ 0 ], panel[ 0 ] ) ); + return this; + } + }); - url: function( index, url ) { - this.anchors.eq( index ).removeData( "cache.tabs" ).data( "load.tabs", url ); - return this; - }, + // length method + $.widget( "ui.tabs", $.ui.tabs, { + length: function() { + return this.anchors.length; + } + }); + + // panel ids (idPrefix option + title attribute) + $.widget( "ui.tabs", $.ui.tabs, { + options: { + idPrefix: "ui-tabs-" + }, + + _tabId: function( tab ) { + var a = tab.is( "li" ) ? tab.find( "a[href]" ) : tab; + a = a[0]; + return $( a ).closest( "li" ).attr( "aria-controls" ) || + a.title && a.title.replace( /\s/g, "_" ).replace( /[^\w\u00c0-\uFFFF\-]/g, "" ) || + this.options.idPrefix + getNextTabId(); + } + }); - length: function() { - return this.anchors.length; - } -}); + // _createPanel method + $.widget( "ui.tabs", $.ui.tabs, { + options: { + panelTemplate: "<div></div>" + }, -$.extend( $.ui.tabs, { - version: "1.8.16" -}); + _createPanel: function( id ) { + return $( this.options.panelTemplate ) + .attr( "id", id ) + .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" ) + .data( "ui-tabs-destroy", true ); + } + }); + + // selected option + $.widget( "ui.tabs", $.ui.tabs, { + _create: function() { + var options = this.options; + if ( options.active === null && options.selected !== undefined ) { + options.active = options.selected === -1 ? false : options.selected; + } + this._super(); + options.selected = options.active; + if ( options.selected === false ) { + options.selected = -1; + } + }, -/* - * Tabs Extensions - */ + _setOption: function( key, value ) { + if ( key !== "selected" ) { + return this._super( key, value ); + } -/* - * Rotate - */ -$.extend( $.ui.tabs.prototype, { - rotation: null, - rotate: function( ms, continuing ) { - var self = this, - o = this.options; - - var rotate = self._rotate || ( self._rotate = function( e ) { - clearTimeout( self.rotation ); - self.rotation = setTimeout(function() { - var t = o.selected; - self.select( ++t < self.anchors.length ? t : 0 ); - }, ms ); - - if ( e ) { - e.stopPropagation(); + var options = this.options; + this._super( "active", value === -1 ? false : value ); + options.selected = options.active; + if ( options.selected === false ) { + options.selected = -1; } - }); + }, - var stop = self._unrotate || ( self._unrotate = !continuing - ? function(e) { - if (e.clientX) { // in case of a true click - self.rotate(null); + _eventHandler: function( event ) { + this._superApply( arguments ); + this.options.selected = this.options.active; + if ( this.options.selected === false ) { + this.options.selected = -1; + } + } + }); + + // show and select event + $.widget( "ui.tabs", $.ui.tabs, { + options: { + show: null, + select: null + }, + _create: function() { + this._super(); + if ( this.options.active !== false ) { + this._trigger( "show", null, this._ui( + this.active.find( ".ui-tabs-anchor" )[ 0 ], + this._getPanelForTab( this.active )[ 0 ] ) ); + } + }, + _trigger: function( type, event, data ) { + var ret = this._superApply( arguments ); + if ( !ret ) { + return false; + } + if ( type === "beforeActivate" && data.newTab.length ) { + ret = this._super( "select", event, { + tab: data.newTab.find( ".ui-tabs-anchor" )[ 0], + panel: data.newPanel[ 0 ], + index: data.newTab.closest( "li" ).index() + }); + } else if ( type === "activate" && data.newTab.length ) { + ret = this._super( "show", event, { + tab: data.newTab.find( ".ui-tabs-anchor" )[ 0 ], + panel: data.newPanel[ 0 ], + index: data.newTab.closest( "li" ).index() + }); + } + return ret; + } + }); + + // select method + $.widget( "ui.tabs", $.ui.tabs, { + select: function( index ) { + index = this._getIndex( index ); + if ( index === -1 ) { + if ( this.options.collapsible && this.options.selected !== -1 ) { + index = this.options.selected; + } else { + return; } } - : function( e ) { - t = o.selected; - rotate(); - }); + this.anchors.eq( index ).trigger( this.options.event + this.eventNamespace ); + } + }); + + // cookie option + (function() { + + var listId = 0; + + $.widget( "ui.tabs", $.ui.tabs, { + options: { + cookie: null // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true } + }, + _create: function() { + var options = this.options, + active; + if ( options.active == null && options.cookie ) { + active = parseInt( this._cookie(), 10 ); + if ( active === -1 ) { + active = false; + } + options.active = active; + } + this._super(); + }, + _cookie: function( active ) { + var cookie = [ this.cookie || + ( this.cookie = this.options.cookie.name || "ui-tabs-" + (++listId) ) ]; + if ( arguments.length ) { + cookie.push( active === false ? -1 : active ); + cookie.push( this.options.cookie ); + } + return $.cookie.apply( null, cookie ); + }, + _refresh: function() { + this._super(); + if ( this.options.cookie ) { + this._cookie( this.options.active, this.options.cookie ); + } + }, + _eventHandler: function( event ) { + this._superApply( arguments ); + if ( this.options.cookie ) { + this._cookie( this.options.active, this.options.cookie ); + } + }, + _destroy: function() { + this._super(); + if ( this.options.cookie ) { + this._cookie( null, this.options.cookie ); + } + } + }); - // start rotation - if ( ms ) { - this.element.bind( "tabsshow", rotate ); - this.anchors.bind( o.event + ".tabs", stop ); - rotate(); - // stop rotation - } else { - clearTimeout( self.rotation ); - this.element.unbind( "tabsshow", rotate ); - this.anchors.unbind( o.event + ".tabs", stop ); - delete this._rotate; - delete this._unrotate; + })(); + + // load event + $.widget( "ui.tabs", $.ui.tabs, { + _trigger: function( type, event, data ) { + var _data = $.extend( {}, data ); + if ( type === "load" ) { + _data.panel = _data.panel[ 0 ]; + _data.tab = _data.tab.find( ".ui-tabs-anchor" )[ 0 ]; + } + return this._super( type, event, _data ); } + }); + + // fx option + // The new animation options (show, hide) conflict with the old show callback. + // The old fx option wins over show/hide anyway (always favor back-compat). + // If a user wants to use the new animation API, they must give up the old API. + $.widget( "ui.tabs", $.ui.tabs, { + options: { + fx: null // e.g. { height: "toggle", opacity: "toggle", duration: 200 } + }, + + _getFx: function() { + var hide, show, + fx = this.options.fx; + + if ( fx ) { + if ( $.isArray( fx ) ) { + hide = fx[ 0 ]; + show = fx[ 1 ]; + } else { + hide = show = fx; + } + } - return this; - } -}); + return fx ? { show: show, hide: hide } : null; + }, + + _toggle: function( event, eventData ) { + var that = this, + toShow = eventData.newPanel, + toHide = eventData.oldPanel, + fx = this._getFx(); + + if ( !fx ) { + return this._super( event, eventData ); + } + + that.running = true; + + function complete() { + that.running = false; + that._trigger( "activate", event, eventData ); + } + + function show() { + eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" ); + + if ( toShow.length && fx.show ) { + toShow + .animate( fx.show, fx.show.duration, function() { + complete(); + }); + } else { + toShow.show(); + complete(); + } + } + + // start out by hiding, then showing, then completing + if ( toHide.length && fx.hide ) { + toHide.animate( fx.hide, fx.hide.duration, function() { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + show(); + }); + } else { + eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" ); + toHide.hide(); + show(); + } + } + }); +} })( jQuery ); |