diff options
6 files changed, 236 insertions, 28 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/FocusUtil.java b/client/src/main/java/com/vaadin/client/ui/FocusUtil.java index 27a1015d77..7b55fa958c 100644 --- a/client/src/main/java/com/vaadin/client/ui/FocusUtil.java +++ b/client/src/main/java/com/vaadin/client/ui/FocusUtil.java @@ -15,6 +15,7 @@ */ package com.vaadin.client.ui; +import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.ui.Focusable; import com.google.gwt.user.client.ui.Widget; @@ -95,4 +96,26 @@ public class FocusUtil { return focusable.getElement().getTabIndex(); } + + public static native Element[] getFocusableChildren(Element parent) + /*-{ + var focusableChildren = parent.querySelectorAll('[type][tabindex]:not([tabindex="-1"]), [role=button][tabindex]:not([tabindex="-1"])'); + return focusableChildren; + }-*/; + + public static void focusOnFirstFocusableElement(Element parent) + { + Element[] focusableChildren = getFocusableChildren(parent); + if (focusableChildren.length > 0) { + focusableChildren[0].focus(); + } + } + + public static void focusOnLastFocusableElement(Element parent) + { + Element[] focusableChildren = getFocusableChildren(parent); + if (focusableChildren.length > 0) { + focusableChildren[focusableChildren.length - 1].focus(); + } + } } diff --git a/client/src/main/java/com/vaadin/client/ui/VWindow.java b/client/src/main/java/com/vaadin/client/ui/VWindow.java index cfdaecdb90..17bfd04ad8 100644 --- a/client/src/main/java/com/vaadin/client/ui/VWindow.java +++ b/client/src/main/java/com/vaadin/client/ui/VWindow.java @@ -46,6 +46,7 @@ import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; +import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; @@ -66,6 +67,8 @@ import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.client.ui.window.WindowMoveEvent; import com.vaadin.client.ui.window.WindowMoveHandler; +import com.vaadin.client.ui.window.WindowOrderEvent; +import com.vaadin.client.ui.window.WindowOrderHandler; import com.vaadin.shared.Connector; import com.vaadin.shared.EventId; import com.vaadin.shared.ui.window.WindowMode; @@ -79,7 +82,10 @@ import com.vaadin.shared.ui.window.WindowRole; public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable { - private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>(); + private static List<VWindow> windowOrder = new ArrayList<VWindow>(); + + private static final HandlerManager WINDOW_ORDER_HANDLER = new HandlerManager( + VWindow.class); private static boolean orderingDefered; @@ -301,14 +307,37 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } public void bringToFront() { - int curIndex = windowOrder.indexOf(this); + bringToFront(true); + } + + private void bringToFront(boolean notifyListeners) { + int curIndex = getWindowOrder(); if (curIndex + 1 < windowOrder.size()) { windowOrder.remove(this); windowOrder.add(this); for (; curIndex < windowOrder.size(); curIndex++) { - windowOrder.get(curIndex).setWindowOrder(curIndex); + VWindow window = windowOrder.get(curIndex); + window.setWindowOrder(curIndex); } } + if (notifyListeners) { + fireOrderEvent(); + } + } + + static void fireOrderEvent() { + fireOrderEvent(windowOrder); + } + + private void doFireOrderEvent() { + List<VWindow> list = new ArrayList<VWindow>(); + list.add(this); + fireOrderEvent(list); + } + + private static void fireOrderEvent(List<VWindow> windows) { + WINDOW_ORDER_HANDLER.fireEvent( + new WindowOrderEvent(new ArrayList<VWindow>(windows))); } /** @@ -321,7 +350,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } private static VWindow getTopmostWindow() { - if (windowOrder.size() > 0) { + if (!windowOrder.isEmpty()) { return windowOrder.get(windowOrder.size() - 1); } return null; @@ -340,13 +369,22 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, windowOrder.add(this); setPopupPosition(order * STACKING_OFFSET_PIXELS, order * STACKING_OFFSET_PIXELS); - + doFireOrderEvent(); } private void setWindowOrder(int order) { setZIndex(order + Z_INDEX); } + /** + * Returns window position in list of opened and shown windows. + * + * @since 7.7.12 + */ + public final int getWindowOrder() { + return windowOrder.indexOf(this); + } + @Override protected void setZIndex(int zIndex) { super.setZIndex(zIndex); @@ -424,16 +462,22 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, Roles.getDialogRole().setAriaLabelledbyProperty(getElement(), Id.of(headerText)); - // Handlers to Prevent tab to leave the window + // Handlers to Prevent tab to leave the window (by circulating focus) // and backspace to cause browser navigation topEventBlocker = new NativePreviewHandler() { @Override public void onPreviewNativeEvent(NativePreviewEvent event) { + if (!getElement() + .isOrHasChild(WidgetUtil.getFocusedElement())) { + return; + } NativeEvent nativeEvent = event.getNativeEvent(); if (nativeEvent.getEventTarget().cast() == topTabStop && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB && nativeEvent.getShiftKey()) { nativeEvent.preventDefault(); + FocusUtil.focusOnLastFocusableElement( + VWindow.this.getElement()); } if (nativeEvent.getEventTarget().cast() == topTabStop && nativeEvent.getKeyCode() == KeyCodes.KEY_BACKSPACE) { @@ -445,11 +489,17 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, bottomEventBlocker = new NativePreviewHandler() { @Override public void onPreviewNativeEvent(NativePreviewEvent event) { + if (!getElement() + .isOrHasChild(WidgetUtil.getFocusedElement())) { + return; + } NativeEvent nativeEvent = event.getNativeEvent(); if (nativeEvent.getEventTarget().cast() == bottomTabStop && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB && !nativeEvent.getShiftKey()) { nativeEvent.preventDefault(); + FocusUtil.focusOnFirstFocusableElement( + VWindow.this.getElement()); } if (nativeEvent.getEventTarget().cast() == bottomTabStop && nativeEvent.getKeyCode() == KeyCodes.KEY_BACKSPACE) { @@ -539,27 +589,28 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, @Override public int compare(VWindow o1, VWindow o2) { - /* - * Order by modality, then by bringtofront sequence. - */ + /* + * Order by modality, then by bringtofront sequence. + */ if (o1.vaadinModality && !o2.vaadinModality) { return 1; - } else if (!o1.vaadinModality && o2.vaadinModality) { + } + if (!o1.vaadinModality && o2.vaadinModality) { return -1; - } else if (o1.bringToFrontSequence > o2.bringToFrontSequence) { + } + if (o1.bringToFrontSequence > o2.bringToFrontSequence) { return 1; - } else if (o1.bringToFrontSequence < o2.bringToFrontSequence) { + } + if (o1.bringToFrontSequence < o2.bringToFrontSequence) { return -1; - } else { - return 0; } + return 0; } }); - for (int i = 0; i < array.length; i++) { - VWindow w = array[i]; + for (VWindow w : array) { if (w.bringToFrontSequence != -1 || w.vaadinModality) { - w.bringToFront(); + w.bringToFront(false); w.bringToFrontSequence = -1; } } @@ -568,9 +619,10 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, private static void focusTopmostModalWindow() { VWindow topmost = getTopmostWindow(); - if ((topmost != null) && (topmost.vaadinModality)) { + if (topmost != null && topmost.vaadinModality) { topmost.focus(); } + fireOrderEvent(); } @Override @@ -697,15 +749,21 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } super.hide(); - int curIndex = windowOrder.indexOf(this); + int curIndex = getWindowOrder(); // Remove window from windowOrder to avoid references being left // hanging. windowOrder.remove(curIndex); // Update the z-indices of any remaining windows + List<VWindow> update = new ArrayList<VWindow>( + windowOrder.size() - curIndex + 1); + update.add(this); while (curIndex < windowOrder.size()) { - windowOrder.get(curIndex).setWindowOrder(curIndex++); + VWindow window = windowOrder.get(curIndex); + window.setWindowOrder(curIndex++); + update.add(window); } focusTopmostModalWindow(); + fireOrderEvent(update); } private void fixIE8FocusCaptureIssue() { @@ -744,8 +802,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } private void showModalityCurtain() { - getModalityCurtain().getStyle() - .setZIndex(windowOrder.indexOf(this) + Z_INDEX); + getModalityCurtain().getStyle().setZIndex(getWindowOrder() + Z_INDEX); if (isShowing()) { getOverlayContainer().insertBefore(getModalityCurtain(), @@ -1213,7 +1270,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, // Override PopupPanel which sets the width to the contents getElement().getStyle().setProperty("width", width); // Update v-has-width in case undefined window is resized - setStyleName("v-has-width", width != null && width.length() > 0); + setStyleName("v-has-width", width != null && !width.isEmpty()); } @Override @@ -1221,7 +1278,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, // Override PopupPanel which sets the height to the contents getElement().getStyle().setProperty("height", height); // Update v-has-height in case undefined window is resized - setStyleName("v-has-height", height != null && height.length() > 0); + setStyleName("v-has-height", height != null && !height.isEmpty()); } private void onDragEvent(Event event) { @@ -1519,8 +1576,21 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } /** + * Adds a Handler for window order change event. + * + * @since 7.7.12 + * + * @return registration object to deregister the handler + */ + public static HandlerRegistration addWindowOrderHandler( + WindowOrderHandler handler) { + return WINDOW_ORDER_HANDLER.addHandler(WindowOrderEvent.getType(), + handler); + } + + /** * Checks if a modal window is currently open. - * + * * @return <code>true</code> if a modal window is open, <code>false</code> * otherwise. */ @@ -1528,5 +1598,4 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, return Document.get().getBody() .hasClassName(MODAL_WINDOW_OPEN_CLASSNAME); } - } diff --git a/client/src/main/java/com/vaadin/client/ui/window/WindowOrderEvent.java b/client/src/main/java/com/vaadin/client/ui/window/WindowOrderEvent.java new file mode 100644 index 0000000000..016f6096f9 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/window/WindowOrderEvent.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2014 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.ui.window; + +import java.util.ArrayList; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.ui.VWindow; + +/** + * Event for window order position updates. + * + * @since 7.7.12 + * + * @author Vaadin Ltd + */ +public class WindowOrderEvent extends GwtEvent<WindowOrderHandler> { + + private static final Type<WindowOrderHandler> TYPE = new Type<WindowOrderHandler>(); + + private final ArrayList<VWindow> windows; + + /** + * Creates a new event with the given order. + * + * @param windows + * The new order position for the VWindow + */ + public WindowOrderEvent(ArrayList<VWindow> windows) { + this.windows = windows; + } + + @Override + public Type<WindowOrderHandler> getAssociatedType() { + return TYPE; + } + + /** + * Returns windows in order. + * + * @return windows in the specific order + */ + public VWindow[] getWindows() { + return windows.toArray(new VWindow[windows.size()]); + } + + @Override + protected void dispatch(WindowOrderHandler handler) { + handler.onWindowOrderChange(this); + } + + /** + * Gets the type of the event. + * + * @return the type of the event + */ + public static Type<WindowOrderHandler> getType() { + return TYPE; + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/window/WindowOrderHandler.java b/client/src/main/java/com/vaadin/client/ui/window/WindowOrderHandler.java new file mode 100644 index 0000000000..cb879095c7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/window/WindowOrderHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2014 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.ui.window; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler for {@link WindowOrderEvent}s. + * + * @since 7.7.12 + * + * @author Vaadin Ltd + */ +public interface WindowOrderHandler extends EventHandler { + + /** + * Called when the VWindow instances changed their order position. + * + * @param event + * Contains windows whose position has changed + */ + public void onWindowOrderChange(WindowOrderEvent event); +}
\ No newline at end of file diff --git a/uitest/src/main/java/com/vaadin/tests/components/window/ModalWindowFocus.java b/uitest/src/main/java/com/vaadin/tests/components/window/ModalWindowFocus.java index 01f27dc954..4aec13ad82 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/window/ModalWindowFocus.java +++ b/uitest/src/main/java/com/vaadin/tests/components/window/ModalWindowFocus.java @@ -71,4 +71,4 @@ public class ModalWindowFocus extends AbstractTestUI { return 17021; } -}
\ No newline at end of file +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/window/BackspaceKeyWithModalOpenedTest.java b/uitest/src/test/java/com/vaadin/tests/components/window/BackspaceKeyWithModalOpenedTest.java index e561d21c71..1c289c3810 100644 --- a/uitest/src/test/java/com/vaadin/tests/components/window/BackspaceKeyWithModalOpenedTest.java +++ b/uitest/src/test/java/com/vaadin/tests/components/window/BackspaceKeyWithModalOpenedTest.java @@ -22,6 +22,7 @@ import static org.openqa.selenium.Keys.BACK_SPACE; import static org.openqa.selenium.Keys.TAB; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; @@ -60,9 +61,14 @@ public class BackspaceKeyWithModalOpenedTest extends MultiBrowserTest { } /** - * Tests that backspace action in the bottom component is prevented + * Tests that backspace action in the bottom component is prevented. + * + * Ignored because the fix to #8855 stops the top and bottom components + * from functioning as focus traps. Meanwhile, navigation with Backspace + * is not anymore supported by reasonable browsers. */ @Test + @Ignore public void testWithFocusOnBottom() throws Exception { TextFieldElement textField = getTextField(); |