* 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#250tags/8.0.0.beta2
[[components.grid.data]] | [[components.grid.data]] | ||||
== Binding to 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 | described in | ||||
<<dummy/../../../framework/datamodel/datamodel-providers.asciidoc#datamodel.dataproviders,"Showing Many Items in a Listing">>. | <<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 | By default, it is bound to List of items. You can set the items with the | ||||
grid.addSelectionListener(event -> { | grid.addSelectionListener(event -> { | ||||
Set<Person> selected = event.getAllSelectedItems(); | Set<Person> selected = event.getAllSelectedItems(); | ||||
Notification.show(selected.size() + " items selected"); | Notification.show(selected.size() + " items selected"); | ||||
} | |||||
}); | |||||
---- | ---- | ||||
Programmatically selecting the value is possible via [methodname]#select(T)#. | Programmatically selecting the value is possible via [methodname]#select(T)#. | ||||
// Allow deleting only if there's any selected | // Allow deleting only if there's any selected | ||||
deleteSelected.setEnabled( | deleteSelected.setEnabled( | ||||
event.getNewSelection().size() > 0); | event.getNewSelection().size() > 0); | ||||
}; | |||||
}); | |||||
---- | ---- | ||||
[source, java] | [source, java] | ||||
---- | ---- | ||||
grid.addCellClickListener(event -> | grid.addCellClickListener(event -> | ||||
Notification.show("Value: " + event.getItem()); | |||||
Notification.show("Value: " + event.getItem())); | |||||
---- | ---- | ||||
The clicked grid cell is also automatically focused. | The clicked grid cell is also automatically focused. | ||||
[[components.grid.columns]] | [[components.grid.columns]] | ||||
== Configuring 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] | [source, java] | ||||
---- | ---- | ||||
In the following, we describe the basic column configuration. | 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]] | [[components.grid.columns.order]] | ||||
=== Column Order | === Column Order | ||||
new ButtonRenderer(clickEvent -> { | new ButtonRenderer(clickEvent -> { | ||||
people.remove(clickEvent.getValue()); | people.remove(clickEvent.getValue()); | ||||
grid.setItems(people); | grid.setItems(people); | ||||
}); | |||||
})); | |||||
---- | ---- | ||||
[classname]#ImageRenderer#:: Renders the cell as an image. | [classname]#ImageRenderer#:: Renders the cell as an image. |
import com.vaadin.data.util.BeanUtil; | import com.vaadin.data.util.BeanUtil; | ||||
import com.vaadin.server.Setter; | import com.vaadin.server.Setter; | ||||
import com.vaadin.shared.util.SharedUtil; | |||||
import com.vaadin.util.ReflectTools; | 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 | * @author Vaadin Ltd | ||||
* | * | ||||
* @param <T> | * @param <T> | ||||
* the type of the bean | * 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 | * Serialized form of a property set. When deserialized, the property set | ||||
* existing cached instance or creates a new one. | * existing cached instance or creates a new one. | ||||
* | * | ||||
* @see #readResolve() | * @see #readResolve() | ||||
* @see BeanBinderPropertyDefinition#writeReplace() | |||||
* @see BeanPropertyDefinition#writeReplace() | |||||
*/ | */ | ||||
private static class SerializedPropertySet implements Serializable { | private static class SerializedPropertySet implements Serializable { | ||||
private final Class<?> beanType; | private final Class<?> beanType; | ||||
* definition is then fetched from the property set. | * definition is then fetched from the property set. | ||||
* | * | ||||
* @see #readResolve() | * @see #readResolve() | ||||
* @see BeanBinderPropertySet#writeReplace() | |||||
* @see BeanPropertySet#writeReplace() | |||||
*/ | */ | ||||
private static class SerializedPropertyDefinition implements Serializable { | private static class SerializedPropertyDefinition implements Serializable { | ||||
private final Class<?> beanType; | private final Class<?> beanType; | ||||
} | } | ||||
} | } | ||||
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 PropertyDescriptor descriptor; | ||||
private final BeanBinderPropertySet<T> propertySet; | |||||
private final BeanPropertySet<T> propertySet; | |||||
public BeanBinderPropertyDefinition( | |||||
BeanBinderPropertySet<T> propertySet, | |||||
public BeanPropertyDefinition(BeanPropertySet<T> propertySet, | |||||
PropertyDescriptor descriptor) { | PropertyDescriptor descriptor) { | ||||
this.propertySet = propertySet; | this.propertySet = propertySet; | ||||
this.descriptor = descriptor; | this.descriptor = descriptor; | ||||
} | } | ||||
@Override | @Override | ||||
public BeanBinderPropertySet<T> getPropertySet() { | |||||
public String getCaption() { | |||||
return SharedUtil.propertyIdToHumanFriendly(getName()); | |||||
} | |||||
@Override | |||||
public BeanPropertySet<T> getPropertySet() { | |||||
return propertySet; | return propertySet; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
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 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; | this.beanType = beanType; | ||||
try { | try { | ||||
definitions = BeanUtil.getBeanPropertyDescriptors(beanType).stream() | definitions = BeanUtil.getBeanPropertyDescriptors(beanType).stream() | ||||
.filter(BeanBinderPropertySet::hasNonObjectReadMethod) | |||||
.map(descriptor -> new BeanBinderPropertyDefinition<>(this, | |||||
.filter(BeanPropertySet::hasNonObjectReadMethod) | |||||
.map(descriptor -> new BeanPropertyDefinition<>(this, | |||||
descriptor)) | descriptor)) | ||||
.collect(Collectors.toMap(BinderPropertyDefinition::getName, | |||||
.collect(Collectors.toMap(PropertyDefinition::getName, | |||||
Function.identity())); | Function.identity())); | ||||
} catch (IntrospectionException e) { | } catch (IntrospectionException e) { | ||||
throw new IllegalArgumentException( | throw new IllegalArgumentException( | ||||
} | } | ||||
/** | /** | ||||
* Gets a {@link BeanBinderPropertySet} for the given bean type. | |||||
* Gets a {@link BeanPropertySet} for the given bean type. | |||||
* | * | ||||
* @param beanType | * @param beanType | ||||
* the bean type to get a property set for, not <code>null</code> | * 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") | @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"); | Objects.requireNonNull(beanType, "Bean type cannot be null"); | ||||
// Cache the reflection results | // Cache the reflection results | ||||
return (BinderPropertySet<T>) instances.computeIfAbsent(beanType, | |||||
BeanBinderPropertySet::new); | |||||
return (PropertySet<T>) instances.computeIfAbsent(beanType, | |||||
BeanPropertySet::new); | |||||
} | } | ||||
@Override | @Override | ||||
public Stream<BinderPropertyDefinition<T, ?>> getProperties() { | |||||
public Stream<PropertyDefinition<T, ?>> getProperties() { | |||||
return definitions.values().stream(); | return definitions.values().stream(); | ||||
} | } | ||||
@Override | @Override | ||||
public Optional<BinderPropertyDefinition<T, ?>> getProperty(String name) { | |||||
public Optional<PropertyDefinition<T, ?>> getProperty(String name) { | |||||
return Optional.ofNullable(definitions.get(name)); | return Optional.ofNullable(definitions.get(name)); | ||||
} | } | ||||
@Override | @Override | ||||
protected BindingBuilder<BEAN, ?> configureBinding( | protected BindingBuilder<BEAN, ?> configureBinding( | ||||
BindingBuilder<BEAN, ?> binding, | BindingBuilder<BEAN, ?> binding, | ||||
BinderPropertyDefinition<BEAN, ?> definition) { | |||||
PropertyDefinition<BEAN, ?> definition) { | |||||
return binding.withValidator( | return binding.withValidator( | ||||
new BeanValidator(beanType, definition.getName())); | new BeanValidator(beanType, definition.getName())); | ||||
} | } |
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.HashSet; | |||||
import java.util.IdentityHashMap; | import java.util.IdentityHashMap; | ||||
import java.util.LinkedHashSet; | import java.util.LinkedHashSet; | ||||
import java.util.List; | import java.util.List; | ||||
/** | /** | ||||
* Completes this binding by connecting the field to the property with | * Completes this binding by connecting the field to the property with | ||||
* the given name. The getter and setter of the property are looked up | * the given name. The getter and setter of the property are looked up | ||||
* using a {@link BinderPropertySet}. | |||||
* using a {@link PropertySet}. | |||||
* <p> | * <p> | ||||
* For a <code>Binder</code> created using the | * For a <code>Binder</code> created using the | ||||
* {@link Binder#Binder(Class)} constructor, introspection will be used | * {@link Binder#Binder(Class)} constructor, introspection will be used | ||||
* if the property has no accessible getter | * if the property has no accessible getter | ||||
* @throws IllegalStateException | * @throws IllegalStateException | ||||
* if the binder is not configured with an appropriate | * if the binder is not configured with an appropriate | ||||
* {@link BinderPropertySet} | |||||
* {@link PropertySet} | |||||
* | * | ||||
* @see Binder.BindingBuilder#bind(ValueProvider, Setter) | * @see Binder.BindingBuilder#bind(ValueProvider, Setter) | ||||
*/ | */ | ||||
"Property name cannot be null"); | "Property name cannot be null"); | ||||
checkUnbound(); | checkUnbound(); | ||||
BinderPropertyDefinition<BEAN, ?> definition = getBinder().propertySet | |||||
PropertyDefinition<BEAN, ?> definition = getBinder().propertySet | |||||
.getProperty(propertyName) | .getProperty(propertyName) | ||||
.orElseThrow(() -> new IllegalArgumentException( | .orElseThrow(() -> new IllegalArgumentException( | ||||
"Could not resolve property name " + propertyName | "Could not resolve property name " + propertyName | ||||
definition); | definition); | ||||
try { | try { | ||||
return ((BindingBuilder) finalBinding).bind(getter, setter); | |||||
Binding binding = ((BindingBuilder) finalBinding).bind(getter, | |||||
setter); | |||||
getBinder().boundProperties.put(propertyName, binding); | |||||
return binding; | |||||
} finally { | } finally { | ||||
getBinder().boundProperties.add(propertyName); | |||||
getBinder().incompleteMemberFieldBindings.remove(getField()); | getBinder().incompleteMemberFieldBindings.remove(getField()); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private final BinderPropertySet<BEAN> propertySet; | |||||
private final PropertySet<BEAN> propertySet; | |||||
/** | /** | ||||
* Property names that have been used for creating a binding. | * 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<>(); | private final Map<HasValue<?>, BindingBuilder<BEAN, ?>> incompleteMemberFieldBindings = new IdentityHashMap<>(); | ||||
private boolean hasChanges = false; | 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 #bindInstanceFields(Object)}, {@link #bind(HasValue, String)} and | ||||
* {@link BindingBuilder#bind(String)}. | * {@link BindingBuilder#bind(String)}. | ||||
* | * | ||||
* @param propertySet | * @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"); | Objects.requireNonNull(propertySet, "propertySet cannot be null"); | ||||
this.propertySet = propertySet; | this.propertySet = propertySet; | ||||
} | } | ||||
* the bean type to use, not <code>null</code> | * the bean type to use, not <code>null</code> | ||||
*/ | */ | ||||
public Binder(Class<BEAN> beanType) { | public Binder(Class<BEAN> beanType) { | ||||
this(BeanBinderPropertySet.get(beanType)); | |||||
this(BeanPropertySet.get(beanType)); | |||||
} | } | ||||
/** | /** | ||||
* {@link #bind(HasValue, String)} or {@link BindingBuilder#bind(String)}. | * {@link #bind(HasValue, String)} or {@link BindingBuilder#bind(String)}. | ||||
*/ | */ | ||||
public Binder() { | public Binder() { | ||||
this(new BinderPropertySet<BEAN>() { | |||||
this(new PropertySet<BEAN>() { | |||||
@Override | @Override | ||||
public Stream<BinderPropertyDefinition<BEAN, ?>> getProperties() { | |||||
public Stream<PropertyDefinition<BEAN, ?>> getProperties() { | |||||
throw new IllegalStateException( | throw new IllegalStateException( | ||||
"A Binder created with the default constructor doesn't support listing properties."); | "A Binder created with the default constructor doesn't support listing properties."); | ||||
} | } | ||||
@Override | @Override | ||||
public Optional<BinderPropertyDefinition<BEAN, ?>> getProperty( | |||||
public Optional<PropertyDefinition<BEAN, ?>> getProperty( | |||||
String name) { | String name) { | ||||
throw new IllegalStateException( | throw new IllegalStateException( | ||||
"A Binder created with the default constructor doesn't support finding properties by name."); | "A Binder created with the default constructor doesn't support finding properties by name."); | ||||
} | } | ||||
/** | /** | ||||
* 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 #bindInstanceFields(Object)}, {@link #bind(HasValue, String)} and | ||||
* {@link BindingBuilder#bind(String)}. | * {@link BindingBuilder#bind(String)}. | ||||
* <p> | * <p> | ||||
* @see Binder#Binder(Class) | * @see Binder#Binder(Class) | ||||
* | * | ||||
* @param propertySet | * @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 | * @return a new binder using the provided property set, not | ||||
* <code>null</code> | * <code>null</code> | ||||
*/ | */ | ||||
public static <BEAN> Binder<BEAN> withPropertySet( | public static <BEAN> Binder<BEAN> withPropertySet( | ||||
BinderPropertySet<BEAN> propertySet) { | |||||
PropertySet<BEAN> propertySet) { | |||||
return new Binder<>(propertySet); | return new Binder<>(propertySet); | ||||
} | } | ||||
/** | /** | ||||
* Binds the given field to the property with the given name. The getter and | * 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> | * <p> | ||||
* For a <code>Binder</code> created using the {@link Binder#Binder(Class)} | * For a <code>Binder</code> created using the {@link Binder#Binder(Class)} | ||||
* constructor, introspection will be used to find a Java Bean property. If | * constructor, introspection will be used to find a Java Bean property. If | ||||
* if the property has no accessible getter | * if the property has no accessible getter | ||||
* @throws IllegalStateException | * @throws IllegalStateException | ||||
* if the binder is not configured with an appropriate | * if the binder is not configured with an appropriate | ||||
* {@link BinderPropertySet} | |||||
* {@link PropertySet} | |||||
* | * | ||||
* @see #bind(HasValue, ValueProvider, Setter) | * @see #bind(HasValue, ValueProvider, Setter) | ||||
*/ | */ | ||||
/** | /** | ||||
* Configures the {@code binding} with the property definition | * Configures the {@code binding} with the property definition | ||||
* {@code definition} before it's being bound. | * {@code definition} before it's being bound. | ||||
* | |||||
* | |||||
* @param binding | * @param binding | ||||
* a binding to configure | * a binding to configure | ||||
* @param definition | * @param definition | ||||
*/ | */ | ||||
protected BindingBuilder<BEAN, ?> configureBinding( | protected BindingBuilder<BEAN, ?> configureBinding( | ||||
BindingBuilder<BEAN, ?> binding, | BindingBuilder<BEAN, ?> binding, | ||||
BinderPropertyDefinition<BEAN, ?> definition) { | |||||
PropertyDefinition<BEAN, ?> definition) { | |||||
return binding; | return binding; | ||||
} | } | ||||
private void handleProperty(Field field, Object objectWithMemberFields, | private void handleProperty(Field field, Object objectWithMemberFields, | ||||
BiConsumer<String, Class<?>> propertyHandler) { | BiConsumer<String, Class<?>> propertyHandler) { | ||||
Optional<BinderPropertyDefinition<BEAN, ?>> descriptor = getPropertyDescriptor( | |||||
Optional<PropertyDefinition<BEAN, ?>> descriptor = getPropertyDescriptor( | |||||
field); | field); | ||||
if (!descriptor.isPresent()) { | if (!descriptor.isPresent()) { | ||||
} | } | ||||
String propertyName = descriptor.get().getName(); | String propertyName = descriptor.get().getName(); | ||||
if (boundProperties.contains(propertyName)) { | |||||
if (boundProperties.containsKey(propertyName)) { | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
propertyHandler.accept(propertyName, descriptor.get().getType()); | 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) { | Field field) { | ||||
PropertyId propertyIdAnnotation = field.getAnnotation(PropertyId.class); | PropertyId propertyIdAnnotation = field.getAnnotation(PropertyId.class); | ||||
String minifiedFieldName = minifyFieldName(propertyId); | String minifiedFieldName = minifyFieldName(propertyId); | ||||
return propertySet.getProperties() | |||||
.map(BinderPropertyDefinition::getName) | |||||
return propertySet.getProperties().map(PropertyDefinition::getName) | |||||
.filter(name -> minifyFieldName(name).equals(minifiedFieldName)) | .filter(name -> minifyFieldName(name).equals(minifiedFieldName)) | ||||
.findFirst().flatMap(propertySet::getProperty); | .findFirst().flatMap(propertySet::getProperty); | ||||
} | } |
import com.vaadin.server.Setter; | import com.vaadin.server.Setter; | ||||
/** | /** | ||||
* A property from a {@link BinderPropertySet}. | |||||
* A property from a {@link PropertySet}. | |||||
* | * | ||||
* @author Vaadin Ltd | * @author Vaadin Ltd | ||||
* @since | * @since | ||||
* | * | ||||
* @param <T> | * @param <T> | ||||
* the type of the binder property set | |||||
* the type of the property set | |||||
* @param <V> | * @param <V> | ||||
* the property type | * 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 | * Gets the value provider that is used for finding the value of this | ||||
* property for a bean. | * property for a bean. | ||||
public String getName(); | 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(); | |||||
} | } |
import java.util.stream.Stream; | 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 | * @author Vaadin Ltd | ||||
* | * | ||||
* @param <T> | * @param <T> | ||||
* the type for which the properties are defined | * 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. | * Gets all known properties as a stream. | ||||
* | * | ||||
* @return a stream of property names, not <code>null</code> | * @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 | * Gets the definition for the named property, or an empty optional if there | ||||
* @return the property definition, or empty optional if property doesn't | * @return the property definition, or empty optional if property doesn't | ||||
* exist | * exist | ||||
*/ | */ | ||||
public Optional<BinderPropertyDefinition<T, ?>> getProperty(String name); | |||||
public Optional<PropertyDefinition<T, ?>> getProperty(String name); | |||||
} | } |
import org.jsoup.nodes.Element; | import org.jsoup.nodes.Element; | ||||
import org.jsoup.select.Elements; | import org.jsoup.select.Elements; | ||||
import com.vaadin.data.BeanPropertySet; | |||||
import com.vaadin.data.Binder; | import com.vaadin.data.Binder; | ||||
import com.vaadin.data.Binder.Binding; | import com.vaadin.data.Binder.Binding; | ||||
import com.vaadin.data.HasDataProvider; | import com.vaadin.data.HasDataProvider; | ||||
import com.vaadin.data.HasValue; | import com.vaadin.data.HasValue; | ||||
import com.vaadin.data.PropertyDefinition; | |||||
import com.vaadin.data.PropertySet; | |||||
import com.vaadin.data.ValueProvider; | import com.vaadin.data.ValueProvider; | ||||
import com.vaadin.data.provider.DataCommunicator; | import com.vaadin.data.provider.DataCommunicator; | ||||
import com.vaadin.data.provider.DataProvider; | import com.vaadin.data.provider.DataProvider; | ||||
* a setter that stores the component value in the row item | * a setter that stores the component value in the row item | ||||
* @return this column | * @return this column | ||||
* | * | ||||
* @see #setEditorBinding(Binding) | |||||
* @see Grid#getEditor() | * @see Grid#getEditor() | ||||
* @see Binder#bind(HasValue, ValueProvider, Setter) | |||||
*/ | */ | ||||
public <C extends HasValue<V> & Component> Column<T, V> setEditorComponent( | public <C extends HasValue<V> & Component> Column<T, V> setEditorComponent( | ||||
C editorComponent, Setter<T, V> setter) { | C editorComponent, Setter<T, V> setter) { | ||||
return setEditorBinding(binding); | return setEditorBinding(binding); | ||||
} | } | ||||
/** | |||||
* 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. | * Gets the grid that this column belongs to. | ||||
* | * | ||||
private Editor<T> editor; | 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() { | 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()); | registerRpc(new GridServerRpcImpl()); | ||||
setDefaultHeaderRow(appendHeaderRow()); | setDefaultHeaderRow(appendHeaderRow()); | ||||
} | } | ||||
} | } | ||||
}); | }); | ||||
// 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); | |||||
} | } | ||||
/** | /** | ||||
userOriginated)); | userOriginated)); | ||||
} | } | ||||
/** | |||||
* 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 | * 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 | * column will use a {@link TextRenderer}. The value is converted to a | ||||
* | * | ||||
* @param columnId | * @param columnId | ||||
* the identifier of the column to get | * 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) { | public Column<T, ?> getColumn(String columnId) { | ||||
return columnIds.get(columnId); | return columnIds.get(columnId); | ||||
* @return editor | * @return editor | ||||
*/ | */ | ||||
protected Editor<T> createEditor() { | protected Editor<T> createEditor() { | ||||
return new EditorImpl<>(); | |||||
return new EditorImpl<>(propertySet); | |||||
} | } | ||||
private void addExtensionComponent(Component c) { | private void addExtensionComponent(Component c) { |
import com.vaadin.data.Binder.Binding; | import com.vaadin.data.Binder.Binding; | ||||
import com.vaadin.data.BinderValidationStatus; | import com.vaadin.data.BinderValidationStatus; | ||||
import com.vaadin.data.BinderValidationStatusHandler; | 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.EditorClientRpc; | ||||
import com.vaadin.shared.ui.grid.editor.EditorServerRpc; | import com.vaadin.shared.ui.grid.editor.EditorServerRpc; | ||||
import com.vaadin.shared.ui.grid.editor.EditorState; | import com.vaadin.shared.ui.grid.editor.EditorState; | ||||
/** | /** | ||||
* Constructor for internal implementation of the Editor. | * 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); | rpc = getRpcProxy(EditorClientRpc.class); | ||||
registerRpc(new EditorServerRpc() { | registerRpc(new EditorServerRpc() { | ||||
} | } | ||||
}); | }); | ||||
setBinder(new Binder<>()); | |||||
setBinder(Binder.withPropertySet(propertySet)); | |||||
} | } | ||||
@Override | @Override |
import com.vaadin.data.provider.bov.Person; | import com.vaadin.data.provider.bov.Person; | ||||
import com.vaadin.tests.server.ClassesSerializableTest; | import com.vaadin.tests.server.ClassesSerializableTest; | ||||
public class BeanBinderPropertySetTest { | |||||
public class BeanPropertySetTest { | |||||
@Test | @Test | ||||
public void testSerializeDeserialize_propertySet() throws Exception { | public void testSerializeDeserialize_propertySet() throws Exception { | ||||
BinderPropertySet<Person> originalPropertySet = BeanBinderPropertySet | |||||
PropertySet<Person> originalPropertySet = BeanPropertySet | |||||
.get(Person.class); | .get(Person.class); | ||||
BinderPropertySet<Person> deserializedPropertySet = ClassesSerializableTest | |||||
PropertySet<Person> deserializedPropertySet = ClassesSerializableTest | |||||
.serializeAndDeserialize(originalPropertySet); | .serializeAndDeserialize(originalPropertySet); | ||||
Assert.assertSame( | Assert.assertSame( | ||||
@Test | @Test | ||||
public void testSerializeDeserialize_propertySet_cacheCleared() | public void testSerializeDeserialize_propertySet_cacheCleared() | ||||
throws Exception { | throws Exception { | ||||
BinderPropertySet<Person> originalPropertySet = BeanBinderPropertySet | |||||
PropertySet<Person> originalPropertySet = BeanPropertySet | |||||
.get(Person.class); | .get(Person.class); | ||||
ByteArrayOutputStream bs = new ByteArrayOutputStream(); | ByteArrayOutputStream bs = new ByteArrayOutputStream(); | ||||
// Simulate deserializing into a different JVM by clearing the instance | // Simulate deserializing into a different JVM by clearing the instance | ||||
// map | // map | ||||
Field instancesField = BeanBinderPropertySet.class | |||||
Field instancesField = BeanPropertySet.class | |||||
.getDeclaredField("instances"); | .getDeclaredField("instances"); | ||||
instancesField.setAccessible(true); | instancesField.setAccessible(true); | ||||
Map<?, ?> instances = (Map<?, ?>) instancesField.get(null); | Map<?, ?> instances = (Map<?, ?>) instancesField.get(null); | ||||
ObjectInputStream in = new ObjectInputStream( | ObjectInputStream in = new ObjectInputStream( | ||||
new ByteArrayInputStream(data)); | new ByteArrayInputStream(data)); | ||||
BinderPropertySet<Person> deserializedPropertySet = (BinderPropertySet<Person>) in | |||||
PropertySet<Person> deserializedPropertySet = (PropertySet<Person>) in | |||||
.readObject(); | .readObject(); | ||||
Assert.assertSame( | Assert.assertSame( | ||||
"Deserialized instance should be the same as in the cache", | "Deserialized instance should be the same as in the cache", | ||||
BeanBinderPropertySet.get(Person.class), | |||||
deserializedPropertySet); | |||||
BeanPropertySet.get(Person.class), deserializedPropertySet); | |||||
Assert.assertNotSame( | Assert.assertNotSame( | ||||
"Deserialized instance should not be the same as the original", | "Deserialized instance should not be the same as the original", | ||||
originalPropertySet, deserializedPropertySet); | originalPropertySet, deserializedPropertySet); | ||||
@Test | @Test | ||||
public void testSerializeDeserialize_propertyDefinition() throws Exception { | public void testSerializeDeserialize_propertyDefinition() throws Exception { | ||||
BinderPropertyDefinition<Person, ?> definition = BeanBinderPropertySet | |||||
PropertyDefinition<Person, ?> definition = BeanPropertySet | |||||
.get(Person.class).getProperty("born") | .get(Person.class).getProperty("born") | ||||
.orElseThrow(RuntimeException::new); | .orElseThrow(RuntimeException::new); | ||||
BinderPropertyDefinition<Person, ?> deserializedDefinition = ClassesSerializableTest | |||||
PropertyDefinition<Person, ?> deserializedDefinition = ClassesSerializableTest | |||||
.serializeAndDeserialize(definition); | .serializeAndDeserialize(definition); | ||||
ValueProvider<Person, ?> getter = deserializedDefinition.getGetter(); | ValueProvider<Person, ?> getter = deserializedDefinition.getGetter(); | ||||
Assert.assertSame( | Assert.assertSame( | ||||
"Deserialized instance should be the same as in the cache", | "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), | .orElseThrow(RuntimeException::new), | ||||
deserializedDefinition); | deserializedDefinition); | ||||
} | } | ||||
@Test | @Test | ||||
public void properties() { | public void properties() { | ||||
BinderPropertySet<Person> propertySet = BeanBinderPropertySet | |||||
.get(Person.class); | |||||
PropertySet<Person> propertySet = BeanPropertySet.get(Person.class); | |||||
Set<String> propertyNames = propertySet.getProperties() | 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")), | Assert.assertEquals(new HashSet<>(Arrays.asList("name", "born")), | ||||
propertyNames); | propertyNames); |
package com.vaadin.data; | package com.vaadin.data; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Locale; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.stream.Stream; | import java.util.stream.Stream; | ||||
public class BinderCustomPropertySetTest { | public class BinderCustomPropertySetTest { | ||||
public static class MapPropertyDefinition | public static class MapPropertyDefinition | ||||
implements BinderPropertyDefinition<Map<String, String>, String> { | |||||
implements PropertyDefinition<Map<String, String>, String> { | |||||
private MapPropertySet propertySet; | private MapPropertySet propertySet; | ||||
private String name; | private String name; | ||||
} | } | ||||
@Override | @Override | ||||
public BinderPropertySet<Map<String, String>> getPropertySet() { | |||||
public PropertySet<Map<String, String>> getPropertySet() { | |||||
return propertySet; | return propertySet; | ||||
} | } | ||||
@Override | |||||
public String getCaption() { | |||||
return name.toUpperCase(Locale.ENGLISH); | |||||
} | |||||
} | } | ||||
public static class MapPropertySet | public static class MapPropertySet | ||||
implements BinderPropertySet<Map<String, String>> { | |||||
implements PropertySet<Map<String, String>> { | |||||
@Override | @Override | ||||
public Stream<BinderPropertyDefinition<Map<String, String>, ?>> getProperties() { | |||||
public Stream<PropertyDefinition<Map<String, String>, ?>> getProperties() { | |||||
return Stream.of("one", "two", "three").map(this::createProperty); | return Stream.of("one", "two", "three").map(this::createProperty); | ||||
} | } | ||||
@Override | @Override | ||||
public Optional<BinderPropertyDefinition<Map<String, String>, ?>> getProperty( | |||||
public Optional<PropertyDefinition<Map<String, String>, ?>> getProperty( | |||||
String name) { | String name) { | ||||
return Optional.of(createProperty(name)); | return Optional.of(createProperty(name)); | ||||
} | } | ||||
private BinderPropertyDefinition<Map<String, String>, ?> createProperty( | |||||
private PropertyDefinition<Map<String, String>, ?> createProperty( | |||||
String name) { | String name) { | ||||
return new MapPropertyDefinition(this, name); | return new MapPropertyDefinition(this, name); | ||||
} | } |
import java.io.Serializable; | import java.io.Serializable; | ||||
/** | |||||
* POJO | |||||
* | |||||
* @author Vaadin Ltd | |||||
*/ | |||||
public class Person implements Serializable { | public class Person implements Serializable { | ||||
private final String name; | |||||
private String name; | |||||
private final int born; | private final int born; | ||||
public Person(String name, int born) { | public Person(String name, int born) { | ||||
return name; | return name; | ||||
} | } | ||||
public void setName(String name) { | |||||
this.name = name; | |||||
} | |||||
public int getBorn() { | public int getBorn() { | ||||
return born; | return born; | ||||
} | } |
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.HashSet; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.Set; | |||||
import java.util.concurrent.atomic.AtomicReference; | import java.util.concurrent.atomic.AtomicReference; | ||||
import java.util.stream.Collectors; | |||||
import java.util.stream.Stream; | |||||
import org.easymock.Capture; | import org.easymock.Capture; | ||||
import org.junit.Assert; | import org.junit.Assert; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import com.vaadin.data.Binder.Binding; | |||||
import com.vaadin.data.ValidationException; | |||||
import com.vaadin.data.ValueProvider; | import com.vaadin.data.ValueProvider; | ||||
import com.vaadin.data.provider.GridSortOrder; | import com.vaadin.data.provider.GridSortOrder; | ||||
import com.vaadin.data.provider.bov.Person; | |||||
import com.vaadin.event.selection.SelectionEvent; | import com.vaadin.event.selection.SelectionEvent; | ||||
import com.vaadin.shared.data.sort.SortDirection; | import com.vaadin.shared.data.sort.SortDirection; | ||||
import com.vaadin.shared.ui.grid.HeightMode; | import com.vaadin.shared.ui.grid.HeightMode; | ||||
import com.vaadin.tests.util.MockUI; | |||||
import com.vaadin.ui.Grid; | import com.vaadin.ui.Grid; | ||||
import com.vaadin.ui.Grid.Column; | import com.vaadin.ui.Grid.Column; | ||||
import com.vaadin.ui.Grid.SelectionMode; | import com.vaadin.ui.Grid.SelectionMode; | ||||
import com.vaadin.ui.TextField; | |||||
import com.vaadin.ui.renderers.NumberRenderer; | import com.vaadin.ui.renderers.NumberRenderer; | ||||
import elemental.json.Json; | |||||
import elemental.json.JsonObject; | |||||
public class GridTest { | public class GridTest { | ||||
private Grid<String> grid; | private Grid<String> grid; | ||||
Assert.assertEquals(0, list.size()); | Assert.assertEquals(0, list.size()); | ||||
Assert.assertTrue(fired.get()); | 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"); | |||||
} | |||||
} | } |