tabs.js 22 KB

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