From: Denis Anisimov Date: Wed, 26 Oct 2016 13:22:00 +0000 (+0300) Subject: Implement focus and blur events for CheckBoxGroup. X-Git-Tag: 8.0.0.alpha6~33 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=9c88657db52d73f6dd8b976ce0d4cada643b2a4d;p=vaadin-framework.git Implement focus and blur events for CheckBoxGroup. Fixes vaadin/framework8-issues#334 Change-Id: I4c7ca424cc4f4a1f0cdecd7671827465ab74ace7 --- diff --git a/client/src/main/java/com/vaadin/client/EventHelper.java b/client/src/main/java/com/vaadin/client/EventHelper.java index 2f23d853d8..0212fae26c 100644 --- a/client/src/main/java/com/vaadin/client/EventHelper.java +++ b/client/src/main/java/com/vaadin/client/EventHelper.java @@ -18,6 +18,8 @@ package com.vaadin.client; import static com.vaadin.shared.EventId.BLUR; import static com.vaadin.shared.EventId.FOCUS; +import java.util.function.Supplier; + import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.DomEvent.Type; @@ -137,9 +139,36 @@ public class EventHelper { ComponentConnector connector, H handler, String eventIdentifier, HandlerRegistration handlerRegistration, Type type, Widget widget) { + return updateHandler(connector, eventIdentifier, handlerRegistration, + () -> widget.addDomHandler(handler, type)); + } + + /** + * Updates handler registered using {@code handlerProvider}: removes it if + * connector doesn't have anymore {@code eventIdentifier} using provided + * {@code handlerRegistration} and adds it via provided + * {@code handlerProvider} if connector has event listener with + * {@code eventIdentifier}. + * + * @param connector + * connector to check event listener presence + * @param eventIdentifier + * event identifier whose presence in the connector is checked + * @param handlerRegistration + * resulting handler registration to remove added handler in case + * of absence event listener + * @param handlerProvider + * the strategy to register handler + * @return handlerRegistration which should be used to remove registered + * handler via {@code handlerProvider} + */ + public static HandlerRegistration updateHandler( + ComponentConnector connector, String eventIdentifier, + HandlerRegistration handlerRegistration, + Supplier handlerProvider) { if (connector.hasEventListener(eventIdentifier)) { if (handlerRegistration == null) { - handlerRegistration = widget.addDomHandler(handler, type); + handlerRegistration = handlerProvider.get(); } } else if (handlerRegistration != null) { handlerRegistration.removeHandler(); @@ -147,4 +176,5 @@ public class EventHelper { } return handlerRegistration; } + } diff --git a/client/src/main/java/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java b/client/src/main/java/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java index 54b687cc3a..a023f152cf 100644 --- a/client/src/main/java/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java +++ b/client/src/main/java/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java @@ -19,6 +19,7 @@ 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.dom.client.HasAllFocusHandlers; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.EventHelper; @@ -42,15 +43,39 @@ public class ConnectorFocusAndBlurHandler private final Widget widget; private HandlerRegistration focusRegistration = null; private HandlerRegistration blurRegistration = null; + private HandlerRegistration stateChangeRegistration = null; - public static void addHandlers(AbstractComponentConnector connector) { - addHandlers(connector, connector.getWidget()); + /** + * Add focus/blur handlers to the widget of the {@code connector}. + * + * @param connector + * connector whose widget is a target to add focus/blur handlers + * @return ConnectorFocusAndBlurHandler instance to remove all registered + * handlers + */ + public static ConnectorFocusAndBlurHandler addHandlers( + AbstractComponentConnector connector) { + return addHandlers(connector, connector.getWidget()); } - public static void addHandlers(AbstractComponentConnector connector, - Widget widget) { - connector.addStateChangeHandler("registeredEventListeners", - new ConnectorFocusAndBlurHandler(connector, widget)); + /** + * Add focus/blur handlers to the widget and a state change handler for the + * {@code connector}. + * + * @param connector + * connector to register state change handler + * @param widget + * widget to register focus/blur handler + * @return ConnectorFocusAndBlurHandler instance to remove all registered + * handlers + */ + public static ConnectorFocusAndBlurHandler addHandlers( + AbstractComponentConnector connector, Widget widget) { + ConnectorFocusAndBlurHandler handler = new ConnectorFocusAndBlurHandler( + connector, widget); + handler.stateChangeRegistration = connector + .addStateChangeHandler("registeredEventListeners", handler); + return handler; } private ConnectorFocusAndBlurHandler(AbstractComponentConnector connector, @@ -61,10 +86,22 @@ public class ConnectorFocusAndBlurHandler @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { - focusRegistration = EventHelper.updateHandler(connector, this, - EventId.FOCUS, focusRegistration, FocusEvent.getType(), widget); - blurRegistration = EventHelper.updateHandler(connector, this, - EventId.BLUR, blurRegistration, BlurEvent.getType(), widget); + if (widget instanceof HasAllFocusHandlers) { + HasAllFocusHandlers focusHandlers = (HasAllFocusHandlers) widget; + focusRegistration = EventHelper.updateHandler(connector, + EventId.FOCUS, focusRegistration, + () -> focusHandlers.addFocusHandler(this)); + blurRegistration = EventHelper.updateHandler(connector, + EventId.BLUR, blurRegistration, + () -> focusHandlers.addBlurHandler(this)); + } else { + focusRegistration = EventHelper.updateHandler(connector, this, + EventId.FOCUS, focusRegistration, FocusEvent.getType(), + widget); + blurRegistration = EventHelper.updateHandler(connector, this, + EventId.BLUR, blurRegistration, BlurEvent.getType(), + widget); + } } @Override @@ -81,6 +118,21 @@ public class ConnectorFocusAndBlurHandler getRpc().blur(); } + /** + * Remove all handlers from the widget and the connector. + */ + public void removeHandlers() { + if (focusRegistration != null) { + focusRegistration.removeHandler(); + } + if (blurRegistration != null) { + blurRegistration.removeHandler(); + } + if (stateChangeRegistration != null) { + stateChangeRegistration.removeHandler(); + } + } + private FocusAndBlurServerRpc getRpc() { return connector.getRpcProxy(FocusAndBlurServerRpc.class); } 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 e47598426b..d99d179d74 100644 --- a/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java +++ b/client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java @@ -18,23 +18,24 @@ package com.vaadin.client.ui; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; 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.FlowPanel; import com.google.gwt.user.client.ui.FocusWidget; -import com.google.gwt.user.client.ui.Focusable; import com.google.gwt.user.client.ui.HasEnabled; -import com.google.gwt.user.client.ui.Panel; 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.shared.Registration; import com.vaadin.shared.ui.ListingJsonConstants; @@ -47,7 +48,7 @@ import elemental.json.JsonObject; * @since 8.0 */ public class VCheckBoxGroup extends Composite implements Field, ClickHandler, - com.vaadin.client.Focusable, HasEnabled { + com.vaadin.client.Focusable, HasEnabled, HasAllFocusHandlers { public static final String CLASSNAME = "v-select-optiongroup"; public static final String CLASSNAME_OPTION = "v-select-option"; @@ -65,7 +66,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler, *

