/* * jQuery UI Accordion @VERSION * * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Accordion * * Depends: * jquery.ui.core.js * jquery.ui.widget.js */ (function( $, undefined ) { $.widget( "ui.accordion", { options: { active: 0, animated: "slide", collapsible: false, event: "click", header: "> li > :first-child,> :not(li):even", heightStyle: "auto", icons: { activeHeader: "ui-icon-triangle-1-s", header: "ui-icon-triangle-1-e" } }, _create: function() { var self = this, options = self.options; self.running = 0; self.element .addClass( "ui-accordion ui-widget ui-helper-reset" ) // in lack of child-selectors in CSS // we need to mark top-LIs in a UL-accordion for some IE-fix .children( "li" ) .addClass( "ui-accordion-li-fix" ); self.headers = self.element.find( options.header ) .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" ) .bind( "mouseenter.accordion", function() { if ( options.disabled ) { return; } $( this ).addClass( "ui-state-hover" ); }) .bind( "mouseleave.accordion", function() { if ( options.disabled ) { return; } $( this ).removeClass( "ui-state-hover" ); }) .bind( "focus.accordion", function() { if ( options.disabled ) { return; } $( this ).addClass( "ui-state-focus" ); }) .bind( "blur.accordion", function() { if ( options.disabled ) { return; } $( this ).removeClass( "ui-state-focus" ); }); self.headers.next() .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ); self.headers.find( ":first-child" ).addClass( "ui-accordion-heading" ); self.active = self._findActive( options.active ) .addClass( "ui-state-default ui-state-active" ) .toggleClass( "ui-corner-all" ) .toggleClass( "ui-corner-top" ); self.active.next().addClass( "ui-accordion-content-active" ); self._createIcons(); self.refresh(); // ARIA self.element.attr( "role", "tablist" ); self.headers .attr( "role", "tab" ) .bind( "keydown.accordion", $.proxy( self, "_keydown" ) ) .next() .attr( "role", "tabpanel" ); self.headers .not( self.active || "" ) .attr({ "aria-expanded": "false", tabIndex: -1 }) .next() .hide(); // make sure at least one header is in the tab order if ( !self.active.length ) { self.headers.eq( 0 ).attr( "tabIndex", 0 ); } else { self.active .attr({ "aria-expanded": "true", tabIndex: 0 }); } // only need links in tab order for Safari if ( !$.browser.safari ) { self.headers.find( "a" ).attr( "tabIndex", -1 ); } if ( options.event ) { self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", $.proxy( self, "_eventHandler" ) ); } }, _createIcons: function() { var icons = this.options.icons; if ( icons ) { $( "" ) .addClass( "ui-icon " + icons.header ) .prependTo( this.headers ); this.active.children( ".ui-icon" ) .removeClass( icons.header ) .addClass( icons.activeHeader ); this.element.addClass( "ui-accordion-icons" ); } }, _destroyIcons: function() { this.headers.children( ".ui-icon" ).remove(); this.element.removeClass( "ui-accordion-icons" ); }, _destroy: function() { var options = this.options; this.element .removeClass( "ui-accordion ui-widget ui-helper-reset" ) .removeAttr( "role" ); this.headers .unbind( ".accordion" ) .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" ) .removeAttr( "role" ) .removeAttr( "aria-expanded" ) .removeAttr( "tabIndex" ); this.headers.find( "a" ).removeAttr( "tabIndex" ); this._destroyIcons(); this.headers.find( "a:first-child" ).removeClass( "ui-accordion-heading" ); var contents = this.headers.next() .css( "display", "" ) .removeAttr( "role" ) .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" ); if ( options.heightStyle !== "content" ) { contents.css( "height", "" ); } }, _setOption: function( key, value ) { if ( key == "active" ) { // _activate() will handle invalid values and update this.options this._activate( value ); return; } this._super( "_setOption", key, value ); if ( key == "icons" ) { this._destroyIcons(); if ( value ) { this._createIcons(); } } // #5332 - opacity doesn't cascade to positioned elements in IE // so we need to add the disabled class to the headers and panels if ( key == "disabled" ) { this.headers.add(this.headers.next()) .toggleClass( "ui-accordion-disabled ui-state-disabled", !!value ); } }, _keydown: function( event ) { if ( this.options.disabled || event.altKey || event.ctrlKey ) { return; } var keyCode = $.ui.keyCode, length = this.headers.length, currentIndex = this.headers.index( event.target ), toFocus = false; switch ( event.keyCode ) { case keyCode.RIGHT: case keyCode.DOWN: toFocus = this.headers[ ( currentIndex + 1 ) % length ]; break; case keyCode.LEFT: case keyCode.UP: toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; break; case keyCode.SPACE: case keyCode.ENTER: this._eventHandler( event ); } if ( toFocus ) { $( event.target ).attr( "tabIndex", -1 ); $( toFocus ).attr( "tabIndex", 0 ); toFocus.focus(); event.preventDefault(); } }, refresh: function() { var options = this.options, maxHeight; if ( options.heightStyle === "fill" ) { if ( $.browser.msie ) { var defOverflow = this.element.parent().css( "overflow" ); this.element.parent().css( "overflow", "hidden"); } maxHeight = this.element.parent().height(); this.element.siblings( ":visible" ).each(function() { var elem = $( this ), position = elem.css( "position" ); if ( position === "absolute" || position === "fixed" ) { return; } maxHeight -= elem.outerHeight( true ); }); if ($.browser.msie) { this.element.parent().css( "overflow", defOverflow ); } this.headers.each(function() { maxHeight -= $( this ).outerHeight( true ); }); this.headers.next() .each(function() { $( this ).height( Math.max( 0, maxHeight - $( this ).innerHeight() + $( this ).height() ) ); }) .css( "overflow", "auto" ); } else if ( options.heightStyle === "auto" ) { maxHeight = 0; this.headers.next() .each(function() { maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() ); }) .height( maxHeight ); } return this; }, _activate: function( index ) { var active = this._findActive( index )[ 0 ]; // we found a header to activate, just delegate to the event handler if ( active ) { if ( active !== this.active[ 0 ] ) { this._eventHandler({ target: active, currentTarget: active, preventDefault: $.noop }); } return; } // no header to activate, check if we can collapse if ( !this.options.collapsible ) { return; } // allow the activation to be canceled var eventData = { oldHeader: this.active, oldContent: this.active.next(), newHeader: $(), newContent: $() }; if ( this._trigger( "beforeActivate", null, eventData ) === false ) { return; } this.active .removeClass( "ui-state-active ui-corner-top" ) .addClass( "ui-state-default ui-corner-all" ) .children( ".ui-icon" ) .removeClass( this.options.icons.activeHeader ) .addClass( this.options.icons.header ); this.active.next().addClass( "ui-accordion-content-active" ); var toHide = this.active.next(), data = { options: this.options, newHeader: $(), oldHeader: this.active, newContent: $(), oldContent: toHide }, toShow = ( this.active = $() ); this._toggle( toShow, toHide, data ); }, // TODO: add tests/docs for negative values in 2.0 (#6854) _findActive: function( selector ) { return typeof selector === "number" ? this.headers.eq( selector ) : $(); }, _eventHandler: function( event ) { var options = this.options, active = this.active, clicked = $( event.currentTarget ), clickedIsActive = clicked[ 0 ] === active[ 0 ], collapsing = clickedIsActive && options.collapsible, toShow = clicked.next(), toHide = active.next(), eventData = { oldHeader: active, oldContent: toHide, newHeader: collapsing ? $() : clicked, newContent: collapsing ? $() : toShow }; event.preventDefault(); if ( options.disabled ) { return; } // if animations are still active, or the active header is the target, ignore click if ( this.running || ( !options.collapsible && clickedIsActive ) ) { return; } // allow the activation to be canceled if ( this._trigger( "beforeActivate", null, eventData ) === false ) { return; } options.active = collapsing ? false : this.headers.index( clicked ); // find elements to show and hide var data = { options: options, newHeader: collapsing ? $() : clicked, oldHeader: active, newContent: collapsing ? $() : toShow, oldContent: toHide }, down = this.headers.index( active[0] ) > this.headers.index( clicked[0] ); // when the call to ._toggle() comes after the class changes // it causes a very odd bug in IE 8 (see #6720) this.active = clickedIsActive ? $() : clicked; this._toggle( toShow, toHide, data, clickedIsActive, down ); // switch classes active .removeClass( "ui-state-active ui-corner-top" ) .addClass( "ui-state-default ui-corner-all" ) .children( ".ui-icon" ) .removeClass( options.icons.activeHeader ) .addClass( options.icons.header ); if ( !clickedIsActive ) { clicked .removeClass( "ui-state-default ui-corner-all" ) .addClass( "ui-state-active ui-corner-top" ) .children( ".ui-icon" ) .removeClass( options.icons.header ) .addClass( options.icons.activeHeader ); clicked .next() .addClass( "ui-accordion-content-active" ); } }, _toggle: function( toShow, toHide, data, clickedIsActive, down ) { var self = this, options = self.options; self.toShow = toShow; self.toHide = toHide; self.data = data; var complete = function() { if ( !self ) { return; } return self._completed.apply( self, arguments ); }; // count elements to animate self.running = toHide.size() === 0 ? toShow.size() : toHide.size(); if ( options.animated ) { var animOptions = {}; if ( options.collapsible && clickedIsActive ) { animOptions = { toShow: $(), toHide: toHide, complete: complete, down: down, autoHeight: options.heightStyle !== "content" }; } else { animOptions = { toShow: toShow, toHide: toHide, complete: complete, down: down, autoHeight: options.heightStyle !== "content" }; } if ( !options.proxied ) { options.proxied = options.animated; } if ( !options.proxiedDuration ) { options.proxiedDuration = options.duration; } options.animated = $.isFunction( options.proxied ) ? options.proxied( animOptions ) : options.proxied; options.duration = $.isFunction( options.proxiedDuration ) ? options.proxiedDuration( animOptions ) : options.proxiedDuration; var animations = $.ui.accordion.animations, duration = options.duration, easing = options.animated; if ( easing && !animations[ easing ] && !$.easing[ easing ] ) { easing = "slide"; } if ( !animations[ easing ] ) { animations[ easing ] = function( options ) { this.slide( options, { easing: easing, duration: duration || 700 }); }; } animations[ easing ]( animOptions ); } else { if ( options.collapsible && clickedIsActive ) { toShow.toggle(); } else { toHide.hide(); toShow.show(); } complete( true ); } // TODO assert that the blur and focus triggers are really necessary, remove otherwise toHide.prev() .attr({ "aria-expanded": "false", tabIndex: -1 }) .blur(); toShow.prev() .attr({ "aria-expanded": "true", tabIndex: 0 }) .focus(); }, _completed: function( cancel ) { this.running = cancel ? 0 : --this.running; if ( this.running ) { return; } if ( this.options.heightStyle === "content" ) { this.toShow.add( this.toHide ).css({ height: "", overflow: "" }); } // other classes are removed before the animation; this one needs to stay until completed this.toHide.removeClass( "ui-accordion-content-active" ); // Work around for rendering bug in IE (#5421) if ( this.toHide.length ) { this.toHide.parent()[0].className = this.toHide.parent()[0].className; } this._trigger( "activate", null, this.data ); } }); $.extend( $.ui.accordion, { version: "@VERSION", animations: { slide: function( options, additions ) { var overflow = options.toShow.css( "overflow" ), percentDone = 0, showProps = {}, hideProps = {}, fxAttrs = [ "height", "paddingTop", "paddingBottom" ], originalWidth; options = $.extend({ easing: "swing", duration: 300 }, options, additions ); if ( !options.toHide.size() ) { originalWidth = options.toShow[0].style.width; options.toShow .show() .width( options.toShow.width() ) .hide() .animate({ height: "show", paddingTop: "show", paddingBottom: "show" }, { duration: options.duration, easing: options.easing, complete: function() { options.toShow.width( originalWidth ); options.complete(); } }); return; } if ( !options.toShow.size() ) { options.toHide.animate({ height: "hide", paddingTop: "hide", paddingBottom: "hide" }, options ); return; } // fix width before calculating height of hidden element var s = options.toShow; originalWidth = s[0].style.width; s.width( parseInt( s.parent().width(), 10 ) - parseInt( s.css( "paddingLeft" ), 10 ) - parseInt( s.css( "paddingRight" ), 10 ) - ( parseInt( s.css( "borderLeftWidth" ), 10 ) || 0 ) - ( parseInt( s.css( "borderRightWidth" ), 10) || 0 ) ); $.each( fxAttrs, function( i, prop ) { hideProps[ prop ] = "hide"; var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ ); showProps[ prop ] = { value: parts[ 1 ], unit: parts[ 2 ] || "px" }; }); options.toShow.css({ height: 0, overflow: "hidden" }).show(); options.toHide .filter( ":hidden" ) .each( options.complete ) .end() .filter( ":visible" ) .animate( hideProps, { step: function( now, settings ) { // only calculate the percent when animating height // IE gets very inconsistent results when animating elements // with small values, which is common for padding if ( settings.prop == "height" ) { percentDone = ( settings.end - settings.start === 0 ) ? 0 : ( settings.now - settings.start ) / ( settings.end - settings.start ); } options.toShow[ 0 ].style[ settings.prop ] = ( percentDone * showProps[ settings.prop ].value ) + showProps[ settings.prop ].unit; }, duration: options.duration, easing: options.easing, complete: function() { if ( !options.autoHeight ) { options.toShow.css( "height", "" ); } options.toShow.css({ width: originalWidth, overflow: overflow }); options.complete(); } }); }, bounceslide: function( options ) { this.slide( options, { easing: options.down ? "easeOutBounce" : "swing", duration: options.down ? 1000 : 200 }); } } }); // DEPRECATED if ( $.uiBackCompat !== false ) { // navigation options (function( $, prototype ) { $.extend( prototype.options, { navigation: false, navigationFilter: function() { return this.href.toLowerCase() === location.href.toLowerCase(); } }); var _create = prototype._create; prototype._create = function() { if ( this.options.navigation ) { var self = this, headers = this.element.find( this.options.header ), content = headers.next(); current = headers.add( content ) .find( "a" ) .filter( this.options.navigationFilter ) [ 0 ]; if ( current ) { headers.add( content ).each( function( index ) { if ( $.contains( this, current ) ) { self.options.active = Math.floor( index / 2 ); return false; } }); } } _create.call( this ); }; }( jQuery, jQuery.ui.accordion.prototype ) ); // height options (function( $, prototype ) { $.extend( prototype.options, { heightStyle: null, // remove default so we fall back to old values autoHeight: true, // use heightStyle: "auto" clearStyle: false, // use heightStyle: "content" fillSpace: false // use heightStyle: "fill" }); var _create = prototype._create, _setOption = prototype._setOption; $.extend( prototype, { _create: function() { this.options.heightStyle = this.options.heightStyle || this._mergeHeightStyle(); _create.call( this ); }, _setOption: function( key, value ) { if ( key === "autoHeight" || key === "clearStyle" || key === "fillSpace" ) { this.options.heightStyle = this._mergeHeightStyle(); } _setOption.apply( this, arguments ); }, _mergeHeightStyle: function() { var options = this.options; if ( options.fillSpace ) { return "fill"; } if ( options.clearStyle ) { return "content"; } if ( options.autoHeight ) { return "auto"; } } }); }( jQuery, jQuery.ui.accordion.prototype ) ); // icon options (function( $, prototype ) { $.extend( prototype.options.icons, { activeHeader: null, // remove default so we fall back to old values headerSelected: "ui-icon-triangle-1-s" }); var _createIcons = prototype._createIcons; prototype._createIcons = function() { this.options.icons.activeHeader = this.options.icons.activeHeader || this.options.icons.headerSelected; _createIcons.call( this ); }; }( jQuery, jQuery.ui.accordion.prototype ) ); // expanded active option, activate method (function( $, prototype ) { prototype.activate = prototype._activate; var _findActive = prototype._findActive; prototype._findActive = function( index ) { if ( index === -1 ) { index = false; } if ( index && typeof index !== "number" ) { index = this.headers.index( this.headers.filter( index ) ); if ( index === -1 ) { index = false; } } return _findActive.call( this, index ); }; }( jQuery, jQuery.ui.accordion.prototype ) ); // resize method jQuery.ui.accordion.prototype.resize = jQuery.ui.accordion.prototype.refresh; (function( $, prototype ) { var _trigger = prototype._trigger; prototype._trigger = function( type, event, data ) { var ret = _trigger.apply( this, arguments ); if ( !ret ) { return false; } if ( type === "beforeActivate" ) { ret = _trigger.call( this, "changestart", event, data ); } else if ( type === "activate" ) { ret = _trigger.call( this, "change", event, data ); } return ret; } }( jQuery, jQuery.ui.accordion.prototype ) ); } })( jQuery );