jquery.ui.tabs.js 21 KB

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