]> source.dussan.org Git - vaadin-framework.git/commitdiff
Patch for #5100 - TabSheet keyboard navigation
authorJohannes Dahlström <johannes.dahlstrom@vaadin.com>
Thu, 16 Feb 2012 15:15:28 +0000 (15:15 +0000)
committerJohannes Dahlström <johannes.dahlstrom@vaadin.com>
Thu, 16 Feb 2012 15:15:28 +0000 (15:15 +0000)
- The active tab button in the tab bar is now focusable by clicking or via tabulator
- When the focus is in the tab bar, the tab can be changed with left and right arrow keys
- Delete key closes the active focused tab if closable
- TODO: programmatic control on server side, configurable keyboard shortcuts

svn changeset:23052/svn branch:6.8

src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java
src/com/vaadin/ui/TabSheet.java

index 38140988adb96730354d475e2f5763f38417701f..2a12238c4bde510c4b0f28f51baa11f00ad58080 100644 (file)
@@ -12,8 +12,19 @@ import com.google.gwt.dom.client.DivElement;
 import com.google.gwt.dom.client.Style;
 import com.google.gwt.dom.client.TableCellElement;
 import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+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;
@@ -21,8 +32,10 @@ import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.ui.ComplexPanel;
 import com.google.gwt.user.client.ui.SimplePanel;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
 import com.vaadin.terminal.gwt.client.ApplicationConnection;
 import com.vaadin.terminal.gwt.client.BrowserInfo;
+import com.vaadin.terminal.gwt.client.Focusable;
 import com.vaadin.terminal.gwt.client.Paintable;
 import com.vaadin.terminal.gwt.client.RenderInformation;
 import com.vaadin.terminal.gwt.client.RenderSpace;
@@ -30,8 +43,10 @@ import com.vaadin.terminal.gwt.client.TooltipInfo;
 import com.vaadin.terminal.gwt.client.UIDL;
 import com.vaadin.terminal.gwt.client.Util;
 import com.vaadin.terminal.gwt.client.VCaption;
+import com.vaadin.terminal.gwt.client.VConsole;
 
