From 25013128a7e2eb80b565d28abcc0af26219098db Mon Sep 17 00:00:00 2001 From: Denis Anisimov Date: Wed, 26 Oct 2016 16:22:00 +0300 Subject: [PATCH] Implement focus/blur listeners for NativeSelect. Fixes vaadin/framework8-issues#332 Change-Id: I19996ea83ed1fbe2b115d92d6be5e6a5e158f283 --- .../AbstractFocusableListingConnector.java | 56 +++++++++++++++++ .../com/vaadin/client/ui/VCheckBoxGroup.java | 44 +++---------- .../com/vaadin/client/ui/VNativeSelect.java | 11 ++-- .../nativeselect/NativeSelectConnector.java | 11 +--- .../optiongroup/CheckBoxGroupConnector.java | 22 +------ .../widgets/FocusableFlowPanelComposite.java | 58 +++++++++++++++++ .../main/java/com/vaadin/ui/NativeSelect.java | 44 ++++++++++++- .../nativeselect/NativeSelectFocusBlur.java | 42 +++++++++++++ .../NativeSelectFocusBlurTest.java | 63 +++++++++++++++++++ 9 files changed, 281 insertions(+), 70 deletions(-) create mode 100644 client/src/main/java/com/vaadin/client/connectors/AbstractFocusableListingConnector.java create mode 100644 client/src/main/java/com/vaadin/client/widgets/FocusableFlowPanelComposite.java create mode 100644 uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlur.java create mode 100644 uitest/src/test/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlurTest.java 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 index 0000000000..996086d36d --- /dev/null +++ b/client/src/main/java/com/vaadin/client/connectors/AbstractFocusableListingConnector.java @@ -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 type which has to allow to register focus/blur handlers + * @param + * the client-side selection model type + */ +public abstract class AbstractFocusableListingConnector> + extends AbstractListingConnector { + + 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(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java b/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java index d99d179d74..6ba7ea75d5 100644 --- a/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java +++ b/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java @@ -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) - *

- * 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> 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 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 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); - } } diff --git a/client/src/main/java/com/vaadin/client/ui/VNativeSelect.java b/client/src/main/java/com/vaadin/client/ui/VNativeSelect.java index efbba52efc..5005c79fbb 100644 --- a/client/src/main/java/com/vaadin/client/ui/VNativeSelect.java +++ b/client/src/main/java/com/vaadin/client/ui/VNativeSelect.java @@ -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); } /** diff --git a/client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java b/client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java index 93861b41dc..20c625a40d 100644 --- a/client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java @@ -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> { +public class NativeSelectConnector extends + AbstractFocusableListingConnector> { 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 dataSource) { if (dataChangeRegistration != null) { diff --git a/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java b/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java index 289a89eadb..5955037e65 100644 --- a/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java @@ -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> { - - private ConnectorFocusAndBlurHandler handler; +public class CheckBoxGroupConnector extends + AbstractFocusableListingConnector> { @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 index 0000000000..4a5d031c3f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widgets/FocusableFlowPanelComposite.java @@ -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); + } +} diff --git a/server/src/main/java/com/vaadin/ui/NativeSelect.java b/server/src/main/java/com/vaadin/ui/NativeSelect.java index be4cbc5e12..bb3ae41df4 100644 --- a/server/src/main/java/com/vaadin/ui/NativeSelect.java +++ b/server/src/main/java/com/vaadin/ui/NativeSelect.java @@ -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 extends AbstractSingleSelect { +public class NativeSelect extends AbstractSingleSelect + 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 extends AbstractSingleSelect { 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 index 0000000000..b665e52fcb --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlur.java @@ -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 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 index 0000000000..7b21465632 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/nativeselect/NativeSelectFocusBlurTest.java @@ -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 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.")); + } +} -- 2.39.5