* For internal use only. May be removed or replaced in the future. */ - public Panel optionsContainer; + private ChildFocusAwareFlowPanel optionsContainer; private boolean htmlContentAllowed = false; @@ -74,7 +75,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler, private List> selectionChangeListeners; public VCheckBoxGroup() { - optionsContainer = new FlowPanel(); + optionsContainer = new ChildFocusAwareFlowPanel(); initWidget(optionsContainer); optionsContainer.setStyleName(CLASSNAME); optionsToItems = new HashMap<>(); @@ -85,40 +86,58 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler, * Build all the options */ public void buildOptions(List items) { - /* - * In order to retain focus, we need to update values rather than - * recreate panel from scratch (#10451). However, the panel will be - * rebuilt (losing focus) if number of elements or their order is - * changed. - */ - - Roles.getRadiogroupRole().set(getElement()); - optionsContainer.clear(); - for (JsonObject item : items) { - String itemHtml = item - .getString(ListingJsonConstants.JSONKEY_ITEM_VALUE); - if (!isHtmlContentAllowed()) { - itemHtml = WidgetUtil.escapeHTML(itemHtml); + Roles.getGroupRole().set(getElement()); + int i = 0; + int widgetsToRemove = optionsContainer.getWidgetCount() - items.size(); + if (widgetsToRemove < 0) { + widgetsToRemove = 0; + } + List remove = new ArrayList<>(widgetsToRemove); + for (Widget widget : optionsContainer) { + if (i < items.size()) { + updateItem((VCheckBox) widget, items.get(i), false); + i++; + } else { + remove.add(widget); } - VCheckBox checkBox = new VCheckBox(); + } + remove.stream().forEach(this::remove); + while (i < items.size()) { + updateItem(new VCheckBox(), items.get(i), true); + i++; + } + } - String iconUrl = item - .getString(ListingJsonConstants.JSONKEY_ITEM_ICON); - if (iconUrl != null && iconUrl.length() != 0) { - Icon icon = client.getIcon(iconUrl); - itemHtml = icon.getElement().getString() + itemHtml; - } + private void remove(Widget widget) { + optionsContainer.remove(widget); + optionsToItems.remove(widget); + } - checkBox.addStyleName(CLASSNAME_OPTION); - checkBox.addClickHandler(this); - checkBox.setHTML(itemHtml); - checkBox.setValue(item - .getBoolean(ListingJsonConstants.JSONKEY_ITEM_SELECTED)); - setOptionEnabled(checkBox, item); + private void updateItem(VCheckBox widget, JsonObject item, + boolean requireInitializations) { + String itemHtml = item + .getString(ListingJsonConstants.JSONKEY_ITEM_VALUE); + if (!isHtmlContentAllowed()) { + itemHtml = WidgetUtil.escapeHTML(itemHtml); + } - optionsContainer.add(checkBox); - optionsToItems.put(checkBox, item); + String iconUrl = item.getString(ListingJsonConstants.JSONKEY_ITEM_ICON); + if (iconUrl != null && iconUrl.length() != 0) { + Icon icon = client.getIcon(iconUrl); + itemHtml = icon.getElement().getString() + itemHtml; } + + widget.setHTML(itemHtml); + widget.setValue( + item.getBoolean(ListingJsonConstants.JSONKEY_ITEM_SELECTED)); + setOptionEnabled(widget, item); + + if (requireInitializations) { + widget.addStyleName(CLASSNAME_OPTION); + widget.addClickHandler(this); + optionsContainer.add(widget); + } + optionsToItems.put(widget, item); } @Override @@ -166,10 +185,7 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler, @Override public void focus() { - Iterator iterator = optionsContainer.iterator(); - if (iterator.hasNext()) { - ((Focusable) iterator.next()).setFocus(true); - } + optionsContainer.focus(); } public boolean isHtmlContentAllowed() { @@ -210,4 +226,14 @@ public class VCheckBoxGroup extends Composite implements Field, ClickHandler, return (Registration) () -> selectionChangeListeners .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/optiongroup/CheckBoxGroupConnector.java b/client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java index 254ae984d2..289a89eadb 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 @@ -24,6 +24,7 @@ import java.util.List; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.connectors.AbstractListingConnector; 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; @@ -38,10 +39,20 @@ import elemental.json.JsonObject; public class CheckBoxGroupConnector extends AbstractListingConnector> { + private ConnectorFocusAndBlurHandler handler; + @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) { @@ -88,4 +99,5 @@ public class CheckBoxGroupConnector public CheckBoxGroupState getState() { return (CheckBoxGroupState) super.getState(); } + } diff --git a/client/src/main/java/com/vaadin/client/widgets/ChildFocusAwareFlowPanel.java b/client/src/main/java/com/vaadin/client/widgets/ChildFocusAwareFlowPanel.java new file mode 100644 index 0000000000..1f801f5838 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/widgets/ChildFocusAwareFlowPanel.java @@ -0,0 +1,183 @@ +/* + * 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 java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Style.OutlineStyle; +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.dom.client.HasAllFocusHandlers; +import com.google.gwt.event.shared.HandlerManager; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.FocusWidget; +import com.google.gwt.user.client.ui.Focusable; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ui.FocusableFlowPanel; + +/** + * Focusable flow panel which fires focus/blur events if it or any of its child + * is focused/blured, but doesn't fire events if it happens between its content + * (child) elements. + * + * @author Vaadin Ltd + * + */ +public class ChildFocusAwareFlowPanel extends FocusableFlowPanel + implements HasAllFocusHandlers { + + private class FocusBlurHandler implements BlurHandler, FocusHandler { + + private boolean blurOccured; + + @Override + public void onBlur(BlurEvent event) { + blurOccured = true; + Scheduler.get().scheduleDeferred(() -> fireBlurEvent(event)); + } + + @Override + public void onFocus(FocusEvent event) { + if (!blurOccured) { + // no blur occured before this focus event + eventBus.fireEvent(event); + } else { + // blur occured before this focus event + // another component inside the panel was + // blurred => do not fire the focus and set blurOccured to + // false, so + // blur will not be fired, too + blurOccured = false; + } + } + + private void fireBlurEvent(BlurEvent event) { + if (blurOccured) { + eventBus.fireEvent(event); + blurOccured = false; + } + } + } + + private final HandlerManager eventBus; + + private final FocusBlurHandler handler = new FocusBlurHandler(); + + private final Map focusRegistrations = new HashMap<>(); + private final Map blurRegistrations = new HashMap<>(); + + /** + * Creates a new panel instance. + */ + public ChildFocusAwareFlowPanel() { + eventBus = new HandlerManager(this); + getElement().getStyle().setOutlineStyle(OutlineStyle.NONE); + super.addFocusHandler(handler); + super.addBlurHandler(handler); + } + + @Override + public void add(Widget widget) { + super.add(widget); + addHandlers(widget); + } + + @Override + public void clear() { + super.clear(); + focusRegistrations.clear(); + blurRegistrations.clear(); + } + + @Override + public void insert(Widget widget, int beforeIndex) { + super.insert(widget, beforeIndex); + addHandlers(widget); + } + + @Override + public boolean remove(int index) { + Widget widget = getWidget(index); + boolean isRemoved = super.remove(index); + if (isRemoved) { + removeHandlers(widget); + } + return isRemoved; + } + + @Override + public boolean remove(Widget widget) { + boolean isRemoved = super.remove(widget); + if (isRemoved) { + removeHandlers(widget); + } + return isRemoved; + } + + @Override + public HandlerRegistration addFocusHandler(FocusHandler handler) { + return eventBus.addHandler(FocusEvent.getType(), handler); + } + + @Override + public HandlerRegistration addBlurHandler(BlurHandler handler) { + return eventBus.addHandler(BlurEvent.getType(), handler); + } + + @Override + public void focus() { + Iterator iterator = iterator(); + if (iterator.hasNext()) { + Widget widget = iterator.next(); + if (widget instanceof Focusable) { + ((Focusable) widget).setFocus(true); + } + } + } + + private void addHandlers(Widget widget) { + if (focusRegistrations.containsKey(widget)) { + assert blurRegistrations.containsKey(widget); + return; + } + if (widget instanceof FocusWidget) { + HandlerRegistration focusRegistration = ((FocusWidget) widget) + .addFocusHandler(handler); + HandlerRegistration blurRegistration = ((FocusWidget) widget) + .addBlurHandler(handler); + focusRegistrations.put(widget, focusRegistration); + blurRegistrations.put(widget, blurRegistration); + } + } + + private void removeHandlers(Widget widget) { + HandlerRegistration focusRegistration = focusRegistrations + .remove(widget); + if (focusRegistration != null) { + focusRegistration.removeHandler(); + } + HandlerRegistration blurRegistration = blurRegistrations.remove(widget); + if (blurRegistration != null) { + blurRegistration.removeHandler(); + } + } + +} diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VOptionGroup.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VOptionGroup.java index b67edfe93c..d4e1869a90 100644 --- a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VOptionGroup.java +++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VOptionGroup.java @@ -95,8 +95,8 @@ public class VOptionGroup extends VOptionGroupBase public VOptionGroup() { super(CLASSNAME); panel = (Panel) optionsContainer; - optionsToKeys = new HashMap(); - optionsEnabled = new HashMap(); + optionsToKeys = new HashMap<>(); + optionsEnabled = new HashMap<>(); wasMultiselect = isMultiselect(); } @@ -113,12 +113,12 @@ public class VOptionGroup extends VOptionGroupBase * rebuilt (losing focus) if number of elements or their order is * changed. */ - HashMap keysToOptions = new HashMap(); + HashMap keysToOptions = new HashMap<>(); for (Map.Entry entry : optionsToKeys.entrySet()) { keysToOptions.put(entry.getValue(), entry.getKey()); } - ArrayList existingwidgets = new ArrayList(); - ArrayList newwidgets = new ArrayList(); + ArrayList existingwidgets = new ArrayList<>(); + ArrayList newwidgets = new ArrayList<>(); // Get current order of elements for (Widget wid : panel) { diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/CheckBox.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/CheckBox.java index d3c7b1a50c..a75815d9df 100644 --- a/compatibility-server/src/main/java/com/vaadin/v7/ui/CheckBox.java +++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/CheckBox.java @@ -23,7 +23,7 @@ import org.jsoup.nodes.Element; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; -import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator; import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.shared.MouseEventDetails; @@ -67,20 +67,12 @@ public class CheckBox extends AbstractField { } }; - FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl( - this) { - @Override - protected void fireEvent(Event event) { - CheckBox.this.fireEvent(event); - } - }; - /** * Creates a new checkbox. */ public CheckBox() { registerRpc(rpc); - registerRpc(focusBlurRpc); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); setValue(Boolean.FALSE); } diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/NativeSelect.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/NativeSelect.java index 3d535f2584..a782956867 100644 --- a/compatibility-server/src/main/java/com/vaadin/v7/ui/NativeSelect.java +++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/NativeSelect.java @@ -20,7 +20,7 @@ import java.util.Collection; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; -import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator; import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.v7.data.Container; @@ -37,33 +37,24 @@ import com.vaadin.v7.event.FieldEvents; public class NativeSelect extends AbstractSelect implements FieldEvents.BlurNotifier, FieldEvents.FocusNotifier { - FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl( - this) { - - @Override - protected void fireEvent(Event event) { - NativeSelect.this.fireEvent(event); - } - }; - public NativeSelect() { super(); - registerRpc(focusBlurRpc); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); } public NativeSelect(String caption, Collection options) { super(caption, options); - registerRpc(focusBlurRpc); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); } public NativeSelect(String caption, Container dataSource) { super(caption, dataSource); - registerRpc(focusBlurRpc); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); } public NativeSelect(String caption) { super(caption); - registerRpc(focusBlurRpc); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); } @Override diff --git a/server/src/main/java/com/vaadin/event/FieldEvents.java b/server/src/main/java/com/vaadin/event/FieldEvents.java index 3a4cfc209e..bb14cc7339 100644 --- a/server/src/main/java/com/vaadin/event/FieldEvents.java +++ b/server/src/main/java/com/vaadin/event/FieldEvents.java @@ -19,6 +19,7 @@ package com.vaadin.event; import java.io.Serializable; import java.lang.reflect.Method; +import com.vaadin.server.SerializableConsumer; import com.vaadin.shared.EventId; import com.vaadin.shared.Registration; import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; @@ -212,4 +213,36 @@ public interface FieldEvents { } } + /** + * Focus and blur server RPC implementation which fires focus or blur event + * using a provided event handler. + * + * @author Vaadin Ltd + * + */ + public static class FocusAndBlurServerRpcDecorator + extends FocusAndBlurServerRpcImpl { + + private final SerializableConsumer eventHandler; + + /** + * Create a new decorator instance. + * + * @param component + * the source events component + * @param eventHandler + * the event handler to delegate event firing + */ + public FocusAndBlurServerRpcDecorator(Component component, + SerializableConsumer eventHandler) { + super(component); + this.eventHandler = eventHandler; + } + + @Override + protected void fireEvent(Event event) { + eventHandler.accept(event); + } + } + } diff --git a/server/src/main/java/com/vaadin/server/SerializableConsumer.java b/server/src/main/java/com/vaadin/server/SerializableConsumer.java new file mode 100644 index 0000000000..817e22f1e2 --- /dev/null +++ b/server/src/main/java/com/vaadin/server/SerializableConsumer.java @@ -0,0 +1,35 @@ +/* + * 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.server; + +import java.io.Serializable; +import java.util.function.Consumer; + +/** + * A {@link Consumer} that is also {@link Serializable}. + * + * @see {@link Consumer} + * @param + * the type of the first argument to the operation + * + * @since 8.0 + * @author Vaadin Ltd + * + */ +@FunctionalInterface +public interface SerializableConsumer extends Consumer, Serializable { + // Only method inherited from Consumer +} diff --git a/server/src/main/java/com/vaadin/ui/AbstractFocusable.java b/server/src/main/java/com/vaadin/ui/AbstractFocusable.java index eb0c957a80..3684d87dcd 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractFocusable.java +++ b/server/src/main/java/com/vaadin/ui/AbstractFocusable.java @@ -15,12 +15,10 @@ */ package com.vaadin.ui; -import java.util.Objects; - import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurNotifier; -import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator; import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.event.FieldEvents.FocusNotifier; @@ -39,12 +37,7 @@ public abstract class AbstractFocusable extends AbstractComponent implements Focusable, FocusNotifier, BlurNotifier { protected AbstractFocusable() { - registerRpc(new FocusAndBlurServerRpcImpl(this) { - @Override - protected void fireEvent(Event event) { - AbstractFocusable.this.fireEvent(event); - } - }); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); } @Override diff --git a/server/src/main/java/com/vaadin/ui/CheckBox.java b/server/src/main/java/com/vaadin/ui/CheckBox.java index 9a6394863e..a0ace05466 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBox.java +++ b/server/src/main/java/com/vaadin/ui/CheckBox.java @@ -25,7 +25,7 @@ import org.jsoup.nodes.Element; import com.vaadin.event.FieldEvents; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; -import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator; import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.shared.MouseEventDetails; @@ -69,20 +69,12 @@ public class CheckBox extends AbstractField } }; - FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl( - this) { - @Override - protected void fireEvent(Event event) { - CheckBox.this.fireEvent(event); - } - }; - /** * Creates a new checkbox. */ public CheckBox() { registerRpc(rpc); - registerRpc(focusBlurRpc); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); setValue(Boolean.FALSE); } diff --git a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java index c088949899..9c25b3e75b 100644 --- a/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java +++ b/server/src/main/java/com/vaadin/ui/CheckBoxGroup.java @@ -19,8 +19,16 @@ package com.vaadin.ui; import java.util.Collection; import com.vaadin.data.Listing; +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.SerializablePredicate; import com.vaadin.server.data.DataSource; +import com.vaadin.shared.Registration; import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState; /** @@ -32,7 +40,8 @@ import com.vaadin.shared.ui.optiongroup.CheckBoxGroupState; * @author Vaadin Ltd * @since 8.0 */ -public class CheckBoxGroup extends AbstractMultiSelect { +public class CheckBoxGroup extends AbstractMultiSelect + implements FocusNotifier, BlurNotifier { /** * Constructs a new CheckBoxGroup with caption. @@ -80,6 +89,7 @@ public class CheckBoxGroup extends AbstractMultiSelect { * @see Listing#setDataSource(DataSource) */ public CheckBoxGroup() { + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); } /** @@ -137,4 +147,32 @@ public class CheckBoxGroup extends AbstractMultiSelect { SerializablePredicate itemEnabledProvider) { super.setItemEnabledProvider(itemEnabledProvider); } + + @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); + } } diff --git a/server/src/main/java/com/vaadin/ui/ComboBox.java b/server/src/main/java/com/vaadin/ui/ComboBox.java index 5c40a744f7..8ceaec125c 100644 --- a/server/src/main/java/com/vaadin/ui/ComboBox.java +++ b/server/src/main/java/com/vaadin/ui/ComboBox.java @@ -26,7 +26,7 @@ import com.vaadin.data.HasValue; import com.vaadin.event.FieldEvents; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; -import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator; import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.server.KeyMapper; @@ -118,14 +118,6 @@ public class ComboBox extends AbstractSingleSelect implements HasValue, } }; - private FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl( - this) { - @Override - protected void fireEvent(Component.Event event) { - ComboBox.this.fireEvent(event); - } - }; - private String filterstring; /** @@ -218,7 +210,7 @@ public class ComboBox extends AbstractSingleSelect implements HasValue, */ private void init() { registerRpc(rpc); - registerRpc(focusBlurRpc); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); addDataGenerator((T data, JsonObject jsonObject) -> { jsonObject.put(DataCommunicatorConstants.NAME, diff --git a/server/src/main/java/com/vaadin/ui/TabSheet.java b/server/src/main/java/com/vaadin/ui/TabSheet.java index 8cf0c26731..b246e14796 100644 --- a/server/src/main/java/com/vaadin/ui/TabSheet.java +++ b/server/src/main/java/com/vaadin/ui/TabSheet.java @@ -31,7 +31,7 @@ import org.jsoup.nodes.Element; import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurNotifier; -import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcDecorator; import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.event.FieldEvents.FocusNotifier; @@ -134,7 +134,7 @@ public class TabSheet extends AbstractComponentContainer super(); registerRpc(rpc); - registerRpc(focusBlurRpc); + registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent)); // expand horizontally by default setWidth(100, UNITS_PERCENTAGE); @@ -675,15 +675,6 @@ public class TabSheet extends AbstractComponentContainer private TabsheetServerRpcImpl rpc = new TabsheetServerRpcImpl(); - private FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl( - this) { - - @Override - protected void fireEvent(Event event) { - TabSheet.this.fireEvent(event); - } - }; - /** * Replaces a component (tab content) with another. This can be used to * change tab contents or to rearrange tabs. The tab position and some diff --git a/uitest/src/main/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupFocusBlur.java b/uitest/src/main/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupFocusBlur.java new file mode 100644 index 0000000000..2b43a8134e --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupFocusBlur.java @@ -0,0 +1,41 @@ +/* + * 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.checkboxgroup; + +import java.util.stream.IntStream; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.CheckBoxGroup; + +/** + * @author Vaadin Ltd + * + */ +public class CheckBoxGroupFocusBlur extends AbstractTestUIWithLog { + + @Override + protected void setup(VaadinRequest request) { + CheckBoxGroup group = new CheckBoxGroup<>(); + group.setItems(IntStream.range(1, 10).mapToObj(Integer::valueOf) + .toArray(Integer[]::new)); + addComponent(group); + + group.addFocusListener(event -> log("Focus Event")); + group.addBlurListener(event -> log("Blur Event")); + } + +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupFocusBlurTest.java b/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupFocusBlurTest.java new file mode 100644 index 0000000000..0da1fa4a88 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupFocusBlurTest.java @@ -0,0 +1,86 @@ +/* + * 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.checkboxgroup; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.testbench.customelements.CheckBoxGroupElement; +import com.vaadin.testbench.elements.LabelElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +/** + * @author Vaadin Ltd + * + */ +public class CheckBoxGroupFocusBlurTest extends MultiBrowserTest { + + @Test + public void focusBlurEvents() { + openTestURL(); + + List checkBoxes = $(CheckBoxGroupElement.class).first() + .findElements(By.tagName("input")); + checkBoxes.get(0).click(); + + // Focus event is fired + Assert.assertTrue(logContainsText("1. Focus Event")); + + checkBoxes.get(1).click(); + // click on the second checkbox doesn't fire anything + Assert.assertFalse(logContainsText("2.")); + + // click in the middle between the first and the second (inside group). + WebElement first = checkBoxes.get(0); + int middle = (first.getLocation().y + first.getSize().height + + checkBoxes.get(1).getLocation().y) / 2; + new Actions(getDriver()).moveByOffset(first.getLocation().x, middle) + .click().build().perform(); + // no new events + Assert.assertFalse(logContainsText("2.")); + + // click to label of a checkbox + $(CheckBoxGroupElement.class).first().findElements(By.tagName("label")) + .get(2).click(); + // no new events + Assert.assertFalse(logContainsText("2.")); + + // click on log label => blur + $(LabelElement.class).first().click(); + // blur event is fired + Assert.assertTrue(logContainsText("2. Blur Event")); + + checkBoxes.get(3).click(); + // Focus event is fired + Assert.assertTrue(logContainsText("3. Focus Event")); + + // move keyboard focus to the next checkbox + checkBoxes.get(3).sendKeys(Keys.TAB); + // no new events + Assert.assertFalse(logContainsText("4.")); + + // select the next checkbox + checkBoxes.get(4).sendKeys(Keys.SPACE); + // no new events + Assert.assertFalse(logContainsText("4.")); + } +}