-public class VTabsheet extends VTabsheetBase {
+public class VTabsheet extends VTabsheetBase implements Focusable,
+        FocusHandler, BlurHandler, KeyDownHandler {
 
     private static class VCloseEvent {
         private Tab tab;
@@ -54,7 +69,8 @@ public class VTabsheet extends VTabsheetBase {
      * Representation of a single "tab" shown in the TabBar
      * 
      */
-    private static class Tab extends SimplePanel {
+    private static class Tab extends SimplePanel implements HasFocusHandlers,
+            HasBlurHandlers, HasKeyDownHandlers {
         private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
         private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
                 + "-first";
@@ -94,6 +110,9 @@ public class VTabsheet extends VTabsheetBase {
                     .getApplicationConnection());
             add(tabCaption);
 
+            addFocusHandler(getTabsheet());
+            addBlurHandler(getTabsheet());
+            addKeyDownHandler(getTabsheet());
         }
 
         public boolean isHiddenOnServer() {
@@ -136,6 +155,9 @@ public class VTabsheet extends VTabsheetBase {
          *            true if the Tab is the first visible Tab
          */
         public void setStyleNames(boolean selected, boolean first) {
+            // TODO #5100 doesn't belong here
+            td.setAttribute("tabindex", selected ? "0" : "-1");
+
             setStyleName(td, TD_FIRST_CLASSNAME, first);
             setStyleName(td, TD_SELECTED_CLASSNAME, selected);
             setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
@@ -143,7 +165,10 @@ public class VTabsheet extends VTabsheetBase {
         }
 
         public void onClose() {
+            VConsole.log("OnClose");
+
             closeHandler.onClose(new VCloseEvent(this));
+            VConsole.log("End OnClose");
         }
 
         public VTabsheet getTabsheet() {
@@ -179,6 +204,25 @@ public class VTabsheet extends VTabsheetBase {
             tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
         }
 
+        public HandlerRegistration addFocusHandler(FocusHandler handler) {
+            return addDomHandler(handler, FocusEvent.getType());
+        }
+
+        public HandlerRegistration addBlurHandler(BlurHandler handler) {
+            return addDomHandler(handler, BlurEvent.getType());
+        }
+
+        public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+            return addDomHandler(handler, KeyDownEvent.getType());
+        }
+
+        public void focus() {
+            td.focus();
+        }
+
+        public void blur() {
+            td.blur();
+        }
     }
 
     private static class TabCaption extends VCaption {
@@ -296,7 +340,6 @@ public class VTabsheet extends VTabsheetBase {
             }
             return width;
         }
-
     }
 
     static class TabBar extends ComplexPanel implements ClickHandler,
@@ -362,6 +405,7 @@ public class VTabsheet extends VTabsheetBase {
             Widget caption = (Widget) event.getSource();
             int index = getWidgetIndex(caption.getParent());
             getTabsheet().onTabSelected(index);
+            getTabsheet().focus();
         }
 
         public VTabsheet getTabsheet() {
@@ -483,7 +527,6 @@ public class VTabsheet extends VTabsheetBase {
             currentFirst.recalculateCaptionWidth();
             return nextVisible;
         }
-
     }
 
     public static final String CLASSNAME = "v-tabsheet";
@@ -494,11 +537,15 @@ public class VTabsheet extends VTabsheetBase {
     // Can't use "style" as it's already in use
     public static final String TAB_STYLE_NAME = "tabstyle";
 
+    private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();
+
     private final Element tabs; // tabbar and 'scroller' container
     private final Element scroller; // tab-scroller element
     private final Element scrollerNext; // tab-scroller next button element
     private final Element scrollerPrev; // tab-scroller prev button element
 
+    private Tab focusedTab;
+
     /**
      * The index of the first visible tab (when scrolled)
      */
@@ -536,6 +583,12 @@ public class VTabsheet extends VTabsheetBase {
         }
         if (client != null && activeTabIndex != tabIndex) {
             tb.selectTab(tabIndex);
+
+            if (focusedTab != null) {
+                focusedTab.blur();
+                tb.getTab(tabIndex).focus();
+            }
+
             addStyleDependentName("loading");
             // run updating variables in deferred command to bypass some FF
             // optimization issues
@@ -581,6 +634,9 @@ public class VTabsheet extends VTabsheetBase {
     public VTabsheet() {
         super(CLASSNAME);
 
+        addHandler(this, FocusEvent.getType());
+        addHandler(this, BlurEvent.getType());
+
         // Tab scrolling
         DOM.setStyleAttribute(getElement(), "overflow", "hidden");
         tabs = DOM.createDiv();
@@ -694,8 +750,13 @@ public class VTabsheet extends VTabsheetBase {
             updateOpenTabSize();
         }
 
-        iLayout();
+        // If a tab was focused before, focus the new active tab
+        if (focusedTab != null && tb.getTabCount() > 0) {
+            focusedTab = tb.getTab(activeTabIndex);
+            focusedTab.focus();
+        }
 
+        iLayout();
         // Re run relative size update to ensure optimal scrollbars
         // TODO isolate to situation that visible tab has undefined height
         try {
@@ -923,9 +984,9 @@ public class VTabsheet extends VTabsheetBase {
         updateOpenTabSize();
         VTabsheet.this.removeStyleDependentName("loading");
         if (previousVisibleWidget != null) {
-            DOM.setStyleAttribute(
-                    DOM.getParent(previousVisibleWidget.getElement()),
-                    "visibility", "");
+            // DOM.setStyleAttribute(
+            // DOM.getParent(previousVisibleWidget.getElement()),
+            // "visibility", "");
             previousVisibleWidget = null;
         }
     }
@@ -1214,4 +1275,53 @@ public class VTabsheet extends VTabsheetBase {
         }
     }
 
+    public void onBlur(BlurEvent event) {
+        focusedTab = null;
+        // TODO Auto-generated method stub
+        VConsole.log("BLUR " + event);
+    }
+
+    public void onFocus(FocusEvent event) {
+        // TODO Auto-generated method stub
+        VConsole.log("FOCUS " + event);
+        if (event.getSource() instanceof Tab) {
+            focusedTab = (Tab) event.getSource();
+        }
+    }
+
+    public void focus() {
+        if (focusedTab == null) {
+            focusedTab = tb.getTab(activeTabIndex);
+            focusedTab.focus();
+        }
+    }
+
+    public void blur() {
+        if (focusedTab != null) {
+            focusedTab.blur();
+            focusedTab = null;
+        }
+    }
+
+    public void onKeyDown(KeyDownEvent event) {
+        if (event.getSource() instanceof Tab) {
+            VConsole.log("KEYDOWN");
+            int keycode = event.getNativeEvent().getKeyCode();
+            if (keycode == KeyCodes.KEY_LEFT) {
+                int newTabIndex = activeTabIndex == 0 ? tb.getTabCount() - 1
+                        : activeTabIndex - 1;
+                onTabSelected(newTabIndex);
+                activeTabIndex = newTabIndex;
+            } else if (keycode == KeyCodes.KEY_RIGHT) {
+                int newTabIndex = (activeTabIndex + 1) % tb.getTabCount();
+                onTabSelected(newTabIndex);
+                activeTabIndex = newTabIndex;
+            } else if (keycode == KeyCodes.KEY_DELETE) {
+                VConsole.log("INDEX=" + activeTabIndex);
+                focusedTab.onClose();
+                removeTab(activeTabIndex);
+            }
+            VConsole.log("tabindex -> " + activeTabIndex);
+        }
+    }
 }
index a13c336943c6fa5b8470ba794174cde48ecb71d7..1275eda69fe0a910acb62f2f925bfb5859ad6cd9 100644 (file)
@@ -14,6 +14,10 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
 import com.vaadin.terminal.ErrorMessage;
 import com.vaadin.terminal.KeyMapper;
 import com.vaadin.terminal.PaintException;
@@ -21,6 +25,7 @@ import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.Resource;
 import com.vaadin.terminal.gwt.client.ui.VTabsheet;
 import com.vaadin.terminal.gwt.server.CommunicationManager;
+import com.vaadin.ui.Component.Focusable;
 import com.vaadin.ui.themes.Reindeer;
 import com.vaadin.ui.themes.Runo;
 
@@ -55,7 +60,8 @@ import com.vaadin.ui.themes.Runo;
  */
 @SuppressWarnings("serial")
 @ClientWidget(VTabsheet.class)
-public class TabSheet extends AbstractComponentContainer {
+public class TabSheet extends AbstractComponentContainer implements Focusable,
+        FocusListener, BlurListener {
 
     /**
      * List of component tabs (tab contents). In addition to being on this list,
@@ -95,6 +101,8 @@ public class TabSheet extends AbstractComponentContainer {
      */
     private CloseHandler closeHandler;
 
+    private int tabIndex;
+
     /**
      * Constructs a new Tabsheet. Tabsheet is immediate by default, and the
      * default close handler removes the tab being closed.
@@ -363,6 +371,10 @@ public class TabSheet extends AbstractComponentContainer {
             target.addAttribute("hidetabs", true);
         }
 
+        if (tabIndex != 0) {
+            target.addAttribute("tabindex", tabIndex);
+        }
+
         target.startTag("tabs");
 
         Collection<Component> orphaned = new HashSet<Component>(paintedTabs);
@@ -1226,4 +1238,27 @@ public class TabSheet extends AbstractComponentContainer {
         return components.indexOf(tab.getComponent());
     }
 
+    public void blur(BlurEvent event) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void focus(FocusEvent event) {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
+    public void focus() {
+        super.focus();
+    }
+
+    public int getTabIndex() {
+        return tabIndex;
+    }
+
+    public void setTabIndex(int tabIndex) {
+        this.tabIndex = tabIndex;
+        requestRepaint();
+    }
+
 }