jquery.ui.accordion.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. /*!
  2. * jQuery UI Accordion 1.9.2
  3. * http://jqueryui.com
  4. *
  5. * Copyright 2012 jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. *
  9. * http://api.jqueryui.com/accordion/
  10. *
  11. * Depends:
  12. * jquery.ui.core.js
  13. * jquery.ui.widget.js
  14. */
  15. (function( $, undefined ) {
  16. var uid = 0,
  17. hideProps = {},
  18. showProps = {};
  19. hideProps.height = hideProps.paddingTop = hideProps.paddingBottom =
  20. hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide";
  21. showProps.height = showProps.paddingTop = showProps.paddingBottom =
  22. showProps.borderTopWidth = showProps.borderBottomWidth = "show";
  23. $.widget( "ui.accordion", {
  24. version: "1.9.2",
  25. options: {
  26. active: 0,
  27. animate: {},
  28. collapsible: false,
  29. event: "click",
  30. header: "> li > :first-child,> :not(li):even",
  31. heightStyle: "auto",
  32. icons: {
  33. activeHeader: "ui-icon-triangle-1-s",
  34. header: "ui-icon-triangle-1-e"
  35. },
  36. // callbacks
  37. activate: null,
  38. beforeActivate: null
  39. },
  40. _create: function() {
  41. var accordionId = this.accordionId = "ui-accordion-" +
  42. (this.element.attr( "id" ) || ++uid),
  43. options = this.options;
  44. this.prevShow = this.prevHide = $();
  45. this.element.addClass( "ui-accordion ui-widget ui-helper-reset" );
  46. this.headers = this.element.find( options.header )
  47. .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
  48. this._hoverable( this.headers );
  49. this._focusable( this.headers );
  50. this.headers.next()
  51. .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
  52. .hide();
  53. // don't allow collapsible: false and active: false / null
  54. if ( !options.collapsible && (options.active === false || options.active == null) ) {
  55. options.active = 0;
  56. }
  57. // handle negative values
  58. if ( options.active < 0 ) {
  59. options.active += this.headers.length;
  60. }
  61. this.active = this._findActive( options.active )
  62. .addClass( "ui-accordion-header-active ui-state-active" )
  63. .toggleClass( "ui-corner-all ui-corner-top" );
  64. this.active.next()
  65. .addClass( "ui-accordion-content-active" )
  66. .show();
  67. this._createIcons();
  68. this.refresh();
  69. // ARIA
  70. this.element.attr( "role", "tablist" );
  71. this.headers
  72. .attr( "role", "tab" )
  73. .each(function( i ) {
  74. var header = $( this ),
  75. headerId = header.attr( "id" ),
  76. panel = header.next(),
  77. panelId = panel.attr( "id" );
  78. if ( !headerId ) {
  79. headerId = accordionId + "-header-" + i;
  80. header.attr( "id", headerId );
  81. }
  82. if ( !panelId ) {
  83. panelId = accordionId + "-panel-" + i;
  84. panel.attr( "id", panelId );
  85. }
  86. header.attr( "aria-controls", panelId );
  87. panel.attr( "aria-labelledby", headerId );
  88. })
  89. .next()
  90. .attr( "role", "tabpanel" );
  91. this.headers
  92. .not( this.active )
  93. .attr({
  94. "aria-selected": "false",
  95. tabIndex: -1
  96. })
  97. .next()
  98. .attr({
  99. "aria-expanded": "false",
  100. "aria-hidden": "true"
  101. })
  102. .hide();
  103. // make sure at least one header is in the tab order
  104. if ( !this.active.length ) {
  105. this.headers.eq( 0 ).attr( "tabIndex", 0 );
  106. } else {
  107. this.active.attr({
  108. "aria-selected": "true",
  109. tabIndex: 0
  110. })
  111. .next()
  112. .attr({
  113. "aria-expanded": "true",
  114. "aria-hidden": "false"
  115. });
  116. }
  117. this._on( this.headers, { keydown: "_keydown" });
  118. this._on( this.headers.next(), { keydown: "_panelKeyDown" });
  119. this._setupEvents( options.event );
  120. },
  121. _getCreateEventData: function() {
  122. return {
  123. header: this.active,
  124. content: !this.active.length ? $() : this.active.next()
  125. };
  126. },
  127. _createIcons: function() {
  128. var icons = this.options.icons;
  129. if ( icons ) {
  130. $( "<span>" )
  131. .addClass( "ui-accordion-header-icon ui-icon " + icons.header )
  132. .prependTo( this.headers );
  133. this.active.children( ".ui-accordion-header-icon" )
  134. .removeClass( icons.header )
  135. .addClass( icons.activeHeader );
  136. this.headers.addClass( "ui-accordion-icons" );
  137. }
  138. },
  139. _destroyIcons: function() {
  140. this.headers
  141. .removeClass( "ui-accordion-icons" )
  142. .children( ".ui-accordion-header-icon" )
  143. .remove();
  144. },
  145. _destroy: function() {
  146. var contents;
  147. // clean up main element
  148. this.element
  149. .removeClass( "ui-accordion ui-widget ui-helper-reset" )
  150. .removeAttr( "role" );
  151. // clean up headers
  152. this.headers
  153. .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
  154. .removeAttr( "role" )
  155. .removeAttr( "aria-selected" )
  156. .removeAttr( "aria-controls" )
  157. .removeAttr( "tabIndex" )
  158. .each(function() {
  159. if ( /^ui-accordion/.test( this.id ) ) {
  160. this.removeAttribute( "id" );
  161. }
  162. });
  163. this._destroyIcons();
  164. // clean up content panels
  165. contents = this.headers.next()
  166. .css( "display", "" )
  167. .removeAttr( "role" )
  168. .removeAttr( "aria-expanded" )
  169. .removeAttr( "aria-hidden" )
  170. .removeAttr( "aria-labelledby" )
  171. .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
  172. .each(function() {
  173. if ( /^ui-accordion/.test( this.id ) ) {
  174. this.removeAttribute( "id" );
  175. }
  176. });
  177. if ( this.options.heightStyle !== "content" ) {
  178. contents.css( "height", "" );
  179. }
  180. },
  181. _setOption: function( key, value ) {
  182. if ( key === "active" ) {
  183. // _activate() will handle invalid values and update this.options
  184. this._activate( value );
  185. return;
  186. }
  187. if ( key === "event" ) {
  188. if ( this.options.event ) {
  189. this._off( this.headers, this.options.event );
  190. }
  191. this._setupEvents( value );
  192. }
  193. this._super( key, value );
  194. // setting collapsible: false while collapsed; open first panel
  195. if ( key === "collapsible" && !value && this.options.active === false ) {
  196. this._activate( 0 );
  197. }
  198. if ( key === "icons" ) {
  199. this._destroyIcons();
  200. if ( value ) {
  201. this._createIcons();
  202. }
  203. }
  204. // #5332 - opacity doesn't cascade to positioned elements in IE
  205. // so we need to add the disabled class to the headers and panels
  206. if ( key === "disabled" ) {
  207. this.headers.add( this.headers.next() )
  208. .toggleClass( "ui-state-disabled", !!value );
  209. }
  210. },
  211. _keydown: function( event ) {
  212. if ( event.altKey || event.ctrlKey ) {
  213. return;
  214. }
  215. var keyCode = $.ui.keyCode,
  216. length = this.headers.length,
  217. currentIndex = this.headers.index( event.target ),
  218. toFocus = false;
  219. switch ( event.keyCode ) {
  220. case keyCode.RIGHT:
  221. case keyCode.DOWN:
  222. toFocus = this.headers[ ( currentIndex + 1 ) % length ];
  223. break;
  224. case keyCode.LEFT:
  225. case keyCode.UP:
  226. toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
  227. break;
  228. case keyCode.SPACE:
  229. case keyCode.ENTER:
  230. this._eventHandler( event );
  231. break;
  232. case keyCode.HOME:
  233. toFocus = this.headers[ 0 ];
  234. break;
  235. case keyCode.END:
  236. toFocus = this.headers[ length - 1 ];
  237. break;
  238. }
  239. if ( toFocus ) {
  240. $( event.target ).attr( "tabIndex", -1 );
  241. $( toFocus ).attr( "tabIndex", 0 );
  242. toFocus.focus();
  243. event.preventDefault();
  244. }
  245. },
  246. _panelKeyDown : function( event ) {
  247. if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
  248. $( event.currentTarget ).prev().focus();
  249. }
  250. },
  251. refresh: function() {
  252. var maxHeight, overflow,
  253. heightStyle = this.options.heightStyle,
  254. parent = this.element.parent();
  255. if ( heightStyle === "fill" ) {
  256. // IE 6 treats height like minHeight, so we need to turn off overflow
  257. // in order to get a reliable height
  258. // we use the minHeight support test because we assume that only
  259. // browsers that don't support minHeight will treat height as minHeight
  260. if ( !$.support.minHeight ) {
  261. overflow = parent.css( "overflow" );
  262. parent.css( "overflow", "hidden");
  263. }
  264. maxHeight = parent.height();
  265. this.element.siblings( ":visible" ).each(function() {
  266. var elem = $( this ),
  267. position = elem.css( "position" );
  268. if ( position === "absolute" || position === "fixed" ) {
  269. return;
  270. }
  271. maxHeight -= elem.outerHeight( true );
  272. });
  273. if ( overflow ) {
  274. parent.css( "overflow", overflow );
  275. }
  276. this.headers.each(function() {
  277. maxHeight -= $( this ).outerHeight( true );
  278. });
  279. this.headers.next()
  280. .each(function() {
  281. $( this ).height( Math.max( 0, maxHeight -
  282. $( this ).innerHeight() + $( this ).height() ) );
  283. })
  284. .css( "overflow", "auto" );
  285. } else if ( heightStyle === "auto" ) {
  286. maxHeight = 0;
  287. this.headers.next()
  288. .each(function() {
  289. maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
  290. })
  291. .height( maxHeight );
  292. }
  293. },
  294. _activate: function( index ) {
  295. var active = this._findActive( index )[ 0 ];
  296. // trying to activate the already active panel
  297. if ( active === this.active[ 0 ] ) {
  298. return;
  299. }
  300. // trying to collapse, simulate a click on the currently active header
  301. active = active || this.active[ 0 ];
  302. this._eventHandler({
  303. target: active,
  304. currentTarget: active,
  305. preventDefault: $.noop
  306. });
  307. },
  308. _findActive: function( selector ) {
  309. return typeof selector === "number" ? this.headers.eq( selector ) : $();
  310. },
  311. _setupEvents: function( event ) {
  312. var events = {};
  313. if ( !event ) {
  314. return;
  315. }
  316. $.each( event.split(" "), function( index, eventName ) {
  317. events[ eventName ] = "_eventHandler";
  318. });
  319. this._on( this.headers, events );
  320. },
  321. _eventHandler: function( event ) {
  322. var options = this.options,
  323. active = this.active,
  324. clicked = $( event.currentTarget ),
  325. clickedIsActive = clicked[ 0 ] === active[ 0 ],
  326. collapsing = clickedIsActive && options.collapsible,
  327. toShow = collapsing ? $() : clicked.next(),
  328. toHide = active.next(),
  329. eventData = {
  330. oldHeader: active,
  331. oldPanel: toHide,
  332. newHeader: collapsing ? $() : clicked,
  333. newPanel: toShow
  334. };
  335. event.preventDefault();
  336. if (
  337. // click on active header, but not collapsible
  338. ( clickedIsActive && !options.collapsible ) ||
  339. // allow canceling activation
  340. ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
  341. return;
  342. }
  343. options.active = collapsing ? false : this.headers.index( clicked );
  344. // when the call to ._toggle() comes after the class changes
  345. // it causes a very odd bug in IE 8 (see #6720)
  346. this.active = clickedIsActive ? $() : clicked;
  347. this._toggle( eventData );
  348. // switch classes
  349. // corner classes on the previously active header stay after the animation
  350. active.removeClass( "ui-accordion-header-active ui-state-active" );
  351. if ( options.icons ) {
  352. active.children( ".ui-accordion-header-icon" )
  353. .removeClass( options.icons.activeHeader )
  354. .addClass( options.icons.header );
  355. }
  356. if ( !clickedIsActive ) {
  357. clicked
  358. .removeClass( "ui-corner-all" )
  359. .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
  360. if ( options.icons ) {
  361. clicked.children( ".ui-accordion-header-icon" )
  362. .removeClass( options.icons.header )
  363. .addClass( options.icons.activeHeader );
  364. }
  365. clicked
  366. .next()
  367. .addClass( "ui-accordion-content-active" );
  368. }
  369. },
  370. _toggle: function( data ) {
  371. var toShow = data.newPanel,
  372. toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
  373. // handle activating a panel during the animation for another activation
  374. this.prevShow.add( this.prevHide ).stop( true, true );
  375. this.prevShow = toShow;
  376. this.prevHide = toHide;
  377. if ( this.options.animate ) {
  378. this._animate( toShow, toHide, data );
  379. } else {
  380. toHide.hide();
  381. toShow.show();
  382. this._toggleComplete( data );
  383. }
  384. toHide.attr({
  385. "aria-expanded": "false",
  386. "aria-hidden": "true"
  387. });
  388. toHide.prev().attr( "aria-selected", "false" );
  389. // if we're switching panels, remove the old header from the tab order
  390. // if we're opening from collapsed state, remove the previous header from the tab order
  391. // if we're collapsing, then keep the collapsing header in the tab order
  392. if ( toShow.length && toHide.length ) {
  393. toHide.prev().attr( "tabIndex", -1 );
  394. } else if ( toShow.length ) {
  395. this.headers.filter(function() {
  396. return $( this ).attr( "tabIndex" ) === 0;
  397. })
  398. .attr( "tabIndex", -1 );
  399. }
  400. toShow
  401. .attr({
  402. "aria-expanded": "true",
  403. "aria-hidden": "false"
  404. })
  405. .prev()
  406. .attr({
  407. "aria-selected": "true",
  408. tabIndex: 0
  409. });
  410. },
  411. _animate: function( toShow, toHide, data ) {
  412. var total, easing, duration,
  413. that = this,
  414. adjust = 0,
  415. down = toShow.length &&
  416. ( !toHide.length || ( toShow.index() < toHide.index() ) ),
  417. animate = this.options.animate || {},
  418. options = down && animate.down || animate,
  419. complete = function() {
  420. that._toggleComplete( data );
  421. };
  422. if ( typeof options === "number" ) {
  423. duration = options;
  424. }
  425. if ( typeof options === "string" ) {
  426. easing = options;
  427. }
  428. // fall back from options to animation in case of partial down settings
  429. easing = easing || options.easing || animate.easing;
  430. duration = duration || options.duration || animate.duration;
  431. if ( !toHide.length ) {
  432. return toShow.animate( showProps, duration, easing, complete );
  433. }
  434. if ( !toShow.length ) {
  435. return toHide.animate( hideProps, duration, easing, complete );
  436. }
  437. total = toShow.show().outerHeight();
  438. toHide.animate( hideProps, {
  439. duration: duration,
  440. easing: easing,
  441. step: function( now, fx ) {
  442. fx.now = Math.round( now );
  443. }
  444. });
  445. toShow
  446. .hide()
  447. .animate( showProps, {
  448. duration: duration,
  449. easing: easing,
  450. complete: complete,
  451. step: function( now, fx ) {
  452. fx.now = Math.round( now );
  453. if ( fx.prop !== "height" ) {
  454. adjust += fx.now;
  455. } else if ( that.options.heightStyle !== "content" ) {
  456. fx.now = Math.round( total - toHide.outerHeight() - adjust );
  457. adjust = 0;
  458. }
  459. }
  460. });
  461. },
  462. _toggleComplete: function( data ) {
  463. var toHide = data.oldPanel;
  464. toHide
  465. .removeClass( "ui-accordion-content-active" )
  466. .prev()
  467. .removeClass( "ui-corner-top" )
  468. .addClass( "ui-corner-all" );
  469. // Work around for rendering bug in IE (#5421)
  470. if ( toHide.length ) {
  471. toHide.parent()[0].className = toHide.parent()[0].className;
  472. }
  473. this._trigger( "activate", null, data );
  474. }
  475. });
  476. // DEPRECATED
  477. if ( $.uiBackCompat !== false ) {
  478. // navigation options
  479. (function( $, prototype ) {
  480. $.extend( prototype.options, {
  481. navigation: false,
  482. navigationFilter: function() {
  483. return this.href.toLowerCase() === location.href.toLowerCase();
  484. }
  485. });
  486. var _create = prototype._create;
  487. prototype._create = function() {
  488. if ( this.options.navigation ) {
  489. var that = this,
  490. headers = this.element.find( this.options.header ),
  491. content = headers.next(),
  492. current = headers.add( content )
  493. .find( "a" )
  494. .filter( this.options.navigationFilter )
  495. [ 0 ];
  496. if ( current ) {
  497. headers.add( content ).each( function( index ) {
  498. if ( $.contains( this, current ) ) {
  499. that.options.active = Math.floor( index / 2 );
  500. return false;
  501. }
  502. });
  503. }
  504. }
  505. _create.call( this );
  506. };
  507. }( jQuery, jQuery.ui.accordion.prototype ) );
  508. // height options
  509. (function( $, prototype ) {
  510. $.extend( prototype.options, {
  511. heightStyle: null, // remove default so we fall back to old values
  512. autoHeight: true, // use heightStyle: "auto"
  513. clearStyle: false, // use heightStyle: "content"
  514. fillSpace: false // use heightStyle: "fill"
  515. });
  516. var _create = prototype._create,
  517. _setOption = prototype._setOption;
  518. $.extend( prototype, {
  519. _create: function() {
  520. this.options.heightStyle = this.options.heightStyle ||
  521. this._mergeHeightStyle();
  522. _create.call( this );
  523. },
  524. _setOption: function( key ) {
  525. if ( key === "autoHeight" || key === "clearStyle" || key === "fillSpace" ) {
  526. this.options.heightStyle = this._mergeHeightStyle();
  527. }
  528. _setOption.apply( this, arguments );
  529. },
  530. _mergeHeightStyle: function() {
  531. var options = this.options;
  532. if ( options.fillSpace ) {
  533. return "fill";
  534. }
  535. if ( options.clearStyle ) {
  536. return "content";
  537. }
  538. if ( options.autoHeight ) {
  539. return "auto";
  540. }
  541. }
  542. });
  543. }( jQuery, jQuery.ui.accordion.prototype ) );
  544. // icon options
  545. (function( $, prototype ) {
  546. $.extend( prototype.options.icons, {
  547. activeHeader: null, // remove default so we fall back to old values
  548. headerSelected: "ui-icon-triangle-1-s"
  549. });
  550. var _createIcons = prototype._createIcons;
  551. prototype._createIcons = function() {
  552. if ( this.options.icons ) {
  553. this.options.icons.activeHeader = this.options.icons.activeHeader ||
  554. this.options.icons.headerSelected;
  555. }
  556. _createIcons.call( this );
  557. };
  558. }( jQuery, jQuery.ui.accordion.prototype ) );
  559. // expanded active option, activate method
  560. (function( $, prototype ) {
  561. prototype.activate = prototype._activate;
  562. var _findActive = prototype._findActive;
  563. prototype._findActive = function( index ) {
  564. if ( index === -1 ) {
  565. index = false;
  566. }
  567. if ( index && typeof index !== "number" ) {
  568. index = this.headers.index( this.headers.filter( index ) );
  569. if ( index === -1 ) {
  570. index = false;
  571. }
  572. }
  573. return _findActive.call( this, index );
  574. };
  575. }( jQuery, jQuery.ui.accordion.prototype ) );
  576. // resize method
  577. jQuery.ui.accordion.prototype.resize = jQuery.ui.accordion.prototype.refresh;
  578. // change events
  579. (function( $, prototype ) {
  580. $.extend( prototype.options, {
  581. change: null,
  582. changestart: null
  583. });
  584. var _trigger = prototype._trigger;
  585. prototype._trigger = function( type, event, data ) {
  586. var ret = _trigger.apply( this, arguments );
  587. if ( !ret ) {
  588. return false;
  589. }
  590. if ( type === "beforeActivate" ) {
  591. ret = _trigger.call( this, "changestart", event, {
  592. oldHeader: data.oldHeader,
  593. oldContent: data.oldPanel,
  594. newHeader: data.newHeader,
  595. newContent: data.newPanel
  596. });
  597. } else if ( type === "activate" ) {
  598. ret = _trigger.call( this, "change", event, {
  599. oldHeader: data.oldHeader,
  600. oldContent: data.oldPanel,
  601. newHeader: data.newHeader,
  602. newContent: data.newPanel
  603. });
  604. }
  605. return ret;
  606. };
  607. }( jQuery, jQuery.ui.accordion.prototype ) );
  608. // animated option
  609. // NOTE: this only provides support for "slide", "bounceslide", and easings
  610. // not the full $.ui.accordion.animations API
  611. (function( $, prototype ) {
  612. $.extend( prototype.options, {
  613. animate: null,
  614. animated: "slide"
  615. });
  616. var _create = prototype._create;
  617. prototype._create = function() {
  618. var options = this.options;
  619. if ( options.animate === null ) {
  620. if ( !options.animated ) {
  621. options.animate = false;
  622. } else if ( options.animated === "slide" ) {
  623. options.animate = 300;
  624. } else if ( options.animated === "bounceslide" ) {
  625. options.animate = {
  626. duration: 200,
  627. down: {
  628. easing: "easeOutBounce",
  629. duration: 1000
  630. }
  631. };
  632. } else {
  633. options.animate = options.animated;
  634. }
  635. }
  636. _create.call( this );
  637. };
  638. }( jQuery, jQuery.ui.accordion.prototype ) );
  639. }
  640. })( jQuery );