From e45e036598d6efcf26ba1d2c27b4eeadcb32a9b9 Mon Sep 17 00:00:00 2001 From: michaelvogt Date: Fri, 14 Jun 2013 17:08:38 +0300 Subject: [PATCH] Prevent to exit a Window with the tab key (#11874) Change-Id: Icd12ec6e2eac626ad493707dfa8288d620bb9bb7 --- client/src/com/vaadin/client/ui/VWindow.java | 142 ++++++++++++++++++ .../client/ui/window/WindowConnector.java | 5 +- server/src/com/vaadin/ui/Window.java | 77 ++++++++++ .../vaadin/shared/ui/window/WindowState.java | 3 + .../window/ExtraWindowShownWaiAria.java | 55 +++++-- 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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; } - } -- 2.39.5