balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

tabs.js (23552B)


      1 /*!
      2  * jQuery UI Tabs 1.12.1
      3  * http://jqueryui.com
      4  *
      5  * Copyright jQuery Foundation and other contributors
      6  * Released under the MIT license.
      7  * http://jquery.org/license
      8  */
      9 
     10 //>>label: Tabs
     11 //>>group: Widgets
     12 //>>description: Transforms a set of container elements into a tab structure.
     13 //>>docs: http://api.jqueryui.com/tabs/
     14 //>>demos: http://jqueryui.com/tabs/
     15 //>>css.structure: ../../themes/base/core.css
     16 //>>css.structure: ../../themes/base/tabs.css
     17 //>>css.theme: ../../themes/base/theme.css
     18 
     19 ( function( factory ) {
     20 	if ( typeof define === "function" && define.amd ) {
     21 
     22 		// AMD. Register as an anonymous module.
     23 		define( [
     24 			"jquery",
     25 			"./core"
     26 		], factory );
     27 	} else {
     28 
     29 		// Browser globals
     30 		factory( jQuery );
     31 	}
     32 }( function( $ ) {
     33 
     34 $.widget( "ui.tabs", {
     35 	version: "1.12.1",
     36 	delay: 300,
     37 	options: {
     38 		active: null,
     39 		classes: {
     40 			"ui-tabs": "ui-corner-all",
     41 			"ui-tabs-nav": "ui-corner-all",
     42 			"ui-tabs-panel": "ui-corner-bottom",
     43 			"ui-tabs-tab": "ui-corner-top"
     44 		},
     45 		collapsible: false,
     46 		event: "click",
     47 		heightStyle: "content",
     48 		hide: null,
     49 		show: null,
     50 
     51 		// Callbacks
     52 		activate: null,
     53 		beforeActivate: null,
     54 		beforeLoad: null,
     55 		load: null
     56 	},
     57 
     58 	_isLocal: ( function() {
     59 		var rhash = /#.*$/;
     60 
     61 		return function( anchor ) {
     62 			var anchorUrl, locationUrl;
     63 
     64 			anchorUrl = anchor.href.replace( rhash, "" );
     65 			locationUrl = location.href.replace( rhash, "" );
     66 
     67 			// Decoding may throw an error if the URL isn't UTF-8 (#9518)
     68 			try {
     69 				anchorUrl = decodeURIComponent( anchorUrl );
     70 			} catch ( error ) {}
     71 			try {
     72 				locationUrl = decodeURIComponent( locationUrl );
     73 			} catch ( error ) {}
     74 
     75 			return anchor.hash.length > 1 && anchorUrl === locationUrl;
     76 		};
     77 	} )(),
     78 
     79 	_create: function() {
     80 		var that = this,
     81 			options = this.options;
     82 
     83 		this.running = false;
     84 
     85 		this._addClass( "ui-tabs", "ui-widget ui-widget-content" );
     86 		this._toggleClass( "ui-tabs-collapsible", null, options.collapsible );
     87 
     88 		this._processTabs();
     89 		options.active = this._initialActive();
     90 
     91 		// Take disabling tabs via class attribute from HTML
     92 		// into account and update option properly.
     93 		if ( $.isArray( options.disabled ) ) {
     94 			options.disabled = $.unique( options.disabled.concat(
     95 				$.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
     96 					return that.tabs.index( li );
     97 				} )
     98 			) ).sort();
     99 		}
    100 
    101 		// Check for length avoids error when initializing empty list
    102 		if ( this.options.active !== false && this.anchors.length ) {
    103 			this.active = this._findActive( options.active );
    104 		} else {
    105 			this.active = $();
    106 		}
    107 
    108 		this._refresh();
    109 
    110 		if ( this.active.length ) {
    111 			this.load( options.active );
    112 		}
    113 	},
    114 
    115 	_initialActive: function() {
    116 		var active = this.options.active,
    117 			collapsible = this.options.collapsible,
    118 			locationHash = location.hash.substring( 1 );
    119 
    120 		if ( active === null ) {
    121 
    122 			// check the fragment identifier in the URL
    123 			if ( locationHash ) {
    124 				this.tabs.each( function( i, tab ) {
    125 					if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
    126 						active = i;
    127 						return false;
    128 					}
    129 				} );
    130 			}
    131 
    132 			// Check for a tab marked active via a class
    133 			if ( active === null ) {
    134 				active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
    135 			}
    136 
    137 			// No active tab, set to false
    138 			if ( active === null || active === -1 ) {
    139 				active = this.tabs.length ? 0 : false;
    140 			}
    141 		}
    142 
    143 		// Handle numbers: negative, out of range
    144 		if ( active !== false ) {
    145 			active = this.tabs.index( this.tabs.eq( active ) );
    146 			if ( active === -1 ) {
    147 				active = collapsible ? false : 0;
    148 			}
    149 		}
    150 
    151 		// Don't allow collapsible: false and active: false
    152 		if ( !collapsible && active === false && this.anchors.length ) {
    153 			active = 0;
    154 		}
    155 
    156 		return active;
    157 	},
    158 
    159 	_getCreateEventData: function() {
    160 		return {
    161 			tab: this.active,
    162 			panel: !this.active.length ? $() : this._getPanelForTab( this.active )
    163 		};
    164 	},
    165 
    166 	_tabKeydown: function( event ) {
    167 		var focusedTab = $( $.ui.safeActiveElement( this.document[ 0 ] ) ).closest( "li" ),
    168 			selectedIndex = this.tabs.index( focusedTab ),
    169 			goingForward = true;
    170 
    171 		if ( this._handlePageNav( event ) ) {
    172 			return;
    173 		}
    174 
    175 		switch ( event.keyCode ) {
    176 		case $.ui.keyCode.RIGHT:
    177 		case $.ui.keyCode.DOWN:
    178 			selectedIndex++;
    179 			break;
    180 		case $.ui.keyCode.UP:
    181 		case $.ui.keyCode.LEFT:
    182 			goingForward = false;
    183 			selectedIndex--;
    184 			break;
    185 		case $.ui.keyCode.END:
    186 			selectedIndex = this.anchors.length - 1;
    187 			break;
    188 		case $.ui.keyCode.HOME:
    189 			selectedIndex = 0;
    190 			break;
    191 		case $.ui.keyCode.SPACE:
    192 
    193 			// Activate only, no collapsing
    194 			event.preventDefault();
    195 			clearTimeout( this.activating );
    196 			this._activate( selectedIndex );
    197 			return;
    198 		case $.ui.keyCode.ENTER:
    199 
    200 			// Toggle (cancel delayed activation, allow collapsing)
    201 			event.preventDefault();
    202 			clearTimeout( this.activating );
    203 
    204 			// Determine if we should collapse or activate
    205 			this._activate( selectedIndex === this.options.active ? false : selectedIndex );
    206 			return;
    207 		default:
    208 			return;
    209 		}
    210 
    211 		// Focus the appropriate tab, based on which key was pressed
    212 		event.preventDefault();
    213 		clearTimeout( this.activating );
    214 		selectedIndex = this._focusNextTab( selectedIndex, goingForward );
    215 
    216 		// Navigating with control/command key will prevent automatic activation
    217 		if ( !event.ctrlKey && !event.metaKey ) {
    218 
    219 			// Update aria-selected immediately so that AT think the tab is already selected.
    220 			// Otherwise AT may confuse the user by stating that they need to activate the tab,
    221 			// but the tab will already be activated by the time the announcement finishes.
    222 			focusedTab.attr( "aria-selected", "false" );
    223 			this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
    224 
    225 			this.activating = this._delay( function() {
    226 				this.option( "active", selectedIndex );
    227 			}, this.delay );
    228 		}
    229 	},
    230 
    231 	_panelKeydown: function( event ) {
    232 		if ( this._handlePageNav( event ) ) {
    233 			return;
    234 		}
    235 
    236 		// Ctrl+up moves focus to the current tab
    237 		if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
    238 			event.preventDefault();
    239 			this.active.trigger( "focus" );
    240 		}
    241 	},
    242 
    243 	// Alt+page up/down moves focus to the previous/next tab (and activates)
    244 	_handlePageNav: function( event ) {
    245 		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
    246 			this._activate( this._focusNextTab( this.options.active - 1, false ) );
    247 			return true;
    248 		}
    249 		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
    250 			this._activate( this._focusNextTab( this.options.active + 1, true ) );
    251 			return true;
    252 		}
    253 	},
    254 
    255 	_findNextTab: function( index, goingForward ) {
    256 		var lastTabIndex = this.tabs.length - 1;
    257 
    258 		function constrain() {
    259 			if ( index > lastTabIndex ) {
    260 				index = 0;
    261 			}
    262 			if ( index < 0 ) {
    263 				index = lastTabIndex;
    264 			}
    265 			return index;
    266 		}
    267 
    268 		while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
    269 			index = goingForward ? index + 1 : index - 1;
    270 		}
    271 
    272 		return index;
    273 	},
    274 
    275 	_focusNextTab: function( index, goingForward ) {
    276 		index = this._findNextTab( index, goingForward );
    277 		this.tabs.eq( index ).trigger( "focus" );
    278 		return index;
    279 	},
    280 
    281 	_setOption: function( key, value ) {
    282 		if ( key === "active" ) {
    283 
    284 			// _activate() will handle invalid values and update this.options
    285 			this._activate( value );
    286 			return;
    287 		}
    288 
    289 		this._super( key, value );
    290 
    291 		if ( key === "collapsible" ) {
    292 			this._toggleClass( "ui-tabs-collapsible", null, value );
    293 
    294 			// Setting collapsible: false while collapsed; open first panel
    295 			if ( !value && this.options.active === false ) {
    296 				this._activate( 0 );
    297 			}
    298 		}
    299 
    300 		if ( key === "event" ) {
    301 			this._setupEvents( value );
    302 		}
    303 
    304 		if ( key === "heightStyle" ) {
    305 			this._setupHeightStyle( value );
    306 		}
    307 	},
    308 
    309 	_sanitizeSelector: function( hash ) {
    310 		return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
    311 	},
    312 
    313 	refresh: function() {
    314 		var options = this.options,
    315 			lis = this.tablist.children( ":has(a[href])" );
    316 
    317 		// Get disabled tabs from class attribute from HTML
    318 		// this will get converted to a boolean if needed in _refresh()
    319 		options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
    320 			return lis.index( tab );
    321 		} );
    322 
    323 		this._processTabs();
    324 
    325 		// Was collapsed or no tabs
    326 		if ( options.active === false || !this.anchors.length ) {
    327 			options.active = false;
    328 			this.active = $();
    329 
    330 		// was active, but active tab is gone
    331 		} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
    332 
    333 			// all remaining tabs are disabled
    334 			if ( this.tabs.length === options.disabled.length ) {
    335 				options.active = false;
    336 				this.active = $();
    337 
    338 			// activate previous tab
    339 			} else {
    340 				this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
    341 			}
    342 
    343 		// was active, active tab still exists
    344 		} else {
    345 
    346 			// make sure active index is correct
    347 			options.active = this.tabs.index( this.active );
    348 		}
    349 
    350 		this._refresh();
    351 	},
    352 
    353 	_refresh: function() {
    354 		this._setOptionDisabled( this.options.disabled );
    355 		this._setupEvents( this.options.event );
    356 		this._setupHeightStyle( this.options.heightStyle );
    357 
    358 		this.tabs.not( this.active ).attr( {
    359 			"aria-selected": "false",
    360 			"aria-expanded": "false",
    361 			tabIndex: -1
    362 		} );
    363 		this.panels.not( this._getPanelForTab( this.active ) )
    364 			.hide()
    365 			.attr( {
    366 				"aria-hidden": "true"
    367 			} );
    368 
    369 		// Make sure one tab is in the tab order
    370 		if ( !this.active.length ) {
    371 			this.tabs.eq( 0 ).attr( "tabIndex", 0 );
    372 		} else {
    373 			this.active
    374 				.attr( {
    375 					"aria-selected": "true",
    376 					"aria-expanded": "true",
    377 					tabIndex: 0
    378 				} );
    379 			this._addClass( this.active, "ui-tabs-active", "ui-state-active" );
    380 			this._getPanelForTab( this.active )
    381 				.show()
    382 				.attr( {
    383 					"aria-hidden": "false"
    384 				} );
    385 		}
    386 	},
    387 
    388 	_processTabs: function() {
    389 		var that = this,
    390 			prevTabs = this.tabs,
    391 			prevAnchors = this.anchors,
    392 			prevPanels = this.panels;
    393 
    394 		this.tablist = this._getList().attr( "role", "tablist" );
    395 		this._addClass( this.tablist, "ui-tabs-nav",
    396 			"ui-helper-reset ui-helper-clearfix ui-widget-header" );
    397 
    398 		// Prevent users from focusing disabled tabs via click
    399 		this.tablist
    400 			.on( "mousedown" + this.eventNamespace, "> li", function( event ) {
    401 				if ( $( this ).is( ".ui-state-disabled" ) ) {
    402 					event.preventDefault();
    403 				}
    404 			} )
    405 
    406 			// Support: IE <9
    407 			// Preventing the default action in mousedown doesn't prevent IE
    408 			// from focusing the element, so if the anchor gets focused, blur.
    409 			// We don't have to worry about focusing the previously focused
    410 			// element since clicking on a non-focusable element should focus
    411 			// the body anyway.
    412 			.on( "focus" + this.eventNamespace, ".ui-tabs-anchor", function() {
    413 				if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
    414 					this.blur();
    415 				}
    416 			} );
    417 
    418 		this.tabs = this.tablist.find( "> li:has(a[href])" )
    419 			.attr( {
    420 				role: "tab",
    421 				tabIndex: -1
    422 			} );
    423 		this._addClass( this.tabs, "ui-tabs-tab", "ui-state-default" );
    424 
    425 		this.anchors = this.tabs.map( function() {
    426 			return $( "a", this )[ 0 ];
    427 		} )
    428 			.attr( {
    429 				role: "presentation",
    430 				tabIndex: -1
    431 			} );
    432 		this._addClass( this.anchors, "ui-tabs-anchor" );
    433 
    434 		this.panels = $();
    435 
    436 		this.anchors.each( function( i, anchor ) {
    437 			var selector, panel, panelId,
    438 				anchorId = $( anchor ).uniqueId().attr( "id" ),
    439 				tab = $( anchor ).closest( "li" ),
    440 				originalAriaControls = tab.attr( "aria-controls" );
    441 
    442 			// Inline tab
    443 			if ( that._isLocal( anchor ) ) {
    444 				selector = anchor.hash;
    445 				panelId = selector.substring( 1 );
    446 				panel = that.element.find( that._sanitizeSelector( selector ) );
    447 
    448 			// remote tab
    449 			} else {
    450 
    451 				// If the tab doesn't already have aria-controls,
    452 				// generate an id by using a throw-away element
    453 				panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id;
    454 				selector = "#" + panelId;
    455 				panel = that.element.find( selector );
    456 				if ( !panel.length ) {
    457 					panel = that._createPanel( panelId );
    458 					panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
    459 				}
    460 				panel.attr( "aria-live", "polite" );
    461 			}
    462 
    463 			if ( panel.length ) {
    464 				that.panels = that.panels.add( panel );
    465 			}
    466 			if ( originalAriaControls ) {
    467 				tab.data( "ui-tabs-aria-controls", originalAriaControls );
    468 			}
    469 			tab.attr( {
    470 				"aria-controls": panelId,
    471 				"aria-labelledby": anchorId
    472 			} );
    473 			panel.attr( "aria-labelledby", anchorId );
    474 		} );
    475 
    476 		this.panels.attr( "role", "tabpanel" );
    477 		this._addClass( this.panels, "ui-tabs-panel", "ui-widget-content" );
    478 
    479 		// Avoid memory leaks (#10056)
    480 		if ( prevTabs ) {
    481 			this._off( prevTabs.not( this.tabs ) );
    482 			this._off( prevAnchors.not( this.anchors ) );
    483 			this._off( prevPanels.not( this.panels ) );
    484 		}
    485 	},
    486 
    487 	// Allow overriding how to find the list for rare usage scenarios (#7715)
    488 	_getList: function() {
    489 		return this.tablist || this.element.find( "ol, ul" ).eq( 0 );
    490 	},
    491 
    492 	_createPanel: function( id ) {
    493 		return $( "<div>" )
    494 			.attr( "id", id )
    495 			.data( "ui-tabs-destroy", true );
    496 	},
    497 
    498 	_setOptionDisabled: function( disabled ) {
    499 		var currentItem, li, i;
    500 
    501 		if ( $.isArray( disabled ) ) {
    502 			if ( !disabled.length ) {
    503 				disabled = false;
    504 			} else if ( disabled.length === this.anchors.length ) {
    505 				disabled = true;
    506 			}
    507 		}
    508 
    509 		// Disable tabs
    510 		for ( i = 0; ( li = this.tabs[ i ] ); i++ ) {
    511 			currentItem = $( li );
    512 			if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
    513 				currentItem.attr( "aria-disabled", "true" );
    514 				this._addClass( currentItem, null, "ui-state-disabled" );
    515 			} else {
    516 				currentItem.removeAttr( "aria-disabled" );
    517 				this._removeClass( currentItem, null, "ui-state-disabled" );
    518 			}
    519 		}
    520 
    521 		this.options.disabled = disabled;
    522 
    523 		this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null,
    524 			disabled === true );
    525 	},
    526 
    527 	_setupEvents: function( event ) {
    528 		var events = {};
    529 		if ( event ) {
    530 			$.each( event.split( " " ), function( index, eventName ) {
    531 				events[ eventName ] = "_eventHandler";
    532 			} );
    533 		}
    534 
    535 		this._off( this.anchors.add( this.tabs ).add( this.panels ) );
    536 
    537 		// Always prevent the default action, even when disabled
    538 		this._on( true, this.anchors, {
    539 			click: function( event ) {
    540 				event.preventDefault();
    541 			}
    542 		} );
    543 		this._on( this.anchors, events );
    544 		this._on( this.tabs, { keydown: "_tabKeydown" } );
    545 		this._on( this.panels, { keydown: "_panelKeydown" } );
    546 
    547 		this._focusable( this.tabs );
    548 		this._hoverable( this.tabs );
    549 	},
    550 
    551 	_setupHeightStyle: function( heightStyle ) {
    552 		var maxHeight,
    553 			parent = this.element.parent();
    554 
    555 		if ( heightStyle === "fill" ) {
    556 			maxHeight = parent.height();
    557 			maxHeight -= this.element.outerHeight() - this.element.height();
    558 
    559 			this.element.siblings( ":visible" ).each( function() {
    560 				var elem = $( this ),
    561 					position = elem.css( "position" );
    562 
    563 				if ( position === "absolute" || position === "fixed" ) {
    564 					return;
    565 				}
    566 				maxHeight -= elem.outerHeight( true );
    567 			} );
    568 
    569 			this.element.children().not( this.panels ).each( function() {
    570 				maxHeight -= $( this ).outerHeight( true );
    571 			} );
    572 
    573 			this.panels.each( function() {
    574 				$( this ).height( Math.max( 0, maxHeight -
    575 					$( this ).innerHeight() + $( this ).height() ) );
    576 			} )
    577 				.css( "overflow", "auto" );
    578 		} else if ( heightStyle === "auto" ) {
    579 			maxHeight = 0;
    580 			this.panels.each( function() {
    581 				maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
    582 			} ).height( maxHeight );
    583 		}
    584 	},
    585 
    586 	_eventHandler: function( event ) {
    587 		var options = this.options,
    588 			active = this.active,
    589 			anchor = $( event.currentTarget ),
    590 			tab = anchor.closest( "li" ),
    591 			clickedIsActive = tab[ 0 ] === active[ 0 ],
    592 			collapsing = clickedIsActive && options.collapsible,
    593 			toShow = collapsing ? $() : this._getPanelForTab( tab ),
    594 			toHide = !active.length ? $() : this._getPanelForTab( active ),
    595 			eventData = {
    596 				oldTab: active,
    597 				oldPanel: toHide,
    598 				newTab: collapsing ? $() : tab,
    599 				newPanel: toShow
    600 			};
    601 
    602 		event.preventDefault();
    603 
    604 		if ( tab.hasClass( "ui-state-disabled" ) ||
    605 
    606 				// tab is already loading
    607 				tab.hasClass( "ui-tabs-loading" ) ||
    608 
    609 				// can't switch durning an animation
    610 				this.running ||
    611 
    612 				// click on active header, but not collapsible
    613 				( clickedIsActive && !options.collapsible ) ||
    614 
    615 				// allow canceling activation
    616 				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
    617 			return;
    618 		}
    619 
    620 		options.active = collapsing ? false : this.tabs.index( tab );
    621 
    622 		this.active = clickedIsActive ? $() : tab;
    623 		if ( this.xhr ) {
    624 			this.xhr.abort();
    625 		}
    626 
    627 		if ( !toHide.length && !toShow.length ) {
    628 			$.error( "jQuery UI Tabs: Mismatching fragment identifier." );
    629 		}
    630 
    631 		if ( toShow.length ) {
    632 			this.load( this.tabs.index( tab ), event );
    633 		}
    634 		this._toggle( event, eventData );
    635 	},
    636 
    637 	// Handles show/hide for selecting tabs
    638 	_toggle: function( event, eventData ) {
    639 		var that = this,
    640 			toShow = eventData.newPanel,
    641 			toHide = eventData.oldPanel;
    642 
    643 		this.running = true;
    644 
    645 		function complete() {
    646 			that.running = false;
    647 			that._trigger( "activate", event, eventData );
    648 		}
    649 
    650 		function show() {
    651 			that._addClass( eventData.newTab.closest( "li" ), "ui-tabs-active", "ui-state-active" );
    652 
    653 			if ( toShow.length && that.options.show ) {
    654 				that._show( toShow, that.options.show, complete );
    655 			} else {
    656 				toShow.show();
    657 				complete();
    658 			}
    659 		}
    660 
    661 		// Start out by hiding, then showing, then completing
    662 		if ( toHide.length && this.options.hide ) {
    663 			this._hide( toHide, this.options.hide, function() {
    664 				that._removeClass( eventData.oldTab.closest( "li" ),
    665 					"ui-tabs-active", "ui-state-active" );
    666 				show();
    667 			} );
    668 		} else {
    669 			this._removeClass( eventData.oldTab.closest( "li" ),
    670 				"ui-tabs-active", "ui-state-active" );
    671 			toHide.hide();
    672 			show();
    673 		}
    674 
    675 		toHide.attr( "aria-hidden", "true" );
    676 		eventData.oldTab.attr( {
    677 			"aria-selected": "false",
    678 			"aria-expanded": "false"
    679 		} );
    680 
    681 		// If we're switching tabs, remove the old tab from the tab order.
    682 		// If we're opening from collapsed state, remove the previous tab from the tab order.
    683 		// If we're collapsing, then keep the collapsing tab in the tab order.
    684 		if ( toShow.length && toHide.length ) {
    685 			eventData.oldTab.attr( "tabIndex", -1 );
    686 		} else if ( toShow.length ) {
    687 			this.tabs.filter( function() {
    688 				return $( this ).attr( "tabIndex" ) === 0;
    689 			} )
    690 				.attr( "tabIndex", -1 );
    691 		}
    692 
    693 		toShow.attr( "aria-hidden", "false" );
    694 		eventData.newTab.attr( {
    695 			"aria-selected": "true",
    696 			"aria-expanded": "true",
    697 			tabIndex: 0
    698 		} );
    699 	},
    700 
    701 	_activate: function( index ) {
    702 		var anchor,
    703 			active = this._findActive( index );
    704 
    705 		// Trying to activate the already active panel
    706 		if ( active[ 0 ] === this.active[ 0 ] ) {
    707 			return;
    708 		}
    709 
    710 		// Trying to collapse, simulate a click on the current active header
    711 		if ( !active.length ) {
    712 			active = this.active;
    713 		}
    714 
    715 		anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
    716 		this._eventHandler( {
    717 			target: anchor,
    718 			currentTarget: anchor,
    719 			preventDefault: $.noop
    720 		} );
    721 	},
    722 
    723 	_findActive: function( index ) {
    724 		return index === false ? $() : this.tabs.eq( index );
    725 	},
    726 
    727 	_getIndex: function( index ) {
    728 
    729 		// meta-function to give users option to provide a href string instead of a numerical index.
    730 		if ( typeof index === "string" ) {
    731 			index = this.anchors.index( this.anchors.filter( "[href$='" +
    732 				$.ui.escapeSelector( index ) + "']" ) );
    733 		}
    734 
    735 		return index;
    736 	},
    737 
    738 	_destroy: function() {
    739 		if ( this.xhr ) {
    740 			this.xhr.abort();
    741 		}
    742 
    743 		this.tablist
    744 			.removeAttr( "role" )
    745 			.off( this.eventNamespace );
    746 
    747 		this.anchors
    748 			.removeAttr( "role tabIndex" )
    749 			.removeUniqueId();
    750 
    751 		this.tabs.add( this.panels ).each( function() {
    752 			if ( $.data( this, "ui-tabs-destroy" ) ) {
    753 				$( this ).remove();
    754 			} else {
    755 				$( this ).removeAttr( "role tabIndex " +
    756 					"aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded" );
    757 			}
    758 		} );
    759 
    760 		this.tabs.each( function() {
    761 			var li = $( this ),
    762 				prev = li.data( "ui-tabs-aria-controls" );
    763 			if ( prev ) {
    764 				li
    765 					.attr( "aria-controls", prev )
    766 					.removeData( "ui-tabs-aria-controls" );
    767 			} else {
    768 				li.removeAttr( "aria-controls" );
    769 			}
    770 		} );
    771 
    772 		this.panels.show();
    773 
    774 		if ( this.options.heightStyle !== "content" ) {
    775 			this.panels.css( "height", "" );
    776 		}
    777 	},
    778 
    779 	enable: function( index ) {
    780 		var disabled = this.options.disabled;
    781 		if ( disabled === false ) {
    782 			return;
    783 		}
    784 
    785 		if ( index === undefined ) {
    786 			disabled = false;
    787 		} else {
    788 			index = this._getIndex( index );
    789 			if ( $.isArray( disabled ) ) {
    790 				disabled = $.map( disabled, function( num ) {
    791 					return num !== index ? num : null;
    792 				} );
    793 			} else {
    794 				disabled = $.map( this.tabs, function( li, num ) {
    795 					return num !== index ? num : null;
    796 				} );
    797 			}
    798 		}
    799 		this._setOptionDisabled( disabled );
    800 	},
    801 
    802 	disable: function( index ) {
    803 		var disabled = this.options.disabled;
    804 		if ( disabled === true ) {
    805 			return;
    806 		}
    807 
    808 		if ( index === undefined ) {
    809 			disabled = true;
    810 		} else {
    811 			index = this._getIndex( index );
    812 			if ( $.inArray( index, disabled ) !== -1 ) {
    813 				return;
    814 			}
    815 			if ( $.isArray( disabled ) ) {
    816 				disabled = $.merge( [ index ], disabled ).sort();
    817 			} else {
    818 				disabled = [ index ];
    819 			}
    820 		}
    821 		this._setOptionDisabled( disabled );
    822 	},
    823 
    824 	load: function( index, event ) {
    825 		index = this._getIndex( index );
    826 		var that = this,
    827 			tab = this.tabs.eq( index ),
    828 			anchor = tab.find( ".ui-tabs-anchor" ),
    829 			panel = this._getPanelForTab( tab ),
    830 			eventData = {
    831 				tab: tab,
    832 				panel: panel
    833 			},
    834 			complete = function( jqXHR, status ) {
    835 				if ( status === "abort" ) {
    836 					that.panels.stop( false, true );
    837 				}
    838 
    839 				that._removeClass( tab, "ui-tabs-loading" );
    840 				panel.removeAttr( "aria-busy" );
    841 
    842 				if ( jqXHR === that.xhr ) {
    843 					delete that.xhr;
    844 				}
    845 			};
    846 
    847 		// Not remote
    848 		if ( this._isLocal( anchor[ 0 ] ) ) {
    849 			return;
    850 		}
    851 
    852 		this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
    853 
    854 		// Support: jQuery <1.8
    855 		// jQuery <1.8 returns false if the request is canceled in beforeSend,
    856 		// but as of 1.8, $.ajax() always returns a jqXHR object.
    857 		if ( this.xhr && this.xhr.statusText !== "canceled" ) {
    858 			this._addClass( tab, "ui-tabs-loading" );
    859 			panel.attr( "aria-busy", "true" );
    860 
    861 			this.xhr
    862 				.done( function( response, status, jqXHR ) {
    863 
    864 					// support: jQuery <1.8
    865 					// http://bugs.jquery.com/ticket/11778
    866 					setTimeout( function() {
    867 						panel.html( response );
    868 						that._trigger( "load", event, eventData );
    869 
    870 						complete( jqXHR, status );
    871 					}, 1 );
    872 				} )
    873 				.fail( function( jqXHR, status ) {
    874 
    875 					// support: jQuery <1.8
    876 					// http://bugs.jquery.com/ticket/11778
    877 					setTimeout( function() {
    878 						complete( jqXHR, status );
    879 					}, 1 );
    880 				} );
    881 		}
    882 	},
    883 
    884 	_ajaxSettings: function( anchor, event, eventData ) {
    885 		var that = this;
    886 		return {
    887 
    888 			// Support: IE <11 only
    889 			// Strip any hash that exists to prevent errors with the Ajax request
    890 			url: anchor.attr( "href" ).replace( /#.*$/, "" ),
    891 			beforeSend: function( jqXHR, settings ) {
    892 				return that._trigger( "beforeLoad", event,
    893 					$.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );
    894 			}
    895 		};
    896 	},
    897 
    898 	_getPanelForTab: function( tab ) {
    899 		var id = $( tab ).attr( "aria-controls" );
    900 		return this.element.find( this._sanitizeSelector( "#" + id ) );
    901 	}
    902 } );
    903 
    904 // DEPRECATED
    905 // TODO: Switch return back to widget declaration at top of file when this is removed
    906 if ( $.uiBackCompat !== false ) {
    907 
    908 	// Backcompat for ui-tab class (now ui-tabs-tab)
    909 	$.widget( "ui.tabs", $.ui.tabs, {
    910 		_processTabs: function() {
    911 			this._superApply( arguments );
    912 			this._addClass( this.tabs, "ui-tab" );
    913 		}
    914 	} );
    915 }
    916 
    917 return $.ui.tabs;
    918 
    919 } ) );