tabs.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870
  1. /*!
  2. * jQuery UI Tabs @VERSION
  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: "@VERSION",
  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. this.tablist = this._getList()
  321. .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
  322. .attr( "role", "tablist" )
  323. // Prevent users from focusing disabled tabs via click
  324. .delegate( "> li", "mousedown" + this.eventNamespace, function( event ) {
  325. if ( $( this ).is( ".ui-state-disabled" ) ) {
  326. event.preventDefault();
  327. }
  328. })
  329. // support: IE <9
  330. // Preventing the default action in mousedown doesn't prevent IE
  331. // from focusing the element, so if the anchor gets focused, blur.
  332. // We don't have to worry about focusing the previously focused
  333. // element since clicking on a non-focusable element should focus
  334. // the body anyway.
  335. .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
  336. if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
  337. this.blur();
  338. }
  339. });
  340. this.tabs = this.tablist.find( "> li:has(a[href])" )
  341. .addClass( "ui-state-default ui-corner-top" )
  342. .attr({
  343. role: "tab",
  344. tabIndex: -1
  345. });
  346. this.anchors = this.tabs.map(function() {
  347. return $( "a", this )[ 0 ];
  348. })
  349. .addClass( "ui-tabs-anchor" )
  350. .attr({
  351. role: "presentation",
  352. tabIndex: -1
  353. });
  354. this.panels = $();
  355. this.anchors.each(function( i, anchor ) {
  356. var selector, panel, panelId,
  357. anchorId = $( anchor ).uniqueId().attr( "id" ),
  358. tab = $( anchor ).closest( "li" ),
  359. originalAriaControls = tab.attr( "aria-controls" );
  360. // inline tab
  361. if ( that._isLocal( anchor ) ) {
  362. selector = anchor.hash;
  363. panelId = selector.substring( 1 );
  364. panel = that.element.find( that._sanitizeSelector( selector ) );
  365. // remote tab
  366. } else {
  367. // If the tab doesn't already have aria-controls,
  368. // generate an id by using a throw-away element
  369. panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id;
  370. selector = "#" + panelId;
  371. panel = that.element.find( selector );
  372. if ( !panel.length ) {
  373. panel = that._createPanel( panelId );
  374. panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
  375. }
  376. panel.attr( "aria-live", "polite" );
  377. }
  378. if ( panel.length) {
  379. that.panels = that.panels.add( panel );
  380. }
  381. if ( originalAriaControls ) {
  382. tab.data( "ui-tabs-aria-controls", originalAriaControls );
  383. }
  384. tab.attr({
  385. "aria-controls": panelId,
  386. "aria-labelledby": anchorId
  387. });
  388. panel.attr( "aria-labelledby", anchorId );
  389. });
  390. this.panels
  391. .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
  392. .attr( "role", "tabpanel" );
  393. },
  394. // allow overriding how to find the list for rare usage scenarios (#7715)
  395. _getList: function() {
  396. return this.tablist || this.element.find( "ol,ul" ).eq( 0 );
  397. },
  398. _createPanel: function( id ) {
  399. return $( "<div>" )
  400. .attr( "id", id )
  401. .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
  402. .data( "ui-tabs-destroy", true );
  403. },
  404. _setupDisabled: function( disabled ) {
  405. if ( $.isArray( disabled ) ) {
  406. if ( !disabled.length ) {
  407. disabled = false;
  408. } else if ( disabled.length === this.anchors.length ) {
  409. disabled = true;
  410. }
  411. }
  412. // disable tabs
  413. for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
  414. if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
  415. $( li )
  416. .addClass( "ui-state-disabled" )
  417. .attr( "aria-disabled", "true" );
  418. } else {
  419. $( li )
  420. .removeClass( "ui-state-disabled" )
  421. .removeAttr( "aria-disabled" );
  422. }
  423. }
  424. this.options.disabled = disabled;
  425. },
  426. _setupEvents: function( event ) {
  427. var events = {};
  428. if ( event ) {
  429. $.each( event.split(" "), function( index, eventName ) {
  430. events[ eventName ] = "_eventHandler";
  431. });
  432. }
  433. this._off( this.anchors.add( this.tabs ).add( this.panels ) );
  434. // Always prevent the default action, even when disabled
  435. this._on( true, this.anchors, {
  436. click: function( event ) {
  437. event.preventDefault();
  438. }
  439. });
  440. this._on( this.anchors, events );
  441. this._on( this.tabs, { keydown: "_tabKeydown" } );
  442. this._on( this.panels, { keydown: "_panelKeydown" } );
  443. this._focusable( this.tabs );
  444. this._hoverable( this.tabs );
  445. },
  446. _setupHeightStyle: function( heightStyle ) {
  447. var maxHeight,
  448. parent = this.element.parent();
  449. if ( heightStyle === "fill" ) {
  450. maxHeight = parent.height();
  451. maxHeight -= this.element.outerHeight() - this.element.height();
  452. this.element.siblings( ":visible" ).each(function() {
  453. var elem = $( this ),
  454. position = elem.css( "position" );
  455. if ( position === "absolute" || position === "fixed" ) {
  456. return;
  457. }
  458. maxHeight -= elem.outerHeight( true );
  459. });
  460. this.element.children().not( this.panels ).each(function() {
  461. maxHeight -= $( this ).outerHeight( true );
  462. });
  463. this.panels.each(function() {
  464. $( this ).height( Math.max( 0, maxHeight -
  465. $( this ).innerHeight() + $( this ).height() ) );
  466. })
  467. .css( "overflow", "auto" );
  468. } else if ( heightStyle === "auto" ) {
  469. maxHeight = 0;
  470. this.panels.each(function() {
  471. maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
  472. }).height( maxHeight );
  473. }
  474. },
  475. _eventHandler: function( event ) {
  476. var options = this.options,
  477. active = this.active,
  478. anchor = $( event.currentTarget ),
  479. tab = anchor.closest( "li" ),
  480. clickedIsActive = tab[ 0 ] === active[ 0 ],
  481. collapsing = clickedIsActive && options.collapsible,
  482. toShow = collapsing ? $() : this._getPanelForTab( tab ),
  483. toHide = !active.length ? $() : this._getPanelForTab( active ),
  484. eventData = {
  485. oldTab: active,
  486. oldPanel: toHide,
  487. newTab: collapsing ? $() : tab,
  488. newPanel: toShow
  489. };
  490. event.preventDefault();
  491. if ( tab.hasClass( "ui-state-disabled" ) ||
  492. // tab is already loading
  493. tab.hasClass( "ui-tabs-loading" ) ||
  494. // can't switch durning an animation
  495. this.running ||
  496. // click on active header, but not collapsible
  497. ( clickedIsActive && !options.collapsible ) ||
  498. // allow canceling activation
  499. ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
  500. return;
  501. }
  502. options.active = collapsing ? false : this.tabs.index( tab );
  503. this.active = clickedIsActive ? $() : tab;
  504. if ( this.xhr ) {
  505. this.xhr.abort();
  506. }
  507. if ( !toHide.length && !toShow.length ) {
  508. $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
  509. }
  510. if ( toShow.length ) {
  511. this.load( this.tabs.index( tab ), event );
  512. }
  513. this._toggle( event, eventData );
  514. },
  515. // handles show/hide for selecting tabs
  516. _toggle: function( event, eventData ) {
  517. var that = this,
  518. toShow = eventData.newPanel,
  519. toHide = eventData.oldPanel;
  520. this.running = true;
  521. function complete() {
  522. that.running = false;
  523. that._trigger( "activate", event, eventData );
  524. }
  525. function show() {
  526. eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
  527. if ( toShow.length && that.options.show ) {
  528. that._show( toShow, that.options.show, complete );
  529. } else {
  530. toShow.show();
  531. complete();
  532. }
  533. }
  534. // start out by hiding, then showing, then completing
  535. if ( toHide.length && this.options.hide ) {
  536. this._hide( toHide, this.options.hide, function() {
  537. eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
  538. show();
  539. });
  540. } else {
  541. eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
  542. toHide.hide();
  543. show();
  544. }
  545. toHide.attr( "aria-hidden", "true" );
  546. eventData.oldTab.attr({
  547. "aria-selected": "false",
  548. "aria-expanded": "false"
  549. });
  550. // If we're switching tabs, remove the old tab from the tab order.
  551. // If we're opening from collapsed state, remove the previous tab from the tab order.
  552. // If we're collapsing, then keep the collapsing tab in the tab order.
  553. if ( toShow.length && toHide.length ) {
  554. eventData.oldTab.attr( "tabIndex", -1 );
  555. } else if ( toShow.length ) {
  556. this.tabs.filter(function() {
  557. return $( this ).attr( "tabIndex" ) === 0;
  558. })
  559. .attr( "tabIndex", -1 );
  560. }
  561. toShow.attr( "aria-hidden", "false" );
  562. eventData.newTab.attr({
  563. "aria-selected": "true",
  564. "aria-expanded": "true",
  565. tabIndex: 0
  566. });
  567. },
  568. _activate: function( index ) {
  569. var anchor,
  570. active = this._findActive( index );
  571. // trying to activate the already active panel
  572. if ( active[ 0 ] === this.active[ 0 ] ) {
  573. return;
  574. }
  575. // trying to collapse, simulate a click on the current active header
  576. if ( !active.length ) {
  577. active = this.active;
  578. }
  579. anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
  580. this._eventHandler({
  581. target: anchor,
  582. currentTarget: anchor,
  583. preventDefault: $.noop
  584. });
  585. },
  586. _findActive: function( index ) {
  587. return index === false ? $() : this.tabs.eq( index );
  588. },
  589. _getIndex: function( index ) {
  590. // meta-function to give users option to provide a href string instead of a numerical index.
  591. if ( typeof index === "string" ) {
  592. index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
  593. }
  594. return index;
  595. },
  596. _destroy: function() {
  597. if ( this.xhr ) {
  598. this.xhr.abort();
  599. }
  600. this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
  601. this.tablist
  602. .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
  603. .removeAttr( "role" );
  604. this.anchors
  605. .removeClass( "ui-tabs-anchor" )
  606. .removeAttr( "role" )
  607. .removeAttr( "tabIndex" )
  608. .removeUniqueId();
  609. this.tablist.unbind( this.eventNamespace );
  610. this.tabs.add( this.panels ).each(function() {
  611. if ( $.data( this, "ui-tabs-destroy" ) ) {
  612. $( this ).remove();
  613. } else {
  614. $( this )
  615. .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
  616. "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
  617. .removeAttr( "tabIndex" )
  618. .removeAttr( "aria-live" )
  619. .removeAttr( "aria-busy" )
  620. .removeAttr( "aria-selected" )
  621. .removeAttr( "aria-labelledby" )
  622. .removeAttr( "aria-hidden" )
  623. .removeAttr( "aria-expanded" )
  624. .removeAttr( "role" );
  625. }
  626. });
  627. this.tabs.each(function() {
  628. var li = $( this ),
  629. prev = li.data( "ui-tabs-aria-controls" );
  630. if ( prev ) {
  631. li
  632. .attr( "aria-controls", prev )
  633. .removeData( "ui-tabs-aria-controls" );
  634. } else {
  635. li.removeAttr( "aria-controls" );
  636. }
  637. });
  638. this.panels.show();
  639. if ( this.options.heightStyle !== "content" ) {
  640. this.panels.css( "height", "" );
  641. }
  642. },
  643. enable: function( index ) {
  644. var disabled = this.options.disabled;
  645. if ( disabled === false ) {
  646. return;
  647. }
  648. if ( index === undefined ) {
  649. disabled = false;
  650. } else {
  651. index = this._getIndex( index );
  652. if ( $.isArray( disabled ) ) {
  653. disabled = $.map( disabled, function( num ) {
  654. return num !== index ? num : null;
  655. });
  656. } else {
  657. disabled = $.map( this.tabs, function( li, num ) {
  658. return num !== index ? num : null;
  659. });
  660. }
  661. }
  662. this._setupDisabled( disabled );
  663. },
  664. disable: function( index ) {
  665. var disabled = this.options.disabled;
  666. if ( disabled === true ) {
  667. return;
  668. }
  669. if ( index === undefined ) {
  670. disabled = true;
  671. } else {
  672. index = this._getIndex( index );
  673. if ( $.inArray( index, disabled ) !== -1 ) {
  674. return;
  675. }
  676. if ( $.isArray( disabled ) ) {
  677. disabled = $.merge( [ index ], disabled ).sort();
  678. } else {
  679. disabled = [ index ];
  680. }
  681. }
  682. this._setupDisabled( disabled );
  683. },
  684. load: function( index, event ) {
  685. index = this._getIndex( index );
  686. var that = this,
  687. tab = this.tabs.eq( index ),
  688. anchor = tab.find( ".ui-tabs-anchor" ),
  689. panel = this._getPanelForTab( tab ),
  690. eventData = {
  691. tab: tab,
  692. panel: panel
  693. };
  694. // not remote
  695. if ( this._isLocal( anchor[ 0 ] ) ) {
  696. return;
  697. }
  698. this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
  699. // support: jQuery <1.8
  700. // jQuery <1.8 returns false if the request is canceled in beforeSend,
  701. // but as of 1.8, $.ajax() always returns a jqXHR object.
  702. if ( this.xhr && this.xhr.statusText !== "canceled" ) {
  703. tab.addClass( "ui-tabs-loading" );
  704. panel.attr( "aria-busy", "true" );
  705. this.xhr
  706. .success(function( response ) {
  707. // support: jQuery <1.8
  708. // http://bugs.jquery.com/ticket/11778
  709. setTimeout(function() {
  710. panel.html( response );
  711. that._trigger( "load", event, eventData );
  712. }, 1 );
  713. })
  714. .complete(function( jqXHR, status ) {
  715. // support: jQuery <1.8
  716. // http://bugs.jquery.com/ticket/11778
  717. setTimeout(function() {
  718. if ( status === "abort" ) {
  719. that.panels.stop( false, true );
  720. }
  721. tab.removeClass( "ui-tabs-loading" );
  722. panel.removeAttr( "aria-busy" );
  723. if ( jqXHR === that.xhr ) {
  724. delete that.xhr;
  725. }
  726. }, 1 );
  727. });
  728. }
  729. },
  730. _ajaxSettings: function( anchor, event, eventData ) {
  731. var that = this;
  732. return {
  733. url: anchor.attr( "href" ),
  734. beforeSend: function( jqXHR, settings ) {
  735. return that._trigger( "beforeLoad", event,
  736. $.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );
  737. }
  738. };
  739. },
  740. _getPanelForTab: function( tab ) {
  741. var id = $( tab ).attr( "aria-controls" );
  742. return this.element.find( this._sanitizeSelector( "#" + id ) );
  743. }
  744. });
  745. }));