2 * jQuery UI Accordion 1.8.21
4 * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
5 * Dual licensed under the MIT or GPL Version 2 licenses.
6 * http://jquery.org/license
8 * http://docs.jquery.com/UI/Accordion
14 (function( $, undefined ) {
16 $.widget( "ui.accordion", {
25 header
: "> li > :first-child,> :not(li):even",
27 header
: "ui-icon-triangle-1-e",
28 headerSelected
: "ui-icon-triangle-1-s"
31 navigationFilter: function() {
32 return this.href
.toLowerCase() === location
.href
.toLowerCase();
38 options
= self
.options
;
43 .addClass( "ui-accordion ui-widget ui-helper-reset" )
44 // in lack of child-selectors in CSS
45 // we need to mark top-LIs in a UL-accordion for some IE-fix
47 .addClass( "ui-accordion-li-fix" );
49 self
.headers
= self
.element
.find( options
.header
)
50 .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
51 .bind( "mouseenter.accordion", function() {
52 if ( options
.disabled
) {
55 $( this ).addClass( "ui-state-hover" );
57 .bind( "mouseleave.accordion", function() {
58 if ( options
.disabled
) {
61 $( this ).removeClass( "ui-state-hover" );
63 .bind( "focus.accordion", function() {
64 if ( options
.disabled
) {
67 $( this ).addClass( "ui-state-focus" );
69 .bind( "blur.accordion", function() {
70 if ( options
.disabled
) {
73 $( this ).removeClass( "ui-state-focus" );
77 .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );
79 if ( options
.navigation
) {
80 var current
= self
.element
.find( "a" ).filter( options
.navigationFilter
).eq( 0 );
81 if ( current
.length
) {
82 var header
= current
.closest( ".ui-accordion-header" );
83 if ( header
.length
) {
84 // anchor within header
87 // anchor within content
88 self
.active
= current
.closest( ".ui-accordion-content" ).prev();
93 self
.active
= self
._findActive( self
.active
|| options
.active
)
94 .addClass( "ui-state-default ui-state-active" )
95 .toggleClass( "ui-corner-all" )
96 .toggleClass( "ui-corner-top" );
97 self
.active
.next().addClass( "ui-accordion-content-active" );
103 self
.element
.attr( "role", "tablist" );
106 .attr( "role", "tab" )
107 .bind( "keydown.accordion", function( event
) {
108 return self
._keydown( event
);
111 .attr( "role", "tabpanel" );
114 .not( self
.active
|| "" )
116 "aria-expanded": "false",
117 "aria-selected": "false",
123 // make sure at least one header is in the tab order
124 if ( !self
.active
.length
) {
125 self
.headers
.eq( 0 ).attr( "tabIndex", 0 );
129 "aria-expanded": "true",
130 "aria-selected": "true",
135 // only need links in tab order for Safari
136 if ( !$.browser
.safari
) {
137 self
.headers
.find( "a" ).attr( "tabIndex", -1 );
140 if ( options
.event
) {
141 self
.headers
.bind( options
.event
.split(" ").join(".accordion ") + ".accordion", function(event
) {
142 self
._clickHandler
.call( self
, event
, this );
143 event
.preventDefault();
148 _createIcons: function() {
149 var options
= this.options
;
150 if ( options
.icons
) {
152 .addClass( "ui-icon " + options
.icons
.header
)
153 .prependTo( this.headers
);
154 this.active
.children( ".ui-icon" )
155 .toggleClass(options
.icons
.header
)
156 .toggleClass(options
.icons
.headerSelected
);
157 this.element
.addClass( "ui-accordion-icons" );
161 _destroyIcons: function() {
162 this.headers
.children( ".ui-icon" ).remove();
163 this.element
.removeClass( "ui-accordion-icons" );
166 destroy: function() {
167 var options
= this.options
;
170 .removeClass( "ui-accordion ui-widget ui-helper-reset" )
171 .removeAttr( "role" );
174 .unbind( ".accordion" )
175 .removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
176 .removeAttr( "role" )
177 .removeAttr( "aria-expanded" )
178 .removeAttr( "aria-selected" )
179 .removeAttr( "tabIndex" );
181 this.headers
.find( "a" ).removeAttr( "tabIndex" );
182 this._destroyIcons();
183 var contents
= this.headers
.next()
184 .css( "display", "" )
185 .removeAttr( "role" )
186 .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
187 if ( options
.autoHeight
|| options
.fillHeight
) {
188 contents
.css( "height", "" );
191 return $.Widget
.prototype.destroy
.call( this );
194 _setOption: function( key
, value
) {
195 $.Widget
.prototype._setOption
.apply( this, arguments
);
197 if ( key
== "active" ) {
198 this.activate( value
);
200 if ( key
== "icons" ) {
201 this._destroyIcons();
206 // #5332 - opacity doesn't cascade to positioned elements in IE
207 // so we need to add the disabled class to the headers and panels
208 if ( key
== "disabled" ) {
209 this.headers
.add(this.headers
.next())
210 [ value
? "addClass" : "removeClass" ](
211 "ui-accordion-disabled ui-state-disabled" );
215 _keydown: function( event
) {
216 if ( this.options
.disabled
|| event
.altKey
|| event
.ctrlKey
) {
220 var keyCode
= $.ui
.keyCode
,
221 length
= this.headers
.length
,
222 currentIndex
= this.headers
.index( event
.target
),
225 switch ( event
.keyCode
) {
228 toFocus
= this.headers
[ ( currentIndex
+ 1 ) % length
];
232 toFocus
= this.headers
[ ( currentIndex
- 1 + length
) % length
];
236 this._clickHandler( { target
: event
.target
}, event
.target
);
237 event
.preventDefault();
241 $( event
.target
).attr( "tabIndex", -1 );
242 $( toFocus
).attr( "tabIndex", 0 );
251 var options
= this.options
,
254 if ( options
.fillSpace
) {
255 if ( $.browser
.msie
) {
256 var defOverflow
= this.element
.parent().css( "overflow" );
257 this.element
.parent().css( "overflow", "hidden");
259 maxHeight
= this.element
.parent().height();
260 if ($.browser
.msie
) {
261 this.element
.parent().css( "overflow", defOverflow
);
264 this.headers
.each(function() {
265 maxHeight
-= $( this ).outerHeight( true );
270 $( this ).height( Math
.max( 0, maxHeight
-
271 $( this ).innerHeight() + $( this ).height() ) );
273 .css( "overflow", "auto" );
274 } else if ( options
.autoHeight
) {
278 maxHeight
= Math
.max( maxHeight
, $( this ).height( "" ).height() );
280 .height( maxHeight
);
286 activate: function( index
) {
287 // TODO this gets called on init, changing the option without an explicit call for that
288 this.options
.active
= index
;
289 // call clickHandler with custom event
290 var active
= this._findActive( index
)[ 0 ];
291 this._clickHandler( { target
: active
}, active
);
296 _findActive: function( selector
) {
298 ? typeof selector
=== "number"
299 ? this.headers
.filter( ":eq(" + selector
+ ")" )
300 : this.headers
.not( this.headers
.not( selector
) )
303 : this.headers
.filter( ":eq(0)" );
306 // TODO isn't event.target enough? why the separate target argument?
307 _clickHandler: function( event
, target
) {
308 var options
= this.options
;
309 if ( options
.disabled
) {
313 // called only when using activate(false) to close all parts programmatically
314 if ( !event
.target
) {
315 if ( !options
.collapsible
) {
319 .removeClass( "ui-state-active ui-corner-top" )
320 .addClass( "ui-state-default ui-corner-all" )
321 .children( ".ui-icon" )
322 .removeClass( options
.icons
.headerSelected
)
323 .addClass( options
.icons
.header
);
324 this.active
.next().addClass( "ui-accordion-content-active" );
325 var toHide
= this.active
.next(),
329 oldHeader
: options
.active
,
333 toShow
= ( this.active
= $( [] ) );
334 this._toggle( toShow
, toHide
, data
);
338 // get the click target
339 var clicked
= $( event
.currentTarget
|| target
),
340 clickedIsActive
= clicked
[0] === this.active
[0];
342 // TODO the option is changed, is that correct?
343 // TODO if it is correct, shouldn't that happen after determining that the click is valid?
344 options
.active
= options
.collapsible
&& clickedIsActive
?
346 this.headers
.index( clicked
);
348 // if animations are still active, or the active header is the target, ignore click
349 if ( this.running
|| ( !options
.collapsible
&& clickedIsActive
) ) {
353 // find elements to show and hide
354 var active
= this.active
,
355 toShow
= clicked
.next(),
356 toHide
= this.active
.next(),
359 newHeader
: clickedIsActive
&& options
.collapsible
? $([]) : clicked
,
360 oldHeader
: this.active
,
361 newContent
: clickedIsActive
&& options
.collapsible
? $([]) : toShow
,
364 down
= this.headers
.index( this.active
[0] ) > this.headers
.index( clicked
[0] );
366 // when the call to ._toggle() comes after the class changes
367 // it causes a very odd bug in IE 8 (see #6720)
368 this.active
= clickedIsActive
? $([]) : clicked
;
369 this._toggle( toShow
, toHide
, data
, clickedIsActive
, down
);
373 .removeClass( "ui-state-active ui-corner-top" )
374 .addClass( "ui-state-default ui-corner-all" )
375 .children( ".ui-icon" )
376 .removeClass( options
.icons
.headerSelected
)
377 .addClass( options
.icons
.header
);
378 if ( !clickedIsActive
) {
380 .removeClass( "ui-state-default ui-corner-all" )
381 .addClass( "ui-state-active ui-corner-top" )
382 .children( ".ui-icon" )
383 .removeClass( options
.icons
.header
)
384 .addClass( options
.icons
.headerSelected
);
387 .addClass( "ui-accordion-content-active" );
393 _toggle: function( toShow
, toHide
, data
, clickedIsActive
, down
) {
395 options
= self
.options
;
397 self
.toShow
= toShow
;
398 self
.toHide
= toHide
;
401 var complete = function() {
405 return self
._completed
.apply( self
, arguments
);
408 // trigger changestart event
409 self
._trigger( "changestart", null, self
.data
);
411 // count elements to animate
412 self
.running
= toHide
.size() === 0 ? toShow
.size() : toHide
.size();
414 if ( options
.animated
) {
415 var animOptions
= {};
417 if ( options
.collapsible
&& clickedIsActive
) {
423 autoHeight
: options
.autoHeight
|| options
.fillSpace
431 autoHeight
: options
.autoHeight
|| options
.fillSpace
435 if ( !options
.proxied
) {
436 options
.proxied
= options
.animated
;
439 if ( !options
.proxiedDuration
) {
440 options
.proxiedDuration
= options
.duration
;
443 options
.animated
= $.isFunction( options
.proxied
) ?
444 options
.proxied( animOptions
) :
447 options
.duration
= $.isFunction( options
.proxiedDuration
) ?
448 options
.proxiedDuration( animOptions
) :
449 options
.proxiedDuration
;
451 var animations
= $.ui
.accordion
.animations
,
452 duration
= options
.duration
,
453 easing
= options
.animated
;
455 if ( easing
&& !animations
[ easing
] && !$.easing
[ easing
] ) {
458 if ( !animations
[ easing
] ) {
459 animations
[ easing
] = function( options
) {
460 this.slide( options
, {
462 duration
: duration
|| 700
467 animations
[ easing
]( animOptions
);
469 if ( options
.collapsible
&& clickedIsActive
) {
479 // TODO assert that the blur and focus triggers are really necessary, remove otherwise
482 "aria-expanded": "false",
483 "aria-selected": "false",
489 "aria-expanded": "true",
490 "aria-selected": "true",
496 _completed: function( cancel
) {
497 this.running
= cancel
? 0 : --this.running
;
498 if ( this.running
) {
502 if ( this.options
.clearStyle
) {
503 this.toShow
.add( this.toHide
).css({
509 // other classes are removed before the animation; this one needs to stay until completed
510 this.toHide
.removeClass( "ui-accordion-content-active" );
511 // Work around for rendering bug in IE (#5421)
512 if ( this.toHide
.length
) {
513 this.toHide
.parent()[0].className
= this.toHide
.parent()[0].className
;
516 this._trigger( "change", null, this.data
);
520 $.extend( $.ui
.accordion
, {
523 slide: function( options
, additions
) {
527 }, options
, additions
);
528 if ( !options
.toHide
.size() ) {
529 options
.toShow
.animate({
532 paddingBottom
: "show"
536 if ( !options
.toShow
.size() ) {
537 options
.toHide
.animate({
540 paddingBottom
: "hide"
544 var overflow
= options
.toShow
.css( "overflow" ),
548 fxAttrs
= [ "height", "paddingTop", "paddingBottom" ],
550 // fix width before calculating height of hidden element
551 var s
= options
.toShow
;
552 originalWidth
= s
[0].style
.width
;
553 s
.width( s
.parent().width()
554 - parseFloat( s
.css( "paddingLeft" ) )
555 - parseFloat( s
.css( "paddingRight" ) )
556 - ( parseFloat( s
.css( "borderLeftWidth" ) ) || 0 )
557 - ( parseFloat( s
.css( "borderRightWidth" ) ) || 0 ) );
559 $.each( fxAttrs
, function( i
, prop
) {
560 hideProps
[ prop
] = "hide";
562 var parts
= ( "" + $.css( options
.toShow
[0], prop
) ).match( /^([\d+-.]+)(.*)$/ );
563 showProps
[ prop
] = {
565 unit
: parts
[ 2 ] || "px"
568 options
.toShow
.css({ height
: 0, overflow
: "hidden" }).show();
571 .each( options
.complete
)
573 .filter( ":visible" )
574 .animate( hideProps
, {
575 step: function( now
, settings
) {
576 // only calculate the percent when animating height
577 // IE gets very inconsistent results when animating elements
578 // with small values, which is common for padding
579 if ( settings
.prop
== "height" ) {
580 percentDone
= ( settings
.end
- settings
.start
=== 0 ) ? 0 :
581 ( settings
.now
- settings
.start
) / ( settings
.end
- settings
.start
);
584 options
.toShow
[ 0 ].style
[ settings
.prop
] =
585 ( percentDone
* showProps
[ settings
.prop
].value
)
586 + showProps
[ settings
.prop
].unit
;
588 duration
: options
.duration
,
589 easing
: options
.easing
,
590 complete: function() {
591 if ( !options
.autoHeight
) {
592 options
.toShow
.css( "height", "" );
595 width
: originalWidth
,
602 bounceslide: function( options
) {
603 this.slide( options
, {
604 easing
: options
.down
? "easeOutBounce" : "swing",
605 duration
: options
.down
? 1000 : 200