Няма описание

tabs.js 23KB

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