summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBogdan Udrescu <bogdan@vaadin.com>2014-08-07 15:31:07 +0300
committerBogdan Udrescu <bogdan@vaadin.com>2014-08-14 17:04:39 +0300
commit8c4ddbb2837c8966551636cf140bba86c9671d1e (patch)
tree44956315838bcd6a530a7d07edcb483a86fb84a7
parentea8374649ba8b3b2d38a49a73eda205faf2f0fe9 (diff)
downloadvaadin-framework-8c4ddbb2837c8966551636cf140bba86c9671d1e.tar.gz
vaadin-framework-8c4ddbb2837c8966551636cf140bba86c9671d1e.zip
TabSheet focus/blur events fixed (#14304)
The blur and focus events should be linked with the TabSheet component as a whole. Any click inside the TabSheet should trigger one single focus and any leave the blur. Change-Id: Id24a2fab12aafe6f7aa3a44635e5b9e935a1cfe1
-rw-r--r--WebContent/html-tests/ComponentFocus.html16
-rw-r--r--client/src/com/vaadin/client/ui/VTabsheet.java694
-rw-r--r--uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html241
-rw-r--r--uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java32
-rw-r--r--uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationTest.java188
5 files changed, 767 insertions, 404 deletions
diff --git a/WebContent/html-tests/ComponentFocus.html b/WebContent/html-tests/ComponentFocus.html
new file mode 100644
index 0000000000..0a822520e0
--- /dev/null
+++ b/WebContent/html-tests/ComponentFocus.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Insert title here</title>
+
+<script type="text/javascript">
+
+</script>
+
+</head>
+<body>
+<button onfocus="console.log('button focus');" onblur="console.log('button blur');">Focus me</button>
+<textarea onfocus="console.log('textarea focus');" onblur="console.log('textarea blur');">Focus me too</textarea>
+</body>
+</html> \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java
index 35fdebf353..a2a6889e15 100644
--- a/client/src/com/vaadin/client/ui/VTabsheet.java
+++ b/client/src/com/vaadin/client/ui/VTabsheet.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -42,9 +42,12 @@ 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.HasMouseDownHandlers;
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.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
@@ -74,7 +77,9 @@ import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc;
import com.vaadin.shared.ui.tabsheet.TabsheetState;
public class VTabsheet extends VTabsheetBase implements Focusable,
- FocusHandler, BlurHandler, KeyDownHandler, SubPartAware {
+ SubPartAware,
+ // TODO: These listeners are due to be removed in 7.3
+ FocusHandler, BlurHandler, KeyDownHandler {
private static class VCloseEvent {
private Tab tab;
@@ -95,10 +100,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Representation of a single "tab" shown in the TabBar
- *
+ *
*/
public static class Tab extends SimplePanel implements HasFocusHandlers,
- HasBlurHandlers, HasKeyDownHandlers {
+ HasBlurHandlers, HasMouseDownHandlers, HasKeyDownHandlers {
private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
+ "-first";
@@ -152,10 +157,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
Roles.getTabRole().setAriaLabelledbyProperty(getElement(),
Id.of(tabCaption.getElement()));
-
- addFocusHandler(getTabsheet());
- addBlurHandler(getTabsheet());
- addKeyDownHandler(getTabsheet());
}
public boolean isHiddenOnServer() {
@@ -197,7 +198,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Toggles the style names for the Tab
- *
+ *
* @param selected
* true if the Tab is selected
* @param first
@@ -282,6 +283,11 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}
@Override
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
+ return addDomHandler(handler, MouseDownEvent.getType());
+ }
+
+ @Override
public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
return addDomHandler(handler, KeyDownEvent.getType());
}
@@ -420,8 +426,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}
- static class TabBar extends ComplexPanel implements ClickHandler,
- VCloseHandler {
+ static class TabBar extends ComplexPanel implements VCloseHandler {
private final Element tr = DOM.createTR();
@@ -443,6 +448,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
setStyleName(spacerTd, CLASSNAME + "-spacertd");
DOM.appendChild(tr, spacerTd);
DOM.appendChild(spacerTd, DOM.createDiv());
+
setElement(el);
}
@@ -460,10 +466,20 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
return DOM.asOld(tr);
}
+ /**
+ * Gets the number of tabs from the tab bar.
+ *
+ * @return the number of tabs from the tab bar.
+ */
public int getTabCount() {
return getWidgetCount();
}
+ /**
+ * Adds a tab to the tab bar.
+ *
+ * @return the added tab.
+ */
public Tab addTab() {
Tab t = new Tab(this);
int tabIndex = getTabCount();
@@ -476,31 +492,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
t.setStyleNames(false, true);
}
- t.addClickHandler(this);
+ getTabsheet().selectionHandler.registerTab(t);
+
t.setCloseHandler(this);
return t;
}
- @Override
- public void onClick(ClickEvent event) {
- TabCaption caption = (TabCaption) event.getSource();
- Element targetElement = event.getNativeEvent().getEventTarget()
- .cast();
- // the tab should not be focused if the close button was clicked
- if (targetElement == caption.getCloseButton()) {
- return;
- }
-
- int index = getWidgetIndex(caption.getParent());
-
- navigateTab(getTabsheet().focusedTabIndex, index);
- getTabsheet().focusedTabIndex = index;
- getTabsheet().focusedTab = getTab(index);
- getTabsheet().focus();
- getTabsheet().loadTabSheet(index);
- }
-
+ /**
+ * Gets the tab sheet instance where the tab bar is attached to.
+ *
+ * @return the tab sheet instance where the tab bar is attached to.
+ */
public VTabsheet getTabsheet() {
return tabsheet;
}
@@ -538,7 +541,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
}
- public void navigateTab(int fromIndex, int toIndex) {
+ public Tab navigateTab(int fromIndex, int toIndex) {
Tab newNavigated = getTab(toIndex);
if (newNavigated == null) {
throw new IllegalArgumentException(
@@ -553,6 +556,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
oldNavigated.setStyleNames(oldNavigated.equals(selected),
isFirstVisibleTab(fromIndex), false);
}
+
+ return newNavigated;
}
public void removeTab(int i) {
@@ -580,7 +585,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Returns the index of the first visible tab
- *
+ *
* @return
*/
private int getFirstVisibleTab() {
@@ -589,7 +594,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Find the next visible tab. Returns -1 if none is found.
- *
+ *
* @param i
* @return
*/
@@ -608,7 +613,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Find the previous visible tab. Returns -1 if none is found.
- *
+ *
* @param i
* @return
*/
@@ -663,7 +668,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/** For internal use only. May be removed or replaced in the future. */
// tabbar and 'scroller' container
public final Element tabs;
- Tab focusedTab;
+
/**
* The tabindex property (position in the browser's focus cycle.) Named like
* this to avoid confusion with activeTabIndex.
@@ -697,9 +702,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
private String currentStyle;
- /** For internal use only. May be removed or replaced in the future. */
- private int focusedTabIndex = 0;
-
/**
* @return Whether the tab could be selected or not.
*/
@@ -720,11 +722,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Load the content of a tab of the provided index.
- *
+ *
* @param index
* of the tab to load
+ *
+ * @return true if the specified sheet gets loaded, otherwise false.
*/
- public void loadTabSheet(int tabIndex) {
+ public boolean loadTabSheet(int tabIndex) {
if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) {
tb.selectTab(tabIndex);
@@ -741,12 +745,16 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
waitingForResponse = true;
tb.getTab(tabIndex).focus(); // move keyboard focus to active tab
+
+ return true;
}
+
+ return false;
}
/**
* Returns the currently displayed widget in the tab panel.
- *
+ *
* @since 7.2
* @return currently displayed content widget
*/
@@ -756,7 +764,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Returns the client to server RPC proxy for the tabsheet.
- *
+ *
* @since 7.2
* @return RPC proxy
*/
@@ -766,10 +774,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* For internal use only.
- *
+ *
* Avoid using this method directly and use appropriate superclass methods
* where applicable.
- *
+ *
* @deprecated since 7.2 - use more specific methods instead (getRpcProxy(),
* getConnectorForWidget(Widget) etc.)
* @return ApplicationConnection
@@ -799,9 +807,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
public VTabsheet() {
super(CLASSNAME);
- addHandler(this, FocusEvent.getType());
- addHandler(this, BlurEvent.getType());
-
// Tab scrolling
getElement().getStyle().setOverflow(Overflow.HIDDEN);
tabs = DOM.createDiv();
@@ -812,18 +817,21 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
Roles.getTablistRole().setAriaHiddenState(scroller, true);
DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
+
scrollerPrev = DOM.createButton();
scrollerPrev.setTabIndex(-1);
DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
+ "Prev");
Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true);
- DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
+ DOM.sinkEvents(scrollerPrev, Event.ONCLICK | Event.ONMOUSEDOWN);
+
scrollerNext = DOM.createButton();
scrollerNext.setTabIndex(-1);
DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
+ "Next");
Roles.getTablistRole().setAriaHiddenState(scrollerNext, true);
- DOM.sinkEvents(scrollerNext, Event.ONCLICK);
+ DOM.sinkEvents(scrollerNext, Event.ONCLICK | Event.ONMOUSEDOWN);
+
DOM.appendChild(getElement(), tabs);
// Tabs
@@ -856,35 +864,68 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
@Override
public void onBrowserEvent(Event event) {
+ com.google.gwt.dom.client.Element eventTarget = DOM
+ .eventGetTarget(event);
+
if (event.getTypeInt() == Event.ONCLICK) {
+
// Tab scrolling
- if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
- int newFirstIndex = tb.scrollLeft(scrollerIndex);
- if (newFirstIndex != -1) {
- scrollerIndex = newFirstIndex;
- updateTabScroller();
- }
- event.stopPropagation();
- return;
- } else if (isClippedTabs()
- && DOM.eventGetTarget(event) == scrollerNext) {
- int newFirstIndex = tb.scrollRight(scrollerIndex);
+ if (eventTarget == scrollerPrev || eventTarget == scrollerNext) {
+ scrollAccordingToScrollTarget(eventTarget);
- if (newFirstIndex != -1) {
- scrollerIndex = newFirstIndex;
- updateTabScroller();
- }
event.stopPropagation();
+ }
+
+ } else if (event.getTypeInt() == Event.ONMOUSEDOWN) {
+
+ if (eventTarget == scrollerPrev || eventTarget == scrollerNext) {
+ // In case the focus was previously on a Tab, we need to cancel
+ // the upcoming blur on the Tab which will follow this mouse
+ // down event.
+ focusBlurManager.cancelNextBlurSchedule();
+
return;
}
}
+
super.onBrowserEvent(event);
}
+ /*
+ * Scroll the tab bar according to the last scrollTarget (the scroll button
+ * pressed).
+ */
+ private void scrollAccordingToScrollTarget(
+ com.google.gwt.dom.client.Element scrollTarget) {
+ if (scrollTarget == null) {
+ return;
+ }
+
+ int newFirstIndex = -1;
+
+ // Scroll left.
+ if (isScrolledTabs() && scrollTarget == scrollerPrev) {
+ newFirstIndex = tb.scrollLeft(scrollerIndex);
+
+ // Scroll right.
+ } else if (isClippedTabs() && scrollTarget == scrollerNext) {
+ newFirstIndex = tb.scrollRight(scrollerIndex);
+ }
+
+ if (newFirstIndex != -1) {
+ scrollerIndex = newFirstIndex;
+ updateTabScroller();
+ }
+
+ // For this to work well, make sure the method gets called only from
+ // user events.
+ selectionHandler.focusTabAtIndex(scrollerIndex);
+ }
+
/**
* Checks if the tab with the selected index has been scrolled out of the
* view (on the left side).
- *
+ *
* @param index
* @return
*/
@@ -1016,7 +1057,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Renders the widget content for a tab sheet.
- *
+ *
* @param newWidget
*/
public void renderContent(Widget newWidget) {
@@ -1239,69 +1280,444 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
}
@Override
- public void onBlur(BlurEvent event) {
- getVTooltip().hideTooltip();
+ public void focus() {
+ getActiveTab().focus();
+ }
- if (focusedTab != null && focusedTab == event.getSource()) {
- focusedTab.removeAssistiveDescription();
- focusedTab = null;
- if (connector.hasEventListener(EventId.BLUR)) {
- connector.getRpcProxy(FocusAndBlurServerRpc.class).blur();
- }
- }
+ public void blur() {
+ getActiveTab().blur();
}
- @Override
- public void onFocus(FocusEvent event) {
- if (focusedTab == null && event.getSource() instanceof Tab) {
- focusedTab = (Tab) event.getSource();
+ /*
+ * Gets the active tab.
+ */
+ private Tab getActiveTab() {
+ return tb.getTab(activeTabIndex);
+ }
+
+ /*
+ * The focus and blur manager instance.
+ */
+ private FocusBlurManager focusBlurManager = new FocusBlurManager();
+
+ /*
+ * Manage the TabSheet component blur event.
+ */
+ private class FocusBlurManager implements FocusedTabProvider {
+
+ // The real tab with focus on it. If the focus goes to another element
+ // in the page this will be null.
+ private Tab focusedTab;
+
+ @Override
+ public Tab getFocusedTab() {
+ return focusedTab;
+ }
+
+ @Override
+ public void setFocusedTab(Tab focusedTab) {
+ this.focusedTab = focusedTab;
+ }
+
+ /**
+ * Process the focus event no matter where it occurs on the tab bar.
+ * We've added this method
+ *
+ * @since
+ * @param newFocusTab
+ * the new focused tab.
+ * @see #blur(Tab)
+ */
+ public void focus(Tab newFocusTab) {
+
if (connector.hasEventListener(EventId.FOCUS)) {
- connector.getRpcProxy(FocusAndBlurServerRpc.class).focus();
+
+ // Send the focus event only first time when we focus on any
+ // tab. The focused tab will be reseted on the last blur.
+ if (focusedTab == null) {
+ connector.getRpcProxy(FocusAndBlurServerRpc.class).focus();
+ }
}
+ removeBlurSchedule();
+
+ setFocusedTab(newFocusTab);
+
if (focusedTab.hasTooltip()) {
focusedTab.setAssistiveDescription(getVTooltip().getUniqueId());
getVTooltip().showAssistive(focusedTab.getTooltipInfo());
}
+
+ }
+
+ /**
+ * Process the blur event for the current focusedTab.
+ *
+ * @param blurSource
+ * the source of the blur.
+ *
+ * @see #focus(Tab)
+ */
+ public void blur(Tab blurSource) {
+ if (focusedTab != null && focusedTab == blurSource) {
+
+ if (connector.hasEventListener(EventId.BLUR)) {
+ scheduleBlur(focusedTab);
+ }
+ }
+ }
+
+ /*
+ * The last blur command to be executed.
+ */
+ private BlurCommand blurCommand;
+
+ /**
+ * Schedule a new blur event for a deferred execution.
+ */
+ public void scheduleBlur(Tab blurSource) {
+
+ if (cancelNextBlurSchedule) {
+
+ // This will set the stopNextBlurCommand back to false as well.
+ removeBlurSchedule();
+
+ // Leave this though to make things clear for the developer.
+ cancelNextBlurSchedule = false;
+ return;
+ }
+
+ removeBlurSchedule();
+
+ blurCommand = new BlurCommand(blurSource, this);
+ blurCommand.scheduleDeferred();
+ }
+
+ /**
+ * Remove the last blur command from execution.
+ */
+ public void removeBlurSchedule() {
+ if (blurCommand != null) {
+ blurCommand.stopSchedule();
+ blurCommand = null;
+ }
+
+ // Maybe this is not the best place to reset this, but we really
+ // want to make sure it'll reset at any time when something
+ // interact with the blur manager. Might change it later.
+ cancelNextBlurSchedule = false;
+ }
+
+ /**
+ * Cancel the next possible scheduled execution.
+ */
+ public void cancelNextBlurSchedule() {
+
+ // Make sure there's still no other command to be executed.
+ removeBlurSchedule();
+
+ cancelNextBlurSchedule = true;
+ }
+
+ // Stupid workaround...
+ private boolean cancelNextBlurSchedule = false;
+
+ }
+
+ /*
+ * Wraps the focused tab.
+ */
+ private interface FocusedTabProvider {
+
+ /**
+ * Gets the focused Tab.
+ *
+ * @return the focused Tab.
+ */
+ Tab getFocusedTab();
+
+ /**
+ * Sets the focused Tab.
+ *
+ * @param focusedTab
+ * the focused Tab.
+ */
+ void setFocusedTab(Tab focusedTab);
+
+ }
+
+ /*
+ * Execute the final blur command.
+ */
+ private class BlurCommand implements Command {
+
+ /*
+ * The blur source.
+ */
+ private Tab blurSource;
+
+ /*
+ * Provide the current focused Tab.
+ */
+ private FocusedTabProvider focusedTabProvider;
+
+ /**
+ * Create the blur command using the blur source.
+ *
+ * @param blurSource
+ * the source.
+ * @param focusedTabProvider
+ * provides the current focused tab.
+ */
+ public BlurCommand(Tab blurSource, FocusedTabProvider focusedTabProvider) {
+ this.blurSource = blurSource;
+ this.focusedTabProvider = focusedTabProvider;
+ }
+
+ /**
+ * Stop the command from being executed.
+ *
+ * @since
+ */
+ public void stopSchedule() {
+ blurSource = null;
+ }
+
+ /**
+ * Schedule the command for a deferred execution.
+ *
+ * @since
+ */
+ public void scheduleDeferred() {
+ Scheduler.get().scheduleDeferred(this);
+ }
+
+ @Override
+ public void execute() {
+
+ Tab focusedTab = focusedTabProvider.getFocusedTab();
+
+ if (blurSource == null) {
+ return;
+ }
+
+ // The focus didn't change since this blur triggered, so
+ // the new focused element is not a tab.
+ if (focusedTab == blurSource) {
+
+ // We're certain there's no focus anymore.
+ focusedTab.removeAssistiveDescription();
+ focusedTabProvider.setFocusedTab(null);
+
+ connector.getRpcProxy(FocusAndBlurServerRpc.class).blur();
+ }
+
+ // Call this to set it to null and be consistent.
+ focusBlurManager.removeBlurSchedule();
}
}
@Override
- public void focus() {
- tb.getTab(activeTabIndex).focus();
+ public void onBlur(BlurEvent event) {
+ selectionHandler.onBlur(event);
}
- public void blur() {
- tb.getTab(activeTabIndex).blur();
+ @Override
+ public void onFocus(FocusEvent event) {
+ selectionHandler.onFocus(event);
}
@Override
public void onKeyDown(KeyDownEvent event) {
- if (event.getSource() instanceof Tab) {
- int keycode = event.getNativeEvent().getKeyCode();
-
- // Scroll throw the tabs.
- if (!event.isAnyModifierKeyDown()) {
- if (keycode == getPreviousTabKey()) {
- selectPreviousTab();
- event.stopPropagation();
- } else if (keycode == getNextTabKey()) {
- selectNextTab();
- event.stopPropagation();
- } else if (keycode == getCloseTabKey()) {
- Tab tab = tb.getTab(activeTabIndex);
- if (tab.isClosable()) {
- tab.onClose();
- }
- } else if (keycode == getSelectTabKey()) {
- loadTabSheet(focusedTabIndex);
+ selectionHandler.onKeyDown(event);
+ }
+
+ /*
+ * The tabs selection handler instance.
+ */
+ private final TabSelectionHandler selectionHandler = new TabSelectionHandler();
+
+ /*
+ * Handle the events for selecting the tabs.
+ */
+ private class TabSelectionHandler implements FocusHandler, BlurHandler,
+ KeyDownHandler, ClickHandler, MouseDownHandler {
+
+ /** For internal use only. May be removed or replaced in the future. */
+ // The current visible focused index.
+ private int focusedTabIndex = 0;
+
+ /**
+ * Register the tab to the selection handler.
+ *
+ * @param tab
+ * the tab to register.
+ */
+ public void registerTab(Tab tab) {
+
+ // TODO: change VTabsheet.this to this in 7.3
+ tab.addBlurHandler(VTabsheet.this);
+ tab.addFocusHandler(VTabsheet.this);
+ tab.addKeyDownHandler(VTabsheet.this);
+
+ tab.addClickHandler(this);
+ tab.addMouseDownHandler(this);
+ }
+
+ @Override
+ public void onBlur(final BlurEvent event) {
+
+ getVTooltip().hideTooltip();
+
+ Object blurSource = event.getSource();
+
+ if (blurSource instanceof Tab) {
+ focusBlurManager.blur((Tab) blurSource);
+ }
+ }
+
+ @Override
+ public void onFocus(FocusEvent event) {
+
+ if (event.getSource() instanceof Tab) {
+ focusBlurManager.focus((Tab) event.getSource());
+ }
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+
+ // IE doesn't trigger focus when click, so we need to make sure
+ // the previous blur deferred command will get killed.
+ focusBlurManager.removeBlurSchedule();
+
+ TabCaption caption = (TabCaption) event.getSource();
+ Element targetElement = event.getNativeEvent().getEventTarget()
+ .cast();
+ // the tab should not be focused if the close button was clicked
+ if (targetElement == caption.getCloseButton()) {
+ return;
+ }
+
+ int index = tb.getWidgetIndex(caption.getParent());
+
+ tb.navigateTab(focusedTabIndex, index);
- // Prevent the page from scrolling when hitting space
- // (select key) to select the current tab.
- event.preventDefault();
+ focusedTabIndex = index;
+
+ if (!loadTabSheet(index)) {
+
+ // This needs to be called at the end, as the activeTabIndex
+ // is set in the loadTabSheet.
+ focus();
+ }
+ }
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+
+ if (event.getSource() instanceof Tab) {
+
+ // IE doesn't trigger focus when click, so we need to make sure
+ // the
+ // next blur deferred command will get killed.
+ focusBlurManager.cancelNextBlurSchedule();
+ }
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (event.getSource() instanceof Tab) {
+ int keycode = event.getNativeEvent().getKeyCode();
+
+ if (!event.isAnyModifierKeyDown()) {
+ if (keycode == getPreviousTabKey()) {
+ selectPreviousTab();
+ event.stopPropagation();
+
+ } else if (keycode == getNextTabKey()) {
+ selectNextTab();
+ event.stopPropagation();
+
+ } else if (keycode == getCloseTabKey()) {
+ Tab tab = tb.getTab(activeTabIndex);
+ if (tab.isClosable()) {
+ tab.onClose();
+ }
+
+ } else if (keycode == getSelectTabKey()) {
+ loadTabSheet(focusedTabIndex);
+
+ // Prevent the page from scrolling when hitting space
+ // (select key) to select the current tab.
+ event.preventDefault();
+ }
}
}
}
+
+ /*
+ * Left arrow key selection.
+ */
+ private void selectPreviousTab() {
+ int newTabIndex = focusedTabIndex;
+ // Find the previous visible and enabled tab if any.
+ do {
+ newTabIndex--;
+ } while (newTabIndex >= 0 && !canSelectTab(newTabIndex));
+
+ if (newTabIndex >= 0) {
+ keySelectTab(newTabIndex);
+ }
+ }
+
+ /*
+ * Right arrow key selection.
+ */
+ private void selectNextTab() {
+ int newTabIndex = focusedTabIndex;
+ // Find the next visible and enabled tab if any.
+ do {
+ newTabIndex++;
+ } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex));
+
+ if (newTabIndex < getTabCount()) {
+ keySelectTab(newTabIndex);
+ }
+ }
+
+ /*
+ * Select the specified tab using left/right key.
+ */
+ private void keySelectTab(int newTabIndex) {
+ Tab tab = tb.getTab(newTabIndex);
+ if (tab == null) {
+ return;
+ }
+
+ // Focus the tab, otherwise the selected one will loose focus and
+ // TabSheet will get blurred.
+ focusTabAtIndex(newTabIndex);
+
+ tb.navigateTab(focusedTabIndex, newTabIndex);
+
+ focusedTabIndex = newTabIndex;
+ }
+
+ /**
+ * Focus the specified tab. Make sure to call this only from user
+ * events, otherwise will break things.
+ *
+ * @param tabIndex
+ * the index of the tab to set.
+ */
+ void focusTabAtIndex(int tabIndex) {
+ Tab tabToFocus = tb.getTab(tabIndex);
+ if (tabToFocus != null) {
+ tabToFocus.focus();
+ }
+ }
+
}
/**
@@ -1341,66 +1757,32 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
return KeyCodes.KEY_DELETE;
}
- private void selectPreviousTab() {
- int newTabIndex = focusedTabIndex;
- // Find the previous visible and enabled tab if any.
- do {
- newTabIndex--;
- } while (newTabIndex >= 0 && !canSelectTab(newTabIndex));
-
- if (newTabIndex >= 0) {
- tb.navigateTab(focusedTabIndex, newTabIndex);
- focusedTabIndex = newTabIndex;
-
- // If this TabSheet already has focus, set the new selected tab
- // as focused.
- if (focusedTab != null) {
- focusedTab = tb.getTab(focusedTabIndex);
- focusedTab.focus();
- }
- }
- }
-
- private void selectNextTab() {
- int newTabIndex = focusedTabIndex;
- // Find the next visible and enabled tab if any.
- do {
- newTabIndex++;
- } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex));
-
- if (newTabIndex < getTabCount()) {
+ private void scrollIntoView(Tab tab) {
- tb.navigateTab(focusedTabIndex, newTabIndex);
- focusedTabIndex = newTabIndex;
+ if (!tab.isHiddenOnServer()) {
- // If this TabSheet already has focus, set the new selected tab
- // as focused.
- if (focusedTab != null) {
- focusedTab = tb.getTab(focusedTabIndex);
- focusedTab.focus();
- }
- }
- }
+ // Check for visibility first as clipped tabs to the right are
+ // always visible.
+ // On IE8 a tab with false visibility would have the bounds of the
+ // full TabBar.
+ if (!tab.isVisible()) {
+ while (!tab.isVisible()) {
+ scrollerIndex = tb.scrollLeft(scrollerIndex);
+ }
+ updateTabScroller();
- private void scrollIntoView(Tab tab) {
- if (!tab.isHiddenOnServer()) {
- if (isClipped(tab)) {
+ } else if (isClipped(tab)) {
while (isClipped(tab) && scrollerIndex != -1) {
scrollerIndex = tb.scrollRight(scrollerIndex);
}
updateTabScroller();
- } else if (!tab.isVisible()) {
- while (!tab.isVisible()) {
- scrollerIndex = tb.scrollLeft(scrollerIndex);
- }
- updateTabScroller();
}
}
}
/**
* Makes tab bar visible.
- *
+ *
* @since 7.2
*/
public void showTabs() {
@@ -1411,7 +1793,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable,
/**
* Makes tab bar invisible.
- *
+ *
* @since 7.2
*/
public void hideTabs() {
diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html
deleted file mode 100644
index 825988173a..0000000000
--- a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.html
+++ /dev/null
@@ -1,241 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-<title>TabKeyboardNavigation</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
-<thead>
-<tr><td rowspan="1" colspan="3">TabKeyboardNavigation</td></tr>
-</thead><tbody>
-<tr>
- <td>open</td>
- <td>/run/TabKeyboardNavigation?restartApplication</td>
- <td></td>
-</tr>
-<tr>
- <td>mouseClick</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
- <td>9,8</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
- <td>right</td>
-</tr>
-<tr>
- <td>pause</td>
- <td>1000</td>
- <td>1000</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[1]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 1</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
- <td>space</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[1]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 2</td>
-</tr>
-<tr>
- <td>screenCapture</td>
- <td></td>
- <td>tab2</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]</td>
- <td>right</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]</td>
- <td>right</td>
-</tr>
-<tr>
- <td>pause</td>
- <td>1000</td>
- <td>1000</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[3]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 2</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]</td>
- <td>space</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[3]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 5</td>
-</tr>
-<tr>
- <td>screenCapture</td>
- <td></td>
- <td>skip-disabled-to-tab5</td>
-</tr>
-<tr>
- <td>click</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
- <td></td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
- <td>right</td>
-</tr>
-<tr>
- <td>pause</td>
- <td>1000</td>
- <td>1000</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[4]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 5</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
- <td>space</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[4]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 6</td>
-</tr>
-<tr>
- <td>click</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
- <td></td>
-</tr>
-<tr>
- <td>click</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
- <td></td>
-</tr>
-<tr>
- <td>click</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
- <td></td>
-</tr>
-<tr>
- <td>click</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
- <td></td>
-</tr>
-<tr>
- <td>click</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
- <td></td>
-</tr>
-<tr>
- <td>click</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VButton[0]/domChild[0]/domChild[0]</td>
- <td></td>
-</tr>
-<tr>
- <td>mouseClick</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[8]/domChild[0]/domChild[0]/domChild[0]</td>
- <td>18,10</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[8]</td>
- <td>right</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[9]</td>
- <td>right</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[10]</td>
- <td>right</td>
-</tr>
-<tr>
- <td>pause</td>
- <td>1000</td>
- <td>1000</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[8]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 9</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[10]</td>
- <td>space</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[8]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 12</td>
-</tr>
-<tr>
- <td>screenCapture</td>
- <td></td>
- <td>scrolled-right-to-tab-12</td>
-</tr>
-<tr>
- <td>mouseClick</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[4]/domChild[0]/domChild[0]/domChild[0]</td>
- <td>11,2</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[4]</td>
- <td>left</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[2]</td>
- <td>left</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]</td>
- <td>left</td>
-</tr>
-<tr>
- <td>pause</td>
- <td>1000</td>
- <td>1000</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 5</td>
-</tr>
-<tr>
- <td>pressSpecialKey</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]</td>
- <td>space</td>
-</tr>
-<tr>
- <td>assertText</td>
- <td>vaadin=runTabKeyboardNavigation::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VTabsheet[0]/VTabsheetPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VLabel[0]</td>
- <td>Tab 1</td>
-</tr>
-<tr>
- <td>screenCapture</td>
- <td></td>
- <td>scrolled-left-to-tab-1</td>
-</tr>
-
-</tbody></table>
-</body>
-</html>
diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java
index ba737f1df8..620f04fe60 100644
--- a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java
+++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigation.java
@@ -6,7 +6,8 @@ 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.tests.components.TestBase;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
import com.vaadin.tests.util.Log;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
@@ -19,7 +20,16 @@ import com.vaadin.ui.TabSheet.Tab;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
-public class TabKeyboardNavigation extends TestBase {
+/**
+ * Test if the click and key tab selection in a tabsheet generate the correct
+ * focus/blur events.
+ *
+ * The solution was broken in ticket (#14304)
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class TabKeyboardNavigation extends AbstractTestUI {
int index = 1;
ArrayList<Component> tabs = new ArrayList<Component>();
@@ -27,18 +37,18 @@ public class TabKeyboardNavigation extends TestBase {
Log focusblur = new Log(10);
@Override
- protected void setup() {
+ protected void setup(VaadinRequest request) {
ts.setWidth("500px");
ts.setHeight("500px");
- ts.addListener(new FocusListener() {
+ ts.addFocusListener(new FocusListener() {
@Override
public void focus(FocusEvent event) {
focusblur.log("Tabsheet focused!");
}
});
- ts.addListener(new BlurListener() {
+ ts.addBlurListener(new BlurListener() {
@Override
public void blur(BlurEvent event) {
focusblur.log("Tabsheet blurred!");
@@ -74,7 +84,7 @@ public class TabKeyboardNavigation extends TestBase {
}
@Override
- protected String getDescription() {
+ protected String getTestDescription() {
return "The tab bar should be focusable and arrow keys should switch tabs. The del key should close a tab if closable.";
}
@@ -83,10 +93,18 @@ public class TabKeyboardNavigation extends TestBase {
return 5100;
}
+ public final static String LABEL_ID = "sheetLabel";
+
+ public final static String labelID(int index) {
+ return LABEL_ID + index;
+ }
+
private Tab addTab() {
Layout content = new VerticalLayout();
tabs.add(content);
- content.addComponent(new Label("Tab " + index));
+ Label label = new Label("Tab " + index);
+ label.setId(labelID(index));
+ content.addComponent(label);
content.addComponent(new TextField());
Tab tab = ts.addTab(content, "Tab " + index, null);
if (index == 2) {
diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationTest.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationTest.java
new file mode 100644
index 0000000000..65307f9492
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabKeyboardNavigationTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.tests.components.tabsheet;
+
+import java.io.IOException;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+/**
+ * Add TB3 test as the TB2 one failed on keyboard events.
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+public class TabKeyboardNavigationTest extends MultiBrowserTest {
+
+ @Test
+ public void testFocus() throws InterruptedException, IOException {
+ openTestURL();
+
+ click(1);
+ sendKeys(1, Keys.ARROW_RIGHT);
+
+ assertSheet(1);
+ sendKeys(2, Keys.SPACE);
+ assertSheet(2);
+ compareScreen("tab2");
+
+ sendKeys(2, Keys.ARROW_RIGHT);
+ sendKeys(3, Keys.ARROW_RIGHT);
+ assertSheet(2);
+
+ sendKeys(5, Keys.SPACE);
+ assertSheet(5);
+ compareScreen("skip-disabled-to-tab5");
+
+ TestBenchElement addTabButton = (TestBenchElement) getDriver()
+ .findElements(By.className("v-button")).get(0);
+
+ click(addTabButton);
+
+ click(5);
+ sendKeys(5, Keys.ARROW_RIGHT);
+ assertSheet(5);
+
+ sendKeys(6, Keys.SPACE);
+ assertSheet(6);
+
+ click(addTabButton);
+ click(addTabButton);
+ click(addTabButton);
+ click(addTabButton);
+ click(addTabButton);
+ click(addTabButton);
+
+ click(8);
+ compareScreen("click-tab-8");
+
+ sendKeys(8, Keys.ARROW_RIGHT);
+ sendKeys(9, Keys.SPACE);
+ click(9);
+ compareScreen("tab-9");
+
+ sendKeys(9, Keys.ARROW_RIGHT);
+ Thread.sleep(DELAY);
+
+ sendKeys(10, Keys.ARROW_RIGHT);
+
+ // Here PhantomJS used to fail. Or when accessing tab2. The fix was to
+ // call the elem.click(x, y) using the (x, y) position instead of the
+ // elem.click() without any arguments.
+ sendKeys(11, Keys.ARROW_RIGHT);
+
+ assertSheet(9);
+ sendKeys(12, Keys.SPACE);
+ assertSheet(12);
+ compareScreen("scrolled-right-to-tab-12");
+
+ click(5);
+
+ sendKeys(5, Keys.ARROW_LEFT);
+
+ // Here IE8 used to fail. A hidden <div> in IE8 would have the bounds of
+ // it's parent, and when trying to see in which direction to scroll
+ // (left or right) to make the key selected tab visible, the
+ // VTabSheet.scrollIntoView(Tab) used to check first whether the tab
+ // isClipped. On IE8 this will always return true for both hidden tabs
+ // on the left and clipped tabs on the right. So instead of going to
+ // left, it'll search all the way to the right.
+ sendKeys(3, Keys.ARROW_LEFT);
+ sendKeys(2, Keys.ARROW_LEFT);
+ assertSheet(5);
+
+ sendKeys(1, Keys.SPACE);
+ assertSheet(1);
+ compareScreen("scrolled-left-to-tab-1");
+ }
+
+ /*
+ * Press key on the element.
+ */
+ private void sendKeys(int tabIndex, Keys key) throws InterruptedException {
+ sendKeys(tab(tabIndex), key);
+ }
+
+ /*
+ * Press key on the element.
+ */
+ private void sendKeys(TestBenchElement element, Keys key)
+ throws InterruptedException {
+
+ element.sendKeys(key);
+ if (DELAY > 0) {
+ sleep(DELAY);
+ }
+ }
+
+ /*
+ * Click on the element.
+ */
+ private void click(int tabIndex) throws InterruptedException {
+ click(tab(tabIndex));
+ }
+
+ /*
+ * Click on the element.
+ */
+ private void click(TestBenchElement element) throws InterruptedException {
+
+ element.click(10, 10);
+ if (DELAY > 0) {
+ sleep(DELAY);
+ }
+ }
+
+ /*
+ * Delay for PhantomJS.
+ */
+ private final static int DELAY = 10;
+
+ private void assertSheet(int index) {
+ String labelCaption = "Tab " + index;
+
+ By id = By.id(TabKeyboardNavigation.labelID(index));
+ WebElement labelElement = getDriver().findElement(id);
+
+ waitForElementPresent(id);
+
+ Assert.assertEquals(labelCaption, labelCaption, labelElement.getText());
+ }
+
+ /*
+ * Provide the tab at specified index.
+ */
+ private TestBenchElement tab(int index) {
+ By by = By.className("v-tabsheet-tabitemcell");
+
+ TestBenchElement element = (TestBenchElement) getDriver().findElements(
+ by).get(index - 1);
+
+ String expected = "Tab " + index;
+ Assert.assertEquals(expected,
+ element.getText().substring(0, expected.length()));
+
+ return element;
+ }
+
+}