]> source.dussan.org Git - vaadin-framework.git/commitdiff
Prevent to exit a Window with the tab key (#11874)
authormichaelvogt <michael@vaadin.com>
Fri, 14 Jun 2013 14:08:38 +0000 (17:08 +0300)
committerVaadin Code Review <review@vaadin.com>
Fri, 12 Jul 2013 07:20:38 +0000 (07:20 +0000)
Change-Id: Icd12ec6e2eac626ad493707dfa8288d620bb9bb7

client/src/com/vaadin/client/ui/VWindow.java
client/src/com/vaadin/client/ui/window/WindowConnector.java
server/src/com/vaadin/ui/Window.java
shared/src/com/vaadin/shared/ui/window/WindowState.java
uitest/src/com/vaadin/tests/components/window/ExtraWindowShownWaiAria.java

index 1756c619a7788679bbf91c87e32cde98c6db7947..2c854977071d698878ad4d0ed35a8c9e566b1ffe 100644 (file)
@@ -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();
+        }
+    }
 }
index ea1073dd18f2dc281228a1ef8cead4bb109b7c51..464ab386c181610cfeafc4e5063f9bdfa41081a5 100644 (file)
@@ -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;
index 980e96c384c73ab2c4454481a45697fbe180ade0..658c821f8812d17c616642c30ece0d9188c3a4bf 100644 (file)
@@ -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;
+    }
 }
index fa430f6c9c570221911d24090d442d331d5c3bf8..55a9b3ec5507e282a0b0cc29553048a265913252 100644 (file)
@@ -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
index b7c929120d7e7f9c69ff4bd5dd8db61a3a39ba93..39989926e745fda4f091ed4bb24d692ddeca81d6 100644 (file)
@@ -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;
     }
-
 }