Change-Id: I80377792ade11946337e2900a7ea84209ae1d060tags/7.0.3
@@ -137,10 +137,15 @@ public abstract class AbstractComponentConnector extends AbstractConnector | |||
* implementation). | |||
*/ | |||
Profiler.enter("AbstractComponentConnector.onStateChanged update tab index"); | |||
if (getState() instanceof TabIndexState | |||
&& getWidget() instanceof Focusable) { | |||
((Focusable) getWidget()) | |||
.setTabIndex(((TabIndexState) getState()).tabIndex); | |||
if (getState() instanceof TabIndexState) { | |||
if (getWidget() instanceof Focusable) { | |||
((Focusable) getWidget()) | |||
.setTabIndex(((TabIndexState) getState()).tabIndex); | |||
} else { | |||
VConsole.error("Tab index received for " | |||
+ Util.getSimpleName(getWidget()) | |||
+ " which does not implement Focusable"); | |||
} | |||
} | |||
Profiler.leave("AbstractComponentConnector.onStateChanged update tab index"); | |||
@@ -0,0 +1,94 @@ | |||
/* | |||
* Copyright 2000-2013 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.client.ui; | |||
import com.google.gwt.user.client.ui.Focusable; | |||
import com.google.gwt.user.client.ui.Widget; | |||
/** | |||
* A helper class used to make it easier for {@link Widget}s to implement | |||
* {@link Focusable}. | |||
* | |||
* @author Vaadin Ltd | |||
* @version @VERSION@ | |||
* @since 7.0.3 | |||
* | |||
*/ | |||
public class FocusUtil { | |||
/** | |||
* Sets the access key property | |||
* | |||
* @param focusable | |||
* The widget for which we want to set the access key. | |||
* @param key | |||
* The access key to set | |||
*/ | |||
public static void setAccessKey(Widget focusable, char key) { | |||
assert (focusable != null && focusable.getElement() != null) : "Can't setAccessKey for a widget without an element"; | |||
focusable.getElement().setPropertyString("accessKey", "" + key); | |||
} | |||
/** | |||
* Explicitly focus/unfocus the given widget. Only one widget can have focus | |||
* at a time, and the widget that does will receive all keyboard events. | |||
* | |||
* @param focusable | |||
* the widget to focus/unfocus | |||
* @param focused | |||
* whether this widget should take focus or release it | |||
*/ | |||
public static void setFocus(Widget focusable, boolean focus) { | |||
assert (focusable != null && focusable.getElement() != null) : "Can't setFocus for a widget without an element"; | |||
if (focus) { | |||
focusable.getElement().focus(); | |||
} else { | |||
focusable.getElement().blur(); | |||
} | |||
} | |||
/** | |||
* Sets the widget's position in the tab index. If more than one widget has | |||
* the same tab index, each such widget will receive focus in an arbitrary | |||
* order. Setting the tab index to <code>-1</code> will cause the widget to | |||
* be removed from the tab order. | |||
* | |||
* @param focusable | |||
* The widget | |||
* @param tabIndex | |||
* the widget's tab index | |||
*/ | |||
public static void setTabIndex(Widget focusable, int tabIndex) { | |||
assert (focusable != null && focusable.getElement() != null) : "Can't setTabIndex for a widget without an element"; | |||
focusable.getElement().setTabIndex(tabIndex); | |||
} | |||
/** | |||
* Gets the widget's position in the tab index. | |||
* | |||
* @param focusable | |||
* The widget | |||
* | |||
* @return the widget's tab index | |||
*/ | |||
public static int getTabIndex(Widget focusable) { | |||
assert (focusable != null && focusable.getElement() != null) : "Can't getTabIndex for a widget without an element"; | |||
return focusable.getElement().getTabIndex(); | |||
} | |||
} |
@@ -53,7 +53,8 @@ import com.vaadin.shared.ui.ui.UIConstants; | |||
*/ | |||
public class VUI extends SimplePanel implements ResizeHandler, | |||
Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable, | |||
HasResizeHandlers, HasScrollHandlers { | |||
com.google.gwt.user.client.ui.Focusable, HasResizeHandlers, | |||
HasScrollHandlers { | |||
private static int MONITOR_PARENT_TIMER_INTERVAL = 1000; | |||
@@ -437,7 +438,7 @@ public class VUI extends SimplePanel implements ResizeHandler, | |||
@Override | |||
public void focus() { | |||
getElement().focus(); | |||
setFocus(true); | |||
} | |||
/** | |||
@@ -462,4 +463,24 @@ public class VUI extends SimplePanel implements ResizeHandler, | |||
return addHandler(scrollHandler, ScrollEvent.getType()); | |||
} | |||
@Override | |||
public int getTabIndex() { | |||
return FocusUtil.getTabIndex(this); | |||
} | |||
@Override | |||
public void setAccessKey(char key) { | |||
FocusUtil.setAccessKey(this, key); | |||
} | |||
@Override | |||
public void setFocus(boolean focused) { | |||
FocusUtil.setFocus(this, focused); | |||
} | |||
@Override | |||
public void setTabIndex(int index) { | |||
FocusUtil.setTabIndex(this, index); | |||
} | |||
} |
@@ -329,10 +329,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN | |||
| Event.ONSCROLL); | |||
// iview is focused when created so element needs tabIndex | |||
// 1 due 0 is at the end of natural tabbing order | |||
DOM.setElementProperty(getWidget().getElement(), "tabIndex", "1"); | |||
RootPanel root = RootPanel.get(rootPanelId); | |||
// Remove the v-app-loading or any splash screen added inside the div by | |||
@@ -347,6 +343,10 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
root.add(getWidget()); | |||
// Set default tab index before focus call. State change handler | |||
// will update this later if needed. | |||
getWidget().setTabIndex(1); | |||
if (applicationConnection.getConfiguration().isStandalone()) { | |||
// set focus to iview element by default to listen possible keyboard | |||
// shortcuts. For embedded applications this is unacceptable as we |
@@ -78,7 +78,7 @@ import com.vaadin.util.CurrentInstance; | |||
* @since 7.0 | |||
*/ | |||
public abstract class UI extends AbstractSingleComponentContainer implements | |||
Action.Container, Action.Notifier, LegacyComponent { | |||
Action.Container, Action.Notifier, LegacyComponent, Focusable { | |||
/** | |||
* The application to which this UI belongs | |||
@@ -183,6 +183,11 @@ public abstract class UI extends AbstractSingleComponentContainer implements | |||
return (UIState) super.getState(); | |||
} | |||
@Override | |||
protected UIState getState(boolean markAsDirty) { | |||
return (UIState) super.getState(markAsDirty); | |||
} | |||
@Override | |||
public Class<? extends UIState> getStateType() { | |||
// This is a workaround for a problem with creating the correct state | |||
@@ -1039,4 +1044,14 @@ public abstract class UI extends AbstractSingleComponentContainer implements | |||
} | |||
super.setContent(content); | |||
} | |||
@Override | |||
public void setTabIndex(int tabIndex) { | |||
getState().tabIndex = tabIndex; | |||
} | |||
@Override | |||
public int getTabIndex() { | |||
return getState(false).tabIndex; | |||
} | |||
} |
@@ -15,10 +15,12 @@ | |||
*/ | |||
package com.vaadin.shared.ui.ui; | |||
import com.vaadin.shared.AbstractComponentState; | |||
import com.vaadin.shared.ui.TabIndexState; | |||
public class UIState extends AbstractComponentState { | |||
public class UIState extends TabIndexState { | |||
{ | |||
primaryStyleName = "v-ui"; | |||
// Default is 1 for legacy reasons | |||
tabIndex = 1; | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
<?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" /> | |||
<link rel="selenium.base" href="http://localhost:8888/" /> | |||
<title>New Test</title> | |||
</head> | |||
<body> | |||
<table cellpadding="1" cellspacing="1" border="1"> | |||
<thead> | |||
<tr><td rowspan="1" colspan="3">New Test</td></tr> | |||
</thead><tbody> | |||
<tr> | |||
<td>open</td> | |||
<td>/run/com.vaadin.tests.components.ui.UITabIndex?restartApplication</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertAttribute</td> | |||
<td>vaadin=runcomvaadintestscomponentsuiUITabIndex::@tabIndex</td> | |||
<td>1</td> | |||
</tr> | |||
<tr> | |||
<td>click</td> | |||
<td>vaadin=runcomvaadintestscomponentsuiUITabIndex::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0]</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertAttribute</td> | |||
<td>vaadin=runcomvaadintestscomponentsuiUITabIndex::@tabIndex</td> | |||
<td>-1</td> | |||
</tr> | |||
<tr> | |||
<td>click</td> | |||
<td>vaadin=runcomvaadintestscomponentsuiUITabIndex::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VButton[0]/domChild[0]/domChild[0]</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertAttribute</td> | |||
<td>vaadin=runcomvaadintestscomponentsuiUITabIndex::@tabIndex</td> | |||
<td>0</td> | |||
</tr> | |||
<tr> | |||
<td>click</td> | |||
<td>vaadin=runcomvaadintestscomponentsuiUITabIndex::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertAttribute</td> | |||
<td>vaadin=runcomvaadintestscomponentsuiUITabIndex::@tabIndex</td> | |||
<td>1</td> | |||
</tr> | |||
</tbody></table> | |||
</body> | |||
</html> |
@@ -0,0 +1,51 @@ | |||
package com.vaadin.tests.components.ui; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
public class UITabIndex extends AbstractTestUI { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
Button b; | |||
b = new Button("Set tabIndex to -1"); | |||
b.addClickListener(new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
setTabIndex(-1); | |||
} | |||
}); | |||
addComponent(b); | |||
b = new Button("Set tabIndex to 0"); | |||
b.addClickListener(new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
setTabIndex(0); | |||
} | |||
}); | |||
addComponent(b); | |||
b = new Button("Set tabIndex to 1"); | |||
b.addClickListener(new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
setTabIndex(1); | |||
} | |||
}); | |||
addComponent(b); | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Tests tab index handling for UI"; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 11129; | |||
} | |||
} |