summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorDenis Anisimov <denis@vaadin.com>2016-10-26 16:22:00 +0300
committerVaadin Code Review <review@vaadin.com>2016-10-31 11:55:10 +0000
commit9c88657db52d73f6dd8b976ce0d4cada643b2a4d (patch)
treefde7408221abc7b25379cba078e2d92d59163526 /client
parentbcd7259e1092badf9abc6aa2146decdcc4611f14 (diff)
downloadvaadin-framework-9c88657db52d73f6dd8b976ce0d4cada643b2a4d.tar.gz
vaadin-framework-9c88657db52d73f6dd8b976ce0d4cada643b2a4d.zip
Implement focus and blur events for CheckBoxGroup.
Fixes vaadin/framework8-issues#334 Change-Id: I4c7ca424cc4f4a1f0cdecd7671827465ab74ace7
Diffstat (limited to 'client')
-rw-r--r--client/src/main/java/com/vaadin/client/EventHelper.java32
-rw-r--r--client/src/main/java/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java72
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VCheckBoxGroup.java106
-rw-r--r--client/src/main/java/com/vaadin/client/ui/optiongroup/CheckBoxGroupConnector.java12
-rw-r--r--client/src/main/java/com/vaadin/client/widgets/ChildFocusAwareFlowPanel.java183
5 files changed, 354 insertions, 51 deletions
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<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;
}
+
}
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,
* <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);
+ }
}
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<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
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<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();
+ }
+ }
+
+}