]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement focus/blur listeners for NativeSelect.
authorDenis Anisimov <denis@vaadin.com>
Wed, 26 Oct 2016 13:22:00 +0000 (16:22 +0300)
committerDenis Anisimov <denis@vaadin.com>
Mon, 31 Oct 2016 12:53:57 +0000 (14:53 +0200)
Fixes vaadin/framework8-issues#332

Change-Id: I19996ea83ed1fbe2b115d92d6be5e6a5e158f283

client/src/main/java/com/vaadin/client/connectors/AbstractFocusableListingConnector.java [new file with mode: 0644]
client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java
client/src/main/java/com/vaadin/client/ui/VNativeSelect.java
client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java
client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java
client/src/main/java/com/vaadin/client/widgets/FocusableFlowPanelComposite.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/NativeSelect.java
uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlur.java [new file with mode: 0644]
uitest/src/test/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlurTest.java [new file with mode: 0644]

diff --git a/client/src/main/java/com/vaadin/client/connectors/AbstractFocusableListingConnector.java b/client/src/main/java/com/vaadin/client/connectors/AbstractFocusableListingConnector.java
new file mode 100644 (file)
index 0000000..996086d
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.connectors;
+
+import com.google.gwt.event.dom.client.HasAllFocusHandlers;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
+import com.vaadin.shared.data.selection.SelectionModel;
+
+/**
+ * Abstract class for listing widget connectors that contains focusable children
+ * to track their focus/blur events.
+ * 
+ * @author Vaadin Ltd
+ *
+ * @param <WIDGET>
+ *            widget type which has to allow to register focus/blur handlers
+ * @param <SELECTIONMODEL>
+ *            the client-side selection model type
+ */
+public abstract class AbstractFocusableListingConnector<WIDGET extends Widget & HasAllFocusHandlers, SELECTIONMODEL extends SelectionModel<?>>
+        extends AbstractListingConnector<SELECTIONMODEL> {
+
+    private ConnectorFocusAndBlurHandler handler;
+
+    @Override
+    protected void init() {
+        handler = ConnectorFocusAndBlurHandler.addHandlers(this);
+    }
+
+    @Override
+    public void onUnregister() {
+        super.onUnregister();
+        handler.removeHandlers();
+        handler = null;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public WIDGET getWidget() {
+        return (WIDGET) super.getWidget();
+    }
+}
index d99d179d7483e77bb6f39929aac1beae134cb787..6ba7ea75d50c9be05e5aa518c5da674632368c41 100644 (file)
@@ -23,19 +23,14 @@ import java.util.Map;
 import java.util.function.BiConsumer;
 
 import com.google.gwt.aria.client.Roles;
-import com.google.gwt.event.dom.client.BlurHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.HasAllFocusHandlers;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FocusWidget;
 import com.google.gwt.user.client.ui.HasEnabled;
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.WidgetUtil;
-import com.vaadin.client.widgets.ChildFocusAwareFlowPanel;
+import com.vaadin.client.widgets.FocusableFlowPanelComposite;
 import com.vaadin.shared.Registration;
 import com.vaadin.shared.ui.ListingJsonConstants;
 
@@ -47,8 +42,8 @@ import elemental.json.JsonObject;
  * @author Vaadin Ltd.
  * @since 8.0
  */
-public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
-        com.vaadin.client.Focusable, HasEnabled, HasAllFocusHandlers {
+public class VCheckBoxGroup extends FocusableFlowPanelComposite implements
+        Field, ClickHandler, com.vaadin.client.Focusable, HasEnabled {
 
     public static final String CLASSNAME = "v-select-optiongroup";
     public static final String CLASSNAME_OPTION = "v-select-option";
@@ -60,14 +55,6 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
      */
     public ApplicationConnection client;
 
-    /**
-     * Widget holding the different options (e.g. ListBox or Panel for radio
-     * buttons) (optional, fallbacks to container Panel)
-     * <p>
-     * For internal use only. May be removed or replaced in the future.
-     */
-    private ChildFocusAwareFlowPanel optionsContainer;
-
     private boolean htmlContentAllowed = false;
 
     private boolean enabled;
@@ -75,9 +62,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
     private List<BiConsumer<JsonObject, Boolean>> selectionChangeListeners;
 
     public VCheckBoxGroup() {
-        optionsContainer = new ChildFocusAwareFlowPanel();
-        initWidget(optionsContainer);
-        optionsContainer.setStyleName(CLASSNAME);
+        getWidget().setStyleName(CLASSNAME);
         optionsToItems = new HashMap<>();
         selectionChangeListeners = new ArrayList<>();
     }
@@ -88,12 +73,12 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
     public void buildOptions(List<JsonObject> items) {
         Roles.getGroupRole().set(getElement());
         int i = 0;
-        int widgetsToRemove = optionsContainer.getWidgetCount() - items.size();
+        int widgetsToRemove = getWidget().getWidgetCount() - items.size();
         if (widgetsToRemove < 0) {
             widgetsToRemove = 0;
         }
         List<Widget> remove = new ArrayList<>(widgetsToRemove);
-        for (Widget widget : optionsContainer) {
+        for (Widget widget : getWidget()) {
             if (i < items.size()) {
                 updateItem((VCheckBox) widget, items.get(i), false);
                 i++;
@@ -109,7 +94,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
     }
 
     private void remove(Widget widget) {
-        optionsContainer.remove(widget);
+        getWidget().remove(widget);
         optionsToItems.remove(widget);
     }
 
@@ -135,7 +120,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
         if (requireInitializations) {
             widget.addStyleName(CLASSNAME_OPTION);
             widget.addClickHandler(this);
-            optionsContainer.add(widget);
+            getWidget().add(widget);
         }
         optionsToItems.put(widget, item);
     }
@@ -161,7 +146,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
     }
 
     public void setTabIndex(int tabIndex) {
-        for (Widget anOptionsContainer : optionsContainer) {
+        for (Widget anOptionsContainer : getWidget()) {
             FocusWidget widget = (FocusWidget) anOptionsContainer;
             widget.setTabIndex(tabIndex);
         }
@@ -185,7 +170,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
 
     @Override
     public void focus() {
-        optionsContainer.focus();
+        getWidget().focus();
     }
 
     public boolean isHtmlContentAllowed() {
@@ -227,13 +212,4 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler,
                 .remove(selectionChanged);
     }
 
-    @Override
-    public HandlerRegistration addFocusHandler(FocusHandler handler) {
-        return optionsContainer.addFocusHandler(handler);
-    }
-
-    @Override
-    public HandlerRegistration addBlurHandler(BlurHandler handler) {
-        return optionsContainer.addBlurHandler(handler);
-    }
 }
index efbba52efc2c5187c5331bed4a4296d96ea20798..5005c79fbb7e0b08a908d891e4ad2f9d64105953 100644 (file)
@@ -17,16 +17,17 @@ package com.vaadin.client.ui;
 
 import java.util.Objects;
 
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.event.dom.client.HasAllFocusHandlers;
 import com.google.gwt.user.client.ui.ListBox;
+import com.vaadin.client.widgets.FocusableFlowPanelComposite;
 
 /**
  * The client-side widget for the {@code NativeSelect} component.
  *
  * @author Vaadin Ltd.
  */
-public class VNativeSelect extends Composite {
+public class VNativeSelect extends FocusableFlowPanelComposite
+        implements HasAllFocusHandlers {
 
     private final ListBox listBox = new ListBox();
 
@@ -34,9 +35,7 @@ public class VNativeSelect extends Composite {
      * Creates a new {@code VNativeSelect} instance.
      */
     public VNativeSelect() {
-        FlowPanel panel = new FlowPanel();
-        panel.add(listBox);
-        initWidget(panel);
+        getWidget().add(listBox);
     }
 
     /**
index 93861b41dc94e6fa8340f3de6208c124f54306b8..20c625a40d9344a02e6eb9c0a239ee3ae930feeb 100644 (file)
@@ -18,7 +18,7 @@ package com.vaadin.client.ui.nativeselect;
 
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.vaadin.client.annotations.OnStateChange;
-import com.vaadin.client.connectors.AbstractListingConnector;
+import com.vaadin.client.connectors.AbstractFocusableListingConnector;
 import com.vaadin.client.data.DataSource;
 import com.vaadin.client.ui.VNativeSelect;
 import com.vaadin.shared.Range;
@@ -41,8 +41,8 @@ import elemental.json.JsonObject;
  * @since 8.0
  */
 @Connect(com.vaadin.ui.NativeSelect.class)
-public class NativeSelectConnector
-        extends AbstractListingConnector<SelectionModel.Single<?>> {
+public class NativeSelectConnector extends
+        AbstractFocusableListingConnector<VNativeSelect, SelectionModel.Single<?>> {
 
     private HandlerRegistration selectionChangeRegistration;
     private Registration dataChangeRegistration;
@@ -67,11 +67,6 @@ public class NativeSelectConnector
         selectionChangeRegistration = null;
     }
 
-    @Override
-    public VNativeSelect getWidget() {
-        return (VNativeSelect) super.getWidget();
-    }
-
     @Override
     public void setDataSource(DataSource<JsonObject> dataSource) {
         if (dataChangeRegistration != null) {
index 289a89eadb5a89d2d3fbacd76eb6fad61aa64ef9..5955037e65efb04b15b6a0406fd8611a33f75e46 100644 (file)
@@ -22,9 +22,8 @@ import java.util.HashSet;
 import java.util.List;
 
 import com.vaadin.client.communication.StateChangeEvent;
-import com.vaadin.client.connectors.AbstractListingConnector;
+import com.vaadin.client.connectors.AbstractFocusableListingConnector;
 import com.vaadin.client.data.DataSource;
-import com.vaadin.client.ui.ConnectorFocusAndBlurHandler;
 import com.vaadin.client.ui.VCheckBoxGroup;
 import com.vaadin.shared.data.selection.MultiSelectServerRpc;
 import com.vaadin.shared.data.selection.SelectionModel;
@@ -36,23 +35,13 @@ import elemental.json.JsonObject;
 
 @Connect(CheckBoxGroup.class)
 // We don't care about the framework-provided selection model at this point
-public class CheckBoxGroupConnector
-        extends AbstractListingConnector<SelectionModel<?>> {
-
-    private ConnectorFocusAndBlurHandler handler;
+public class CheckBoxGroupConnector extends
+        AbstractFocusableListingConnector<VCheckBoxGroup, SelectionModel<?>> {
 
     @Override
     protected void init() {
         super.init();
         getWidget().addSelectionChangeHandler(this::selectionChanged);
-        handler = ConnectorFocusAndBlurHandler.addHandlers(this);
-    }
-
-    @Override
-    public void onUnregister() {
-        super.onUnregister();
-        handler.removeHandlers();
-        handler = null;
     }
 
     private void selectionChanged(JsonObject changedItem, Boolean selected) {
@@ -90,11 +79,6 @@ public class CheckBoxGroupConnector
         getWidget().buildOptions(items);
     }
 
-    @Override
-    public VCheckBoxGroup getWidget() {
-        return (VCheckBoxGroup) super.getWidget();
-    }
-
     @Override
     public CheckBoxGroupState getState() {
         return (CheckBoxGroupState) super.getState();
diff --git a/client/src/main/java/com/vaadin/client/widgets/FocusableFlowPanelComposite.java b/client/src/main/java/com/vaadin/client/widgets/FocusableFlowPanelComposite.java
new file mode 100644 (file)
index 0000000..4a5d031
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.client.widgets;
+
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasAllFocusHandlers;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.ui.Composite;
+
+/**
+ * Focusable composite whose widget is {@link ChildFocusAwareFlowPanel} (flow
+ * panel that tracks focus/blur events from its children).
+ * 
+ * @author Vaadin Ltd
+ *
+ */
+public abstract class FocusableFlowPanelComposite extends Composite
+        implements HasAllFocusHandlers {
+
+    private final ChildFocusAwareFlowPanel panel;
+
+    /**
+     * Creates a new instance.
+     */
+    protected FocusableFlowPanelComposite() {
+        panel = new ChildFocusAwareFlowPanel();
+        initWidget(panel);
+    }
+
+    @Override
+    protected final ChildFocusAwareFlowPanel getWidget() {
+        return (ChildFocusAwareFlowPanel) super.getWidget();
+    }
+
+    @Override
+    public HandlerRegistration addFocusHandler(FocusHandler handler) {
+        return panel.addFocusHandler(handler);
+    }
+
+    @Override
+    public HandlerRegistration addBlurHandler(BlurHandler handler) {
+        return panel.addBlurHandler(handler);
+    }
+}
index be4cbc5e121e5acdb60990390c3172f24e834149..bb3ae41df4c8030477f06d6813ed33b6d6c6eec8 100644 (file)
@@ -18,7 +18,15 @@ package com.vaadin.ui;
 
 import java.util.Collection;
 
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.BlurNotifier;
+import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.event.FieldEvents.FocusNotifier;
 import com.vaadin.server.data.DataSource;
+import com.vaadin.shared.Registration;
 import com.vaadin.shared.data.DataCommunicatorConstants;
 import com.vaadin.shared.ui.nativeselect.NativeSelectState;
 
@@ -34,14 +42,16 @@ import com.vaadin.shared.ui.nativeselect.NativeSelectState;
  * 
  * @see com.vaadin.ui.ComboBox
  */
-public class NativeSelect<T> extends AbstractSingleSelect<T> {
+public class NativeSelect<T> extends AbstractSingleSelect<T>
+        implements FocusNotifier, BlurNotifier {
 
     /**
      * Creates a new {@code NativeSelect} with an empty caption and no items.
      */
     public NativeSelect() {
-        addDataGenerator((item, json) -> json.put(
-                DataCommunicatorConstants.DATA, String.valueOf(item)));
+        registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent));
+        addDataGenerator((item, json) -> json
+                .put(DataCommunicatorConstants.DATA, String.valueOf(item)));
 
         setSelectionModel(new SimpleSingleSelection());
     }
@@ -85,6 +95,34 @@ public class NativeSelect<T> extends AbstractSingleSelect<T> {
         setDataSource(dataSource);
     }
 
+    @Override
+    public Registration addFocusListener(FocusListener listener) {
+        addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+                FocusListener.focusMethod);
+        return () -> removeListener(FocusEvent.EVENT_ID, FocusEvent.class,
+                listener);
+    }
+
+    @Override
+    @Deprecated
+    public void removeFocusListener(FocusListener listener) {
+        removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+    }
+
+    @Override
+    public Registration addBlurListener(BlurListener listener) {
+        addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+                BlurListener.blurMethod);
+        return () -> removeListener(BlurEvent.EVENT_ID, BlurEvent.class,
+                listener);
+    }
+
+    @Override
+    @Deprecated
+    public void removeBlurListener(BlurListener listener) {
+        removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+    }
+
     @Override
     protected NativeSelectState getState() {
         return getState(true);
diff --git a/uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlur.java b/uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlur.java
new file mode 100644 (file)
index 0000000..b665e52
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.tests.components.nativeselect;
+
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.NativeSelect;
+
+/**
+ * @author Vaadin Ltd
+ *
+ */
+public class NativeSelectFocusBlur extends AbstractTestUIWithLog {
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        NativeSelect<Integer> select = new NativeSelect<>();
+        select.setItems(IntStream.range(1, 10).mapToObj(Integer::valueOf)
+                .collect(Collectors.toList()));
+
+        addComponent(select);
+        select.addFocusListener(event -> log("Focus Event"));
+        select.addBlurListener(event -> log("Blur Event"));
+    }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlurTest.java b/uitest/src/test/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlurTest.java
new file mode 100644 (file)
index 0000000..7b21465
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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.
+ */
+package com.vaadin.tests.components.nativeselect;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.Keys;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.customelements.NativeSelectElement;
+import com.vaadin.testbench.elements.LabelElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+/**
+ * @author Vaadin Ltd
+ *
+ */
+public class NativeSelectFocusBlurTest extends MultiBrowserTest {
+
+    @Test
+    public void focusBlurEvents() {
+        openTestURL();
+
+        NativeSelectElement nativeSelect = $(NativeSelectElement.class).first();
+        nativeSelect.click();
+
+        // Focus event is fired
+        Assert.assertTrue(logContainsText("1. Focus Event"));
+
+        List<TestBenchElement> options = nativeSelect.getOptions();
+        options.get(1).click();
+        // No any new event
+        Assert.assertFalse(logContainsText("2."));
+
+        // click on log label => blur
+        $(LabelElement.class).first().click();
+        // blur event is fired
+        Assert.assertTrue(logContainsText("2. Blur Event"));
+
+        nativeSelect.click();
+        // Focus event is fired
+        Assert.assertTrue(logContainsText("3. Focus Event"));
+
+        options.get(1).sendKeys(Keys.ARROW_UP, Keys.ENTER);
+        // No any new event
+        Assert.assertFalse(logContainsText("4."));
+    }
+}