summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/main/java/com/vaadin/client/ui/FocusUtil.java23
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VWindow.java121
-rw-r--r--client/src/main/java/com/vaadin/client/ui/window/WindowOrderEvent.java74
-rw-r--r--client/src/main/java/com/vaadin/client/ui/window/WindowOrderHandler.java36
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/window/ModalWindowFocus.java2
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/window/BackspaceKeyWithModalOpenedTest.java8
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();