]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement focus and blur events for CheckBoxGroup.
authorDenis Anisimov <denis@vaadin.com>
Wed, 26 Oct 2016 13:22:00 +0000 (16:22 +0300)
committerVaadin Code Review <review@vaadin.com>
Mon, 31 Oct 2016 11:55:10 +0000 (11:55 +0000)
Fixes vaadin/framework8-issues#334

Change-Id: I4c7ca424cc4f4a1f0cdecd7671827465ab74ace7

17 files changed:
client/src/main/java/com/vaadin/client/EventHelper.java
client/src/main/java/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java
client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java
client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java
client/src/main/java/com/vaadin/client/widgets/ChildFocusAwareFlowPanel.java [new file with mode: 0644]
compatibility-client/src/main/java/com/vaadin/v7/client/ui/VOptionGroup.java
compatibility-server/src/main/java/com/vaadin/v7/ui/CheckBox.java
compatibility-server/src/main/java/com/vaadin/v7/ui/NativeSelect.java
server/src/main/java/com/vaadin/event/FieldEvents.java
server/src/main/java/com/vaadin/server/SerializableConsumer.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/AbstractFocusable.java
server/src/main/java/com/vaadin/ui/CheckBox.java
server/src/main/java/com/vaadin/ui/CheckBoxGroup.java
server/src/main/java/com/vaadin/ui/ComboBox.java
server/src/main/java/com/vaadin/ui/TabSheet.java
uitest/src/main/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupFocusBlur.java [new file with mode: 0644]
uitest/src/test/java/com/vaadin/tests/components/checkboxgroup/CheckBoxGroupFocusBlurTest.java [new file with mode: 0644]

index 2f23d853d884a053265dce5b723f4ff22fcb531d..0212fae26ca0779107fe47e0d7b866af3d041bcb 100644 (file)
@@ -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<H> 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 <H extends EventHandler, W extends Widget> HandlerRegistration updateHandler(
+            ComponentConnector connector, String eventIdentifier,
+            HandlerRegistration handlerRegistration,
+            Supplier<HandlerRegistration> 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;
     }
+
 }
index 54b687cc3adcc6c60a65b90e34eb0b0e2f936e80..a023f152cf4aacecc19b9a1a9204b2a5ad0e224e 100644 (file)
@@ -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);
     }
index e47598426b92e27f44409acecd086fa2b9200d08..d99d179d7483e77bb6f39929aac1beae134cb787 100644 (file)
@@ -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,
      * <p>
      * 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<BiConsumer<JsonObject, Boolean>> 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<JsonObject> 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<Widget> 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<Widget> 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);
+    }
 }
index 254ae984d2e922fa1f875b252d0629e6cd79f969..289a89eadb5a89d2d3fbacd76eb6fad61aa64ef9 100644 (file)
@@ -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<SelectionModel<?>> {
 
+    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 (file)
index 0000000..1f801f5
--- /dev/null
@@ -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<Widget, HandlerRegistration> focusRegistrations = new HashMap<>();
+    private final Map<Widget, HandlerRegistration> 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<Widget> 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();
+        }
+    }
+
+}
index b67edfe93c5b9478fde716eb88f869691be7d303..d4e1869a90fb7c176d5e54493fe52feea630563a 100644 (file)
@@ -95,8 +95,8 @@ public class VOptionGroup extends VOptionGroupBase
     public VOptionGroup() {
         super(CLASSNAME);
         panel = (Panel) optionsContainer;
-        optionsToKeys = new HashMap<CheckBox, String>();
-        optionsEnabled = new HashMap<CheckBox, Boolean>();
+        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<String, CheckBox> keysToOptions = new HashMap<String, CheckBox>();
+        HashMap<String, CheckBox> keysToOptions = new HashMap<>();
         for (Map.Entry<CheckBox, String> entry : optionsToKeys.entrySet()) {
             keysToOptions.put(entry.getValue(), entry.getKey());
         }
-        ArrayList<Widget> existingwidgets = new ArrayList<Widget>();
-        ArrayList<Widget> newwidgets = new ArrayList<Widget>();
+        ArrayList<Widget> existingwidgets = new ArrayList<>();
+        ArrayList<Widget> newwidgets = new ArrayList<>();
 
         // Get current order of elements
         for (Widget wid : panel) {
index d3c7b1a50c1bedf7967d7eab607852d9ecb05542..a75815d9df52612fe465a0cde9d29ecbaa07731b 100644 (file)
@@ -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<Boolean> {
         }
     };
 
-    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);
     }
 
index 3d535f25841e5d06e494378bf989bf00f59426d7..a782956867b8f7683559c4f9e4bfc20fff2e1937 100644 (file)
@@ -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
index 3a4cfc209e8ee5a7e858143f1cead9bd9c1e0f35..bb14cc73398341a338612ef3b37def8970946574 100644 (file)
@@ -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<Event> 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<Event> 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 (file)
index 0000000..817e22f
--- /dev/null
@@ -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 <T>
+ *            the type of the first argument to the operation
+ * 
+ * @since 8.0
+ * @author Vaadin Ltd
+ *
+ */
+@FunctionalInterface
+public interface SerializableConsumer<T> extends Consumer<T>, Serializable {
+    // Only method inherited from Consumer
+}
index eb0c957a803cbc39ec0836bcbfe0251fb6314144..3684d87dcd7ff687c0a8c0f7b85b75c8ec1829cb 100644 (file)
  */
 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
index 9a6394863e608d546cc8f6c1d619c7831e1496e1..a0ace0546645326dc4e507bdf570788de47a3514 100644 (file)
@@ -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<Boolean>
         }
     };
 
-    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);
     }
 
index c088949899284306b709529b24ad18a731d3080d..9c25b3e75b1b683aa6773067465704f6c75b0c17 100644 (file)
@@ -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<T> extends AbstractMultiSelect<T> {
+public class CheckBoxGroup<T> extends AbstractMultiSelect<T>
+        implements FocusNotifier, BlurNotifier {
 
     /**
      * Constructs a new CheckBoxGroup with caption.
@@ -80,6 +89,7 @@ public class CheckBoxGroup<T> extends AbstractMultiSelect<T> {
      * @see Listing#setDataSource(DataSource)
      */
     public CheckBoxGroup() {
+        registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent));
     }
 
     /**
@@ -137,4 +147,32 @@ public class CheckBoxGroup<T> extends AbstractMultiSelect<T> {
             SerializablePredicate<T> 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);
+    }
 }
index 5c40a744f77100242deaf21bcdfbf90882620620..8ceaec125c4957d47d2b37f7bd775e104a5b707a 100644 (file)
@@ -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<T> extends AbstractSingleSelect<T> implements HasValue<T>,
         }
     };
 
-    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<T> extends AbstractSingleSelect<T> implements HasValue<T>,
      */
     private void init() {
         registerRpc(rpc);
-        registerRpc(focusBlurRpc);
+        registerRpc(new FocusAndBlurServerRpcDecorator(this, this::fireEvent));
 
         addDataGenerator((T data, JsonObject jsonObject) -> {
             jsonObject.put(DataCommunicatorConstants.NAME,
index 8cf0c267315f01b79cdd8f62e6b2321badebb9af..b246e147968ca1ccdacc14c6a954aa3dea95bee0 100644 (file)
@@ -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 (file)
index 0000000..2b43a81
--- /dev/null
@@ -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<Integer> 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 (file)
index 0000000..0da1fa4
--- /dev/null
@@ -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<WebElement> 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."));
+    }
+}