jquery.ui.accordion 1.8.18与jQuery UI Tabs 1.8.18冲突的解决办法
笔者在项目中用到了jQuery的Tabs,同时在其中的一个Tab放上了一组accordion,此时,出现了一个神奇的现象,所有的Tabs都是正常的,但当切换到accordion中时,点击内容都只是收起来,却不展开了,跟踪了一下,发现了jQuery.ui.accordion 1.8.18中的一个小bug。
下边是笔者使用的jquery.ui.accordion的代码:
/* * jQuery UI Accordion 1.8.18 * * 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",autoHeight: true,clearStyle: false,collapsible: false,event: "click",fillSpace: false,header: "> li > :first-child,> :not(li):even",icons: {header: "ui-icon-triangle-1-e",headerSelected: "ui-icon-triangle-1-s"},navigation: false,navigationFilter: function() {return this.href.toLowerCase() === location.href.toLowerCase();}},_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" );if ( options.navigation ) {var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );if ( current.length ) {var header = current.closest( ".ui-accordion-header" );if ( header.length ) {// anchor within headerself.active = header;} else {// anchor within contentself.active = current.closest( ".ui-accordion-content" ).prev();}}}self.active = self._findActive( self.active || 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.resize();// ARIAself.element.attr( "role", "tablist" );self.headers.attr( "role", "tab" ).bind( "keydown.accordion", function( event ) {return self._keydown( event );}).next().attr( "role", "tabpanel" );self.headers.not( self.active || "" ).attr({"aria-expanded": "false","aria-selected": "false",tabIndex: -1}).next().hide();// make sure at least one header is in the tab orderif ( !self.active.length ) {self.headers.eq( 0 ).attr( "tabIndex", 0 );} else {self.active.attr({"aria-expanded": "true","aria-selected": "true",tabIndex: 0});}// only need links in tab order for Safariif ( !$.browser.safari ) {self.headers.find( "a" ).attr( "tabIndex", -1 );}if ( options.event ) {self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {self._clickHandler.call( self, event, this );event.preventDefault();});}},_createIcons: function() {var options = this.options;if ( options.icons ) {$( "<span></span>" ).addClass( "ui-icon " + options.icons.header ).prependTo( this.headers );this.active.children( ".ui-icon" ).toggleClass(options.icons.header).toggleClass(options.icons.headerSelected);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( "aria-selected" ).removeAttr( "tabIndex" );this.headers.find( "a" ).removeAttr( "tabIndex" );this._destroyIcons();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.autoHeight || options.fillHeight ) {contents.css( "height", "" );}return $.Widget.prototype.destroy.call( this );},_setOption: function( key, value ) {$.Widget.prototype._setOption.apply( this, arguments );if ( key == "active" ) {this.activate( 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 panelsif ( key == "disabled" ) {this.headers.add(this.headers.next())[ value ? "addClass" : "removeClass" ]("ui-accordion-disabled ui-state-disabled" );}},_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._clickHandler( { target: event.target }, event.target );event.preventDefault();}if ( toFocus ) {$( event.target ).attr( "tabIndex", -1 );$( toFocus ).attr( "tabIndex", 0 );toFocus.focus();return false;}return true;},resize: function() {var options = this.options,maxHeight;if ( options.fillSpace ) {if ( $.browser.msie ) {var defOverflow = this.element.parent().css( "overflow" );this.element.parent().css( "overflow", "hidden");}maxHeight = this.element.parent().height();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.autoHeight ) {maxHeight = 0;this.headers.next().each(function() {maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );}).height( maxHeight );}return this;},activate: function( index ) {// TODO this gets called on init, changing the option without an explicit call for thatthis.options.active = index;// call clickHandler with custom eventvar active = this._findActive( index )[ 0 ];this._clickHandler( { target: active }, active );return this;},_findActive: function( selector ) {return selector? typeof selector === "number"? this.headers.filter( ":eq(" + selector + ")" ): this.headers.not( this.headers.not( selector ) ): selector === false? $( [] ): this.headers.filter( ":eq(0)" );},// TODO isn't event.target enough? why the separate target argument?_clickHandler: function( event, target ) {var options = this.options;if ( options.disabled ) {return;}// called only when using activate(false) to close all parts programmaticallyif ( !event.target ) {if ( !options.collapsible ) {return;}this.active.removeClass( "ui-state-active ui-corner-top" ).addClass( "ui-state-default ui-corner-all" ).children( ".ui-icon" ).removeClass( options.icons.headerSelected ).addClass( options.icons.header );this.active.next().addClass( "ui-accordion-content-active" );var toHide = this.active.next(),data = {options: options,newHeader: $( [] ),oldHeader: options.active,newContent: $( [] ),oldContent: toHide},toShow = ( this.active = $( [] ) );this._toggle( toShow, toHide, data );return;}// get the click targetvar clicked = $( event.currentTarget || target ),clickedIsActive = clicked[0] === this.active[0];// TODO the option is changed, is that correct?// TODO if it is correct, shouldn't that happen after determining that the click is valid?options.active = options.collapsible && clickedIsActive ?false :this.headers.index( clicked );// if animations are still active, or the active header is the target, ignore clickif ( this.running || ( !options.collapsible && clickedIsActive ) ) {return;}// find elements to show and hidevar active = this.active,toShow = clicked.next(),toHide = this.active.next(),data = {options: options,newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,oldHeader: this.active,newContent: clickedIsActive && options.collapsible ? $([]) : toShow,oldContent: toHide},down = this.headers.index( this.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 classesactive.removeClass( "ui-state-active ui-corner-top" ).addClass( "ui-state-default ui-corner-all" ).children( ".ui-icon" ).removeClass( options.icons.headerSelected ).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.headerSelected );clicked.next().addClass( "ui-accordion-content-active" );}return;},_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 );};// trigger changestart eventself._trigger( "changestart", null, self.data );// count elements to animateself.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.autoHeight || options.fillSpace};} else {animOptions = {toShow: toShow,toHide: toHide,complete: complete,down: down,autoHeight: options.autoHeight || options.fillSpace};}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 otherwisetoHide.prev().attr({"aria-expanded": "false","aria-selected": "false",tabIndex: -1}).blur();toShow.prev().attr({"aria-expanded": "true","aria-selected": "true",tabIndex: 0}).focus();},_completed: function( cancel ) {this.running = cancel ? 0 : --this.running;if ( this.running ) {return;}if ( this.options.clearStyle ) {this.toShow.add( this.toHide ).css({height: "",overflow: ""});}// other classes are removed before the animation; this one needs to stay until completedthis.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( "change", null, this.data );}});$.extend( $.ui.accordion, {version: "1.8.18",animations: {slide: function( options, additions ) {options = $.extend({easing: "swing",duration: 300}, options, additions );if ( !options.toHide.size() ) {options.toShow.animate({height: "show",paddingTop: "show",paddingBottom: "show"}, options );return;}if ( !options.toShow.size() ) {options.toHide.animate({height: "hide",paddingTop: "hide",paddingBottom: "hide"}, options );return;}var overflow = options.toShow.css( "overflow" ),percentDone = 0,showProps = {},hideProps = {},fxAttrs = [ "height", "paddingTop", "paddingBottom" ],originalWidth;// fix width before calculating height of hidden elementvar s = options.toShow;originalWidth = s[0].style.width;s.width( s.parent().width()- parseFloat( s.css( "paddingLeft" ) )- parseFloat( s.css( "paddingRight" ) )- ( parseFloat( s.css( "borderLeftWidth" ) ) || 0 )- ( parseFloat( s.css( "borderRightWidth" ) ) || 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 paddingif ( 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.toShow.css("height","auto");//2012-11-12日修改options.complete();}});},bounceslide: function( options ) {this.slide( options, {easing: options.down ? "easeOutBounce" : "swing",duration: options.down ? 1000 : 200});}}});})( jQuery );
代码中注释2012-11-12日修改的是笔者加上的,原来的代码没有,加上这行代码后,jquery.ui.accordion就正常了