From 3d9f6b5bc74d09a08f6a116b6b31d93d72971f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Scott=20Gonz=C3=A1lez?= Date: Thu, 16 Feb 2012 16:51:46 -0500 Subject: [PATCH] Accordion: Rewrote the animation code. Fixes #4178 - Accordion animation a bit jumpy in some browsers. Fixes #7371 - Accordion: Incorrect size when zoomed. --- tests/unit/accordion/accordion_defaults.js | 2 +- .../accordion_defaults_deprecated.js | 1 + .../unit/accordion/accordion_test_helpers.js | 6 +- ui/jquery.ui.accordion.js | 267 +++++++----------- 4 files changed, 112 insertions(+), 164 deletions(-) diff --git a/tests/unit/accordion/accordion_defaults.js b/tests/unit/accordion/accordion_defaults.js index 9aa58e650..9ad85bbce 100644 --- a/tests/unit/accordion/accordion_defaults.js +++ b/tests/unit/accordion/accordion_defaults.js @@ -1,7 +1,7 @@ commonWidgetTests( "accordion", { defaults: { active: 0, - animated: "slide", + animate: {}, collapsible: false, disabled: false, event: "click", diff --git a/tests/unit/accordion/accordion_defaults_deprecated.js b/tests/unit/accordion/accordion_defaults_deprecated.js index 3f45a1f10..99f5ac827 100644 --- a/tests/unit/accordion/accordion_defaults_deprecated.js +++ b/tests/unit/accordion/accordion_defaults_deprecated.js @@ -1,6 +1,7 @@ commonWidgetTests( "accordion", { defaults: { active: 0, + animate: null, animated: "slide", autoHeight: true, clearStyle: false, diff --git a/tests/unit/accordion/accordion_test_helpers.js b/tests/unit/accordion/accordion_test_helpers.js index 66d60a469..7b316e983 100644 --- a/tests/unit/accordion/accordion_test_helpers.js +++ b/tests/unit/accordion/accordion_test_helpers.js @@ -18,13 +18,13 @@ function accordion_equalHeights( accordion, min, max ) { } function accordion_setupTeardown() { - var animated = $.ui.accordion.prototype.options.animated; + var animate = $.ui.accordion.prototype.options.animate; return { setup: function() { - $.ui.accordion.prototype.options.animated = false; + $.ui.accordion.prototype.options.animate = false; }, teardown: function() { - $.ui.accordion.prototype.options.animated = animated; + $.ui.accordion.prototype.options.animate = animate; } }; } diff --git a/ui/jquery.ui.accordion.js b/ui/jquery.ui.accordion.js index c56f3cbbf..8731c17ca 100644 --- a/ui/jquery.ui.accordion.js +++ b/ui/jquery.ui.accordion.js @@ -17,7 +17,7 @@ $.widget( "ui.accordion", { version: "@VERSION", options: { active: 0, - animated: "slide", + animate: {}, collapsible: false, event: "click", header: "> li > :first-child,> :not(li):even", @@ -35,7 +35,7 @@ $.widget( "ui.accordion", { _create: function() { var options = this.options; - this.lastToggle = {}; + this.prevShow = this.prevHide = $(); this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ); this.headers = this.element.find( options.header ) @@ -62,6 +62,7 @@ $.widget( "ui.accordion", { this.active.next().addClass( "ui-accordion-content-active" ); this._createIcons(); + this.originalHeight = this.element[0].style.height; this.refresh(); // ARIA @@ -156,6 +157,7 @@ $.widget( "ui.accordion", { .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 ( this.options.heightStyle !== "content" ) { + this.element.css( "height", this.originalHeight ); contents.css( "height", "" ); } }, @@ -229,12 +231,12 @@ $.widget( "ui.accordion", { }, refresh: function() { - var options = this.options, + var heightStyle = this.options.heightStyle, parent = this.element.parent(), maxHeight, overflow; - if ( options.heightStyle === "fill" ) { + if ( heightStyle === "fill" ) { // IE 6 treats height like minHeight, so we need to turn off overflow // in order to get a reliable height // we use the minHeight support test because we assume that only @@ -267,7 +269,7 @@ $.widget( "ui.accordion", { $( this ).innerHeight() + $( this ).height() ) ); }) .css( "overflow", "auto" ); - } else if ( options.heightStyle === "auto" ) { + } else if ( heightStyle === "auto" ) { maxHeight = 0; this.headers.next() .each(function() { @@ -276,7 +278,9 @@ $.widget( "ui.accordion", { .height( maxHeight ); } - return this; + if ( heightStyle !== "content" ) { + this.element.height( this.element.height() ); + } }, _activate: function( index ) { @@ -361,41 +365,20 @@ $.widget( "ui.accordion", { }, _toggle: function( data ) { - var that = this, - options = this.options, - toShow = data.newContent, - toHide = data.oldContent; + var toShow = data.newContent, + toHide = this.prevShow.length ? this.prevShow : data.oldContent; - function complete() { - that._completed( data ); - } + // handle activating a panel during the animation for another activation + this.prevShow.add( this.prevHide ).stop( true, true ); + this.prevShow = toShow; + this.prevHide = toHide; - if ( options.animated ) { - var animations = $.ui.accordion.animations, - animation = options.animated, - additional; - - if ( !animations[ animation ] ) { - additional = { - easing: $.easing[ animation ] ? animation : "slide", - duration: 700 - }; - animation = "slide"; - } - - animations[ animation ]({ - widget: this, - toShow: toShow, - toHide: toHide, - prevShow: this.lastToggle.toShow, - prevHide: this.lastToggle.toHide, - complete: complete, - down: toShow.length && ( !toHide.length || ( toShow.index() < toHide.index() ) ) - }, additional ); + if ( this.options.animate ) { + this._animate( toShow, toHide, data ); } else { toHide.hide(); toShow.show(); - complete(); + this._completed( data ); } // TODO assert that the blur and focus triggers are really necessary, remove otherwise @@ -415,17 +398,51 @@ $.widget( "ui.accordion", { .focus(); }, + _animate: function( toShow, toHide, data ) { + var total, easing, duration, + that = this, + down = toShow.length && + ( !toHide.length || ( toShow.index() < toHide.index() ) ), + animate = this.options.animate || {}, + options = down && animate.down || animate, + complete = function() { + toShow.removeData( "accordionHeight" ); + that._completed( data ); + }; + + if ( typeof options === "number" ) { + duration = options; + } + if ( typeof options === "string" ) { + easing = options; + } + // fall back from options to animation in case of partial down settings + easing = easing || options.easing || animate.easing; + duration = duration || options.duration || animate.duration; + + if ( !toHide.size() ) { + return toShow.animate( showProps, duration, easing, complete ); + } + if ( !toShow.size() ) { + return toHide.animate( hideProps, duration, easing, complete ); + } + + total = toShow.show().outerHeight(); + toHide.animate( hideProps, duration, easing ); + toShow + .hide() + .data( "accordionHeight", { + total: total, + toHide: toHide + }) + .animate( this.options.heightStyle === "content" ? showProps : showPropsAdjust, + duration, easing, complete ); + }, + _completed: function( data ) { var toShow = data.newContent, toHide = data.oldContent; - if ( this.options.heightStyle === "content" ) { - toShow.add( toHide ).css({ - height: "", - overflow: "" - }); - } - // other classes are removed before the animation; this one needs to stay until completed toHide.removeClass( "ui-accordion-content-active" ); // Work around for rendering bug in IE (#5421) @@ -437,123 +454,19 @@ $.widget( "ui.accordion", { } }); -$.extend( $.ui.accordion, { - animations: { - slide: function( options, additions ) { - if ( options.prevShow || options.prevHide ) { - options.prevHide.stop( true, true ); - options.toHide = options.prevShow; - } - - var showOverflow = options.toShow.css( "overflow" ), - showOverflowX = options.toHide.css( "overflowX" ), - showOverflowY = options.toHide.css( "overflowY" ), - hideOverflow = options.toHide.css( "overflow" ), - hideOverflowX = options.toHide.css( "overflowX" ), - hideOverflowY = options.toHide.css( "overflowY" ), - percentDone = 0, - showProps = {}, - hideProps = {}, - fxAttrs = [ "height", "paddingTop", "paddingBottom" ], - originalWidth; - options = $.extend({ - easing: "swing", - duration: 300 - }, options, additions ); - - options.widget.lastToggle = options; - - 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( 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+-.]+)(.*)$/ ), - // work around bug when a panel has no height - #7335 - propVal = prop === "height" && parts[ 1 ] === "0" ? 1 : parts[ 1 ]; - showProps[ prop ] = { - value: propVal, - 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 ) { - if ( settings.prop == "height" || settings.prop == "paddingTop" || settings.prop == "paddingBottom" ) { - 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() { - options.toShow.css({ - width: originalWidth, - overflow: showOverflow, - overflowX: showOverflowX, - overflowY: showOverflowY - }); - options.toHide.css({ - overflow: hideOverflow, - overflowX: hideOverflowX, - overflowY: hideOverflowY - }); - options.complete(); - } - }); - }, - bounceslide: function( options ) { - this.slide( options, { - easing: options.down ? "easeOutBounce" : "swing", - duration: options.down ? 1000 : 200 - }); - } - } -}); +$.fx.step.accordionHeight = function( fx ) { + var elem = $( fx.elem ), + data = elem.data( "accordionHeight" ); + elem.height( data.total - elem.outerHeight() - data.toHide.outerHeight() + elem.height() ); +}; +var hideProps = {}, + showProps = {}, + showPropsAdjust = {}; +hideProps.height = hideProps.paddingTop = hideProps.paddingBottom = + hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide"; +showProps.height = showProps.paddingTop = showProps.paddingBottom = + showProps.borderTopWidth = showProps.borderBottomWidth = "show"; +$.extend( showPropsAdjust, showProps, { accordionHeight: "show" } ); @@ -697,6 +610,40 @@ if ( $.uiBackCompat !== false ) { return ret; }; }( jQuery, jQuery.ui.accordion.prototype ) ); + + // animated option + // NOTE: this only provides support for "slide", "bounceslide", and easings + // not the full $.ui.accordion.animations API + (function( $, prototype ) { + $.extend( prototype.options, { + animate: null, + animated: "slide" + }); + + var _create = prototype._create; + prototype._create = function() { + var options = this.options; + if ( options.animate === null ) { + if ( !options.animated ) { + options.animate = false; + } else if ( options.animated === "slide" ) { + options.animate = 300; + } else if ( options.animated === "bounceslide" ) { + options.animate = { + duration: 200, + down: { + easing: "easeOutBounce", + duration: 1000 + } + } + } else { + options.animate = options.animated; + } + } + + _create.call( this ); + }; + }( jQuery, jQuery.ui.accordion.prototype ) ); } })( jQuery );