You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

623 lines
16 KiB

  1. /*!
  2. * jQuery UI Accordion 1.13.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: Accordion
  10. //>>group: Widgets
  11. /* eslint-disable max-len */
  12. //>>description: Displays collapsible content panels for presenting information in a limited amount of space.
  13. /* eslint-enable max-len */
  14. //>>docs: http://api.jqueryui.com/accordion/
  15. //>>demos: http://jqueryui.com/accordion/
  16. //>>css.structure: ../../themes/base/core.css
  17. //>>css.structure: ../../themes/base/accordion.css
  18. //>>css.theme: ../../themes/base/theme.css
  19. ( function( factory ) {
  20. "use strict";
  21. if ( typeof define === "function" && define.amd ) {
  22. // AMD. Register as an anonymous module.
  23. define( [
  24. "jquery",
  25. "../version",
  26. "../keycode",
  27. "../unique-id",
  28. "../widget"
  29. ], factory );
  30. } else {
  31. // Browser globals
  32. factory( jQuery );
  33. }
  34. } )( function( $ ) {
  35. "use strict";
  36. return $.widget( "ui.accordion", {
  37. version: "1.13.1",
  38. options: {
  39. active: 0,
  40. animate: {},
  41. classes: {
  42. "ui-accordion-header": "ui-corner-top",
  43. "ui-accordion-header-collapsed": "ui-corner-all",
  44. "ui-accordion-content": "ui-corner-bottom"
  45. },
  46. collapsible: false,
  47. event: "click",
  48. header: function( elem ) {
  49. return elem.find( "> li > :first-child" ).add( elem.find( "> :not(li)" ).even() );
  50. },
  51. heightStyle: "auto",
  52. icons: {
  53. activeHeader: "ui-icon-triangle-1-s",
  54. header: "ui-icon-triangle-1-e"
  55. },
  56. // Callbacks
  57. activate: null,
  58. beforeActivate: null
  59. },
  60. hideProps: {
  61. borderTopWidth: "hide",
  62. borderBottomWidth: "hide",
  63. paddingTop: "hide",
  64. paddingBottom: "hide",
  65. height: "hide"
  66. },
  67. showProps: {
  68. borderTopWidth: "show",
  69. borderBottomWidth: "show",
  70. paddingTop: "show",
  71. paddingBottom: "show",
  72. height: "show"
  73. },
  74. _create: function() {
  75. var options = this.options;
  76. this.prevShow = this.prevHide = $();
  77. this._addClass( "ui-accordion", "ui-widget ui-helper-reset" );
  78. this.element.attr( "role", "tablist" );
  79. // Don't allow collapsible: false and active: false / null
  80. if ( !options.collapsible && ( options.active === false || options.active == null ) ) {
  81. options.active = 0;
  82. }
  83. this._processPanels();
  84. // handle negative values
  85. if ( options.active < 0 ) {
  86. options.active += this.headers.length;
  87. }
  88. this._refresh();
  89. },
  90. _getCreateEventData: function() {
  91. return {
  92. header: this.active,
  93. panel: !this.active.length ? $() : this.active.next()
  94. };
  95. },
  96. _createIcons: function() {
  97. var icon, children,
  98. icons = this.options.icons;
  99. if ( icons ) {
  100. icon = $( "<span>" );
  101. this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header );
  102. icon.prependTo( this.headers );
  103. children = this.active.children( ".ui-accordion-header-icon" );
  104. this._removeClass( children, icons.header )
  105. ._addClass( children, null, icons.activeHeader )
  106. ._addClass( this.headers, "ui-accordion-icons" );
  107. }
  108. },
  109. _destroyIcons: function() {
  110. this._removeClass( this.headers, "ui-accordion-icons" );
  111. this.headers.children( ".ui-accordion-header-icon" ).remove();
  112. },
  113. _destroy: function() {
  114. var contents;
  115. // Clean up main element
  116. this.element.removeAttr( "role" );
  117. // Clean up headers
  118. this.headers
  119. .removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" )
  120. .removeUniqueId();
  121. this._destroyIcons();
  122. // Clean up content panels
  123. contents = this.headers.next()
  124. .css( "display", "" )
  125. .removeAttr( "role aria-hidden aria-labelledby" )
  126. .removeUniqueId();
  127. if ( this.options.heightStyle !== "content" ) {
  128. contents.css( "height", "" );
  129. }
  130. },
  131. _setOption: function( key, value ) {
  132. if ( key === "active" ) {
  133. // _activate() will handle invalid values and update this.options
  134. this._activate( value );
  135. return;
  136. }
  137. if ( key === "event" ) {
  138. if ( this.options.event ) {
  139. this._off( this.headers, this.options.event );
  140. }
  141. this._setupEvents( value );
  142. }
  143. this._super( key, value );
  144. // Setting collapsible: false while collapsed; open first panel
  145. if ( key === "collapsible" && !value && this.options.active === false ) {
  146. this._activate( 0 );
  147. }
  148. if ( key === "icons" ) {
  149. this._destroyIcons();
  150. if ( value ) {
  151. this._createIcons();
  152. }
  153. }
  154. },
  155. _setOptionDisabled: function( value ) {
  156. this._super( value );
  157. this.element.attr( "aria-disabled", value );
  158. // Support: IE8 Only
  159. // #5332 / #6059 - opacity doesn't cascade to positioned elements in IE
  160. // so we need to add the disabled class to the headers and panels
  161. this._toggleClass( null, "ui-state-disabled", !!value );
  162. this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled",
  163. !!value );
  164. },
  165. _keydown: function( event ) {
  166. if ( event.altKey || event.ctrlKey ) {
  167. return;
  168. }
  169. var keyCode = $.ui.keyCode,
  170. length = this.headers.length,
  171. currentIndex = this.headers.index( event.target ),
  172. toFocus = false;
  173. switch ( event.keyCode ) {
  174. case keyCode.RIGHT:
  175. case keyCode.DOWN:
  176. toFocus = this.headers[ ( currentIndex + 1 ) % length ];
  177. break;
  178. case keyCode.LEFT:
  179. case keyCode.UP:
  180. toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
  181. break;
  182. case keyCode.SPACE:
  183. case keyCode.ENTER:
  184. this._eventHandler( event );
  185. break;
  186. case keyCode.HOME:
  187. toFocus = this.headers[ 0 ];
  188. break;
  189. case keyCode.END:
  190. toFocus = this.headers[ length - 1 ];
  191. break;
  192. }
  193. if ( toFocus ) {
  194. $( event.target ).attr( "tabIndex", -1 );
  195. $( toFocus ).attr( "tabIndex", 0 );
  196. $( toFocus ).trigger( "focus" );
  197. event.preventDefault();
  198. }
  199. },
  200. _panelKeyDown: function( event ) {
  201. if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
  202. $( event.currentTarget ).prev().trigger( "focus" );
  203. }
  204. },
  205. refresh: function() {
  206. var options = this.options;
  207. this._processPanels();
  208. // Was collapsed or no panel
  209. if ( ( options.active === false && options.collapsible === true ) ||
  210. !this.headers.length ) {
  211. options.active = false;
  212. this.active = $();
  213. // active false only when collapsible is true
  214. } else if ( options.active === false ) {
  215. this._activate( 0 );
  216. // was active, but active panel is gone
  217. } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
  218. // all remaining panel are disabled
  219. if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) {
  220. options.active = false;
  221. this.active = $();
  222. // activate previous panel
  223. } else {
  224. this._activate( Math.max( 0, options.active - 1 ) );
  225. }
  226. // was active, active panel still exists
  227. } else {
  228. // make sure active index is correct
  229. options.active = this.headers.index( this.active );
  230. }
  231. this._destroyIcons();
  232. this._refresh();
  233. },
  234. _processPanels: function() {
  235. var prevHeaders = this.headers,
  236. prevPanels = this.panels;
  237. if ( typeof this.options.header === "function" ) {
  238. this.headers = this.options.header( this.element );
  239. } else {
  240. this.headers = this.element.find( this.options.header );
  241. }
  242. this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed",
  243. "ui-state-default" );
  244. this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide();
  245. this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" );
  246. // Avoid memory leaks (#10056)
  247. if ( prevPanels ) {
  248. this._off( prevHeaders.not( this.headers ) );
  249. this._off( prevPanels.not( this.panels ) );
  250. }
  251. },
  252. _refresh: function() {
  253. var maxHeight,
  254. options = this.options,
  255. heightStyle = options.heightStyle,
  256. parent = this.element.parent();
  257. this.active = this._findActive( options.active );
  258. this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" )
  259. ._removeClass( this.active, "ui-accordion-header-collapsed" );
  260. this._addClass( this.active.next(), "ui-accordion-content-active" );
  261. this.active.next().show();
  262. this.headers
  263. .attr( "role", "tab" )
  264. .each( function() {
  265. var header = $( this ),
  266. headerId = header.uniqueId().attr( "id" ),
  267. panel = header.next(),
  268. panelId = panel.uniqueId().attr( "id" );
  269. header.attr( "aria-controls", panelId );
  270. panel.attr( "aria-labelledby", headerId );
  271. } )
  272. .next()
  273. .attr( "role", "tabpanel" );
  274. this.headers
  275. .not( this.active )
  276. .attr( {
  277. "aria-selected": "false",
  278. "aria-expanded": "false",
  279. tabIndex: -1
  280. } )
  281. .next()
  282. .attr( {
  283. "aria-hidden": "true"
  284. } )
  285. .hide();
  286. // Make sure at least one header is in the tab order
  287. if ( !this.active.length ) {
  288. this.headers.eq( 0 ).attr( "tabIndex", 0 );
  289. } else {
  290. this.active.attr( {
  291. "aria-selected": "true",
  292. "aria-expanded": "true",
  293. tabIndex: 0
  294. } )
  295. .next()
  296. .attr( {
  297. "aria-hidden": "false"
  298. } );
  299. }
  300. this._createIcons();
  301. this._setupEvents( options.event );
  302. if ( heightStyle === "fill" ) {
  303. maxHeight = parent.height();
  304. this.element.siblings( ":visible" ).each( function() {
  305. var elem = $( this ),
  306. position = elem.css( "position" );
  307. if ( position === "absolute" || position === "fixed" ) {
  308. return;
  309. }
  310. maxHeight -= elem.outerHeight( true );
  311. } );
  312. this.headers.each( function() {
  313. maxHeight -= $( this ).outerHeight( true );
  314. } );
  315. this.headers.next()
  316. .each( function() {
  317. $( this ).height( Math.max( 0, maxHeight -
  318. $( this ).innerHeight() + $( this ).height() ) );
  319. } )
  320. .css( "overflow", "auto" );
  321. } else if ( heightStyle === "auto" ) {
  322. maxHeight = 0;
  323. this.headers.next()
  324. .each( function() {
  325. var isVisible = $( this ).is( ":visible" );
  326. if ( !isVisible ) {
  327. $( this ).show();
  328. }
  329. maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
  330. if ( !isVisible ) {
  331. $( this ).hide();
  332. }
  333. } )
  334. .height( maxHeight );
  335. }
  336. },
  337. _activate: function( index ) {
  338. var active = this._findActive( index )[ 0 ];
  339. // Trying to activate the already active panel
  340. if ( active === this.active[ 0 ] ) {
  341. return;
  342. }
  343. // Trying to collapse, simulate a click on the currently active header
  344. active = active || this.active[ 0 ];
  345. this._eventHandler( {
  346. target: active,
  347. currentTarget: active,
  348. preventDefault: $.noop
  349. } );
  350. },
  351. _findActive: function( selector ) {
  352. return typeof selector === "number" ? this.headers.eq( selector ) : $();
  353. },
  354. _setupEvents: function( event ) {
  355. var events = {
  356. keydown: "_keydown"
  357. };
  358. if ( event ) {
  359. $.each( event.split( " " ), function( index, eventName ) {
  360. events[ eventName ] = "_eventHandler";
  361. } );
  362. }
  363. this._off( this.headers.add( this.headers.next() ) );
  364. this._on( this.headers, events );
  365. this._on( this.headers.next(), { keydown: "_panelKeyDown" } );
  366. this._hoverable( this.headers );
  367. this._focusable( this.headers );
  368. },
  369. _eventHandler: function( event ) {
  370. var activeChildren, clickedChildren,
  371. options = this.options,
  372. active = this.active,
  373. clicked = $( event.currentTarget ),
  374. clickedIsActive = clicked[ 0 ] === active[ 0 ],
  375. collapsing = clickedIsActive && options.collapsible,
  376. toShow = collapsing ? $() : clicked.next(),
  377. toHide = active.next(),
  378. eventData = {
  379. oldHeader: active,
  380. oldPanel: toHide,
  381. newHeader: collapsing ? $() : clicked,
  382. newPanel: toShow
  383. };
  384. event.preventDefault();
  385. if (
  386. // click on active header, but not collapsible
  387. ( clickedIsActive && !options.collapsible ) ||
  388. // allow canceling activation
  389. ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
  390. return;
  391. }
  392. options.active = collapsing ? false : this.headers.index( clicked );
  393. // When the call to ._toggle() comes after the class changes
  394. // it causes a very odd bug in IE 8 (see #6720)
  395. this.active = clickedIsActive ? $() : clicked;
  396. this._toggle( eventData );
  397. // Switch classes
  398. // corner classes on the previously active header stay after the animation
  399. this._removeClass( active, "ui-accordion-header-active", "ui-state-active" );
  400. if ( options.icons ) {
  401. activeChildren = active.children( ".ui-accordion-header-icon" );
  402. this._removeClass( activeChildren, null, options.icons.activeHeader )
  403. ._addClass( activeChildren, null, options.icons.header );
  404. }
  405. if ( !clickedIsActive ) {
  406. this._removeClass( clicked, "ui-accordion-header-collapsed" )
  407. ._addClass( clicked, "ui-accordion-header-active", "ui-state-active" );
  408. if ( options.icons ) {
  409. clickedChildren = clicked.children( ".ui-accordion-header-icon" );
  410. this._removeClass( clickedChildren, null, options.icons.header )
  411. ._addClass( clickedChildren, null, options.icons.activeHeader );
  412. }
  413. this._addClass( clicked.next(), "ui-accordion-content-active" );
  414. }
  415. },
  416. _toggle: function( data ) {
  417. var toShow = data.newPanel,
  418. toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
  419. // Handle activating a panel during the animation for another activation
  420. this.prevShow.add( this.prevHide ).stop( true, true );
  421. this.prevShow = toShow;
  422. this.prevHide = toHide;
  423. if ( this.options.animate ) {
  424. this._animate( toShow, toHide, data );
  425. } else {
  426. toHide.hide();
  427. toShow.show();
  428. this._toggleComplete( data );
  429. }
  430. toHide.attr( {
  431. "aria-hidden": "true"
  432. } );
  433. toHide.prev().attr( {
  434. "aria-selected": "false",
  435. "aria-expanded": "false"
  436. } );
  437. // if we're switching panels, remove the old header from the tab order
  438. // if we're opening from collapsed state, remove the previous header from the tab order
  439. // if we're collapsing, then keep the collapsing header in the tab order
  440. if ( toShow.length && toHide.length ) {
  441. toHide.prev().attr( {
  442. "tabIndex": -1,
  443. "aria-expanded": "false"
  444. } );
  445. } else if ( toShow.length ) {
  446. this.headers.filter( function() {
  447. return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0;
  448. } )
  449. .attr( "tabIndex", -1 );
  450. }
  451. toShow
  452. .attr( "aria-hidden", "false" )
  453. .prev()
  454. .attr( {
  455. "aria-selected": "true",
  456. "aria-expanded": "true",
  457. tabIndex: 0
  458. } );
  459. },
  460. _animate: function( toShow, toHide, data ) {
  461. var total, easing, duration,
  462. that = this,
  463. adjust = 0,
  464. boxSizing = toShow.css( "box-sizing" ),
  465. down = toShow.length &&
  466. ( !toHide.length || ( toShow.index() < toHide.index() ) ),
  467. animate = this.options.animate || {},
  468. options = down && animate.down || animate,
  469. complete = function() {
  470. that._toggleComplete( data );
  471. };
  472. if ( typeof options === "number" ) {
  473. duration = options;
  474. }
  475. if ( typeof options === "string" ) {
  476. easing = options;
  477. }
  478. // fall back from options to animation in case of partial down settings
  479. easing = easing || options.easing || animate.easing;
  480. duration = duration || options.duration || animate.duration;
  481. if ( !toHide.length ) {
  482. return toShow.animate( this.showProps, duration, easing, complete );
  483. }
  484. if ( !toShow.length ) {
  485. return toHide.animate( this.hideProps, duration, easing, complete );
  486. }
  487. total = toShow.show().outerHeight();
  488. toHide.animate( this.hideProps, {
  489. duration: duration,
  490. easing: easing,
  491. step: function( now, fx ) {
  492. fx.now = Math.round( now );
  493. }
  494. } );
  495. toShow
  496. .hide()
  497. .animate( this.showProps, {
  498. duration: duration,
  499. easing: easing,
  500. complete: complete,
  501. step: function( now, fx ) {
  502. fx.now = Math.round( now );
  503. if ( fx.prop !== "height" ) {
  504. if ( boxSizing === "content-box" ) {
  505. adjust += fx.now;
  506. }
  507. } else if ( that.options.heightStyle !== "content" ) {
  508. fx.now = Math.round( total - toHide.outerHeight() - adjust );
  509. adjust = 0;
  510. }
  511. }
  512. } );
  513. },
  514. _toggleComplete: function( data ) {
  515. var toHide = data.oldPanel,
  516. prev = toHide.prev();
  517. this._removeClass( toHide, "ui-accordion-content-active" );
  518. this._removeClass( prev, "ui-accordion-header-active" )
  519. ._addClass( prev, "ui-accordion-header-collapsed" );
  520. // Work around for rendering bug in IE (#5421)
  521. if ( toHide.length ) {
  522. toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;
  523. }
  524. this._trigger( "activate", null, data );
  525. }
  526. } );
  527. } );