Fixes vaadin/framework8-issues#334 Change-Id: I4c7ca424cc4f4a1f0cdecd7671827465ab74ace7tags/8.0.0.alpha6
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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) { |
@@ -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); | |||
} | |||
@@ -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 |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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 | |||
} |
@@ -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 |
@@ -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); | |||
} | |||
@@ -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); | |||
} | |||
} |
@@ -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, |
@@ -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 |
@@ -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")); | |||
} | |||
} |
@@ -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.")); | |||
} | |||
} |