From e620bfb97271d16b6d7d329841b41cb33a594dce Mon Sep 17 00:00:00 2001 From: Jouni Koivuviita Date: Tue, 10 Jun 2014 22:34:59 +0300 Subject: [PATCH] Fix multiple VOverlay animation-in/out related issues Any running animation-in is now finished before animation-out is triggered. Moved windowClone variable into WindowConnector where it should be. Change-Id: I0ee733d05558d46a08e5e46f821104ad98783118 --- .../src/com/vaadin/client/AnimationUtil.java | 14 +- .../com/vaadin/client/ui/VNotification.java | 77 ++++++----- client/src/com/vaadin/client/ui/VOverlay.java | 124 +++++++++++------- client/src/com/vaadin/client/ui/VWindow.java | 12 -- .../client/ui/window/WindowConnector.java | 27 +++- 5 files changed, 154 insertions(+), 100 deletions(-) diff --git a/client/src/com/vaadin/client/AnimationUtil.java b/client/src/com/vaadin/client/AnimationUtil.java index 9f80d98ef2..2077cee3c0 100644 --- a/client/src/com/vaadin/client/AnimationUtil.java +++ b/client/src/com/vaadin/client/AnimationUtil.java @@ -15,6 +15,7 @@ */ package com.vaadin.client; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style; @@ -57,7 +58,7 @@ public class AnimationUtil { } /** For internal use only. May be removed or replaced in the future. */ - public static native void registerAnimationEndEventListener(Element elem, + public static native JavaScriptObject addAnimationEndListener(Element elem, AnimationEndListener listener) /*-{ var callbackFunc = $entry(function(e) { @@ -71,10 +72,19 @@ public class AnimationUtil { elem._vaadin_animationend_callbacks = []; } elem._vaadin_animationend_callbacks.push(callbackFunc); + + return callbackFunc; + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public static native void removeAnimationEndListener(Element elem, + JavaScriptObject listener) + /*-{ + elem.removeEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, listener, false); }-*/; /** For internal use only. May be removed or replaced in the future. */ - public static native void unregisterAnimationEndEventListeners(Element elem) + public static native void removeAllAnimationEndListeners(Element elem) /*-{ if(elem._vaadin_animationend_callbacks) { var callbacks = elem._vaadin_animationend_callbacks; diff --git a/client/src/com/vaadin/client/ui/VNotification.java b/client/src/com/vaadin/client/ui/VNotification.java index 855da517f9..2aa8e8f879 100644 --- a/client/src/com/vaadin/client/ui/VNotification.java +++ b/client/src/com/vaadin/client/ui/VNotification.java @@ -77,7 +77,7 @@ public class VNotification extends VOverlay { private static final ArrayList notifications = new ArrayList(); private boolean infiniteDelay = false; - private int cssTransitionDelay = -1; + private int cssAnimationDelay = -1; private int x = -1; private int y = -1; @@ -137,10 +137,10 @@ public class VNotification extends VOverlay { private void setDelay(int delayMsec) { if (delayMsec < 0) { infiniteDelay = true; - cssTransitionDelay = 0; + cssAnimationDelay = 0; } else { infiniteDelay = false; - cssTransitionDelay = delayMsec; + cssAnimationDelay = delayMsec; } } @@ -238,15 +238,16 @@ public class VNotification extends VOverlay { removeStyleDependentName(temporaryStyle); temporaryStyle = null; } - if (style != null) { + if (style != null && style.length() > 0) { temporaryStyle = style; addStyleName(style); addStyleDependentName(style); } + setPosition(position); super.show(); + updatePositionOffsets(position); notifications.add(this); - setPosition(position); positionOrSizeUpdated(); /** * Android 4 fails to render notifications correctly without a little @@ -257,25 +258,53 @@ public class VNotification extends VOverlay { } } + private void hideWithoutDelay() { + cssAnimationDelay = 0; + hide(); + } + @Override public void hide() { - DOM.removeEventPreview(this); - if (cssTransitionDelay >= 0) { - AnimationUtil.setAnimationDelay(getElement(), cssTransitionDelay - + "ms"); + // Run only once + if (notifications.contains(this)) { + DOM.removeEventPreview(this); + if (cssAnimationDelay >= 0) { + AnimationUtil.setAnimationDelay(getElement(), cssAnimationDelay + + "ms"); + } + super.hide(); + notifications.remove(this); + fireEvent(new HideEvent(this)); } - super.hide(); - notifications.remove(this); - fireEvent(new HideEvent(this)); } - public void setPosition(com.vaadin.shared.Position position) { + private void updatePositionOffsets(com.vaadin.shared.Position position) { final Element el = getElement(); // Remove all offsets (GWT PopupPanel defaults) el.getStyle().clearTop(); el.getStyle().clearLeft(); + switch (position) { + case MIDDLE_LEFT: + case MIDDLE_RIGHT: + center(); + el.getStyle().clearLeft(); + break; + case TOP_CENTER: + case BOTTOM_CENTER: + center(); + el.getStyle().clearTop(); + break; + case MIDDLE_CENTER: + center(); + break; + } + } + + public void setPosition(com.vaadin.shared.Position position) { + final Element el = getElement(); + // Remove any previous positions el.removeClassName(STYLENAME_POSITION_TOP); el.removeClassName(STYLENAME_POSITION_RIGHT); @@ -295,16 +324,10 @@ public class VNotification extends VOverlay { el.addClassName(STYLENAME_POSITION_RIGHT); break; case MIDDLE_LEFT: - center(); - // Centering sets the left offset - el.getStyle().clearLeft(); el.addClassName(STYLENAME_POSITION_MIDDLE); el.addClassName(STYLENAME_POSITION_LEFT); break; case MIDDLE_RIGHT: - center(); - // Centering sets the left offset - el.getStyle().clearLeft(); el.addClassName(STYLENAME_POSITION_MIDDLE); el.addClassName(STYLENAME_POSITION_RIGHT); break; @@ -317,32 +340,21 @@ public class VNotification extends VOverlay { el.addClassName(STYLENAME_POSITION_LEFT); break; case TOP_CENTER: - center(); - // Centering sets the top offset - el.getStyle().clearTop(); el.addClassName(STYLENAME_POSITION_TOP); el.addClassName(STYLENAME_POSITION_CENTER); break; case BOTTOM_CENTER: - center(); - // Centering sets the top offset - el.getStyle().clearTop(); el.addClassName(STYLENAME_POSITION_BOTTOM); el.addClassName(STYLENAME_POSITION_CENTER); break; case ASSISTIVE: el.addClassName(STYLENAME_POSITION_ASSISTIVE); break; - default: - case MIDDLE_CENTER: - center(); - break; } } @Override public void onBrowserEvent(Event event) { - DOM.removeEventPreview(this); hide(); } @@ -353,12 +365,12 @@ public class VNotification extends VOverlay { if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) { if (type == Event.ONCLICK) { if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) { - hide(); + hideWithoutDelay(); return false; } } else if (type == Event.ONKEYDOWN && event.getKeyCode() == KeyCodes.KEY_ESCAPE) { - hide(); + hideWithoutDelay(); return false; } if (temporaryStyle == STYLE_SYSTEM) { @@ -370,7 +382,6 @@ public class VNotification extends VOverlay { // default switch (type) { case Event.ONMOUSEMOVE: - if (x < 0) { x = DOM.eventGetClientX(event); y = DOM.eventGetClientY(event); diff --git a/client/src/com/vaadin/client/ui/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java index ab493ce383..ac4e57268a 100644 --- a/client/src/com/vaadin/client/ui/VOverlay.java +++ b/client/src/com/vaadin/client/ui/VOverlay.java @@ -21,13 +21,13 @@ import java.util.logging.Logger; import com.google.gwt.animation.client.Animation; import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.IFrameElement; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.BorderStyle; -import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.CloseEvent; @@ -443,6 +443,8 @@ public class VOverlay extends PopupPanel implements CloseHandler { current = null; } + private JavaScriptObject animateInListener; + private boolean maybeShowWithAnimation() { boolean isAttached = isAttached() && isShowing(); super.show(); @@ -467,8 +469,25 @@ public class VOverlay extends PopupPanel implements CloseHandler { } if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - AnimationUtil.registerAnimationEndEventListener(getElement(), - getAnimationEndListener(false)); + animateInListener = AnimationUtil.addAnimationEndListener( + getElement(), new AnimationEndListener() { + @Override + public void onAnimationEnd(NativeEvent event) { + String animationName = AnimationUtil + .getAnimationName(event); + if (animationName + .contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + AnimationUtil.removeAnimationEndListener( + getElement(), animateInListener); + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + } + } + } + }); return true; } else { removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); @@ -936,64 +955,75 @@ public class VOverlay extends PopupPanel implements CloseHandler { * @see com.google.gwt.user.client.ui.PopupPanel#hide(boolean) */ @Override - public void hide(boolean autoClosed) { + public void hide(final boolean autoClosed) { if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9()) { super.hide(autoClosed); } else { - // Check if animations are used - addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); - if (isShadowEnabled()) { - shadow.addClassName(CLASSNAME_SHADOW + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_OUT); - } - ComputedStyle cs = new ComputedStyle(getElement()); - String animationName = AnimationUtil.getAnimationName(cs); - if (animationName == null) { - animationName = ""; - } - - if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { - AnimationUtil.registerAnimationEndEventListener(getElement(), - getAnimationEndListener(autoClosed)); - // No event previews should happen after the animation has - // started - VOverlay.this.setPreviewingAllNativeEvents(false); + if (getStyleName().contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + AnimationUtil.addAnimationEndListener(getElement(), + new AnimationEndListener() { + @Override + public void onAnimationEnd(NativeEvent event) { + if (AnimationUtil + .getAnimationName(event) + .contains( + ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + VOverlay.this.hide(autoClosed); + } + } + }); } else { - removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); + // Check if animations are used + addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); if (isShadowEnabled()) { - shadow.removeClassName(CLASSNAME_SHADOW + "-" + shadow.addClassName(CLASSNAME_SHADOW + "-" + ADDITIONAL_CLASSNAME_ANIMATE_OUT); } - super.hide(autoClosed); - } - } - } - - private AnimationEndListener getAnimationEndListener( - final boolean autoClosed) { - return new AnimationEndListener() { + ComputedStyle cs = new ComputedStyle(getElement()); + String animationName = AnimationUtil.getAnimationName(cs); + if (animationName == null) { + animationName = ""; + } - @Override - public void onAnimationEnd(NativeEvent event) { - AnimationUtil - .unregisterAnimationEndEventListeners(getElement()); - String animationName = AnimationUtil.getAnimationName(event); - if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { - removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); - if (isShadowEnabled()) { - shadow.removeClassName(CLASSNAME_SHADOW + "-" - + ADDITIONAL_CLASSNAME_ANIMATE_IN); - } - } else if (animationName - .contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { + if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { + AnimationUtil.addAnimationEndListener(getElement(), + new AnimationEndListener() { + @Override + public void onAnimationEnd(NativeEvent event) { + String animationName = AnimationUtil + .getAnimationName(event); + if (animationName + .contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { + AnimationUtil + .removeAllAnimationEndListeners(getElement()); + // Remove both animation styles just in + // case + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + shadow.removeClassName(CLASSNAME_SHADOW + + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_OUT); + } + VOverlay.super.hide(autoClosed); + } + } + }); + // No event previews should happen after the animation has + // started + VOverlay.this.setPreviewingAllNativeEvents(false); + } else { removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); if (isShadowEnabled()) { shadow.removeClassName(CLASSNAME_SHADOW + "-" + ADDITIONAL_CLASSNAME_ANIMATE_OUT); } - VOverlay.super.hide(autoClosed); + super.hide(autoClosed); } } - }; + } } } \ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index e2cc33744e..7c1a21f654 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -30,7 +30,6 @@ import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Position; @@ -201,9 +200,6 @@ public class VWindow extends VWindowOverlay implements /** For internal use only. May be removed or replaced in the future. */ public int bringToFrontSequence = -1; - /** For internal use only. May be removed or replaced in the future. */ - public Node windowClone; - private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200, new ScheduledCommand() { @@ -1037,14 +1033,6 @@ public class VWindow extends VWindowOverlay implements } private void onCloseClick() { - // Take a copy of the contents, since the server will detach all - // children of this window, and the window will be emptied during the - // next hierarchy update (we need to keep the contents visible for the - // duration of a possible 'out-animation') - // This is used by the WindowConnector in the case the window is - // actually closed and removed from the UI - windowClone = getElement().getFirstChild().cloneNode(true); - // Send the close event to the server client.updateVariable(id, "close", true, true); } diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java index 40185099d3..2a2f031144 100644 --- a/client/src/com/vaadin/client/ui/window/WindowConnector.java +++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java @@ -19,6 +19,7 @@ import java.util.logging.Logger; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Unit; @@ -58,6 +59,8 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector SimpleManagedLayout, PostLayoutListener, MayScrollChildren, WindowMoveHandler { + private Node windowClone; + private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { @Override protected void fireClick(NativeEvent event, @@ -193,14 +196,16 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector // We always have 1 child, unless the child is hidden getWidget().contentPanel.setWidget(getContentWidget()); - // If the window is removed from the UI, add the copy of the contents to - // the window (in case of an 'out-animation') - if (getParent() == null) { + if (getParent() == null && windowClone != null) { + // If the window is removed from the UI, add the copy of the + // contents to the window (in case of an 'out-animation') getWidget().getElement().removeAllChildren(); - getWidget().getElement().appendChild(getWidget().windowClone); + getWidget().getElement().appendChild(windowClone); + + // Clean reference + windowClone = null; } - // Clean reference - getWidget().windowClone = null; + } @Override @@ -277,6 +282,16 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector window.center(); } window.positionOrSizeUpdated(); + + if (getParent() != null) { + // Take a copy of the contents, since the server will detach all + // children of this window when it's closed, and the window will be + // emptied during the following hierarchy update (we need to keep + // the contents visible for the duration of a possible + // 'out-animation') + windowClone = getWidget().getElement().getFirstChild() + .cloneNode(true); + } } @Override -- 2.39.5