diff options
author | Artur <artur@vaadin.com> | 2017-03-13 16:33:45 +0200 |
---|---|---|
committer | Teemu Suo-Anttila <tsuoanttila@users.noreply.github.com> | 2017-03-16 16:21:24 +0200 |
commit | 981c9146f3d02189d455cfd244fd6c7da82bddd6 (patch) | |
tree | 54c8dafcba950a177e472a26eff59eaa329bca33 /server | |
parent | 7742e9f103872080603efe9c7aca9b19030e2c5b (diff) | |
download | vaadin-framework-981c9146f3d02189d455cfd244fd6c7da82bddd6.tar.gz vaadin-framework-981c9146f3d02189d455cfd244fd6c7da82bddd6.zip |
Read/write Grid item type to declarative and create columns correctly (#8769)
Fixes #8467
Diffstat (limited to 'server')
3 files changed, 438 insertions, 18 deletions
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java index 2d0af54c45..564ab6f073 100644 --- a/server/src/main/java/com/vaadin/ui/Grid.java +++ b/server/src/main/java/com/vaadin/ui/Grid.java @@ -72,6 +72,7 @@ import com.vaadin.server.SerializableComparator; import com.vaadin.server.SerializableFunction; import com.vaadin.server.SerializableSupplier; import com.vaadin.server.Setter; +import com.vaadin.server.VaadinServiceClassLoaderUtil; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.Registration; import com.vaadin.shared.data.DataCommunicatorConstants; @@ -134,6 +135,8 @@ import elemental.json.JsonValue; public class Grid<T> extends AbstractListing<T> implements HasComponents, HasDataProvider<T>, SortNotifier<GridSortOrder<T>> { + private static final String DECLARATIVE_DATA_ITEM_TYPE = "data-item-type"; + /** * A callback method for fetching items. The callback is provided with a * list of sort orders, offset index and limit. @@ -1999,7 +2002,9 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, private Editor<T> editor; - private final PropertySet<T> propertySet; + private PropertySet<T> propertySet; + + private Class<T> beanType = null; /** * Creates a new grid without support for creating columns based on property @@ -2040,6 +2045,7 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, */ public Grid(Class<T> beanType) { this(BeanPropertySet.get(beanType)); + this.beanType = beanType; } /** @@ -2054,24 +2060,14 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, * the property set implementation to use, not <code>null</code>. */ protected Grid(PropertySet<T> propertySet) { - Objects.requireNonNull(propertySet, "propertySet cannot be null"); - this.propertySet = propertySet; - registerRpc(new GridServerRpcImpl()); - setDefaultHeaderRow(appendHeaderRow()); - setSelectionModel(new SingleSelectionModelImpl<>()); detailsManager = new DetailsManager<>(); addExtension(detailsManager); addDataGenerator(detailsManager); - editor = createEditor(); - if (editor instanceof Extension) { - addExtension((Extension) editor); - } - addDataGenerator((item, json) -> { String styleName = styleGenerator.apply(item); if (styleName != null && !styleName.isEmpty()) { @@ -2085,12 +2081,38 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, } }); + setPropertySet(propertySet); + // Automatically add columns for all available properties propertySet.getProperties().map(PropertyDefinition::getName) .forEach(this::addColumn); } /** + * Sets the property set to use for this grid. Does not create or update + * columns in any way but will delete and re-create the editor. + * <p> + * This is only meant to be called from constructors and readDesign, at a + * stage where it does not matter if you throw away the editor. + * + * @param propertySet + * the property set to use + */ + protected void setPropertySet(PropertySet<T> propertySet) { + Objects.requireNonNull(propertySet, "propertySet cannot be null"); + this.propertySet = propertySet; + + if (editor instanceof Extension) { + removeExtension((Extension) editor); + } + editor = createEditor(); + if (editor instanceof Extension) { + addExtension((Extension) editor); + } + + } + + /** * Creates a grid using a custom {@link PropertySet} implementation for * creating a default set of columns and for resolving property names with * {@link #addColumn(String)} and @@ -2162,6 +2184,19 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, this(caption, DataProvider.ofCollection(items)); } + /** + * Gets the bean type used by this grid. + * <p> + * The bean type is used to automatically set up a column added using a + * property name. + * + * @return the used bean type or <code>null</code> if no bean type has been + * defined + */ + public Class<T> getBeanType() { + return beanType; + } + public <V> void fireColumnVisibilityChangeEvent(Column<T, V> column, boolean hidden, boolean userOriginated) { fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden, @@ -3535,6 +3570,11 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, @Override protected void doReadDesign(Element design, DesignContext context) { Attributes attrs = design.attributes(); + if (design.hasAttr(DECLARATIVE_DATA_ITEM_TYPE)) { + String itemType = design.attr(DECLARATIVE_DATA_ITEM_TYPE); + setBeanType(itemType); + } + if (attrs.hasKey("selection-mode")) { setSelectionMode(DesignAttributeHandler.readAttribute( "selection-mode", attrs, SelectionMode.class)); @@ -3559,9 +3599,59 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, } } + /** + * Sets the bean type to use for property mapping. + * <p> + * This method is responsible also for setting or updating the property set + * so that it matches the given bean type. + * <p> + * Protected mostly for Designer needs, typically should not be overridden + * or even called. + * + * @param beanTypeClassName + * the fully qualified class name of the bean type + */ + @SuppressWarnings("unchecked") + protected void setBeanType(String beanTypeClassName) { + setBeanType((Class<T>) resolveClass(beanTypeClassName)); + } + + /** + * Sets the bean type to use for property mapping. + * <p> + * This method is responsible also for setting or updating the property set + * so that it matches the given bean type. + * <p> + * Protected mostly for Designer needs, typically should not be overridden + * or even called. + * + * @param beanType + * the bean type class + */ + protected void setBeanType(Class<T> beanType) { + this.beanType = beanType; + setPropertySet(BeanPropertySet.get(beanType)); + } + + private Class<?> resolveClass(String qualifiedClassName) { + try { + Class<?> resolvedClass = Class.forName(qualifiedClassName, true, + VaadinServiceClassLoaderUtil.findDefaultClassLoader()); + return resolvedClass; + } catch (ClassNotFoundException | SecurityException e) { + throw new IllegalArgumentException( + "Unable to find class " + qualifiedClassName, e); + } + + } + @Override protected void doWriteDesign(Element design, DesignContext designContext) { Attributes attr = design.attributes(); + if (this.beanType != null) { + design.attr(DECLARATIVE_DATA_ITEM_TYPE, + this.beanType.getCanonicalName()); + } DesignAttributeHandler.writeAttribute("selection-allowed", attr, isReadOnly(), false, Boolean.class, designContext); @@ -3640,14 +3730,24 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents, for (Element col : colgroups.get(0).getElementsByTag("col")) { String id = DesignAttributeHandler.readAttribute("column-id", col.attributes(), null, String.class); - DeclarativeValueProvider<T> provider = new DeclarativeValueProvider<>(); - Column<T, String> column = new Column<>(provider, - new HtmlRenderer()); - addColumn(getGeneratedIdentifier(), column); - if (id != null) { - column.setId(id); + + // If there is a property with a matching name available, + // map to that + Optional<PropertyDefinition<T, ?>> property = propertySet + .getProperties().filter(p -> p.getName().equals(id)) + .findFirst(); + Column<T, ?> column; + if (property.isPresent()) { + column = addColumn(id); + } else { + DeclarativeValueProvider<T> provider = new DeclarativeValueProvider<>(); + column = new Column<>(provider, new HtmlRenderer()); + addColumn(getGeneratedIdentifier(), column); + if (id != null) { + column.setId(id); + } + providers.add(provider); } - providers.add(provider); column.readDesign(col, context); } diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridCustomPropertySetTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridCustomPropertySetTest.java new file mode 100644 index 0000000000..6277e85ae8 --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridCustomPropertySetTest.java @@ -0,0 +1,176 @@ +/* + * 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.server.component.grid; + +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.data.PropertyDefinition; +import com.vaadin.data.PropertySet; +import com.vaadin.data.ValueProvider; +import com.vaadin.server.Setter; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.Column; + +public class GridCustomPropertySetTest { + + public static class MyBeanWithoutGetters { + public String str; + public int number; + + public MyBeanWithoutGetters(String str, int number) { + this.str = str; + this.number = number; + } + } + + public static class GridWithCustomPropertySet + extends Grid<MyBeanWithoutGetters> { + + private final class MyBeanPropertySet + implements PropertySet<MyBeanWithoutGetters> { + + private PropertyDefinition<MyBeanWithoutGetters, String> strDef = new StrDefinition( + this); + private PropertyDefinition<MyBeanWithoutGetters, Integer> numberDef = new NumberDefinition( + this); + + @Override + public Stream<PropertyDefinition<MyBeanWithoutGetters, ?>> getProperties() { + return Stream.of(strDef, numberDef); + } + + @Override + public Optional<PropertyDefinition<MyBeanWithoutGetters, ?>> getProperty( + String name) { + return getProperties().filter(pd -> pd.getName().equals(name)) + .findFirst(); + } + } + + private final class StrDefinition + implements PropertyDefinition<MyBeanWithoutGetters, String> { + private PropertySet<MyBeanWithoutGetters> propertySet; + + public StrDefinition( + PropertySet<MyBeanWithoutGetters> propertySet) { + this.propertySet = propertySet; + } + + @Override + public ValueProvider<MyBeanWithoutGetters, String> getGetter() { + return bean -> bean.str; + } + + @Override + public Optional<Setter<MyBeanWithoutGetters, String>> getSetter() { + return Optional.of((bean, value) -> bean.str = value); + } + + @Override + public Class<String> getType() { + return String.class; + } + + @Override + public String getName() { + return "string"; + } + + @Override + public String getCaption() { + return "The String"; + } + + @Override + public PropertySet<MyBeanWithoutGetters> getPropertySet() { + return propertySet; + } + + } + + private final class NumberDefinition + implements PropertyDefinition<MyBeanWithoutGetters, Integer> { + private PropertySet<MyBeanWithoutGetters> propertySet; + + public NumberDefinition( + PropertySet<MyBeanWithoutGetters> propertySet) { + this.propertySet = propertySet; + } + + @Override + public ValueProvider<MyBeanWithoutGetters, Integer> getGetter() { + return bean -> bean.number; + } + + @Override + public Optional<Setter<MyBeanWithoutGetters, Integer>> getSetter() { + return Optional.of((bean, value) -> bean.number = value); + } + + @Override + public Class<Integer> getType() { + return Integer.class; + } + + @Override + public String getName() { + return "numbah"; + } + + @Override + public String getCaption() { + return "The Number"; + } + + @Override + public PropertySet<MyBeanWithoutGetters> getPropertySet() { + return propertySet; + } + + } + + public GridWithCustomPropertySet() { + super(); + setPropertySet(new MyBeanPropertySet()); + } + + } + + @Test + public void customPropertySet() { + GridWithCustomPropertySet customGrid = new GridWithCustomPropertySet(); + Assert.assertEquals(0, customGrid.getColumns().size()); + + Column<MyBeanWithoutGetters, Integer> numberColumn = (Column<MyBeanWithoutGetters, Integer>) customGrid + .addColumn("numbah"); + Assert.assertEquals(1, customGrid.getColumns().size()); + Assert.assertEquals("The Number", numberColumn.getCaption()); + Assert.assertEquals(24, (int) numberColumn.getValueProvider() + .apply(new MyBeanWithoutGetters("foo", 24))); + + Column<MyBeanWithoutGetters, String> stringColumn = (Column<MyBeanWithoutGetters, String>) customGrid + .addColumn("string"); + Assert.assertEquals(2, customGrid.getColumns().size()); + Assert.assertEquals("The String", stringColumn.getCaption()); + Assert.assertEquals("foo", stringColumn.getValueProvider() + .apply(new MyBeanWithoutGetters("foo", 24))); + } + +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridDeclarativeTest.java index a24d4dac15..a905b1d900 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/grid/GridDeclarativeTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridDeclarativeTest.java @@ -20,8 +20,12 @@ import java.lang.reflect.Method; import java.util.List; import java.util.Locale; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.parser.Tag; +import org.jsoup.select.Elements; +import org.jsoup.select.Selector; import org.junit.Assert; import org.junit.Test; @@ -31,7 +35,10 @@ import com.vaadin.data.provider.DataProvider; import com.vaadin.data.provider.Query; import com.vaadin.shared.ui.ContentMode; import com.vaadin.shared.ui.grid.HeightMode; +import com.vaadin.tests.data.bean.Address; +import com.vaadin.tests.data.bean.Country; import com.vaadin.tests.data.bean.Person; +import com.vaadin.tests.data.bean.Sex; import com.vaadin.tests.server.component.abstractlisting.AbstractListingDeclarativeTest; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.Column; @@ -744,4 +751,141 @@ public class GridDeclarativeTest extends AbstractListingDeclarativeTest<Grid> { return person; } + @Test + public void beanItemType() throws Exception { + Class<Person> beanClass = Person.class; + String beanClassName = beanClass.getName(); + //@formatter:off + String design = String.format( "<%s data-item-type=\"%s\"></%s>", + getComponentTag() , beanClassName, getComponentTag()); + //@formatter:on + + @SuppressWarnings("unchecked") + Grid<Person> grid = read(design); + Assert.assertEquals(beanClass, grid.getBeanType()); + + testWrite(design, grid); + } + + @Test + public void beanGridDefaultColumns() { + Grid<Person> grid = new Grid<>(Person.class); + String design = write(grid, false); + assertDeclarativeColumnCount(11, design); + + Person testPerson = new Person("the first", "the last", "The email", 64, + Sex.MALE, new Address("the street", 12313, "The city", + Country.SOUTH_AFRICA)); + @SuppressWarnings("unchecked") + Grid<Person> readGrid = read(design); + + assertColumns(11, grid.getColumns(), readGrid.getColumns(), testPerson); + } + + private void assertDeclarativeColumnCount(int i, String design) { + Document html = Jsoup.parse(design); + Elements cols = Selector.select("vaadin-grid", html) + .select("colgroup > col"); + Assert.assertEquals("Number of columns in the design file", i, + cols.size()); + + } + + private void assertColumns(int expectedCount, + List<Column<Person, ?>> expectedColumns, + List<Column<Person, ?>> columns, Person testPerson) { + Assert.assertEquals(expectedCount, expectedColumns.size()); + Assert.assertEquals(expectedCount, columns.size()); + for (int i = 0; i < expectedColumns.size(); i++) { + Column<Person, ?> expectedColumn = expectedColumns.get(i); + Column<Person, ?> column = columns.get(i); + + // Property mapping + Assert.assertEquals(expectedColumn.getId(), column.getId()); + // Not tested because of + // https://github.com/vaadin/framework/issues/8752 + // Header caption + // Assert.assertEquals(expectedColumn.getCaption(), + // column.getCaption()); + + // Value providers are not stored in the declarative file + // so this only works for bean properties + if (column.getId() != null + && !column.getId().equals("column" + i)) { + Assert.assertEquals( + expectedColumn.getValueProvider().apply(testPerson), + column.getValueProvider().apply(testPerson)); + } + } + + } + + @Test + public void beanGridNoColumns() { + Grid<Person> grid = new Grid<>(Person.class); + grid.setColumns(); + String design = write(grid, false); + assertDeclarativeColumnCount(0, design); + + Person testPerson = new Person("the first", "the last", "The email", 64, + Sex.MALE, new Address("the street", 12313, "The city", + Country.SOUTH_AFRICA)); + @SuppressWarnings("unchecked") + Grid<Person> readGrid = read(design); + + assertColumns(0, grid.getColumns(), readGrid.getColumns(), testPerson); + + // Can add a mapped property + Assert.assertEquals("The email", readGrid.addColumn("email") + .getValueProvider().apply(testPerson)); + } + + @Test + public void beanGridOnlyCustomColumns() { + // Writes columns without propertyId even though name matches, reads + // columns without propertyId mapping, can add new columns using + // propertyId + Grid<Person> grid = new Grid<>(Person.class); + grid.setColumns(); + grid.addColumn(Person::getFirstName).setCaption("First Name"); + String design = write(grid, false); + assertDeclarativeColumnCount(1, design); + Person testPerson = new Person("the first", "the last", "The email", 64, + Sex.MALE, new Address("the street", 12313, "The city", + Country.SOUTH_AFRICA)); + @SuppressWarnings("unchecked") + Grid<Person> readGrid = read(design); + + assertColumns(1, grid.getColumns(), readGrid.getColumns(), testPerson); + // First name should not be mapped to the property + Assert.assertNull(readGrid.getColumns().get(0).getValueProvider() + .apply(testPerson)); + + // Can add a mapped property + Assert.assertEquals("the last", readGrid.addColumn("lastName") + .getValueProvider().apply(testPerson)); + } + + @Test + public void beanGridOneCustomizedColumn() { + // Writes columns with propertyId except one without + // Reads columns to match initial setup + Grid<Person> grid = new Grid<>(Person.class); + grid.addColumn( + person -> person.getFirstName() + " " + person.getLastName()) + .setCaption("First and Last"); + String design = write(grid, false); + assertDeclarativeColumnCount(12, design); + Person testPerson = new Person("the first", "the last", "The email", 64, + Sex.MALE, new Address("the street", 12313, "The city", + Country.SOUTH_AFRICA)); + @SuppressWarnings("unchecked") + Grid<Person> readGrid = read(design); + + assertColumns(12, grid.getColumns(), readGrid.getColumns(), testPerson); + // First and last name should not be mapped to anything but should exist + Assert.assertNull(readGrid.getColumns().get(11).getValueProvider() + .apply(testPerson)); + + } } |