Change-Id: I1160e7a384b1816204eb7f4b0f52f83ed9e230c0tags/7.7.4
@@ -0,0 +1,59 @@ | |||
/* | |||
* 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.client.ui; | |||
import com.vaadin.client.Focusable; | |||
public class VCustomField extends VCustomComponent implements Focusable { | |||
private Focusable focusDelegate; | |||
@Override | |||
public void focus() { | |||
if (focusDelegate != null) { | |||
focusDelegate.focus(); | |||
} | |||
} | |||
/** | |||
* Sets the focusable widget to focus instead of this custom field. | |||
* | |||
* @param focusDelegate | |||
* the widget to delegate focus to | |||
*/ | |||
public void setFocusDelegate(Focusable focusDelegate) { | |||
this.focusDelegate = focusDelegate; | |||
} | |||
/** | |||
* Sets the focusable widget to focus instead of this custom field. | |||
* | |||
* @param focusDelegate | |||
* the widget to delegate focus to | |||
*/ | |||
public void setFocusDelegate( | |||
final com.google.gwt.user.client.ui.Focusable focusDelegate) { | |||
this.focusDelegate = new Focusable() { | |||
@Override | |||
public void focus() { | |||
focusDelegate.setFocus(true); | |||
} | |||
}; | |||
} | |||
} |
@@ -17,16 +17,20 @@ package com.vaadin.client.ui.customfield; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.logging.Logger; | |||
import com.google.gwt.event.shared.HandlerRegistration; | |||
import com.google.gwt.user.client.ui.Widget; | |||
import com.vaadin.client.ComponentConnector; | |||
import com.vaadin.client.ConnectorHierarchyChangeEvent; | |||
import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler; | |||
import com.vaadin.client.Focusable; | |||
import com.vaadin.client.HasComponentsConnector; | |||
import com.vaadin.client.communication.StateChangeEvent; | |||
import com.vaadin.client.ui.AbstractFieldConnector; | |||
import com.vaadin.client.ui.VCustomComponent; | |||
import com.vaadin.client.ui.VCustomField; | |||
import com.vaadin.shared.ui.Connect; | |||
import com.vaadin.shared.ui.customfield.CustomFieldState; | |||
import com.vaadin.ui.CustomField; | |||
@Connect(value = CustomField.class) | |||
@@ -43,8 +47,13 @@ public class CustomFieldConnector extends AbstractFieldConnector | |||
} | |||
@Override | |||
public VCustomComponent getWidget() { | |||
return (VCustomComponent) super.getWidget(); | |||
public CustomFieldState getState() { | |||
return (CustomFieldState) super.getState(); | |||
} | |||
@Override | |||
public VCustomField getWidget() { | |||
return (VCustomField) super.getWidget(); | |||
} | |||
@Override | |||
@@ -52,6 +61,32 @@ public class CustomFieldConnector extends AbstractFieldConnector | |||
// NOP, custom field does not render the caption of its content | |||
} | |||
@Override | |||
public void onStateChanged(StateChangeEvent stateChangeEvent) { | |||
super.onStateChanged(stateChangeEvent); | |||
if (getState().focusDelegate != null) { | |||
Widget widget = ((ComponentConnector) getState().focusDelegate) | |||
.getWidget(); | |||
if (widget instanceof Focusable) { | |||
getWidget().setFocusDelegate((Focusable) widget); | |||
} else if (widget instanceof com.google.gwt.user.client.ui.Focusable) { | |||
getWidget().setFocusDelegate( | |||
(com.google.gwt.user.client.ui.Focusable) widget); | |||
} else { | |||
getLogger().warning( | |||
"The given focus delegate does not implement Focusable: " | |||
+ widget.getClass().getName()); | |||
} | |||
} else { | |||
getWidget().setFocusDelegate((Focusable) null); | |||
} | |||
} | |||
private static Logger getLogger() { | |||
return Logger.getLogger(CustomFieldConnector.class.getName()); | |||
} | |||
@Override | |||
public void onConnectorHierarchyChange( | |||
ConnectorHierarchyChangeEvent event) { |
@@ -20,6 +20,7 @@ import java.io.Serializable; | |||
import java.util.Iterator; | |||
import com.vaadin.data.Property; | |||
import com.vaadin.shared.ui.customfield.CustomFieldState; | |||
/** | |||
* A {@link Field} whose UI content can be constructed by the user, enabling the | |||
@@ -150,4 +151,64 @@ public abstract class CustomField<T> extends AbstractField<T> | |||
public Iterator<Component> iterator() { | |||
return new ComponentIterator(); | |||
} | |||
@Override | |||
protected CustomFieldState getState() { | |||
return (CustomFieldState) super.getState(); | |||
} | |||
@Override | |||
protected CustomFieldState getState(boolean markAsDirty) { | |||
return (CustomFieldState) super.getState(markAsDirty); | |||
} | |||
/** | |||
* Sets the component to which all methods from the {@link Focusable} | |||
* interface should be delegated. | |||
* <p> | |||
* Set this to a wrapped field to include that field in the tabbing order, | |||
* to make it receive focus when {@link #focus()} is called and to make it | |||
* be correctly focused when used as a Grid editor component. | |||
* <p> | |||
* By default, {@link Focusable} events are handled by the super class and | |||
* ultimately ignored. | |||
* | |||
* @param focusDelegate | |||
* the focusable component to which focus events are redirected | |||
*/ | |||
public void setFocusDelegate(Focusable focusDelegate) { | |||
getState().focusDelegate = focusDelegate; | |||
} | |||
private Focusable getFocusable() { | |||
return (Focusable) getState(false).focusDelegate; | |||
} | |||
@Override | |||
public void focus() { | |||
if (getFocusable() != null) { | |||
getFocusable().focus(); | |||
} else { | |||
super.focus(); | |||
} | |||
} | |||
@Override | |||
public int getTabIndex() { | |||
if (getFocusable() != null) { | |||
return getFocusable().getTabIndex(); | |||
} else { | |||
return super.getTabIndex(); | |||
} | |||
} | |||
@Override | |||
public void setTabIndex(int tabIndex) { | |||
if (getFocusable() != null) { | |||
getFocusable().setTabIndex(tabIndex); | |||
} else { | |||
super.setTabIndex(tabIndex); | |||
} | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
/* | |||
* 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.shared.ui.customfield; | |||
import com.vaadin.shared.AbstractFieldState; | |||
import com.vaadin.shared.Connector; | |||
public class CustomFieldState extends AbstractFieldState { | |||
/** | |||
* The component which should receive focus events instead of the custom | |||
* field wrapper. | |||
*/ | |||
public Connector focusDelegate; | |||
} |
@@ -73,6 +73,8 @@ public class GridEditorCustomField extends AbstractTestUIWithLog { | |||
Button addCountryButton = new Button("New"); | |||
fieldLayout.addComponent(addCountryButton); | |||
setFocusDelegate(cityComboBox); | |||
return fieldLayout; | |||
} | |||
@@ -17,13 +17,14 @@ package com.vaadin.tests.components.grid; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.Keys; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.ComboBoxElement; | |||
import com.vaadin.testbench.elements.GridElement; | |||
import com.vaadin.testbench.elements.GridElement.GridEditorElement; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
import com.vaadin.tests.tb3.newelements.ComboBoxElement; | |||
@TestCategory("grid") | |||
public class GridEditorCustomFieldTest extends MultiBrowserTest { | |||
@@ -43,4 +44,18 @@ public class GridEditorCustomFieldTest extends MultiBrowserTest { | |||
Assert.assertEquals("Oslo", grid.getCell(0, 2).getText()); | |||
} | |||
@Test | |||
public void tabReachesCustomField() { | |||
openTestURL(); | |||
GridElement grid = $(GridElement.class).first(); | |||
grid.getCell(0, 1).doubleClick(); | |||
GridEditorElement editor = grid.getEditor(); | |||
editor.getField(0).sendKeys(Keys.TAB, Keys.TAB); | |||
ComboBoxElement comboBoxInCustomField = editor.getField(2) | |||
.$(ComboBoxElement.class).first(); | |||
assertElementsEquals(comboBoxInCustomField.getInputField(), | |||
getActiveElement()); | |||
} | |||
} |