2 * jQuery UI Accordion 1.8.10
4 * Copyright 2011, 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",
122 // make sure at least one header is in the tab order
123 if ( !self.active.length ) {
124 self.headers.eq( 0 ).attr( "tabIndex", 0 );
128 "aria-expanded": "true",
133 // only need links in tab order for Safari
134 if ( !$.browser.safari ) {
135 self.headers.find( "a" ).attr( "tabIndex", -1 );
138 if ( options.event ) {
139 self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {
140 self._clickHandler.call( self, event, this );
141 event.preventDefault();
146 _createIcons: function() {
147 var options = this.options;
148 if ( options.icons ) {
150 .addClass( "ui-icon " + options.icons.header )
151 .prependTo( this.headers );
152 this.active.children( ".ui-icon" )
153 .toggleClass(options.icons.header)
154 .toggleClass(options.icons.headerSelected);
155 this.element.addClass( "ui-accordion-icons" );
159 _destroyIcons: function() {
160 this.headers.children( ".ui-icon" ).remove();
161 this.element.removeClass( "ui-accordion-icons" );
164 destroy: function() {
165 var options = this.options;
168 .removeClass( "ui-accordion ui-widget ui-helper-reset" )
169 .removeAttr( "role" );
172 .unbind( ".accordion" )
173 .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" )
174 .removeAttr( "role" )
175 .removeAttr( "aria-expanded" )
176 .removeAttr( "tabIndex" );
178 this.headers.find( "a" ).removeAttr( "tabIndex" );
179 this._destroyIcons();
180 var contents = this.headers.next()
181 .css( "display", "" )
182 .removeAttr( "role" )
183 .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
184 if ( options.autoHeight || options.fillHeight ) {
185 contents.css( "height", "" );
188 return $.Widget.prototype.destroy.call( this );
191 _setOption: function( key, value ) {
192 $.Widget.prototype._setOption.apply( this, arguments );
194 if ( key == "active" ) {
195 this.activate( value );
197 if ( key == "icons" ) {
198 this._destroyIcons();
203 // #5332 - opacity doesn't cascade to positioned elements in IE
204 // so we need to add the disabled class to the headers and panels
205 if ( key == "disabled" ) {
206 this.headers.add(this.headers.next())
207 [ value ? "addClass" : "removeClass" ](
208 "ui-accordion-disabled ui-state-disabled" );
212 _keydown: function( event ) {
213 if ( this.options.disabled || event.altKey || event.ctrlKey ) {
217 var keyCode = $.ui.keyCode,
218 length = this.headers.length,
219 currentIndex = this.headers.index( event.target ),
222 switch ( event.keyCode ) {
225 toFocus = this.headers[ ( currentIndex + 1 ) % length ];
229 toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
233 this._clickHandler( { target: event.target }, event.target );
234 event.preventDefault();
238 $( event.target ).attr( "tabIndex", -1 );
239 $( toFocus ).attr( "tabIndex", 0 );
248 var options = this.options,
251 if ( options.fillSpace ) {
252 if ( $.browser.msie ) {
253 var defOverflow = this.element.parent().css( "overflow" );
254 this.element.parent().css( "overflow", "hidden");
256 maxHeight = this.element.parent().height();
257 if ($.browser.msie) {
258 this.element.parent().css( "overflow", defOverflow );
261 this.headers.each(function() {
262 maxHeight -= $( this ).outerHeight( true );
267 $( this ).height( Math.max( 0, maxHeight -
268 $( this ).innerHeight() + $( this ).height() ) );
270 .css( "overflow", "auto" );
271 } else if ( options.autoHeight ) {
275 maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
277 .height( maxHeight );
283 activate: function( index ) {
284 // TODO this gets called on init, changing the option without an explicit call for that
285 this.options.active = index;
286 // call clickHandler with custom event
287 var active = this._findActive( index )[ 0 ];
288 this._clickHandler( { target: active }, active );
293 _findActive: function( selector ) {
295 ? typeof selector === "number"
296 ? this.headers.filter( ":eq(" + selector + ")" )
297 : this.headers.not( this.headers.not( selector ) )
300 : this.headers.filter( ":eq(0)" );
303 // TODO isn't event.target enough? why the separate target argument?
304 _clickHandler: function( event, target ) {
305 var options = this.options;
306 if ( options.disabled ) {
310 // called only when using activate(false) to close all parts programmatically
311 if ( !event.target ) {
312 if ( !options.collapsible ) {
316 .removeClass( "ui-state-active ui-corner-top" )
317 .addClass( "ui-state-default ui-corner-all" )
318 .children( ".ui-icon" )
319 .removeClass( options.icons.headerSelected )
320 .addClass( options.icons.header );
321 this.active.next().addClass( "ui-accordion-content-active" );
322 var toHide = this.active.next(),
326 oldHeader: options.active,
330 toShow = ( this.active = $( [] ) );
331 this._toggle( toShow, toHide, data );
335 // get the click target
336 var clicked = $( event.currentTarget || target ),
337 clickedIsActive = clicked[0] === this.active[0];
339 // TODO the option is changed, is that correct?
340 // TODO if it is correct, shouldn't that happen after determining that the click is valid?
341 options.active = options.collapsible && clickedIsActive ?
343 this.headers.index( clicked );
345 // if animations are still active, or the active header is the target, ignore click
346 if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
350 // find elements to show and hide
351 var active = this.active,
352 toShow = clicked.next(),
353 toHide = this.active.next(),
356 newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
357 oldHeader: this.active,
358 newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
361 down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );
363 // when the call to ._toggle() comes after the class changes
364 // it causes a very odd bug in IE 8 (see #6720)
365 this.active = clickedIsActive ? $([]) : clicked;
366 this._toggle( toShow, toHide, data, clickedIsActive, down );
370 .removeClass( "ui-state-active ui-corner-top" )
371 .addClass( "ui-state-default ui-corner-all" )
372 .children( ".ui-icon" )
373 .removeClass( options.icons.headerSelected )
374 .addClass( options.icons.header );
375 if ( !clickedIsActive ) {
377 .removeClass( "ui-state-default ui-corner-all" )
378 .addClass( "ui-state-active ui-corner-top" )
379 .children( ".ui-icon" )
380 .removeClass( options.icons.header )
381 .addClass( options.icons.headerSelected );
384 .addClass( "ui-accordion-content-active" );
390 _toggle: function( toShow, toHide, data, clickedIsActive, down ) {
392 options = self.options;
394 self.toShow = toShow;
395 self.toHide = toHide;
398 var complete = function() {
402 return self._completed.apply( self, arguments );
405 // trigger changestart event
406 self._trigger( "changestart", null, self.data );
408 // count elements to animate
409 self.running = toHide.size() === 0 ? toShow.size() : toHide.size();
411 if ( options.animated ) {
412 var animOptions = {};
414 if ( options.collapsible && clickedIsActive ) {
420 autoHeight: options.autoHeight || options.fillSpace
428 autoHeight: options.autoHeight || options.fillSpace
432 if ( !options.proxied ) {
433 options.proxied = options.animated;
436 if ( !options.proxiedDuration ) {
437 options.proxiedDuration = options.duration;
440 options.animated = $.isFunction( options.proxied ) ?
441 options.proxied( animOptions ) :
444 options.duration = $.isFunction( options.proxiedDuration ) ?
445 options.proxiedDuration( animOptions ) :
446 options.proxiedDuration;
448 var animations = $.ui.accordion.animations,
449 duration = options.duration,
450 easing = options.animated;
452 if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
455 if ( !animations[ easing ] ) {
456 animations[ easing ] = function( options ) {
457 this.slide( options, {
459 duration: duration || 700
464 animations[ easing ]( animOptions );
466 if ( options.collapsible && clickedIsActive ) {
476 // TODO assert that the blur and focus triggers are really necessary, remove otherwise
479 "aria-expanded": "false",
485 "aria-expanded": "true",
491 _completed: function( cancel ) {
492 this.running = cancel ? 0 : --this.running;
493 if ( this.running ) {
497 if ( this.options.clearStyle ) {
498 this.toShow.add( this.toHide ).css({
504 // other classes are removed before the animation; this one needs to stay until completed
505 this.toHide.removeClass( "ui-accordion-content-active" );
506 // Work around for rendering bug in IE (#5421)
507 if ( this.toHide.length ) {
508 this.toHide.parent()[0].className = this.toHide.parent()[0].className;
511 this._trigger( "change", null, this.data );
515 $.extend( $.ui.accordion, {
518 slide: function( options, additions ) {
522 }, options, additions );
523 if ( !options.toHide.size() ) {
524 options.toShow.animate({
527 paddingBottom: "show"
531 if ( !options.toShow.size() ) {
532 options.toHide.animate({
535 paddingBottom: "hide"
539 var overflow = options.toShow.css( "overflow" ),
543 fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
545 // fix width before calculating height of hidden element
546 var s = options.toShow;
547 originalWidth = s[0].style.width;
548 s.width( parseInt( s.parent().width(), 10 )
549 - parseInt( s.css( "paddingLeft" ), 10 )
550 - parseInt( s.css( "paddingRight" ), 10 )
551 - ( parseInt( s.css( "borderLeftWidth" ), 10 ) || 0 )
552 - ( parseInt( s.css( "borderRightWidth" ), 10) || 0 ) );
554 $.each( fxAttrs, function( i, prop ) {
555 hideProps[ prop ] = "hide";
557 var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
558 showProps[ prop ] = {
560 unit: parts[ 2 ] || "px"
563 options.toShow.css({ height: 0, overflow: "hidden" }).show();
566 .each( options.complete )
568 .filter( ":visible" )
569 .animate( hideProps, {
570 step: function( now, settings ) {
571 // only calculate the percent when animating height
572 // IE gets very inconsistent results when animating elements
573 // with small values, which is common for padding
574 if ( settings.prop == "height" ) {
575 percentDone = ( settings.end - settings.start === 0 ) ? 0 :
576 ( settings.now - settings.start ) / ( settings.end - settings.start );
579 options.toShow[ 0 ].style[ settings.prop ] =
580 ( percentDone * showProps[ settings.prop ].value )
581 + showProps[ settings.prop ].unit;
583 duration: options.duration,
584 easing: options.easing,
585 complete: function() {
586 if ( !options.autoHeight ) {
587 options.toShow.css( "height", "" );
590 width: originalWidth,
597 bounceslide: function( options ) {
598 this.slide( options, {
599 easing: options.down ? "easeOutBounce" : "swing",
600 duration: options.down ? 1000 : 200