// Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Animated zippy widget implementation. * * @author eae@google.com (Emil A Eklund) * @see ../demos/zippy.html */ goog.provide('goog.ui.AnimatedZippy'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.events'); goog.require('goog.fx.Animation'); goog.require('goog.fx.Transition'); goog.require('goog.fx.easing'); goog.require('goog.ui.Zippy'); goog.require('goog.ui.ZippyEvent'); /** * Zippy widget. Expandable/collapsible container, clicking the header toggles * the visibility of the content. * * @param {Element|string|null} header Header element, either element * reference, string id or null if no header exists. * @param {Element|string} content Content element, either element reference or * string id. * @param {boolean=} opt_expanded Initial expanded/visibility state. Defaults to * false. * @param {goog.dom.DomHelper=} opt_domHelper An optional DOM helper. * @constructor * @extends {goog.ui.Zippy} */ goog.ui.AnimatedZippy = function(header, content, opt_expanded, opt_domHelper) { var domHelper = opt_domHelper || goog.dom.getDomHelper(); // Create wrapper element and move content into it. var elWrapper = domHelper.createDom(goog.dom.TagName.DIV, {'style': 'overflow:hidden'}); var elContent = domHelper.getElement(content); elContent.parentNode.replaceChild(elWrapper, elContent); elWrapper.appendChild(elContent); /** * Content wrapper, used for animation. * @type {Element} * @private */ this.elWrapper_ = elWrapper; /** * Reference to animation or null if animation is not active. * @type {goog.fx.Animation} * @private */ this.anim_ = null; // Call constructor of super class. goog.ui.Zippy.call( this, header, elContent, opt_expanded, undefined, domHelper); // Set initial state. // NOTE: Set the class names as well otherwise animated zippys // start with empty class names. var expanded = this.isExpanded(); this.elWrapper_.style.display = expanded ? '' : 'none'; this.updateHeaderClassName(expanded); }; goog.inherits(goog.ui.AnimatedZippy, goog.ui.Zippy); goog.tagUnsealableClass(goog.ui.AnimatedZippy); /** * Constants for event names. * * @const */ goog.ui.AnimatedZippy.Events = { // The beginning of the animation when the zippy state toggles. TOGGLE_ANIMATION_BEGIN: goog.events.getUniqueId('toggleanimationbegin'), // The end of the animation when the zippy state toggles. TOGGLE_ANIMATION_END: goog.events.getUniqueId('toggleanimationend') }; /** * Duration of expand/collapse animation, in milliseconds. * @type {number} */ goog.ui.AnimatedZippy.prototype.animationDuration = 500; /** * Acceleration function for expand/collapse animation. * @type {!Function} */ goog.ui.AnimatedZippy.prototype.animationAcceleration = goog.fx.easing.easeOut; /** * @return {boolean} Whether the zippy is in the process of being expanded or * collapsed. */ goog.ui.AnimatedZippy.prototype.isBusy = function() { return this.anim_ != null; }; /** * Sets expanded state. * * @param {boolean} expanded Expanded/visibility state. * @override */ goog.ui.AnimatedZippy.prototype.setExpanded = function(expanded) { if (this.isExpanded() == expanded && !this.anim_) { return; } // Reset display property of wrapper to allow content element to be // measured. if (this.elWrapper_.style.display == 'none') { this.elWrapper_.style.display = ''; } // Measure content element. var h = this.getContentElement().offsetHeight; // Stop active animation (if any) and determine starting height. var startH = 0; if (this.anim_) { expanded = this.isExpanded(); goog.events.removeAll(this.anim_); this.anim_.stop(false); var marginTop = parseInt(this.getContentElement().style.marginTop, 10); startH = h - Math.abs(marginTop); } else { startH = expanded ? 0 : h; } // Updates header class name after the animation has been stopped. this.updateHeaderClassName(expanded); // Set up expand/collapse animation. this.anim_ = new goog.fx.Animation( [0, startH], [0, expanded ? h : 0], this.animationDuration, this.animationAcceleration); var events = [ goog.fx.Transition.EventType.BEGIN, goog.fx.Animation.EventType.ANIMATE, goog.fx.Transition.EventType.END ]; goog.events.listen(this.anim_, events, this.onAnimate_, false, this); goog.events.listen( this.anim_, goog.fx.Transition.EventType.BEGIN, goog.bind(this.onAnimationBegin_, this, expanded)); goog.events.listen( this.anim_, goog.fx.Transition.EventType.END, goog.bind(this.onAnimationCompleted_, this, expanded)); // Start animation. this.anim_.play(false); }; /** * Called during animation * * @param {goog.events.Event} e The event. * @private */ goog.ui.AnimatedZippy.prototype.onAnimate_ = function(e) { var contentElement = this.getContentElement(); var h = contentElement.offsetHeight; contentElement.style.marginTop = (e.y - h) + 'px'; }; /** * Called once the expand/collapse animation has started. * * @param {boolean} expanding Expanded/visibility state. * @private */ goog.ui.AnimatedZippy.prototype.onAnimationBegin_ = function(expanding) { this.dispatchEvent(new goog.ui.ZippyEvent( goog.ui.AnimatedZippy.Events.TOGGLE_ANIMATION_BEGIN, this, expanding)); }; /** * Called once the expand/collapse animation has completed. * * @param {boolean} expanded Expanded/visibility state. * @private */ goog.ui.AnimatedZippy.prototype.onAnimationCompleted_ = function(expanded) { // Fix wrong end position if the content has changed during the animation. if (expanded) { this.getContentElement().style.marginTop = '0'; } goog.events.removeAll(/** @type {!goog.fx.Animation} */ (this.anim_)); this.setExpandedInternal(expanded); this.anim_ = null; if (!expanded) { this.elWrapper_.style.display = 'none'; } // Fire toggle event. this.dispatchEvent( new goog.ui.ZippyEvent(goog.ui.Zippy.Events.TOGGLE, this, expanded)); this.dispatchEvent(new goog.ui.ZippyEvent( goog.ui.AnimatedZippy.Events.TOGGLE_ANIMATION_END, this, expanded)); };