aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/src/main/java/com/vaadin/data/BeanBinder.java19
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java121
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java11
-rw-r--r--server/src/test/java/com/vaadin/data/BinderSingleSelectTest.java109
-rw-r--r--shared/src/main/java/com/vaadin/shared/data/selection/SelectionModel.java23
5 files changed, 259 insertions, 24 deletions
diff --git a/server/src/main/java/com/vaadin/data/BeanBinder.java b/server/src/main/java/com/vaadin/data/BeanBinder.java
index dd5790b8b6..64436c26a8 100644
--- a/server/src/main/java/com/vaadin/data/BeanBinder.java
+++ b/server/src/main/java/com/vaadin/data/BeanBinder.java
@@ -20,7 +20,6 @@ import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -28,8 +27,6 @@ import java.util.function.Predicate;
import com.vaadin.data.util.BeanUtil;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.validator.BeanValidator;
-import com.vaadin.ui.Component;
-import com.vaadin.ui.UI;
/**
* A {@code Binder} subclass specialized for binding <em>beans</em>: classes
@@ -177,7 +174,7 @@ public class BeanBinder<BEAN> extends Binder<BEAN> {
if (BeanValidator.checkBeanValidationAvailable()) {
finalBinding = finalBinding.withValidator(new BeanValidator(
- getBinder().beanType, propertyName, getLocale()));
+ getBinder().beanType, propertyName, findLocale()));
}
PropertyDescriptor descriptor = getDescriptor(propertyName);
@@ -242,20 +239,6 @@ public class BeanBinder<BEAN> extends Binder<BEAN> {
throw new RuntimeException(exception);
});
}
-
- private Locale getLocale() {
- Locale l = null;
- if (getField() instanceof Component) {
- l = ((Component) getField()).getLocale();
- }
- if (l == null && UI.getCurrent() != null) {
- l = UI.getCurrent().getLocale();
- }
- if (l == null) {
- l = Locale.getDefault();
- }
- return l;
- }
}
private final Class<? extends BEAN> beanType;
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index 0a7e33c556..88549e12c3 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -38,7 +38,10 @@ import com.vaadin.server.ErrorMessage;
import com.vaadin.server.UserError;
import com.vaadin.shared.Registration;
import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.AbstractSingleSelect;
+import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
+import com.vaadin.ui.UI;
/**
* Connects one or more {@code Field} components to properties of a backing data
@@ -497,6 +500,25 @@ public class Binder<BEAN> implements Serializable {
}
}
+ /**
+ * Finds an appropriate locale to be used in conversion and validation.
+ *
+ * @return the found locale, not null
+ */
+ protected Locale findLocale() {
+ Locale l = null;
+ if (getField() instanceof Component) {
+ l = ((Component) getField()).getLocale();
+ }
+ if (l == null && UI.getCurrent() != null) {
+ l = UI.getCurrent().getLocale();
+ }
+ if (l == null) {
+ l = Locale.getDefault();
+ }
+ return l;
+ }
+
private void bind(BEAN bean) {
setFieldValue(bean);
onValueChange = getField()
@@ -521,7 +543,7 @@ public class Binder<BEAN> implements Serializable {
private ValidationStatus<TARGET> doValidation() {
FIELDVALUE fieldValue = field.getValue();
Result<TARGET> dataValue = converterValidatorChain.convertToModel(
- fieldValue, ((AbstractComponent) field).getLocale());
+ fieldValue, findLocale());
return new ValidationStatus<>(this, dataValue);
}
@@ -543,8 +565,7 @@ public class Binder<BEAN> implements Serializable {
private FIELDVALUE convertDataToFieldType(BEAN bean) {
return converterValidatorChain.convertToPresentation(
- getter.apply(bean),
- ((AbstractComponent) getField()).getLocale());
+ getter.apply(bean), findLocale());
}
/**
@@ -673,6 +694,8 @@ public class Binder<BEAN> implements Serializable {
* @param field
* the field to be bound, not null
* @return the new binding
+ *
+ * @see #bind(HasValue, Function, BiConsumer)
*/
public <FIELDVALUE> Binding<BEAN, FIELDVALUE, FIELDVALUE> forField(
HasValue<FIELDVALUE> field) {
@@ -686,6 +709,45 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * Creates a new binding for the given single select component. The returned
+ * binding may be further configured before invoking
+ * {@link Binding#bind(Function, BiConsumer) Binding.bind} which completes
+ * the binding. Until {@code Binding.bind} is called, the binding has no
+ * effect.
+ *
+ * @param <SELECTVALUE>
+ * the item type of the select
+ * @param select
+ * the select to be bound, not null
+ * @return the new binding
+ *
+ * @see #bind(AbstractSingleSelect, Function, BiConsumer)
+ */
+ public <SELECTVALUE> Binding<BEAN, SELECTVALUE, SELECTVALUE> forSelect(
+ AbstractSingleSelect<SELECTVALUE> select) {
+ return forField(new HasValue<SELECTVALUE>() {
+
+ @Override
+ public void setValue(SELECTVALUE value) {
+ select.setSelectedItem(value);
+ }
+
+ @Override
+ public SELECTVALUE getValue() {
+ return select.getSelectedItem().orElse(null);
+ }
+
+ @Override
+ public Registration addValueChangeListener(
+ ValueChangeListener<? super SELECTVALUE> listener) {
+ return select.addSelectionListener(e -> listener.accept(
+ new ValueChange<>(select, getValue(), e
+ .isUserOriginated())));
+ }
+ });
+ }
+
+ /**
* Binds a field to a bean property represented by the given getter and
* setter pair. The functions are used to update the field value from the
* property and to store the field value to the property, respectively.
@@ -735,6 +797,59 @@ public class Binder<BEAN> implements Serializable {
}
/**
+ * Binds a single select to a bean property represented by the given getter
+ * and setter pair. The functions are used to update the selection from the
+ * property and to store the selection to the property, respectively.
+ * <p>
+ * Use the {@link #forSelect(AbstractSingleSelect)} method instead if you
+ * want to further configure the new binding.
+ * <p>
+ * When a bean is bound with {@link Binder#bind(BEAN)}, the selected item is
+ * set to the return value of the given getter. The property value is then
+ * updated via the given setter whenever the selected item changes. The
+ * setter may be null; in that case the property value is never updated and
+ * the binding is said to be <i>read-only</i>. A null property value
+ * corresponds to no selection and vice versa.
+ * <p>
+ * If the Binder is already bound to some item, the newly bound select is
+ * associated with the corresponding bean property as described above.
+ * <p>
+ * The getter and setter can be arbitrary functions, for instance
+ * implementing user-defined conversion or validation. However, in the most
+ * basic use case you can simply pass a pair of method references to this
+ * method as follows:
+ *
+ * <pre>
+ * class Person {
+ * public enum Title { MR, MS, MISS, MRS, DR, PROF };
+ *
+ * public Title getTitle() { ... }
+ * public void setTitle(Title title) { ... }
+ * }
+ *
+ * NativeSelect<Title> titleSelect = new NativeSelect<>();
+ * titleSelect.setItems(Title.values());
+ * binder.bind(titleSelect, Person::getTitle, Person::setTitle);
+ * </pre>
+ *
+ * @param <SELECTVALUE>
+ * the item type of the select
+ * @param select
+ * the select to bind, not null
+ * @param getter
+ * the function to get the value of the property to the
+ * selection, not null
+ * @param setter
+ * the function to save the selection to the property or null if
+ * read-only
+ */
+ public <SELECTVALUE> void bind(AbstractSingleSelect<SELECTVALUE> select,
+ Function<BEAN, SELECTVALUE> getter,
+ BiConsumer<BEAN, SELECTVALUE> setter) {
+ forSelect(select).bind(getter, setter);
+ }
+
+ /**
* Binds the given bean to all the fields added to this Binder. To remove
* the binding, call {@link #unbind()}.
* <p>
diff --git a/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java b/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java
index 823650f443..81c22cffda 100644
--- a/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java
+++ b/server/src/main/java/com/vaadin/ui/AbstractSingleSelect.java
@@ -254,6 +254,17 @@ public abstract class AbstractSingleSelect<T> extends
return getSelectionModel().getSelectedItem();
}
+ /**
+ * Sets the current selection to the given item or clears selection if given
+ * {@code null}.
+ *
+ * @param item
+ * the item to select or {@code null} to clear selection
+ */
+ public void setSelectedItem(T item) {
+ getSelectionModel().setSelectedItem(item);
+ }
+
@Override
protected AbstractSingleSelectState getState() {
return (AbstractSingleSelectState) super.getState();
diff --git a/server/src/test/java/com/vaadin/data/BinderSingleSelectTest.java b/server/src/test/java/com/vaadin/data/BinderSingleSelectTest.java
new file mode 100644
index 0000000000..84747483d9
--- /dev/null
+++ b/server/src/test/java/com/vaadin/data/BinderSingleSelectTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.tests.data.bean.Person;
+import com.vaadin.tests.data.bean.Sex;
+import com.vaadin.ui.NativeSelect;
+
+public class BinderSingleSelectTest extends
+ BinderTestBase<Binder<Person>, Person> {
+
+ private NativeSelect<Sex> select;
+
+ @Before
+ public void setUp() {
+ binder = new Binder<>();
+ item = new Person();
+ select = new NativeSelect<>();
+ select.setItems(Sex.values());
+ }
+
+ @Test
+ public void personBound_bindSelectByShortcut_selectionUpdated() {
+ item.setSex(Sex.FEMALE);
+ binder.bind(item);
+ binder.bind(select, Person::getSex, Person::setSex);
+
+ assertSame(Sex.FEMALE, select.getSelectedItem().orElse(null));
+ }
+
+ @Test
+ public void personBound_bindSelect_selectionUpdated() {
+ item.setSex(Sex.MALE);
+ binder.bind(item);
+ binder.forSelect(select).bind(Person::getSex, Person::setSex);
+
+ assertSame(Sex.MALE, select.getSelectedItem().orElse(null));
+ }
+
+ @Test
+ public void selectBound_bindPersonWithNullSex_selectedItemNotPresent() {
+ bindSex();
+
+ assertFalse(select.getSelectedItem().isPresent());
+ }
+
+ @Test
+ public void selectBound_bindPerson_selectionUpdated() {
+ item.setSex(Sex.FEMALE);
+ bindSex();
+
+ assertSame(Sex.FEMALE, select.getSelectedItem().orElse(null));
+ }
+
+ @Test
+ public void bound_setSelection_beanValueUpdated() {
+ bindSex();
+
+ select.select(Sex.MALE);
+
+ assertSame(Sex.MALE, item.getSex());
+ }
+
+ @Test
+ public void bound_deselect_beanValueUpdatedToNull() {
+ item.setSex(Sex.MALE);
+ bindSex();
+
+ select.deselect(Sex.MALE);
+
+ assertNull(item.getSex());
+ }
+
+ @Test
+ public void unbound_changeSelection_beanValueNotUpdated() {
+ item.setSex(Sex.UNKNOWN);
+ bindSex();
+ binder.unbind();
+
+ select.select(Sex.FEMALE);
+
+ assertSame(Sex.UNKNOWN, item.getSex());
+ }
+
+ protected void bindSex() {
+ binder.forSelect(select).bind(Person::getSex, Person::setSex);
+ binder.bind(item);
+ }
+}
diff --git a/shared/src/main/java/com/vaadin/shared/data/selection/SelectionModel.java b/shared/src/main/java/com/vaadin/shared/data/selection/SelectionModel.java
index 115c43013b..8711d6a9c8 100644
--- a/shared/src/main/java/com/vaadin/shared/data/selection/SelectionModel.java
+++ b/shared/src/main/java/com/vaadin/shared/data/selection/SelectionModel.java
@@ -58,11 +58,28 @@ public interface SelectionModel<T> extends Serializable {
public Optional<T> getSelectedItem();
/**
+ * Sets the current selection to the given item, or clears selection if
+ * given {@code null}.
+ *
+ * @param item
+ * the item to select or {@code null} to clear selection
+ */
+ public default void setSelectedItem(T item) {
+ if (item != null) {
+ select(item);
+ } else {
+ deselectAll();
+ }
+ }
+
+ /**
* Returns a singleton set of the currently selected item or an empty
* set if no item is selected.
*
* @return a singleton set of the selected item if any, an empty set
* otherwise
+ *
+ * @see #getSelectedItem()
*/
@Override
default Set<T> getSelectedItems() {
@@ -85,13 +102,13 @@ public interface SelectionModel<T> extends Serializable {
*/
@Override
public void select(T item);
-
}
/**
- * Returns an immutable set of the currently selected items.
+ * Returns an immutable set of the currently selected items. It is safe to
+ * invoke other {@code SelectionModel} methods while iterating over the set.
* <p>
- * <i>Implementation note:</i> the iteration order of the items in the
+ * <em>Implementation note:</em> the iteration order of the items in the
* returned set should be well-defined and documented by the implementing
* class.
*