]> source.dussan.org Git - vaadin-framework.git/commitdiff
Displaying tooltip in slot for touch devices (#15353)
authorAlexey Fansky <alexey.fansky@effective-soft.com>
Sat, 7 Feb 2015 00:25:44 +0000 (16:25 -0800)
committerMarkus Koivisto <markus@vaadin.com>
Wed, 4 Mar 2015 14:19:01 +0000 (16:19 +0200)
Change-Id: Ia2fce4dbfc205b44622557017afff19c4a2ef7df

client/src/com/vaadin/client/VTooltip.java
client/src/com/vaadin/client/ui/orderedlayout/Slot.java
uitest/src/com/vaadin/tests/components/TouchDevicesTooltip.java [new file with mode: 0644]

index 453563370ce0bd664182c6d748193ae00ce190c8..a9406935dcc2b86869a6a5377da2cdc731337c6a 100644 (file)
@@ -20,17 +20,7 @@ import com.google.gwt.aria.client.RelevantValue;
 import com.google.gwt.aria.client.Roles;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.DomEvent;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseDownHandler;
-import com.google.gwt.event.dom.client.MouseMoveEvent;
-import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.*;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.Timer;
@@ -388,7 +378,8 @@ public class VTooltip extends VOverlay {
     }
 
     private class TooltipEventHandler implements MouseMoveHandler,
-            KeyDownHandler, FocusHandler, BlurHandler, MouseDownHandler {
+            KeyDownHandler, FocusHandler, BlurHandler, MouseDownHandler,
+            MouseUpHandler, TouchStartHandler {
 
         /**
          * Current element hovered
@@ -400,6 +391,11 @@ public class VTooltip extends VOverlay {
          */
         private boolean handledByFocus;
 
+        /**
+         * Indicates whether the tooltip is being called after a touch event.
+         */
+        private boolean touchInitiated = false;
+
         /**
          * Locate the tooltip for given element
          * 
@@ -450,7 +446,19 @@ public class VTooltip extends VOverlay {
 
         @Override
         public void onMouseMove(MouseMoveEvent mme) {
-            handleShowHide(mme, false);
+            if (!touchInitiated) {
+                handleShowHide(mme, false);
+            }
+        }
+
+        @Override
+        public void onMouseUp(MouseUpEvent event) {
+            touchInitiated = false;
+        }
+
+        @Override
+        public void onTouchStart(TouchStartEvent te) {
+            touchInitiated = true;
         }
 
         @Override
@@ -550,9 +558,11 @@ public class VTooltip extends VOverlay {
         Profiler.enter("VTooltip.connectHandlersToWidget");
         widget.addDomHandler(tooltipEventHandler, MouseMoveEvent.getType());
         widget.addDomHandler(tooltipEventHandler, MouseDownEvent.getType());
+        widget.addDomHandler(tooltipEventHandler, MouseUpEvent.getType());
         widget.addDomHandler(tooltipEventHandler, KeyDownEvent.getType());
         widget.addDomHandler(tooltipEventHandler, FocusEvent.getType());
         widget.addDomHandler(tooltipEventHandler, BlurEvent.getType());
+        widget.addDomHandler(tooltipEventHandler, TouchStartEvent.getType());
         Profiler.leave("VTooltip.connectHandlersToWidget");
     }
 
index b97cf7398948583463e6e96e7a07932702fd257c..616667c3671c4fe331f29784032a7b3ab74d3bcd 100644 (file)
@@ -22,9 +22,12 @@ import com.google.gwt.aria.client.Roles;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.*;
 import com.google.gwt.user.client.ui.SimplePanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.BrowserInfo;
@@ -44,6 +47,7 @@ import com.vaadin.shared.ui.AlignmentInfo;
 public final class Slot extends SimplePanel {
 
     private static final String ALIGN_CLASS_PREFIX = "v-align-";
+    private static final int TOUCH_ERROR_MESSAGE_HIDE_DELAY = 200;
 
     private final VAbstractOrderedLayout layout;
 
@@ -55,8 +59,13 @@ public final class Slot extends SimplePanel {
     private Element captionText;
     private Icon icon;
     private Element errorIcon;
+    private Element errorMessage;
     private Element requiredIcon;
 
+    private HandlerRegistration focusRegistration;
+    private HandlerRegistration blurRegistration;
+    private boolean labelClicked = false;
+
     private ElementResizeListener captionResizeListener;
 
     private ElementResizeListener widgetResizeListener;
@@ -582,9 +591,21 @@ public final class Slot extends SimplePanel {
                 errorIcon.setClassName("v-errorindicator");
             }
             caption.appendChild(errorIcon);
-        } else if (errorIcon != null) {
-            errorIcon.removeFromParent();
-            errorIcon = null;
+
+            if(BrowserInfo.get().isTouchDevice()) {
+                addFocusHandlerToWidget(error, widget);
+                addBlurHandlerToWidget(widget);
+            }
+
+        } else {
+            if (errorIcon != null) {
+                errorIcon.removeFromParent();
+                errorIcon = null;
+            }
+
+            if (errorMessage != null) {
+                removeErrorMessageAndHandlers();
+            }
         }
 
         if (caption != null) {
@@ -651,6 +672,81 @@ public final class Slot extends SimplePanel {
         }
     }
 
+    private void removeErrorMessageAndHandlers() {
+        errorMessage.removeFromParent();
+        errorMessage = null;
+
+        if (focusRegistration != null) {
+            focusRegistration.removeHandler();
+            focusRegistration = null;
+        }
+
+        if(blurRegistration != null) {
+            blurRegistration.removeHandler();
+            blurRegistration = null;
+        }
+    }
+
+    private void addFocusHandlerToWidget(final String error, Widget widget) {
+        focusRegistration = widget.addHandler(new FocusHandler() {
+            @Override
+            public void onFocus(FocusEvent event) {
+                if(labelClicked) {
+                    labelClicked = false;
+                    return;
+                }
+                if (errorMessage == null) {
+                    errorMessage = DOM.createDiv();
+                    errorMessage.setClassName("v-touch-error-message");
+                }
+                errorMessage.setInnerHTML(error);
+                captionWrap.appendChild(errorMessage);
+            }
+        }, FocusEvent.getType());
+    }
+
+    private void addBlurHandlerToWidget(final Widget widget) {
+        blurRegistration = widget.addHandler(new BlurHandler() {
+            @Override
+            public void onBlur(BlurEvent event) {
+                if(errorMessage != null) {
+                    addClickHandlerToErrorMessage(widget);
+                }
+                scheduleErrorMessageHide(TOUCH_ERROR_MESSAGE_HIDE_DELAY);
+            }
+        }, BlurEvent.getType());
+    }
+
+    private void scheduleErrorMessageHide(int delay) {
+        //Delaying hiding to allow error message click handler
+        //do his job and return the focus back if error message was tapped
+        Timer hideTimer = new Timer() {
+            @Override
+            public void run() {
+                if(errorMessage != null) {
+                    errorMessage.removeFromParent();
+                    errorMessage = null;
+                }
+            }
+        };
+        hideTimer.schedule(delay);
+    }
+
+    private void addClickHandlerToErrorMessage(final Widget widget) {
+        Event.sinkEvents(errorMessage, Event.ONCLICK);
+        Event.setEventListener(errorMessage, new EventListener() {
+            @Override
+            public void onBrowserEvent(Event event) {
+                if(Event.ONCLICK == event.getTypeInt()) {
+                    errorMessage.removeFromParent();
+                    errorMessage = null;
+                    labelClicked = true;
+                    widget.getElement().focus();
+                }
+            }
+        });
+    }
+
     /**
      * Does the slot have a caption
      */
diff --git a/uitest/src/com/vaadin/tests/components/TouchDevicesTooltip.java b/uitest/src/com/vaadin/tests/components/TouchDevicesTooltip.java
new file mode 100644 (file)
index 0000000..1a3b4cd
--- /dev/null
@@ -0,0 +1,57 @@
+package com.vaadin.tests.components;
+
+import com.vaadin.data.util.converter.StringToIntegerConverter;
+import com.vaadin.data.validator.IntegerRangeValidator;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.TextField;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+public class TouchDevicesTooltip extends AbstractTestUI {
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        final Label errorLabel = new Label("No error");
+        addComponent(errorLabel);
+
+        TextField textField = new TextField("Value");
+        textField.setConverter(new StringToIntegerConverter());
+        textField.addValidator(new IntegerRangeValidator("incorrect value", 0, 100));
+        textField.setImmediate(true);
+        textField.setValue("-5");
+        addComponent(textField);
+
+        TextField textField2 = new TextField("Value2");
+        textField2.setConverter(new StringToIntegerConverter());
+        textField2.addValidator(new IntegerRangeValidator("incorrect value2", 0, 100));
+        textField2.setImmediate(true);
+        textField2.setValue("-5");
+        addComponent(textField2);
+    }
+
+    public static class Bean {
+        @NotNull
+        @Min(0)
+        private Integer value;
+
+        public Integer getValue() {
+            return value;
+        }
+
+        public void setValue(Integer value) {
+            this.value = value;
+        }
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 15353;
+    }
+
+    @Override
+    public String getDescription() {
+        return "Displaying error message in slot for touch devices";
+    }
+}