aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormichaelvogt <michael@vaadin.com>2013-06-14 17:08:38 +0300
committerVaadin Code Review <review@vaadin.com>2013-07-12 07:20:38 +0000
commite45e036598d6efcf26ba1d2c27b4eeadcb32a9b9 (patch)
tree3b4465ae54ec442f77fc1880f82b31172f56fda3
parent104e472d21e92c3023abc4cfe96e67861424927c (diff)
downloadvaadin-framework-e45e036598d6efcf26ba1d2c27b4eeadcb32a9b9.tar.gz
vaadin-framework-e45e036598d6efcf26ba1d2c27b4eeadcb32a9b9.zip
Prevent to exit a Window with the tab key (#11874)
Change-Id: Icd12ec6e2eac626ad493707dfa8288d620bb9bb7
-rw-r--r--client/src/com/vaadin/client/ui/VWindow.java142
-rw-r--r--client/src/com/vaadin/client/ui/window/WindowConnector.java5
-rw-r--r--server/src/com/vaadin/ui/Window.java77
-rw-r--r--shared/src/com/vaadin/shared/ui/window/WindowState.java3
-rw-r--r--uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java55
5 files changed, 270 insertions, 12 deletions
diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java
index 1756c619a7..2c85497707 100644
--- a/client/src/com/vaadin/client/ui/VWindow.java
+++ b/client/src/com/vaadin/client/ui/VWindow.java
@@ -25,6 +25,7 @@ import com.google.gwt.aria.client.RelevantValue;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
@@ -39,10 +40,13 @@ import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
@@ -152,6 +156,18 @@ public class VWindow extends VWindowOverlay implements
private String assistivePrefix;
private String assistivePostfix;
+ private Element topTabStop;
+ private Element bottomTabStop;
+
+ private NativePreviewHandler topEventBlocker;
+ private NativePreviewHandler bottomEventBlocker;
+
+ private HandlerRegistration topBlockerRegistration;
+ private HandlerRegistration bottomBlockerRegistration;
+
+ // Prevents leaving the window with the Tab key when true
+ private boolean doTabStop;
+
/**
* If centered (via UIDL), the window should stay in the centered -mode
* until a position is received from the server, or the user moves or
@@ -211,6 +227,12 @@ public class VWindow extends VWindowOverlay implements
* window is open.
*/
getApplicationConnection().getUIConnector().getWidget().storeFocus();
+
+ /*
+ * When this window gets reattached, set the tabstop to the previous
+ * state.
+ */
+ setTabStopEnabled(doTabStop);
}
@Override
@@ -225,6 +247,18 @@ public class VWindow extends VWindowOverlay implements
*/
getApplicationConnection().getUIConnector().getWidget()
.focusStoredElement();
+
+ removeTabBlockHandlers();
+ }
+
+ private void removeTabBlockHandlers() {
+ if (topBlockerRegistration != null) {
+ topBlockerRegistration.removeHandler();
+ topBlockerRegistration = null;
+
+ bottomBlockerRegistration.removeHandler();
+ bottomBlockerRegistration = null;
+ }
}
public void bringToFront() {
@@ -290,6 +324,9 @@ public class VWindow extends VWindowOverlay implements
protected void constructDOM() {
setStyleName(CLASSNAME);
+ topTabStop = DOM.createDiv();
+ DOM.setElementAttribute(topTabStop, "tabindex", "0");
+
header = DOM.createDiv();
DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
headerText = DOM.createDiv();
@@ -309,15 +346,20 @@ public class VWindow extends VWindowOverlay implements
DOM.setElementAttribute(closeBox, "tabindex", "0");
DOM.appendChild(footer, resizeBox);
+ bottomTabStop = DOM.createDiv();
+ DOM.setElementAttribute(bottomTabStop, "tabindex", "0");
+
wrapper = DOM.createDiv();
DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
+ DOM.appendChild(wrapper, topTabStop);
DOM.appendChild(wrapper, header);
DOM.appendChild(wrapper, maximizeRestoreBox);
DOM.appendChild(wrapper, closeBox);
DOM.appendChild(header, headerText);
DOM.appendChild(wrapper, contents);
DOM.appendChild(wrapper, footer);
+ DOM.appendChild(wrapper, bottomTabStop);
DOM.appendChild(super.getContainerElement(), wrapper);
sinkEvents(Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.TOUCHEVENTS
@@ -338,6 +380,83 @@ public class VWindow extends VWindowOverlay implements
AriaHelper.ensureHasId(headerText);
Roles.getDialogRole().setAriaLabelledbyProperty(getElement(),
Id.of(headerText));
+
+ // Handlers to Prevent tab to leave the window
+ topEventBlocker = new NativePreviewHandler() {
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ if (nativeEvent.getEventTarget().cast() == topTabStop
+ && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB
+ && nativeEvent.getShiftKey()) {
+ nativeEvent.preventDefault();
+ }
+ }
+ };
+
+ bottomEventBlocker = new NativePreviewHandler() {
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ NativeEvent nativeEvent = event.getNativeEvent();
+ if (nativeEvent.getEventTarget().cast() == bottomTabStop
+ && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB
+ && !nativeEvent.getShiftKey()) {
+ nativeEvent.preventDefault();
+ }
+ }
+ };
+ }
+
+ /**
+ * Sets the message that is provided to users of assistive devices when the
+ * user reaches the top of the window when leaving a window with the tab key
+ * is prevented.
+ * <p>
+ * This message is not visible on the screen.
+ *
+ * @param topMessage
+ * String provided when the user navigates with Shift-Tab keys to
+ * the top of the window
+ */
+ public void setTabStopTopAssistiveText(String topMessage) {
+ Roles.getNoteRole().setAriaLabelProperty(topTabStop, topMessage);
+ }
+
+ /**
+ * Sets the message that is provided to users of assistive devices when the
+ * user reaches the bottom of the window when leaving a window with the tab
+ * key is prevented.
+ * <p>
+ * This message is not visible on the screen.
+ *
+ * @param bottomMessage
+ * String provided when the user navigates with the Tab key to
+ * the bottom of the window
+ */
+ public void setTabStopBottomAssistiveText(String bottomMessage) {
+ Roles.getNoteRole().setAriaLabelProperty(bottomTabStop, bottomMessage);
+ }
+
+ /**
+ * Gets the message that is provided to users of assistive devices when the
+ * user reaches the top of the window when leaving a window with the tab key
+ * is prevented.
+ *
+ * @return the top message
+ */
+ public String getTabStopTopAssistiveText() {
+ return Roles.getNoteRole().getAriaLabelProperty(topTabStop);
+ }
+
+ /**
+ * Gets the message that is provided to users of assistive devices when the
+ * user reaches the bottom of the window when leaving a window with the tab
+ * key is prevented.
+ *
+ * @return the bottom message
+ */
+ public String getTabStopBottomAssistiveText() {
+ return Roles.getNoteRole().getAriaLabelProperty(bottomTabStop);
}
/**
@@ -1203,4 +1322,27 @@ public class VWindow extends VWindowOverlay implements
Roles.getDialogRole().set(getElement());
}
}
+
+ /**
+ * Registers the handlers that prevent to leave the window using the
+ * Tab-key.
+ *
+ * @param doTabStop
+ * true to prevent leaving the window, false to allow leaving the
+ * window
+ */
+ public void setTabStopEnabled(boolean doTabStop) {
+ this.doTabStop = doTabStop;
+
+ if (doTabStop) {
+ if (topBlockerRegistration == null) {
+ topBlockerRegistration = Event
+ .addNativePreviewHandler(topEventBlocker);
+ bottomBlockerRegistration = Event
+ .addNativePreviewHandler(bottomEventBlocker);
+ }
+ } else {
+ removeTabBlockHandlers();
+ }
+ }
}
diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java
index ea1073dd18..464ab386c1 100644
--- a/client/src/com/vaadin/client/ui/window/WindowConnector.java
+++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java
@@ -301,9 +301,12 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
window.setCaption(state.caption, iconURL);
window.setWaiAriaRole(getState().role);
-
window.setAssistiveDescription(state.contentDescription);
+ window.setTabStopEnabled(getState().assistiveTabStop);
+ window.setTabStopTopAssistiveText(getState().assistiveTabStopTopText);
+ window.setTabStopBottomAssistiveText(getState().assistiveTabStopBottomText);
+
clickEventHandler.handleEventHandlerRegistration();
window.immediate = state.immediate;
diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java
index 980e96c384..658c821f88 100644
--- a/server/src/com/vaadin/ui/Window.java
+++ b/server/src/com/vaadin/ui/Window.java
@@ -1100,4 +1100,81 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
public WindowRole getAssistiveRole() {
return getState().role;
}
+
+ /**
+ * Set if it should be prevented to set the focus to a component outside the
+ * window with the tab key.
+ * <p>
+ * This is meant to help users of assistive devices to not leaving the
+ * window unintentionally.
+ *
+ * @param tabStop
+ * true to keep the focus inside the window when reaching the top
+ * or bottom, false (default) to allow leaving the window
+ */
+ public void setTabStopEnabled(boolean tabStop) {
+ getState().assistiveTabStop = tabStop;
+ }
+
+ /**
+ * Get if it is prevented to leave a window with the tab key.
+ *
+ * @return true when the focus is limited to inside the window, false when
+ * focus can leave the window
+ */
+ public boolean isTabStopEnabled() {
+ return getState().assistiveTabStop;
+ }
+
+ /**
+ * Sets the message that is provided to users of assistive devices when the
+ * user reaches the top of the window when leaving a window with the tab key
+ * is prevented.
+ * <p>
+ * This message is not visible on the screen.
+ *
+ * @param topMessage
+ * String provided when the user navigates with Shift-Tab keys to
+ * the top of the window
+ */
+ public void setTabStopTopAssistiveText(String topMessage) {
+ getState().assistiveTabStopTopText = topMessage;
+ }
+
+ /**
+ * Sets the message that is provided to users of assistive devices when the
+ * user reaches the bottom of the window when leaving a window with the tab
+ * key is prevented.
+ * <p>
+ * This message is not visible on the screen.
+ *
+ * @param bottomMessage
+ * String provided when the user navigates with the Tab key to
+ * the bottom of the window
+ */
+ public void setTabStopBottomAssistiveText(String bottomMessage) {
+ getState().assistiveTabStopBottomText = bottomMessage;
+ }
+
+ /**
+ * Gets the message that is provided to users of assistive devices when the
+ * user reaches the top of the window when leaving a window with the tab key
+ * is prevented.
+ *
+ * @return the top message
+ */
+ public String getTabStopTopAssistiveText() {
+ return getState().assistiveTabStopTopText;
+ }
+
+ /**
+ * Gets the message that is provided to users of assistive devices when the
+ * user reaches the bottom of the window when leaving a window with the tab
+ * key is prevented.
+ *
+ * @return the bottom message
+ */
+ public String getTabStopBottomAssistiveText() {
+ return getState().assistiveTabStopBottomText;
+ }
}
diff --git a/shared/src/com/vaadin/shared/ui/window/WindowState.java b/shared/src/com/vaadin/shared/ui/window/WindowState.java
index fa430f6c9c..55a9b3ec55 100644
--- a/shared/src/com/vaadin/shared/ui/window/WindowState.java
+++ b/shared/src/com/vaadin/shared/ui/window/WindowState.java
@@ -43,4 +43,7 @@ public class WindowState extends PanelState {
public String assistivePostfix = "";
public Connector[] contentDescription;
public WindowRole role = WindowRole.DIALOG;
+ public boolean assistiveTabStop = false;
+ public String assistiveTabStopTopText = "Top of dialog";
+ public String assistiveTabStopBottomText = "Bottom of Dialog";
} \ No newline at end of file
diff --git a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java
index b7c929120d..39989926e7 100644
--- a/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java
+++ b/uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java
@@ -1,8 +1,9 @@
package com.vaadin.tests.components.window;
import com.vaadin.server.ThemeResource;
+import com.vaadin.server.VaadinRequest;
import com.vaadin.shared.ui.window.WindowState.WindowRole;
-import com.vaadin.tests.components.TestBase;
+import com.vaadin.tests.components.AbstractTestUI;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.CheckBox;
@@ -12,16 +13,25 @@ import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Window;
-public class ExtraWindowShownWaiAria extends TestBase {
+public class ExtraWindowShownWaiAria extends AbstractTestUI {
@Override
- protected void setup() {
+ protected void setup(VaadinRequest request) {
final CheckBox modal = new CheckBox("Modal dialog");
+ modal.setTabIndex(7);
final CheckBox additionalDescription = new CheckBox(
"Additional Description");
+ final CheckBox tabStop = new CheckBox(
+ "Prevent leaving window with Tab key");
+ final CheckBox tabOrder = new CheckBox("Change Taborder");
final TextField prefix = new TextField("Prefix: ");
final TextField postfix = new TextField("Postfix: ");
+ final TextField topTabStopMessage = new TextField(
+ "Top Tab Stop Message");
+ final TextField bottomTabStopMessage = new TextField(
+ "Bottom Tab Stop Message");
+
Button simple = new Button("Open Alert Dialog",
new Button.ClickListener() {
@@ -50,13 +60,20 @@ public class ExtraWindowShownWaiAria extends TestBase {
description2);
}
- layout.addComponent(new Button("Close",
+ w.setTabStopEnabled(tabStop.getValue());
+ w.setTabStopTopAssistiveText(topTabStopMessage
+ .getValue());
+ w.setTabStopBottomAssistiveText(bottomTabStopMessage
+ .getValue());
+
+ Button close = new Button("Close",
new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
w.close();
}
- }));
+ });
+ layout.addComponent(close);
Button iconButton = new Button("A button with icon");
iconButton.setIcon(new ThemeResource(
"../runo/icons/16/ok.png"));
@@ -64,6 +81,10 @@ public class ExtraWindowShownWaiAria extends TestBase {
event.getButton().getUI().addWindow(w);
iconButton.focus();
+
+ if (tabOrder.getValue()) {
+ close.setTabIndex(5);
+ }
}
});
@@ -96,6 +117,12 @@ public class ExtraWindowShownWaiAria extends TestBase {
description2);
}
+ w.setTabStopEnabled(tabStop.getValue());
+ w.setTabStopTopAssistiveText(topTabStopMessage
+ .getValue());
+ w.setTabStopBottomAssistiveText(bottomTabStopMessage
+ .getValue());
+
TextField name = new TextField("Name:");
form.addComponent(name);
@@ -112,28 +139,34 @@ public class ExtraWindowShownWaiAria extends TestBase {
event.getButton().getUI().addWindow(w);
name.focus();
+
+ if (tabOrder.getValue()) {
+ name.setTabIndex(5);
+ }
}
});
getLayout().addComponent(complex);
getLayout().addComponent(modal);
getLayout().addComponent(additionalDescription);
+ getLayout().addComponent(tabStop);
+ getLayout().addComponent(tabOrder);
getLayout().addComponent(prefix);
getLayout().addComponent(postfix);
+ getLayout().addComponent(topTabStopMessage);
+ getLayout().addComponent(bottomTabStopMessage);
+
}
@Override
- protected String getDescription() {
- // TODO Auto-generated method stub
- return null;
+ protected String getTestDescription() {
+ return "Test for WAI-ARIA implementation";
}
@Override
protected Integer getTicketNumber() {
- // TODO Auto-generated method stub
- return null;
+ return 11821;
}
-
}