Browse Source

Allow defining a focus delegate component for CustomField (#20336)

tags/8.0.0.alpha10
Pekka Hyvönen 7 years ago
parent
commit
04f30c6892

+ 59
- 0
client/src/main/java/com/vaadin/client/ui/VCustomField.java View File

@@ -0,0 +1,59 @@
/*
* Copyright 2000-2016 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);
}
};

}

}

+ 37
- 8
client/src/main/java/com/vaadin/client/ui/customfield/CustomFieldConnector.java View File

@@ -17,15 +17,18 @@ 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;
@@ -44,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
@@ -53,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) {
@@ -122,9 +156,4 @@ public class CustomFieldConnector extends AbstractFieldConnector
}
}

@Override
public CustomFieldState getState() {
return (CustomFieldState) super.getState();
}

}

+ 60
- 0
compatibility-client/src/main/java/com/vaadin/v7/client/ui/VCustomField.java View File

@@ -0,0 +1,60 @@
/*
* Copyright 2000-2016 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.v7.client.ui;

import com.vaadin.client.Focusable;

@Deprecated
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);
}
};

}

}

+ 32
- 3
compatibility-client/src/main/java/com/vaadin/v7/client/ui/customfield/CustomFieldConnector.java View File

@@ -17,16 +17,19 @@ package com.vaadin.v7.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.shared.ui.Connect;
import com.vaadin.v7.client.ui.AbstractFieldConnector;
import com.vaadin.v7.client.ui.VCustomComponent;
import com.vaadin.v7.client.ui.VCustomField;
import com.vaadin.v7.ui.CustomField;

@Connect(value = CustomField.class)
@@ -43,8 +46,8 @@ public class CustomFieldConnector extends AbstractFieldConnector
}

@Override
public VCustomComponent getWidget() {
return (VCustomComponent) super.getWidget();
public VCustomField getWidget() {
return (VCustomField) super.getWidget();
}

@Override
@@ -52,6 +55,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) {

+ 50
- 1
compatibility-server/src/main/java/com/vaadin/v7/ui/CustomField.java View File

@@ -130,7 +130,7 @@ public abstract class CustomField<T> extends AbstractField<T>

private class ComponentIterator
implements Iterator<Component>, Serializable {
boolean first = (root != null);
boolean first = root != null;

@Override
public boolean hasNext() {
@@ -153,4 +153,53 @@ public abstract class CustomField<T> extends AbstractField<T>
public Iterator<Component> iterator() {
return new ComponentIterator();
}

/**
* 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);
}
}
}

+ 10
- 0
compatibility-shared/src/main/java/com/vaadin/v7/shared/AbstractFieldState.java View File

@@ -15,6 +15,7 @@
*/
package com.vaadin.v7.shared;

import com.vaadin.shared.Connector;
import com.vaadin.shared.annotations.NoLayout;

/**
@@ -34,4 +35,13 @@ public class AbstractFieldState extends AbstractLegacyComponentState {
*/
@NoLayout
public int tabIndex = 0;

/**
* The component which should receive focus events instead of the custom
* field wrapper.
* <p>
* This is not used in all fields, but needs to be here for the time being
* (#20468).
*/
public Connector focusDelegate;
}

+ 50
- 0
server/src/main/java/com/vaadin/ui/CustomField.java View File

@@ -155,4 +155,54 @@ public abstract class CustomField<T> extends AbstractField<T>
public Iterator<Component> iterator() {
return new ComponentIterator();
}

/**
* 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);
}
}

}

+ 8
- 1
shared/src/main/java/com/vaadin/shared/ui/customfield/CustomFieldState.java View File

@@ -16,14 +16,21 @@
package com.vaadin.shared.ui.customfield;

import com.vaadin.shared.AbstractFieldState;
import com.vaadin.shared.Connector;

/**
* State class for CustomField.
*
*
* @author Vaadin Ltd
* @since 8.0
*
*/
public class CustomFieldState extends AbstractFieldState {

/**
* The component which should receive focus events instead of the custom
* field wrapper.
*/
public Connector focusDelegate;

}

