2 * jQuery UI Accordion 1.9.2
5 * Copyright 2012 jQuery Foundation and other contributors
6 * Released under the MIT license.
7 * http://jquery.org/license
9 * http://api.jqueryui.com/accordion/
15 (function( $, undefined ) {
21 hideProps
.height
= hideProps
.paddingTop
= hideProps
.paddingBottom
=
22 hideProps
.borderTopWidth
= hideProps
.borderBottomWidth
= "hide";
23 showProps
.height
= showProps
.paddingTop
= showProps
.paddingBottom
=
24 showProps
.borderTopWidth
= showProps
.borderBottomWidth
= "show";
26 $.widget( "ui.accordion", {
33 header
: "> li > :first-child,> :not(li):even",
36 activeHeader
: "ui-icon-triangle-1-s",
37 header
: "ui-icon-triangle-1-e"
46 var accordionId
= this.accordionId
= "ui-accordion-" +
47 (this.element
.attr( "id" ) || ++uid
),
48 options
= this.options
;
50 this.prevShow
= this.prevHide
= $();
51 this.element
.addClass( "ui-accordion ui-widget ui-helper-reset" );
53 this.headers
= this.element
.find( options
.header
)
54 .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
55 this._hoverable( this.headers
);
56 this._focusable( this.headers
);
59 .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
62 // don't allow collapsible: false and active: false / null
63 if ( !options
.collapsible
&& (options
.active
=== false || options
.active
== null) ) {
66 // handle negative values
67 if ( options
.active
< 0 ) {
68 options
.active
+= this.headers
.length
;
70 this.active
= this._findActive( options
.active
)
71 .addClass( "ui-accordion-header-active ui-state-active" )
72 .toggleClass( "ui-corner-all ui-corner-top" );
74 .addClass( "ui-accordion-content-active" )
81 this.element
.attr( "role", "tablist" );
84 .attr( "role", "tab" )
86 var header
= $( this ),
87 headerId
= header
.attr( "id" ),
88 panel
= header
.next(),
89 panelId
= panel
.attr( "id" );
91 headerId
= accordionId
+ "-header-" + i
;
92 header
.attr( "id", headerId
);
95 panelId
= accordionId
+ "-panel-" + i
;
96 panel
.attr( "id", panelId
);
98 header
.attr( "aria-controls", panelId
);
99 panel
.attr( "aria-labelledby", headerId
);
102 .attr( "role", "tabpanel" );
107 "aria-selected": "false",
112 "aria-expanded": "false",
113 "aria-hidden": "true"
117 // make sure at least one header is in the tab order
118 if ( !this.active
.length
) {
119 this.headers
.eq( 0 ).attr( "tabIndex", 0 );
122 "aria-selected": "true",
127 "aria-expanded": "true",
128 "aria-hidden": "false"
132 this._on( this.headers
, { keydown
: "_keydown" });
133 this._on( this.headers
.next(), { keydown
: "_panelKeyDown" });
134 this._setupEvents( options
.event
);
137 _getCreateEventData: function() {
140 content
: !this.active
.length
? $() : this.active
.next()
144 _createIcons: function() {
145 var icons
= this.options
.icons
;
148 .addClass( "ui-accordion-header-icon ui-icon " + icons
.header
)
149 .prependTo( this.headers
);
150 this.active
.children( ".ui-accordion-header-icon" )
151 .removeClass( icons
.header
)
152 .addClass( icons
.activeHeader
);
153 this.headers
.addClass( "ui-accordion-icons" );
157 _destroyIcons: function() {
159 .removeClass( "ui-accordion-icons" )
160 .children( ".ui-accordion-header-icon" )
164 _destroy: function() {
167 // clean up main element
169 .removeClass( "ui-accordion ui-widget ui-helper-reset" )
170 .removeAttr( "role" );
174 .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" )
175 .removeAttr( "role" )
176 .removeAttr( "aria-selected" )
177 .removeAttr( "aria-controls" )
178 .removeAttr( "tabIndex" )
180 if ( /^ui-accordion/.test( this.id
) ) {
181 this.removeAttribute( "id" );
184 this._destroyIcons();
186 // clean up content panels
187 contents
= this.headers
.next()
188 .css( "display", "" )
189 .removeAttr( "role" )
190 .removeAttr( "aria-expanded" )
191 .removeAttr( "aria-hidden" )
192 .removeAttr( "aria-labelledby" )
193 .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
195 if ( /^ui-accordion/.test( this.id
) ) {
196 this.removeAttribute( "id" );
199 if ( this.options
.heightStyle
!== "content" ) {
200 contents
.css( "height", "" );
204 _setOption: function( key
, value
) {
205 if ( key
=== "active" ) {
206 // _activate() will handle invalid values and update this.options
207 this._activate( value
);
211 if ( key
=== "event" ) {
212 if ( this.options
.event
) {
213 this._off( this.headers
, this.options
.event
);
215 this._setupEvents( value
);
218 this._super( key
, value
);
220 // setting collapsible: false while collapsed; open first panel
221 if ( key
=== "collapsible" && !value
&& this.options
.active
=== false ) {
225 if ( key
=== "icons" ) {
226 this._destroyIcons();
232 // #5332 - opacity doesn't cascade to positioned elements in IE
233 // so we need to add the disabled class to the headers and panels
234 if ( key
=== "disabled" ) {
235 this.headers
.add( this.headers
.next() )
236 .toggleClass( "ui-state-disabled", !!value
);
240 _keydown: function( event
) {
241 if ( event
.altKey
|| event
.ctrlKey
) {
245 var keyCode
= $.ui
.keyCode
,
246 length
= this.headers
.length
,
247 currentIndex
= this.headers
.index( event
.target
),
250 switch ( event
.keyCode
) {
253 toFocus
= this.headers
[ ( currentIndex
+ 1 ) % length
];
257 toFocus
= this.headers
[ ( currentIndex
- 1 + length
) % length
];
261 this._eventHandler( event
);
264 toFocus
= this.headers
[ 0 ];
267 toFocus
= this.headers
[ length
- 1 ];
272 $( event
.target
).attr( "tabIndex", -1 );
273 $( toFocus
).attr( "tabIndex", 0 );
275 event
.preventDefault();
279 _panelKeyDown : function( event
) {
280 if ( event
.keyCode
=== $.ui
.keyCode
.UP
&& event
.ctrlKey
) {
281 $( event
.currentTarget
).prev().focus();
285 refresh: function() {
286 var maxHeight
, overflow
,
287 heightStyle
= this.options
.heightStyle
,
288 parent
= this.element
.parent();
291 if ( heightStyle
=== "fill" ) {
292 // IE 6 treats height like minHeight, so we need to turn off overflow
293 // in order to get a reliable height
294 // we use the minHeight support test because we assume that only
295 // browsers that don't support minHeight will treat height as minHeight
296 if ( !$.support
.minHeight
) {
297 overflow
= parent
.css( "overflow" );
298 parent
.css( "overflow", "hidden");
300 maxHeight
= parent
.height();
301 this.element
.siblings( ":visible" ).each(function() {
302 var elem
= $( this ),
303 position
= elem
.css( "position" );
305 if ( position
=== "absolute" || position
=== "fixed" ) {
308 maxHeight
-= elem
.outerHeight( true );
311 parent
.css( "overflow", overflow
);
314 this.headers
.each(function() {
315 maxHeight
-= $( this ).outerHeight( true );
320 $( this ).height( Math
.max( 0, maxHeight
-
321 $( this ).innerHeight() + $( this ).height() ) );
323 .css( "overflow", "auto" );
324 } else if ( heightStyle
=== "auto" ) {
328 maxHeight
= Math
.max( maxHeight
, $( this ).css( "height", "" ).height() );
330 .height( maxHeight
);
334 _activate: function( index
) {
335 var active
= this._findActive( index
)[ 0 ];
337 // trying to activate the already active panel
338 if ( active
=== this.active
[ 0 ] ) {
342 // trying to collapse, simulate a click on the currently active header
343 active
= active
|| this.active
[ 0 ];
347 currentTarget
: active
,
348 preventDefault
: $.noop
352 _findActive: function( selector
) {
353 return typeof selector
=== "number" ? this.headers
.eq( selector
) : $();
356 _setupEvents: function( event
) {
361 $.each( event
.split(" "), function( index
, eventName
) {
362 events
[ eventName
] = "_eventHandler";
364 this._on( this.headers
, events
);
367 _eventHandler: function( event
) {
368 var options
= this.options
,
369 active
= this.active
,
370 clicked
= $( event
.currentTarget
),
371 clickedIsActive
= clicked
[ 0 ] === active
[ 0 ],
372 collapsing
= clickedIsActive
&& options
.collapsible
,
373 toShow
= collapsing
? $() : clicked
.next(),
374 toHide
= active
.next(),
378 newHeader
: collapsing
? $() : clicked
,
382 event
.preventDefault();
385 // click on active header, but not collapsible
386 ( clickedIsActive
&& !options
.collapsible
) ||
387 // allow canceling activation
388 ( this._trigger( "beforeActivate", event
, eventData
) === false ) ) {
392 options
.active
= collapsing
? false : this.headers
.index( clicked
);
394 // when the call to ._toggle() comes after the class changes
395 // it causes a very odd bug in IE 8 (see #6720)
396 this.active
= clickedIsActive
? $() : clicked
;
397 this._toggle( eventData
);
400 // corner classes on the previously active header stay after the animation
401 active
.removeClass( "ui-accordion-header-active ui-state-active" );
402 if ( options
.icons
) {
403 active
.children( ".ui-accordion-header-icon" )
404 .removeClass( options
.icons
.activeHeader
)
405 .addClass( options
.icons
.header
);
408 if ( !clickedIsActive
) {
410 .removeClass( "ui-corner-all" )
411 .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
412 if ( options
.icons
) {
413 clicked
.children( ".ui-accordion-header-icon" )
414 .removeClass( options
.icons
.header
)
415 .addClass( options
.icons
.activeHeader
);
420 .addClass( "ui-accordion-content-active" );
424 _toggle: function( data
) {
425 var toShow
= data
.newPanel
,
426 toHide
= this.prevShow
.length
? this.prevShow
: data
.oldPanel
;
428 // handle activating a panel during the animation for another activation
429 this.prevShow
.add( this.prevHide
).stop( true, true );
430 this.prevShow
= toShow
;
431 this.prevHide
= toHide
;
433 if ( this.options
.animate
) {
434 this._animate( toShow
, toHide
, data
);
438 this._toggleComplete( data
);
442 "aria-expanded": "false",
443 "aria-hidden": "true"
445 toHide
.prev().attr( "aria-selected", "false" );
446 // if we're switching panels, remove the old header from the tab order
447 // if we're opening from collapsed state, remove the previous header from the tab order
448 // if we're collapsing, then keep the collapsing header in the tab order
449 if ( toShow
.length
&& toHide
.length
) {
450 toHide
.prev().attr( "tabIndex", -1 );
451 } else if ( toShow
.length
) {
452 this.headers
.filter(function() {
453 return $( this ).attr( "tabIndex" ) === 0;
455 .attr( "tabIndex", -1 );
460 "aria-expanded": "true",
461 "aria-hidden": "false"
465 "aria-selected": "true",
470 _animate: function( toShow
, toHide
, data
) {
471 var total
, easing
, duration
,
474 down
= toShow
.length
&&
475 ( !toHide
.length
|| ( toShow
.index() < toHide
.index() ) ),
476 animate
= this.options
.animate
|| {},
477 options
= down
&& animate
.down
|| animate
,
478 complete = function() {
479 that
._toggleComplete( data
);
482 if ( typeof options
=== "number" ) {
485 if ( typeof options
=== "string" ) {
488 // fall back from options to animation in case of partial down settings
489 easing
= easing
|| options
.easing
|| animate
.easing
;
490 duration
= duration
|| options
.duration
|| animate
.duration
;
492 if ( !toHide
.length
) {
493 return toShow
.animate( showProps
, duration
, easing
, complete
);
495 if ( !toShow
.length
) {
496 return toHide
.animate( hideProps
, duration
, easing
, complete
);
499 total
= toShow
.show().outerHeight();
500 toHide
.animate( hideProps
, {
503 step: function( now
, fx
) {
504 fx
.now
= Math
.round( now
);
509 .animate( showProps
, {
513 step: function( now
, fx
) {
514 fx
.now
= Math
.round( now
);
515 if ( fx
.prop
!== "height" ) {
517 } else if ( that
.options
.heightStyle
!== "content" ) {
518 fx
.now
= Math
.round( total
- toHide
.outerHeight() - adjust
);
525 _toggleComplete: function( data
) {
526 var toHide
= data
.oldPanel
;
529 .removeClass( "ui-accordion-content-active" )
531 .removeClass( "ui-corner-top" )
532 .addClass( "ui-corner-all" );
534 // Work around for rendering bug in IE (#5421)
535 if ( toHide
.length
) {
536 toHide
.parent()[0].className
= toHide
.parent()[0].className
;
539 this._trigger( "activate", null, data
);
546 if ( $.uiBackCompat
!== false ) {
547 // navigation options
548 (function( $, prototype ) {
549 $.extend( prototype.options
, {
551 navigationFilter: function() {
552 return this.href
.toLowerCase() === location
.href
.toLowerCase();
556 var _create
= prototype._create
;
557 prototype._create = function() {
558 if ( this.options
.navigation
) {
560 headers
= this.element
.find( this.options
.header
),
561 content
= headers
.next(),
562 current
= headers
.add( content
)
564 .filter( this.options
.navigationFilter
)
567 headers
.add( content
).each( function( index
) {
568 if ( $.contains( this, current
) ) {
569 that
.options
.active
= Math
.floor( index
/ 2 );
575 _create
.call( this );
577 }( jQuery
, jQuery
.ui
.accordion
.prototype ) );
580 (function( $, prototype ) {
581 $.extend( prototype.options
, {
582 heightStyle
: null, // remove default so we fall back to old values
583 autoHeight
: true, // use heightStyle: "auto"
584 clearStyle
: false, // use heightStyle: "content"
585 fillSpace
: false // use heightStyle: "fill"
588 var _create
= prototype._create
,
589 _setOption
= prototype._setOption
;
591 $.extend( prototype, {
592 _create: function() {
593 this.options
.heightStyle
= this.options
.heightStyle
||
594 this._mergeHeightStyle();
596 _create
.call( this );
599 _setOption: function( key
) {
600 if ( key
=== "autoHeight" || key
=== "clearStyle" || key
=== "fillSpace" ) {
601 this.options
.heightStyle
= this._mergeHeightStyle();
603 _setOption
.apply( this, arguments
);
606 _mergeHeightStyle: function() {
607 var options
= this.options
;
609 if ( options
.fillSpace
) {
613 if ( options
.clearStyle
) {
617 if ( options
.autoHeight
) {
622 }( jQuery
, jQuery
.ui
.accordion
.prototype ) );
625 (function( $, prototype ) {
626 $.extend( prototype.options
.icons
, {
627 activeHeader
: null, // remove default so we fall back to old values
628 headerSelected
: "ui-icon-triangle-1-s"
631 var _createIcons
= prototype._createIcons
;
632 prototype._createIcons = function() {
633 if ( this.options
.icons
) {
634 this.options
.icons
.activeHeader
= this.options
.icons
.activeHeader
||
635 this.options
.icons
.headerSelected
;
637 _createIcons
.call( this );
639 }( jQuery
, jQuery
.ui
.accordion
.prototype ) );
641 // expanded active option, activate method
642 (function( $, prototype ) {
643 prototype.activate
= prototype._activate
;
645 var _findActive
= prototype._findActive
;
646 prototype._findActive = function( index
) {
647 if ( index
=== -1 ) {
650 if ( index
&& typeof index
!== "number" ) {
651 index
= this.headers
.index( this.headers
.filter( index
) );
652 if ( index
=== -1 ) {
656 return _findActive
.call( this, index
);
658 }( jQuery
, jQuery
.ui
.accordion
.prototype ) );
661 jQuery
.ui
.accordion
.prototype.resize
= jQuery
.ui
.accordion
.prototype.refresh
;
664 (function( $, prototype ) {
665 $.extend( prototype.options
, {
670 var _trigger
= prototype._trigger
;
671 prototype._trigger = function( type
, event
, data
) {
672 var ret
= _trigger
.apply( this, arguments
);
677 if ( type
=== "beforeActivate" ) {
678 ret
= _trigger
.call( this, "changestart", event
, {
679 oldHeader
: data
.oldHeader
,
680 oldContent
: data
.oldPanel
,
681 newHeader
: data
.newHeader
,
682 newContent
: data
.newPanel
684 } else if ( type
=== "activate" ) {
685 ret
= _trigger
.call( this, "change", event
, {
686 oldHeader
: data
.oldHeader
,
687 oldContent
: data
.oldPanel
,
688 newHeader
: data
.newHeader
,
689 newContent
: data
.newPanel
694 }( jQuery
, jQuery
.ui
.accordion
.prototype ) );
697 // NOTE: this only provides support for "slide", "bounceslide", and easings
698 // not the full $.ui.accordion.animations API
699 (function( $, prototype ) {
700 $.extend( prototype.options
, {
705 var _create
= prototype._create
;
706 prototype._create = function() {
707 var options
= this.options
;
708 if ( options
.animate
=== null ) {
709 if ( !options
.animated
) {
710 options
.animate
= false;
711 } else if ( options
.animated
=== "slide" ) {
712 options
.animate
= 300;
713 } else if ( options
.animated
=== "bounceslide" ) {
717 easing
: "easeOutBounce",
722 options
.animate
= options
.animated
;
726 _create
.call( this );
728 }( jQuery
, jQuery
.ui
.accordion
.prototype ) );