aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeif Åstrand <legioth@gmail.com>2017-02-01 15:30:57 +0200
committerPekka Hyvönen <pekka@vaadin.com>2017-02-01 15:30:57 +0200
commit953e7212d84619332cba22888aa653462f9c1706 (patch)
tree08ff65e0d812dc507dcf816c5c49743256eeff23
parent38b475330868d2d7b0d0b2da0a14be4040ca89ae (diff)
downloadvaadin-framework-953e7212d84619332cba22888aa653462f9c1706.tar.gz
vaadin-framework-953e7212d84619332cba22888aa653462f9c1706.zip
Make Grid add columns based on bean properties (#8392)
* Make Grid add columns based on bean properties The property set concept used for Binder is slightly generalized and used by Grid as well to support similar functionality. Fixes vaadin/framework8-issues#250
-rw-r--r--documentation/components/components-grid.asciidoc36
-rw-r--r--server/src/main/java/com/vaadin/data/BeanPropertySet.java (renamed from server/src/main/java/com/vaadin/data/BeanBinderPropertySet.java)51
-rw-r--r--server/src/main/java/com/vaadin/data/BeanValidationBinder.java2
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java77
-rw-r--r--server/src/main/java/com/vaadin/data/PropertyDefinition.java (renamed from server/src/main/java/com/vaadin/data/BinderPropertyDefinition.java)19
-rw-r--r--server/src/main/java/com/vaadin/data/PropertySet.java (renamed from server/src/main/java/com/vaadin/data/BinderPropertySet.java)9
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java165
-rw-r--r--server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java8
-rw-r--r--server/src/test/java/com/vaadin/data/BeanPropertySetTest.java (renamed from server/src/test/java/com/vaadin/data/BeanBinderPropertySetTest.java)27
-rw-r--r--server/src/test/java/com/vaadin/data/BinderCustomPropertySetTest.java18
-rw-r--r--server/src/test/java/com/vaadin/data/provider/bov/Person.java11
-rw-r--r--server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java91
12 files changed, 404 insertions, 110 deletions
diff --git a/documentation/components/components-grid.asciidoc b/documentation/components/components-grid.asciidoc
index 3ea0e578f3..52d2301a46 100644
--- a/documentation/components/components-grid.asciidoc
+++ b/documentation/components/components-grid.asciidoc
@@ -42,7 +42,7 @@ cell style generator.
[[components.grid.data]]
== Binding to Data
-[classname]#Grid# is normally used by binding it to a ,
+[classname]#Grid# is normally used by binding it to a data provider,
described in
<<dummy/../../../framework/datamodel/datamodel-providers.asciidoc#datamodel.dataproviders,"Showing Many Items in a Listing">>.
By default, it is bound to List of items. You can set the items with the
@@ -96,7 +96,7 @@ grid.setSelectionMode(SelectionMode.MULTI);
grid.addSelectionListener(event -> {
Set<Person> selected = event.getAllSelectedItems();
Notification.show(selected.size() + " items selected");
-}
+});
----
Programmatically selecting the value is possible via [methodname]#select(T)#.
@@ -214,7 +214,7 @@ selectionModel.addMultiSelectionListener(event -> {
// Allow deleting only if there's any selected
deleteSelected.setEnabled(
event.getNewSelection().size() > 0);
-};
+});
----
@@ -241,7 +241,7 @@ and column.
[source, java]
----
grid.addCellClickListener(event ->
- Notification.show("Value: " + event.getItem());
+ Notification.show("Value: " + event.getItem()));
----
The clicked grid cell is also automatically focused.
@@ -255,15 +255,11 @@ well as disable cell focus, in a custom theme. See <<components.grid.css>>.
[[components.grid.columns]]
== Configuring Columns
-Columns are normally defined in the container data source. The
-[methodname]#addColumn()# method can be used to add columns to [classname]#Grid#.
-
-Column configuration is defined in [classname]#Grid.Column# objects, which can
-be obtained from the grid with [methodname]#getColumns()#.
+The [methodname]#addColumn()# method can be used to add columns to [classname]#Grid#.
-The setter methods in [classname]#Column# have _fluent API_, so you can easily chain
-the configuration calls for columns if you want to.
+Column configuration is defined in [classname]#Grid.Column# objects, which are returned by `addColumn` and can also be obtained from the grid with [methodname]#getColumns()#.
+The setter methods in [classname]#Column# have _fluent API_, so you can easily chain the configuration calls for columns if you want to.
[source, java]
----
@@ -275,6 +271,22 @@ grid.addColumn(Person:getBirthDate, new DateRenderer())
In the following, we describe the basic column configuration.
+[[components.grid.columns.automatic]]
+=== Automatically Adding Columns
+
+You can configure `Grid` to automatically add columns based on the properties in a bean.
+To do this, you need to pass the `Class` of the bean type to the constructor when creating a grid.
+You can then further configure the columns based on the bean property name.
+
+[source, java]
+----
+Grid<Person> grid = new Grid<>(Person.class);
+
+grid.getColumn("birthDate").setWidth("100px");
+
+grid.setItems(people);
+----
+
[[components.grid.columns.order]]
=== Column Order
@@ -424,7 +436,7 @@ grid.addColumn(person -> "Delete",
new ButtonRenderer(clickEvent -> {
people.remove(clickEvent.getValue());
grid.setItems(people);
- });
+ }));
----
[classname]#ImageRenderer#:: Renders the cell as an image.
diff --git a/server/src/main/java/com/vaadin/data/BeanBinderPropertySet.java b/server/src/main/java/com/vaadin/data/BeanPropertySet.java
index d5fb2b4f5d..d6ab364aff 100644
--- a/server/src/main/java/com/vaadin/data/BeanBinderPropertySet.java
+++ b/server/src/main/java/com/vaadin/data/BeanPropertySet.java
@@ -32,10 +32,11 @@ import java.util.stream.Stream;
import com.vaadin.data.util.BeanUtil;
import com.vaadin.server.Setter;
+import com.vaadin.shared.util.SharedUtil;
import com.vaadin.util.ReflectTools;
/**
- * A {@link BinderPropertySet} that uses reflection to find bean properties.
+ * A {@link PropertySet} that uses reflection to find bean properties.
*
* @author Vaadin Ltd
*
@@ -44,7 +45,7 @@ import com.vaadin.util.ReflectTools;
* @param <T>
* the type of the bean
*/
-public class BeanBinderPropertySet<T> implements BinderPropertySet<T> {
+public class BeanPropertySet<T> implements PropertySet<T> {
/**
* Serialized form of a property set. When deserialized, the property set
@@ -52,7 +53,7 @@ public class BeanBinderPropertySet<T> implements BinderPropertySet<T> {
* existing cached instance or creates a new one.
*
* @see #readResolve()
- * @see BeanBinderPropertyDefinition#writeReplace()
+ * @see BeanPropertyDefinition#writeReplace()
*/
private static class SerializedPropertySet implements Serializable {
private final Class<?> beanType;
@@ -77,7 +78,7 @@ public class BeanBinderPropertySet<T> implements BinderPropertySet<T> {
* definition is then fetched from the property set.
*
* @see #readResolve()
- * @see BeanBinderPropertySet#writeReplace()
+ * @see BeanPropertySet#writeReplace()
*/
private static class SerializedPropertyDefinition implements Serializable {
private final Class<?> beanType;
@@ -102,14 +103,13 @@ public class BeanBinderPropertySet<T> implements BinderPropertySet<T> {
}
}
- private static class BeanBinderPropertyDefinition<T, V>
- implements BinderPropertyDefinition<T, V> {
+ private static class BeanPropertyDefinition<T, V>
+ implements PropertyDefinition<T, V> {
private final PropertyDescriptor descriptor;
- private final BeanBinderPropertySet<T> propertySet;
+ private final BeanPropertySet<T> propertySet;
- public BeanBinderPropertyDefinition(
- BeanBinderPropertySet<T> propertySet,
+ public BeanPropertyDefinition(BeanPropertySet<T> propertySet,
PropertyDescriptor descriptor) {
this.propertySet = propertySet;
this.descriptor = descriptor;
@@ -156,7 +156,12 @@ public class BeanBinderPropertySet<T> implements BinderPropertySet<T> {
}
@Override
- public BeanBinderPropertySet<T> getPropertySet() {
+ public String getCaption() {
+ return SharedUtil.propertyIdToHumanFriendly(getName());
+ }
+
+ @Override
+ public BeanPropertySet<T> getPropertySet() {
return propertySet;
}
@@ -171,21 +176,21 @@ public class BeanBinderPropertySet<T> implements BinderPropertySet<T> {
}
}
- private static final ConcurrentMap<Class<?>, BeanBinderPropertySet<?>> instances = new ConcurrentHashMap<>();
+ private static final ConcurrentMap<Class<?>, BeanPropertySet<?>> instances = new ConcurrentHashMap<>();
private final Class<T> beanType;
- private final Map<String, BinderPropertyDefinition<T, ?>> definitions;
+ private final Map<String, PropertyDefinition<T, ?>> definitions;
- private BeanBinderPropertySet(Class<T> beanType) {
+ private BeanPropertySet(Class<T> beanType) {
this.beanType = beanType;
try {
definitions = BeanUtil.getBeanPropertyDescriptors(beanType).stream()
- .filter(BeanBinderPropertySet::hasNonObjectReadMethod)
- .map(descriptor -> new BeanBinderPropertyDefinition<>(this,
+ .filter(BeanPropertySet::hasNonObjectReadMethod)
+ .map(descriptor -> new BeanPropertyDefinition<>(this,
descriptor))
- .collect(Collectors.toMap(BinderPropertyDefinition::getName,
+ .collect(Collectors.toMap(PropertyDefinition::getName,
Function.identity()));
} catch (IntrospectionException e) {
throw new IllegalArgumentException(
@@ -196,28 +201,28 @@ public class BeanBinderPropertySet<T> implements BinderPropertySet<T> {
}
/**
- * Gets a {@link BeanBinderPropertySet} for the given bean type.
+ * Gets a {@link BeanPropertySet} for the given bean type.
*
* @param beanType
* the bean type to get a property set for, not <code>null</code>
- * @return the bean binder property set, not <code>null</code>
+ * @return the bean property set, not <code>null</code>
*/
@SuppressWarnings("unchecked")
- public static <T> BinderPropertySet<T> get(Class<? extends T> beanType) {
+ public static <T> PropertySet<T> get(Class<? extends T> beanType) {
Objects.requireNonNull(beanType, "Bean type cannot be null");
// Cache the reflection results
- return (BinderPropertySet<T>) instances.computeIfAbsent(beanType,
- BeanBinderPropertySet::new);
+ return (PropertySet<T>) instances.computeIfAbsent(beanType,
+ BeanPropertySet::new);
}
@Override
- public Stream<BinderPropertyDefinition<T, ?>> getProperties() {
+ public Stream<PropertyDefinition<T, ?>> getProperties() {
return definitions.values().stream();
}
@Override
- public Optional<BinderPropertyDefinition<T, ?>> getProperty(String name) {
+ public Optional<PropertyDefinition<T, ?>> getProperty(String name) {
return Optional.ofNullable(definitions.get(name));
}
diff --git a/server/src/main/java/com/vaadin/data/BeanValidationBinder.java b/server/src/main/java/com/vaadin/data/BeanValidationBinder.java
index 5e3b220ffd..34af4156a4 100644
--- a/server/src/main/java/com/vaadin/data/BeanValidationBinder.java
+++ b/server/src/main/java/com/vaadin/data/BeanValidationBinder.java
@@ -56,7 +56,7 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
@Override
protected BindingBuilder<BEAN, ?> configureBinding(
BindingBuilder<BEAN, ?> binding,
- BinderPropertyDefinition<BEAN, ?> definition) {
+ PropertyDefinition<BEAN, ?> definition) {
return binding.withValidator(
new BeanValidator(beanType, definition.getName()));
}
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index f291c791b7..d36c9996fd 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -195,7 +194,7 @@ public class Binder<BEAN> implements Serializable {
/**
* Completes this binding by connecting the field to the property with
* the given name. The getter and setter of the property are looked up
- * using a {@link BinderPropertySet}.
+ * using a {@link PropertySet}.
* <p>
* For a <code>Binder</code> created using the
* {@link Binder#Binder(Class)} constructor, introspection will be used
@@ -217,7 +216,7 @@ public class Binder<BEAN> implements Serializable {
* if the property has no accessible getter
* @throws IllegalStateException
* if the binder is not configured with an appropriate
- * {@link BinderPropertySet}
+ * {@link PropertySet}
*
* @see Binder.BindingBuilder#bind(ValueProvider, Setter)
*/
@@ -608,7 +607,7 @@ public class Binder<BEAN> implements Serializable {
"Property name cannot be null");
checkUnbound();
- BinderPropertyDefinition<BEAN, ?> definition = getBinder().propertySet
+ PropertyDefinition<BEAN, ?> definition = getBinder().propertySet
.getProperty(propertyName)
.orElseThrow(() -> new IllegalArgumentException(
"Could not resolve property name " + propertyName
@@ -627,9 +626,11 @@ public class Binder<BEAN> implements Serializable {
definition);
try {
- return ((BindingBuilder) finalBinding).bind(getter, setter);
+ Binding binding = ((BindingBuilder) finalBinding).bind(getter,
+ setter);
+ getBinder().boundProperties.put(propertyName, binding);
+ return binding;
} finally {
- getBinder().boundProperties.add(propertyName);
getBinder().incompleteMemberFieldBindings.remove(getField());
}
}
@@ -1044,12 +1045,12 @@ public class Binder<BEAN> implements Serializable {
}
}
- private final BinderPropertySet<BEAN> propertySet;
+ private final PropertySet<BEAN> propertySet;
/**
* Property names that have been used for creating a binding.
*/
- private final Set<String> boundProperties = new HashSet<>();
+ private final Map<String, Binding<BEAN, ?>> boundProperties = new HashMap<>();
private final Map<HasValue<?>, BindingBuilder<BEAN, ?>> incompleteMemberFieldBindings = new IdentityHashMap<>();
@@ -1072,16 +1073,15 @@ public class Binder<BEAN> implements Serializable {
private boolean hasChanges = false;
/**
- * Creates a binder using a custom {@link BinderPropertySet} implementation
- * for finding and resolving property names for
+ * Creates a binder using a custom {@link PropertySet} implementation for
+ * finding and resolving property names for
* {@link #bindInstanceFields(Object)}, {@link #bind(HasValue, String)} and
* {@link BindingBuilder#bind(String)}.
*
* @param propertySet
- * the binder property set implementation to use, not
- * <code>null</code>.
+ * the property set implementation to use, not <code>null</code>.
*/
- protected Binder(BinderPropertySet<BEAN> propertySet) {
+ protected Binder(PropertySet<BEAN> propertySet) {
Objects.requireNonNull(propertySet, "propertySet cannot be null");
this.propertySet = propertySet;
}
@@ -1094,7 +1094,7 @@ public class Binder<BEAN> implements Serializable {
* the bean type to use, not <code>null</code>
*/
public Binder(Class<BEAN> beanType) {
- this(BeanBinderPropertySet.get(beanType));
+ this(BeanPropertySet.get(beanType));
}
/**
@@ -1106,15 +1106,15 @@ public class Binder<BEAN> implements Serializable {
* {@link #bind(HasValue, String)} or {@link BindingBuilder#bind(String)}.
*/
public Binder() {
- this(new BinderPropertySet<BEAN>() {
+ this(new PropertySet<BEAN>() {
@Override
- public Stream<BinderPropertyDefinition<BEAN, ?>> getProperties() {
+ public Stream<PropertyDefinition<BEAN, ?>> getProperties() {
throw new IllegalStateException(
"A Binder created with the default constructor doesn't support listing properties.");
}
@Override
- public Optional<BinderPropertyDefinition<BEAN, ?>> getProperty(
+ public Optional<PropertyDefinition<BEAN, ?>> getProperty(
String name) {
throw new IllegalStateException(
"A Binder created with the default constructor doesn't support finding properties by name.");
@@ -1123,8 +1123,8 @@ public class Binder<BEAN> implements Serializable {
}
/**
- * Creates a binder using a custom {@link BinderPropertySet} implementation
- * for finding and resolving property names for
+ * Creates a binder using a custom {@link PropertySet} implementation for
+ * finding and resolving property names for
* {@link #bindInstanceFields(Object)}, {@link #bind(HasValue, String)} and
* {@link BindingBuilder#bind(String)}.
* <p>
@@ -1137,13 +1137,12 @@ public class Binder<BEAN> implements Serializable {
* @see Binder#Binder(Class)
*
* @param propertySet
- * the binder property set implementation to use, not
- * <code>null</code>.
+ * the property set implementation to use, not <code>null</code>.
* @return a new binder using the provided property set, not
* <code>null</code>
*/
public static <BEAN> Binder<BEAN> withPropertySet(
- BinderPropertySet<BEAN> propertySet) {
+ PropertySet<BEAN> propertySet) {
return new Binder<>(propertySet);
}
@@ -1272,7 +1271,7 @@ public class Binder<BEAN> implements Serializable {
/**
* Binds the given field to the property with the given name. The getter and
- * setter of the property are looked up using a {@link BinderPropertySet}.
+ * setter of the property are looked up using a {@link PropertySet}.
* <p>
* For a <code>Binder</code> created using the {@link Binder#Binder(Class)}
* constructor, introspection will be used to find a Java Bean property. If
@@ -1297,7 +1296,7 @@ public class Binder<BEAN> implements Serializable {
* if the property has no accessible getter
* @throws IllegalStateException
* if the binder is not configured with an appropriate
- * {@link BinderPropertySet}
+ * {@link PropertySet}
*
* @see #bind(HasValue, ValueProvider, Setter)
*/
@@ -1964,7 +1963,7 @@ public class Binder<BEAN> implements Serializable {
/**
* Configures the {@code binding} with the property definition
* {@code definition} before it's being bound.
- *
+ *
* @param binding
* a binding to configure
* @param definition
@@ -1973,7 +1972,7 @@ public class Binder<BEAN> implements Serializable {
*/
protected BindingBuilder<BEAN, ?> configureBinding(
BindingBuilder<BEAN, ?> binding,
- BinderPropertyDefinition<BEAN, ?> definition) {
+ PropertyDefinition<BEAN, ?> definition) {
return binding;
}
@@ -2217,7 +2216,7 @@ public class Binder<BEAN> implements Serializable {
private void handleProperty(Field field, Object objectWithMemberFields,
BiConsumer<String, Class<?>> propertyHandler) {
- Optional<BinderPropertyDefinition<BEAN, ?>> descriptor = getPropertyDescriptor(
+ Optional<PropertyDefinition<BEAN, ?>> descriptor = getPropertyDescriptor(
field);
if (!descriptor.isPresent()) {
@@ -2225,7 +2224,7 @@ public class Binder<BEAN> implements Serializable {
}
String propertyName = descriptor.get().getName();
- if (boundProperties.contains(propertyName)) {
+ if (boundProperties.containsKey(propertyName)) {
return;
}
@@ -2237,10 +2236,25 @@ public class Binder<BEAN> implements Serializable {
}
propertyHandler.accept(propertyName, descriptor.get().getType());
- boundProperties.add(propertyName);
+ assert boundProperties.containsKey(propertyName);
+ }
+
+ /**
+ * Gets the binding for a property name. Bindings are available by property
+ * name if bound using {@link #bind(HasValue, String)},
+ * {@link BindingBuilder#bind(String)} or indirectly using
+ * {@link #bindInstanceFields(Object)}.
+ *
+ * @param propertyName
+ * the property name of the binding to get
+ * @return the binding corresponding to the property name, or an empty
+ * optional if there is no binding with that property name
+ */
+ public Optional<Binding<BEAN, ?>> getBinding(String propertyName) {
+ return Optional.ofNullable(boundProperties.get(propertyName));
}
- private Optional<BinderPropertyDefinition<BEAN, ?>> getPropertyDescriptor(
+ private Optional<PropertyDefinition<BEAN, ?>> getPropertyDescriptor(
Field field) {
PropertyId propertyIdAnnotation = field.getAnnotation(PropertyId.class);
@@ -2254,8 +2268,7 @@ public class Binder<BEAN> implements Serializable {
String minifiedFieldName = minifyFieldName(propertyId);
- return propertySet.getProperties()
- .map(BinderPropertyDefinition::getName)
+ return propertySet.getProperties().map(PropertyDefinition::getName)
.filter(name -> minifyFieldName(name).equals(minifiedFieldName))
.findFirst().flatMap(propertySet::getProperty);
}
diff --git a/server/src/main/java/com/vaadin/data/BinderPropertyDefinition.java b/server/src/main/java/com/vaadin/data/PropertyDefinition.java
index b4145a8c4f..79bb2159b4 100644
--- a/server/src/main/java/com/vaadin/data/BinderPropertyDefinition.java
+++ b/server/src/main/java/com/vaadin/data/PropertyDefinition.java
@@ -21,17 +21,17 @@ import java.util.Optional;
import com.vaadin.server.Setter;
/**
- * A property from a {@link BinderPropertySet}.
+ * A property from a {@link PropertySet}.
*
* @author Vaadin Ltd
* @since
*
* @param <T>
- * the type of the binder property set
+ * the type of the property set
* @param <V>
* the property type
*/
-public interface BinderPropertyDefinition<T, V> extends Serializable {
+public interface PropertyDefinition<T, V> extends Serializable {
/**
* Gets the value provider that is used for finding the value of this
* property for a bean.
@@ -62,9 +62,16 @@ public interface BinderPropertyDefinition<T, V> extends Serializable {
public String getName();
/**
- * Gets the {@link BinderPropertySet} that this property belongs to.
+ * Gets the human readable caption to show for this property.
*
- * @return the binder property set, not <code>null</code>
+ * @return the caption to show, not <code>null</code>
*/
- public BinderPropertySet<T> getPropertySet();
+ public String getCaption();
+
+ /**
+ * Gets the {@link PropertySet} that this property belongs to.
+ *
+ * @return the property set, not <code>null</code>
+ */
+ public PropertySet<T> getPropertySet();
}
diff --git a/server/src/main/java/com/vaadin/data/BinderPropertySet.java b/server/src/main/java/com/vaadin/data/PropertySet.java
index 6252a228aa..7b557dc293 100644
--- a/server/src/main/java/com/vaadin/data/BinderPropertySet.java
+++ b/server/src/main/java/com/vaadin/data/PropertySet.java
@@ -20,7 +20,8 @@ import java.util.Optional;
import java.util.stream.Stream;
/**
- * Describes a set of properties that can be used with a {@link Binder}.
+ * Describes a set of properties that can be used for configuration based on
+ * property names instead of setter and getter callbacks.
*
* @author Vaadin Ltd
*
@@ -29,13 +30,13 @@ import java.util.stream.Stream;
* @param <T>
* the type for which the properties are defined
*/
-public interface BinderPropertySet<T> extends Serializable {
+public interface PropertySet<T> extends Serializable {
/**
* Gets all known properties as a stream.
*
* @return a stream of property names, not <code>null</code>
*/
- public Stream<BinderPropertyDefinition<T, ?>> getProperties();
+ public Stream<PropertyDefinition<T, ?>> getProperties();
/**
* Gets the definition for the named property, or an empty optional if there
@@ -46,5 +47,5 @@ public interface BinderPropertySet<T> extends Serializable {
* @return the property definition, or empty optional if property doesn't
* exist
*/
- public Optional<BinderPropertyDefinition<T, ?>> getProperty(String name);
+ public Optional<PropertyDefinition<T, ?>> getProperty(String name);
}
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
index 4052282fdc..daf1cd1c69 100644
--- a/server/src/main/java/com/vaadin/ui/Grid.java
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -41,10 +41,13 @@ import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
+import com.vaadin.data.BeanPropertySet;
import com.vaadin.data.Binder;
import com.vaadin.data.Binder.Binding;
import com.vaadin.data.HasDataProvider;
import com.vaadin.data.HasValue;
+import com.vaadin.data.PropertyDefinition;
+import com.vaadin.data.PropertySet;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.DataCommunicator;
import com.vaadin.data.provider.DataProvider;
@@ -1609,7 +1612,9 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
* a setter that stores the component value in the row item
* @return this column
*
+ * @see #setEditorBinding(Binding)
* @see Grid#getEditor()
+ * @see Binder#bind(HasValue, ValueProvider, Setter)
*/
public <C extends HasValue<V> & Component> Column<T, V> setEditorComponent(
C editorComponent, Setter<T, V> setter) {
@@ -1624,6 +1629,47 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
}
/**
+ * Sets a component to use for editing values of this columns in the
+ * editor row. This method can only be used if the column has an
+ * {@link #setId(String) id} and the {@link Grid} has been created using
+ * {@link Grid#Grid(Class)} or some other way that allows finding
+ * properties based on property names.
+ * <p>
+ * This is a shorthand for use in simple cases where no validator or
+ * converter is needed. Use {@link #setEditorBinding(Binding)} to
+ * support more complex cases.
+ * <p>
+ * <strong>Note:</strong> The same component cannot be used for multiple
+ * columns.
+ *
+ * @param editorComponent
+ * the editor component
+ * @return this column
+ *
+ * @see #setEditorBinding(Binding)
+ * @see Grid#getEditor()
+ * @see Binder#bind(HasValue, String)
+ * @see Grid#Grid(Class)
+ */
+ public <F, C extends HasValue<F> & Component> Column<T, V> setEditorComponent(
+ C editorComponent) {
+ Objects.requireNonNull(editorComponent,
+ "Editor component cannot be null");
+
+ String propertyName = getId();
+ if (propertyName == null) {
+ throw new IllegalStateException(
+ "setEditorComponent without a setter can only be used if the column has an id. "
+ + "Use another setEditorComponent(Component, Setter) or setEditorBinding(Binding) instead.");
+ }
+
+ Binding<T, F> binding = getGrid().getEditor().getBinder()
+ .bind(editorComponent, propertyName);
+
+ return setEditorBinding(binding);
+ }
+
+ /**
* Gets the grid that this column belongs to.
*
* @return the grid that this column belongs to, or <code>null</code> if
@@ -1851,10 +1897,64 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
private Editor<T> editor;
+ private final PropertySet<T> propertySet;
+
/**
- * Constructor for the {@link Grid} component.
+ * Creates a new grid without support for creating columns based on property
+ * names. Use an alternative constructor, such as {@link Grid#Grid(Class)},
+ * to create a grid that automatically sets up columns based on the type of
+ * presented data.
+ *
+ * @see #Grid(Class)
+ * @see #withPropertySet(PropertySet)
*/
public Grid() {
+ this(new PropertySet<T>() {
+ @Override
+ public Stream<PropertyDefinition<T, ?>> getProperties() {
+ // No columns configured by default
+ return Stream.empty();
+ }
+
+ @Override
+ public Optional<PropertyDefinition<T, ?>> getProperty(String name) {
+ throw new IllegalStateException(
+ "A Grid created without a bean type class literal or a custom property set"
+ + " doesn't support finding properties by name.");
+ }
+ });
+ }
+
+ /**
+ * Creates a new grid that uses reflection based on the provided bean type
+ * to automatically set up an initial set of columns. All columns will be
+ * configured using the same {@link Object#toString()} renderer that is used
+ * by {@link #addColumn(ValueProvider)}.
+ *
+ * @param beanType
+ * the bean type to use, not <code>null</code>
+ * @see #Grid()
+ * @see #withPropertySet(PropertySet)
+ */
+ public Grid(Class<T> beanType) {
+ this(BeanPropertySet.get(beanType));
+ }
+
+ /**
+ * Creates a grid using a custom {@link PropertySet} implementation for
+ * configuring the initial columns and resolving property names for
+ * {@link #addColumn(String)} and
+ * {@link Column#setEditorComponent(HasValue)}.
+ *
+ * @see #withPropertySet(PropertySet)
+ *
+ * @param propertySet
+ * 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());
@@ -1882,6 +1982,33 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
}
}
});
+
+ // Automatically add columns for all available properties
+ propertySet.getProperties().map(PropertyDefinition::getName)
+ .forEach(this::addColumn);
+ }
+
+ /**
+ * 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
+ * {@link Column#setEditorComponent(HasValue)}.
+ * <p>
+ * This functionality is provided as static method instead of as a public
+ * constructor in order to make it possible to use a custom property set
+ * without creating a subclass while still leaving the public constructors
+ * focused on the common use cases.
+ *
+ * @see Grid#Grid()
+ * @see Grid#Grid(Class)
+ *
+ * @param propertySet
+ * the property set implementation to use, not <code>null</code>.
+ * @return a new grid using the provided property set, not <code>null</code>
+ */
+ public static <BEAN> Grid<BEAN> withPropertySet(
+ PropertySet<BEAN> propertySet) {
+ return new Grid<>(propertySet);
}
/**
@@ -1940,6 +2067,37 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
}
/**
+ * Adds a new column with the given property name. The property name will be
+ * used as the {@link Column#getId() column id} and the
+ * {@link Column#getCaption() column caption} will be set based on the
+ * property definition.
+ * <p>
+ * This method can only be used for a <code>Grid</code> created using
+ * {@link Grid#Grid(Class)} or {@link #withPropertySet(PropertySet)}.
+ *
+ * @param propertyName
+ * the property name of the new column, not <code>null</code>
+ * @return the newly added column, not <code>null</code>
+ */
+ public Column<T, ?> addColumn(String propertyName) {
+ Objects.requireNonNull(propertyName, "Property name cannot be null");
+
+ if (getColumn(propertyName) != null) {
+ throw new IllegalStateException(
+ "There is already a column for " + propertyName);
+ }
+
+ PropertyDefinition<T, ?> definition = propertySet
+ .getProperty(propertyName)
+ .orElseThrow(() -> new IllegalArgumentException(
+ "Could not resolve property name " + propertyName
+ + " from " + propertySet));
+
+ return addColumn(definition.getGetter()).setId(definition.getName())
+ .setCaption(definition.getCaption());
+ }
+
+ /**
* Adds a new text column to this {@link Grid} with a value provider. The
* column will use a {@link TextRenderer}. The value is converted to a
* String using {@link Object#toString()}. Sorting in memory is executed by
@@ -2070,7 +2228,8 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
*
* @param columnId
* the identifier of the column to get
- * @return the column corresponding to the given column identifier
+ * @return the column corresponding to the given column identifier, or
+ * <code>null</code> if there is no such column
*/
public Column<T, ?> getColumn(String columnId) {
return columnIds.get(columnId);
@@ -2983,7 +3142,7 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
* @return editor
*/
protected Editor<T> createEditor() {
- return new EditorImpl<>();
+ return new EditorImpl<>(propertySet);
}
private void addExtensionComponent(Component c) {
diff --git a/server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java b/server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java
index a001a5026a..dae7c61ee7 100644
--- a/server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java
+++ b/server/src/main/java/com/vaadin/ui/components/grid/EditorImpl.java
@@ -27,6 +27,7 @@ import com.vaadin.data.Binder;
import com.vaadin.data.Binder.Binding;
import com.vaadin.data.BinderValidationStatus;
import com.vaadin.data.BinderValidationStatusHandler;
+import com.vaadin.data.PropertySet;
import com.vaadin.shared.ui.grid.editor.EditorClientRpc;
import com.vaadin.shared.ui.grid.editor.EditorServerRpc;
import com.vaadin.shared.ui.grid.editor.EditorState;
@@ -112,8 +113,11 @@ public class EditorImpl<T> extends AbstractGridExtension<T>
/**
* Constructor for internal implementation of the Editor.
+ *
+ * @param propertySet
+ * the property set to use for configuring the default binder
*/
- public EditorImpl() {
+ public EditorImpl(PropertySet<T> propertySet) {
rpc = getRpcProxy(EditorClientRpc.class);
registerRpc(new EditorServerRpc() {
@@ -142,7 +146,7 @@ public class EditorImpl<T> extends AbstractGridExtension<T>
}
});
- setBinder(new Binder<>());
+ setBinder(Binder.withPropertySet(propertySet));
}
@Override
diff --git a/server/src/test/java/com/vaadin/data/BeanBinderPropertySetTest.java b/server/src/test/java/com/vaadin/data/BeanPropertySetTest.java
index cf56dd9368..4e888846df 100644
--- a/server/src/test/java/com/vaadin/data/BeanBinderPropertySetTest.java
+++ b/server/src/test/java/com/vaadin/data/BeanPropertySetTest.java
@@ -32,13 +32,13 @@ import org.junit.Test;
import com.vaadin.data.provider.bov.Person;
import com.vaadin.tests.server.ClassesSerializableTest;
-public class BeanBinderPropertySetTest {
+public class BeanPropertySetTest {
@Test
public void testSerializeDeserialize_propertySet() throws Exception {
- BinderPropertySet<Person> originalPropertySet = BeanBinderPropertySet
+ PropertySet<Person> originalPropertySet = BeanPropertySet
.get(Person.class);
- BinderPropertySet<Person> deserializedPropertySet = ClassesSerializableTest
+ PropertySet<Person> deserializedPropertySet = ClassesSerializableTest
.serializeAndDeserialize(originalPropertySet);
Assert.assertSame(
@@ -49,7 +49,7 @@ public class BeanBinderPropertySetTest {
@Test
public void testSerializeDeserialize_propertySet_cacheCleared()
throws Exception {
- BinderPropertySet<Person> originalPropertySet = BeanBinderPropertySet
+ PropertySet<Person> originalPropertySet = BeanPropertySet
.get(Person.class);
ByteArrayOutputStream bs = new ByteArrayOutputStream();
@@ -59,7 +59,7 @@ public class BeanBinderPropertySetTest {
// Simulate deserializing into a different JVM by clearing the instance
// map
- Field instancesField = BeanBinderPropertySet.class
+ Field instancesField = BeanPropertySet.class
.getDeclaredField("instances");
instancesField.setAccessible(true);
Map<?, ?> instances = (Map<?, ?>) instancesField.get(null);
@@ -67,13 +67,12 @@ public class BeanBinderPropertySetTest {
ObjectInputStream in = new ObjectInputStream(
new ByteArrayInputStream(data));
- BinderPropertySet<Person> deserializedPropertySet = (BinderPropertySet<Person>) in
+ PropertySet<Person> deserializedPropertySet = (PropertySet<Person>) in
.readObject();
Assert.assertSame(
"Deserialized instance should be the same as in the cache",
- BeanBinderPropertySet.get(Person.class),
- deserializedPropertySet);
+ BeanPropertySet.get(Person.class), deserializedPropertySet);
Assert.assertNotSame(
"Deserialized instance should not be the same as the original",
originalPropertySet, deserializedPropertySet);
@@ -81,11 +80,11 @@ public class BeanBinderPropertySetTest {
@Test
public void testSerializeDeserialize_propertyDefinition() throws Exception {
- BinderPropertyDefinition<Person, ?> definition = BeanBinderPropertySet
+ PropertyDefinition<Person, ?> definition = BeanPropertySet
.get(Person.class).getProperty("born")
.orElseThrow(RuntimeException::new);
- BinderPropertyDefinition<Person, ?> deserializedDefinition = ClassesSerializableTest
+ PropertyDefinition<Person, ?> deserializedDefinition = ClassesSerializableTest
.serializeAndDeserialize(definition);
ValueProvider<Person, ?> getter = deserializedDefinition.getGetter();
@@ -97,19 +96,17 @@ public class BeanBinderPropertySetTest {
Assert.assertSame(
"Deserialized instance should be the same as in the cache",
- BeanBinderPropertySet.get(Person.class).getProperty("born")
+ BeanPropertySet.get(Person.class).getProperty("born")
.orElseThrow(RuntimeException::new),
deserializedDefinition);
}
@Test
public void properties() {
- BinderPropertySet<Person> propertySet = BeanBinderPropertySet
- .get(Person.class);
+ PropertySet<Person> propertySet = BeanPropertySet.get(Person.class);
Set<String> propertyNames = propertySet.getProperties()
- .map(BinderPropertyDefinition::getName)
- .collect(Collectors.toSet());
+ .map(PropertyDefinition::getName).collect(Collectors.toSet());
Assert.assertEquals(new HashSet<>(Arrays.asList("name", "born")),
propertyNames);
diff --git a/server/src/test/java/com/vaadin/data/BinderCustomPropertySetTest.java b/server/src/test/java/com/vaadin/data/BinderCustomPropertySetTest.java
index d8ebf4da10..65bfb36c95 100644
--- a/server/src/test/java/com/vaadin/data/BinderCustomPropertySetTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderCustomPropertySetTest.java
@@ -16,6 +16,7 @@
package com.vaadin.data;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
@@ -28,7 +29,7 @@ import com.vaadin.ui.TextField;
public class BinderCustomPropertySetTest {
public static class MapPropertyDefinition
- implements BinderPropertyDefinition<Map<String, String>, String> {
+ implements PropertyDefinition<Map<String, String>, String> {
private MapPropertySet propertySet;
private String name;
@@ -65,26 +66,31 @@ public class BinderCustomPropertySetTest {
}
@Override
- public BinderPropertySet<Map<String, String>> getPropertySet() {
+ public PropertySet<Map<String, String>> getPropertySet() {
return propertySet;
}
+ @Override
+ public String getCaption() {
+ return name.toUpperCase(Locale.ENGLISH);
+ }
+
}
public static class MapPropertySet
- implements BinderPropertySet<Map<String, String>> {
+ implements PropertySet<Map<String, String>> {
@Override
- public Stream<BinderPropertyDefinition<Map<String, String>, ?>> getProperties() {
+ public Stream<PropertyDefinition<Map<String, String>, ?>> getProperties() {
return Stream.of("one", "two", "three").map(this::createProperty);
}
@Override
- public Optional<BinderPropertyDefinition<Map<String, String>, ?>> getProperty(
+ public Optional<PropertyDefinition<Map<String, String>, ?>> getProperty(
String name) {
return Optional.of(createProperty(name));
}
- private BinderPropertyDefinition<Map<String, String>, ?> createProperty(
+ private PropertyDefinition<Map<String, String>, ?> createProperty(
String name) {
return new MapPropertyDefinition(this, name);
}
diff --git a/server/src/test/java/com/vaadin/data/provider/bov/Person.java b/server/src/test/java/com/vaadin/data/provider/bov/Person.java
index 40e2b0cb74..054cf3aeea 100644
--- a/server/src/test/java/com/vaadin/data/provider/bov/Person.java
+++ b/server/src/test/java/com/vaadin/data/provider/bov/Person.java
@@ -17,13 +17,8 @@ package com.vaadin.data.provider.bov;
import java.io.Serializable;
-/**
- * POJO
- *
- * @author Vaadin Ltd
- */
public class Person implements Serializable {
- private final String name;
+ private String name;
private final int born;
public Person(String name, int born) {
@@ -35,6 +30,10 @@ public class Person implements Serializable {
return name;
}
+ public void setName(String name) {
+ this.name = name;
+ }
+
public int getBorn() {
return born;
}
diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java
index 601a63115e..fc644761bf 100644
--- a/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java
+++ b/server/src/test/java/com/vaadin/tests/server/component/grid/GridTest.java
@@ -6,25 +6,37 @@ import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.easymock.Capture;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import com.vaadin.data.Binder.Binding;
+import com.vaadin.data.ValidationException;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.GridSortOrder;
+import com.vaadin.data.provider.bov.Person;
import com.vaadin.event.selection.SelectionEvent;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.grid.HeightMode;
+import com.vaadin.tests.util.MockUI;
import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.Column;
import com.vaadin.ui.Grid.SelectionMode;
+import com.vaadin.ui.TextField;
import com.vaadin.ui.renderers.NumberRenderer;
+import elemental.json.Json;
+import elemental.json.JsonObject;
+
public class GridTest {
private Grid<String> grid;
@@ -262,4 +274,83 @@ public class GridTest {
Assert.assertEquals(0, list.size());
Assert.assertTrue(fired.get());
}
+
+ @Test
+ public void beanGrid() {
+ Grid<Person> grid = new Grid<>(Person.class);
+
+ Column<Person, ?> nameColumn = grid.getColumn("name");
+ Column<Person, ?> bornColumn = grid.getColumn("born");
+
+ Assert.assertNotNull(nameColumn);
+ Assert.assertNotNull(bornColumn);
+
+ Assert.assertEquals("Name", nameColumn.getCaption());
+ Assert.assertEquals("Born", bornColumn.getCaption());
+
+ JsonObject json = getRowData(grid, new Person("Lorem", 2000));
+
+ Set<String> values = Stream.of(json.keys()).map(json::getString)
+ .collect(Collectors.toSet());
+
+ Assert.assertEquals(new HashSet<>(Arrays.asList("Lorem", "2000")),
+ values);
+ }
+
+ @Test
+ public void beanGrid_editor() throws ValidationException {
+ Grid<Person> grid = new Grid<>(Person.class);
+
+ Column<Person, ?> nameColumn = grid.getColumn("name");
+
+ TextField nameField = new TextField();
+ nameColumn.setEditorComponent(nameField);
+
+ Optional<Binding<Person, ?>> maybeBinding = grid.getEditor().getBinder()
+ .getBinding("name");
+ Assert.assertTrue(maybeBinding.isPresent());
+
+ Binding<Person, ?> binding = maybeBinding.get();
+ Assert.assertSame(nameField, binding.getField());
+
+ Person person = new Person("Lorem", 2000);
+ grid.getEditor().getBinder().setBean(person);
+
+ Assert.assertEquals("Lorem", nameField.getValue());
+
+ nameField.setValue("Ipsum");
+ Assert.assertEquals("Ipsum", person.getName());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void oneArgSetEditor_nonBeanGrid() {
+ Grid<Person> grid = new Grid<>();
+ Column<Person, String> nameCol = grid.addColumn(Person::getName)
+ .setId("name");
+
+ nameCol.setEditorComponent(new TextField());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void addExistingColumnById_throws() {
+ Grid<Person> grid = new Grid<>(Person.class);
+ grid.addColumn("name");
+ }
+
+ private static <T> JsonObject getRowData(Grid<T> grid, T row) {
+ JsonObject json = Json.createObject();
+ if (grid.getColumns().isEmpty()) {
+ return json;
+ }
+
+ // generateData only works if Grid is attached
+ new MockUI().setContent(grid);
+
+ grid.getColumns().forEach(column -> column.generateData(row, json));
+
+ // Detach again
+ grid.getUI().setContent(null);
+
+ return json.getObject("d");
+ }
}