+ 144
- 0
uitest/src/main/java/com/vaadin/tests/components/grid/GridEditorCustomField.java View File

@@ -0,0 +1,144 @@
/*
* Copyright 2000-2016 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.grid;

import java.util.HashSet;
import java.util.Set;

import com.vaadin.annotations.Theme;
import com.vaadin.data.Binder;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.data.ListDataProvider;
import com.vaadin.server.data.Query;
import com.vaadin.tests.components.AbstractTestUIWithLog;
import com.vaadin.tests.fieldgroup.ComplexPerson;
import com.vaadin.ui.Button;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomField;
import com.vaadin.ui.Grid;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.TextField;

@Theme("tests-valo-disabled-animations")
public class GridEditorCustomField extends AbstractTestUIWithLog {

private static final String LAST_NAME_IDENTIFIER = "lastName";
private static final String FIRST_NAME_IDENTIFIER = "firstName";
private static final String ADDRESS_CITY_IDENTIFIER = "address.city";

@Override
protected void setup(VaadinRequest request) {
Grid<ComplexPerson> grid = createGrid();

ListDataProvider<ComplexPerson> dataProvider = ComplexPerson
.createDataProvider(100);

grid.setDataProvider(dataProvider);

Set<String> cities = new HashSet<>();
dataProvider.fetch(new Query<>()).forEach(person -> {
cities.add(person.getAddress().getCity());
});
CustomCitySelect cityEditor = new CustomCitySelect(
cities.toArray(new String[cities.size()]));

TextField firstNameField = new TextField();
TextField lastNameField = new TextField();
Binder<ComplexPerson> binder = new Binder<>();

binder.bind(firstNameField, ComplexPerson::getFirstName,
ComplexPerson::setFirstName);
binder.bind(lastNameField, ComplexPerson::getLastName,
ComplexPerson::setLastName);
binder.bind(cityEditor, person -> person.getAddress().getCity(),
(person, city) -> person.getAddress().setCity(city));

grid.getEditor().setBinder(binder);
grid.getColumn(ADDRESS_CITY_IDENTIFIER).setEditorComponent(cityEditor);
grid.getColumn(FIRST_NAME_IDENTIFIER)
.setEditorComponent(firstNameField);
grid.getColumn(LAST_NAME_IDENTIFIER).setEditorComponent(lastNameField);

addComponent(grid);
}

private Grid<ComplexPerson> createGrid() {
Grid<ComplexPerson> grid = new Grid<>();
grid.setWidth("800px");
grid.addColumn(FIRST_NAME_IDENTIFIER, person -> person.getFirstName())
.setCaption("First Name");
grid.addColumn(LAST_NAME_IDENTIFIER, person -> person.getLastName())
.setCaption("Last Name");
grid.addColumn(ADDRESS_CITY_IDENTIFIER,
person -> person.getAddress().getCity())
.setCaption("City Name");
grid.getEditor().setEnabled(true);

return grid;
}

public static class CustomCitySelect extends CustomField<String> {
private HorizontalLayout fieldLayout;
private String[] values;
private ComboBox<String> cityComboBox;
private String cachedValue;

public CustomCitySelect(String... values) {
this.values = values;
}

@Override
protected Component initContent() {
fieldLayout = new HorizontalLayout();
fieldLayout.setWidth("100%");

cityComboBox = new ComboBox<>();
cityComboBox.setItems(values);
if (cachedValue != null) {
cityComboBox.setValue(cachedValue);
cachedValue = null;
}

fieldLayout.addComponent(cityComboBox);
fieldLayout.setExpandRatio(cityComboBox, 1.0f);

Button addCountryButton = new Button("New");
fieldLayout.addComponent(addCountryButton);

setFocusDelegate(cityComboBox);

return fieldLayout;
}

@Override
public String getValue() {
if (cityComboBox == null) {
return null;
}
return cityComboBox.getValue();
}

@Override
protected void doSetValue(String value) {
if (cityComboBox == null) {
getContent();
}
cityComboBox.setValue(value);
}
}

}

+ 20
- 0
uitest/src/main/java/com/vaadin/tests/fieldgroup/ComplexPerson.java View File

@@ -1,9 +1,13 @@
package com.vaadin.tests.fieldgroup;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import com.vaadin.server.data.DataProvider;
import com.vaadin.server.data.ListDataProvider;
import com.vaadin.tests.util.TestDataGenerator;
import com.vaadin.v7.data.util.BeanItemContainer;

@@ -25,6 +29,10 @@ public class ComplexPerson {
this.firstName = firstName;
}

public void setLastName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}
@@ -90,6 +98,18 @@ public class ComplexPerson {
return bic;
}

public static ListDataProvider<ComplexPerson> createDataProvider(int size) {
List<ComplexPerson> list = new ArrayList<>();
Random r = new Random(size);

for (int i = 0; i < size; i++) {
ComplexPerson cp = ComplexPerson.create(r);
list.add(cp);
}

return DataProvider.create(list);
}

public static ComplexPerson create(Random r) {
ComplexPerson cp = new ComplexPerson();
cp.setFirstName(TestDataGenerator.getFirstName(r));

+ 0
- 100
uitest/src/main/java/com/vaadin/v7/tests/components/grid/GridEditorCustomField.java View File

@@ -1,100 +0,0 @@
/*
* Copyright 2000-2016 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.v7.tests.components.grid;

import java.util.HashSet;
import java.util.Set;

import com.vaadin.server.VaadinRequest;
import com.vaadin.tests.components.AbstractTestUIWithLog;
import com.vaadin.tests.fieldgroup.ComplexPerson;
import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.v7.ui.ComboBox;
import com.vaadin.v7.ui.CustomField;
import com.vaadin.v7.ui.Grid;

public class GridEditorCustomField extends AbstractTestUIWithLog {

@Override
protected void setup(VaadinRequest request) {
Grid grid = new PersonTestGrid(100);
grid.setWidth("800px");
grid.setColumns("firstName", "lastName", "address.city");
grid.setEditorEnabled(true);
Set<String> cities = new HashSet<>();
for (Object o : grid.getContainerDataSource().getItemIds()) {
ComplexPerson p = (ComplexPerson) o;
cities.add(p.getAddress().getCity());
}
CustomCitySelect cityEditor = new CustomCitySelect(
cities.toArray(new String[cities.size()]));
grid.getColumn("address.city").setEditorField(cityEditor);
addComponent(grid);
}

public static class CustomCitySelect extends CustomField<String> {
private HorizontalLayout fieldLayout;
private String[] values;
private ComboBox cityComboBox;

public CustomCitySelect(String... values) {
this.values = values;
}

@Override
protected Component initContent() {
fieldLayout = new HorizontalLayout();
fieldLayout.setWidth("100%");

cityComboBox = new ComboBox();
for (String value : values) {
cityComboBox.addItem(value);
}
fieldLayout.addComponent(cityComboBox);
fieldLayout.setExpandRatio(cityComboBox, 1.0f);

Button addCountryButton = new Button("New");
fieldLayout.addComponent(addCountryButton);

return fieldLayout;
}

@Override
public Class<String> getType() {
return String.class;
}

@Override
protected void setInternalValue(String newValue) {
super.setInternalValue(newValue);
if (cityComboBox == null) {
return;
}
cityComboBox.setValue(newValue);
}

@Override
public String getInternalValue() {
if (cityComboBox == null) {
return null;
}
return (String) cityComboBox.getValue();
}
}

}

uitest/src/test/java/com/vaadin/v7/tests/components/grid/GridEditorCustomFieldTest.java → uitest/src/test/java/com/vaadin/tests/components/grid/GridEditorCustomFieldTest.java View File

@@ -13,17 +13,18 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.v7.tests.components.grid;
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.customelements.ComboBoxElement;
import com.vaadin.testbench.customelements.GridElement;
import com.vaadin.testbench.elements.GridElement.GridEditorElement;
import com.vaadin.testbench.parallel.TestCategory;
import com.vaadin.tests.tb3.MultiBrowserTest;
import com.vaadin.testbench.customelements.GridElement;

@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());
}
}

Loading…
Cancel
Save