summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenis Anisimov <denis@vaadin.com>2016-08-16 12:47:55 +0300
committerVaadin Code Review <review@vaadin.com>2016-08-17 19:30:05 +0000
commit7a1adf6b3b9878600759eaed26d679c69b603375 (patch)
tree0e4195b11cb3dc8dc831687ab5d4f1770eb8c72f
parentea3894e96d615c2ee006cb550703bc3e7efd4721 (diff)
downloadvaadin-framework-7a1adf6b3b9878600759eaed26d679c69b603375.tar.gz
vaadin-framework-7a1adf6b3b9878600759eaed26d679c69b603375.zip
Binding.withStatusChangeHandler and Binding.withStatusLabel (#30).
Change-Id: Iecd8bd88d94b98829dfaec43b8635b1e93df330f
-rw-r--r--documentation/datamodel/datamodel-forms.asciidoc4
-rw-r--r--server/src/main/java/com/vaadin/data/Binder.java158
-rw-r--r--server/src/main/java/com/vaadin/data/StatusChangeHandler.java40
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationStatus.java35
-rw-r--r--server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java93
-rw-r--r--server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java63
-rw-r--r--server/src/test/java/com/vaadin/data/BinderTest.java163
7 files changed, 538 insertions, 18 deletions
diff --git a/documentation/datamodel/datamodel-forms.asciidoc b/documentation/datamodel/datamodel-forms.asciidoc
index 8acbffdd7a..fc21226358 100644
--- a/documentation/datamodel/datamodel-forms.asciidoc
+++ b/documentation/datamodel/datamodel-forms.asciidoc
@@ -151,9 +151,9 @@ binder.forField(nameField)
name -> name.length() >= 3,
"Full name must contain at least three characters")
.withStatusChangeHandler(statusChange -> {
- nameStatus.setValue(statusChange.getMessage());
+ nameStatus.setValue(statusChange.getMessage().orElse(""));
// Only show the label when validation has failed
- boolean error = statusChange.getStatus() == Status.ERROR;
+ boolean error = statusChange.getStatus() == ValidationStatus.ERROR;
nameStatus.setVisible(error);
})
.bind(Person::getName, Person::setName);
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java
index 5ce80d2c0b..daac947c07 100644
--- a/server/src/main/java/com/vaadin/data/Binder.java
+++ b/server/src/main/java/com/vaadin/data/Binder.java
@@ -33,6 +33,7 @@ import com.vaadin.event.Registration;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.UserError;
import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Label;
/**
* Connects one or more {@code Field} components to properties of a backing data
@@ -263,6 +264,80 @@ public class Binder<BEAN> implements Serializable {
public HasValue<FIELDVALUE> getField();
/**
+ * Sets the given {@code label} to show an error message if validation
+ * fails.
+ * <p>
+ * The validation state of each field is updated whenever the user
+ * modifies the value of that field. The validation state is by default
+ * shown using {@link AbstractComponent#setComponentError} which is used
+ * by the layout that the field is shown in. Most built-in layouts will
+ * show this as a red exclamation mark icon next to the component, so
+ * that hovering or tapping the icon shows a tooltip with the message
+ * text.
+ * <p>
+ * This method allows to customize the way a binder displays error
+ * messages to get more flexibility than what
+ * {@link AbstractComponent#setComponentError} provides (it replaces the
+ * default behavior).
+ * <p>
+ * This is just a shorthand for
+ * {@link #withStatusChangeHandler(StatusChangeHandler)} method where
+ * the handler instance hides the {@code label} if there is no error and
+ * shows it with validation error message if validation fails. It means
+ * that it cannot be called after
+ * {@link #withStatusChangeHandler(StatusChangeHandler)} method call or
+ * {@link #withStatusChangeHandler(StatusChangeHandler)} after this
+ * method call.
+ *
+ * @see #withStatusChangeHandler(StatusChangeHandler)
+ * @see AbstractComponent#setComponentError(ErrorMessage)
+ * @param label
+ * label to show validation status for the field
+ * @return this binding, for chaining
+ */
+ public default Binding<BEAN, FIELDVALUE, TARGET> withStatusLabel(
+ Label label) {
+ return withStatusChangeHandler(event -> {
+ label.setValue(event.getMessage().orElse(""));
+ // Only show the label when validation has failed
+ label.setVisible(
+ ValidationStatus.ERROR.equals(event.getStatus()));
+ });
+ }
+
+ /**
+ * Sets a {@link StatusChangeHandler} to track validation status
+ * changes.
+ * <p>
+ * The validation state of each field is updated whenever the user
+ * modifies the value of that field. The validation state is by default
+ * shown using {@link AbstractComponent#setComponentError} which is used
+ * by the layout that the field is shown in. Most built-in layouts will
+ * show this as a red exclamation mark icon next to the component, so
+ * that hovering or tapping the icon shows a tooltip with the message
+ * text.
+ * <p>
+ * This method allows to customize the way a binder displays error
+ * messages to get more flexibility than what
+ * {@link AbstractComponent#setComponentError} provides (it replaces the
+ * default behavior).
+ * <p>
+ * The method may be called only once. It means there is no chain unlike
+ * {@link #withValidator(Validator)} or
+ * {@link #withConverter(Converter)}. Also it means that the shorthand
+ * method {@link #withStatusLabel(Label)} also may not be called after
+ * this method.
+ *
+ * @see #withStatusLabel(Label)
+ * @see AbstractComponent#setComponentError(ErrorMessage)
+ * @param handler
+ * status change handler
+ * @return this binding, for chaining
+ */
+ public Binding<BEAN, FIELDVALUE, TARGET> withStatusChangeHandler(
+ StatusChangeHandler handler);
+
+ /**
* Validates the field value and returns a {@code Result} instance
* representing the outcome of the validation.
*
@@ -293,6 +368,8 @@ public class Binder<BEAN> implements Serializable {
private final HasValue<FIELDVALUE> field;
private Registration onValueChange;
+ private StatusChangeHandler statusChangeHandler;
+ private boolean isStatusHandlerChanged;
private Function<BEAN, TARGET> getter;
private BiConsumer<BEAN, TARGET> setter;
@@ -310,11 +387,15 @@ public class Binder<BEAN> implements Serializable {
* the binder this instance is connected to
* @param field
* the field to bind
+ * @param statusChangeHandler
+ * handler to track validation status
*/
@SuppressWarnings("unchecked")
- protected BindingImpl(Binder<BEAN> binder, HasValue<FIELDVALUE> field) {
+ protected BindingImpl(Binder<BEAN> binder, HasValue<FIELDVALUE> field,
+ StatusChangeHandler statusChangeHandler) {
this(binder, field,
- (Converter<FIELDVALUE, TARGET>) Converter.identity());
+ (Converter<FIELDVALUE, TARGET>) Converter.identity(),
+ statusChangeHandler);
}
/**
@@ -327,12 +408,16 @@ public class Binder<BEAN> implements Serializable {
* the field to bind
* @param converterValidatorChain
* the converter/validator chain to use
+ * @param statusChangeHandler
+ * handler to track validation status
*/
protected BindingImpl(Binder<BEAN> binder, HasValue<FIELDVALUE> field,
- Converter<FIELDVALUE, TARGET> converterValidatorChain) {
+ Converter<FIELDVALUE, TARGET> converterValidatorChain,
+ StatusChangeHandler statusChangeHandler) {
this.field = field;
this.binder = binder;
this.converterValidatorChain = converterValidatorChain;
+ this.statusChangeHandler = statusChangeHandler;
}
@Override
@@ -372,9 +457,21 @@ public class Binder<BEAN> implements Serializable {
checkUnbound();
Objects.requireNonNull(converter, "converter cannot be null");
- BindingImpl<BEAN, FIELDVALUE, NEWTARGET> newBinding = new BindingImpl<>(
- binder, field, converterValidatorChain.chain(converter));
- return newBinding;
+ return createNewBinding(converter);
+ }
+
+ @Override
+ public Binding<BEAN, FIELDVALUE, TARGET> withStatusChangeHandler(
+ StatusChangeHandler handler) {
+ Objects.requireNonNull(handler, "Handler may not be null");
+ if (isStatusHandlerChanged) {
+ throw new IllegalStateException(
+ "A StatusChangeHandler has already been set");
+ }
+ isStatusHandlerChanged = true;
+ checkUnbound();
+ statusChangeHandler = handler;
+ return this;
}
private void bind(BEAN bean) {
@@ -448,6 +545,23 @@ public class Binder<BEAN> implements Serializable {
public HasValue<FIELDVALUE> getField() {
return field;
}
+
+ private <NEWTARGET> BindingImpl<BEAN, FIELDVALUE, NEWTARGET> createNewBinding(
+ Converter<TARGET, NEWTARGET> converter) {
+ BindingImpl<BEAN, FIELDVALUE, NEWTARGET> newBinding = new BindingImpl<>(
+ binder, field, converterValidatorChain.chain(converter),
+ statusChangeHandler);
+ return newBinding;
+ }
+
+ private void fireStatusChangeEvent(Result<?> result) {
+ ValidationStatusChangeEvent event = new ValidationStatusChangeEvent(
+ getField(),
+ result.isError() ? ValidationStatus.ERROR
+ : ValidationStatus.OK,
+ result.getMessage().orElse(null));
+ statusChangeHandler.accept(event);
+ }
}
/**
@@ -601,13 +715,10 @@ public class Binder<BEAN> implements Serializable {
public List<ValidationError<?>> validate() {
List<ValidationError<?>> resultErrors = new ArrayList<>();
for (BindingImpl<BEAN, ?, ?> binding : bindings) {
- clearError(binding.field);
-
- binding.validate().ifError(errorMessage -> {
- resultErrors.add(
- new ValidationError<>(binding.field, errorMessage));
- handleError(binding.field, errorMessage);
- });
+ Result<?> result = binding.validate();
+ binding.fireStatusChangeEvent(result);
+ result.ifError(errorMessage -> resultErrors
+ .add(new ValidationError<>(binding.field, errorMessage)));
}
return resultErrors;
}
@@ -635,7 +746,6 @@ public class Binder<BEAN> implements Serializable {
public void load(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null");
bindings.forEach(binding -> binding.setFieldValue(bean));
-
}
/**
@@ -662,11 +772,11 @@ public class Binder<BEAN> implements Serializable {
* the field to bind
* @return the new incomplete binding
*/
- protected <FIELDVALUE> BindingImpl<BEAN, FIELDVALUE, FIELDVALUE> createBinding(
+ protected <FIELDVALUE> Binding<BEAN, FIELDVALUE, FIELDVALUE> createBinding(
HasValue<FIELDVALUE> field) {
Objects.requireNonNull(field, "field cannot be null");
BindingImpl<BEAN, FIELDVALUE, FIELDVALUE> b = new BindingImpl<BEAN, FIELDVALUE, FIELDVALUE>(
- this, field);
+ this, field, this::handleValidationStatusChange);
return b;
}
@@ -700,6 +810,22 @@ public class Binder<BEAN> implements Serializable {
if (field instanceof AbstractComponent) {
((AbstractComponent) field).setComponentError(new UserError(error));
}
+
+ }
+
+ /**
+ * Default {@link StatusChangeHandler} functional method implementation.
+ *
+ * @param event
+ * the validation event
+ */
+ private void handleValidationStatusChange(
+ ValidationStatusChangeEvent event) {
+ HasValue<?> source = event.getSource();
+ clearError(source);
+ if (Objects.equals(ValidationStatus.ERROR, event.getStatus())) {
+ handleError(source, event.getMessage().get());
+ }
}
}
diff --git a/server/src/main/java/com/vaadin/data/StatusChangeHandler.java b/server/src/main/java/com/vaadin/data/StatusChangeHandler.java
new file mode 100644
index 0000000000..0fbfcf3d46
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/StatusChangeHandler.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2014 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 java.io.Serializable;
+import java.util.function.Consumer;
+
+import com.vaadin.data.Binder.Binding;
+
+/**
+ * Validation status change handler.
+ * <p>
+ * Register an instance of this class using
+ * {@link Binding#withStatusChangeHandler(StatusChangeHandler) to be able to
+ * listen to validation status updates.
+ *
+ * @see Binding#withStatusChangeHandler(StatusChangeHandler)
+ * @see ValidationStatusChangeEvent
+ *
+ * @author Vaadin Ltd
+ * @since 8.0
+ *
+ */
+public interface StatusChangeHandler
+ extends Consumer<ValidationStatusChangeEvent>, Serializable {
+
+}
diff --git a/server/src/main/java/com/vaadin/data/ValidationStatus.java b/server/src/main/java/com/vaadin/data/ValidationStatus.java
new file mode 100644
index 0000000000..9582ad8153
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/ValidationStatus.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2014 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 com.vaadin.data.Binder.Binding;
+
+/**
+ * Validation status.
+ * <p>
+ * The status is the part of {@link ValidationStatusChangeEvent} which indicate
+ * whether the validation failed or not.
+ *
+ * @see ValidationStatusChangeEvent
+ * @see Binding#withStatusChangeHandler(StatusChangeHandler)
+ * @see StatusChangeHandler
+ *
+ * @author Vaadin Ltd
+ * @since 8.0
+ */
+public enum ValidationStatus {
+ OK, ERROR;
+}
diff --git a/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java b/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java
new file mode 100644
index 0000000000..fba7e5c510
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/ValidationStatusChangeEvent.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2014 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 java.util.EventObject;
+import java.util.Objects;
+import java.util.Optional;
+
+import com.vaadin.data.Binder.Binding;
+
+/**
+ * Validation status change event which is fired each time when validation is
+ * done. Use {@link Binding#withStatusChangeHandler(StatusChangeHandler)} method
+ * to add your validation handler to listen to the event.
+ *
+ * @see Binding#withStatusChangeHandler(StatusChangeHandler)
+ * @see StatusChangeHandler
+ *
+ * @author Vaadin Ltd
+ * @since 8.0
+ *
+ */
+public class ValidationStatusChangeEvent extends EventObject {
+
+ private final ValidationStatus status;
+ private final String message;
+
+ /**
+ * Creates a new status change event.
+ * <p>
+ * The {@code message} must be null if the {@code status} is
+ * {@link ValidationStatus#OK}.
+ *
+ * @param source
+ * field whose status has changed, not {@code null}
+ * @param status
+ * updated status value, not {@code null}
+ * @param message
+ * error message if status is ValidationStatus.ERROR, may be
+ * {@code null}
+ */
+ public ValidationStatusChangeEvent(HasValue<?> source,
+ ValidationStatus status, String message) {
+ super(source);
+ Objects.requireNonNull(source, "Event source may not be null");
+ Objects.requireNonNull(status, "Status may not be null");
+ if (Objects.equals(status, ValidationStatus.OK) && message != null) {
+ throw new IllegalStateException(
+ "Message must be null if status is not an error");
+ }
+ this.status = status;
+ this.message = message;
+ }
+
+ /**
+ * Returns validation status of the event.
+ *
+ * @return validation status
+ */
+ public ValidationStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * Returns error validation message if status is
+ * {@link ValidationStatus#ERROR}.
+ *
+ * @return an optional validation error status or an empty optional if
+ * status is not an error
+ */
+ public Optional<String> getMessage() {
+ return Optional.ofNullable(message);
+ }
+
+ @Override
+ public HasValue<?> getSource() {
+ return (HasValue<?>) super.getSource();
+ }
+
+}
diff --git a/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java b/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
index 6fcfe61ce3..053f4756c4 100644
--- a/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderBookOfVaadinTest.java
@@ -18,6 +18,7 @@ package com.vaadin.data;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Before;
@@ -28,6 +29,7 @@ import com.vaadin.data.util.converter.StringToIntegerConverter;
import com.vaadin.data.validator.EmailValidator;
import com.vaadin.server.AbstractErrorMessage;
import com.vaadin.ui.AbstractField;
+import com.vaadin.ui.Label;
import com.vaadin.ui.PopupDateField;
import com.vaadin.ui.Slider;
@@ -308,4 +310,65 @@ public class BinderBookOfVaadinTest {
}
+ @Test
+ public void withStatusLabelExample() {
+ Label emailStatus = new Label();
+
+ String msg = "This doesn't look like a valid email address";
+ binder.forField(field).withValidator(new EmailValidator(msg))
+ .withStatusLabel(emailStatus)
+ .bind(BookPerson::getEmail, BookPerson::setEmail);
+
+ field.setValue("foo");
+ binder.validate();
+
+ Assert.assertTrue(emailStatus.isVisible());
+ Assert.assertEquals(msg, emailStatus.getValue());
+
+ field.setValue("foo@vaadin.com");
+ binder.validate();
+
+ Assert.assertFalse(emailStatus.isVisible());
+ Assert.assertEquals("", emailStatus.getValue());
+ }
+
+ @Test
+ public void withStatusChangeHandlerExample() {
+ Label nameStatus = new Label();
+ AtomicReference<ValidationStatusChangeEvent> event = new AtomicReference<>();
+
+ String msg = "Full name must contain at least three characters";
+ binder.forField(field).withValidator(name -> name.length() >= 3, msg)
+ .withStatusChangeHandler(statusChange -> {
+ nameStatus.setValue(statusChange.getMessage().orElse(""));
+ // Only show the label when validation has failed
+ boolean error = statusChange
+ .getStatus() == ValidationStatus.ERROR;
+ nameStatus.setVisible(error);
+ event.set(statusChange);
+ }).bind(BookPerson::getLastName, BookPerson::setLastName);
+
+ field.setValue("aa");
+ binder.validate();
+
+ Assert.assertTrue(nameStatus.isVisible());
+ Assert.assertEquals(msg, nameStatus.getValue());
+ Assert.assertNotNull(event.get());
+ ValidationStatusChangeEvent evt = event.get();
+ Assert.assertEquals(ValidationStatus.ERROR, evt.getStatus());
+ Assert.assertEquals(msg, evt.getMessage().get());
+ Assert.assertEquals(field, evt.getSource());
+
+ field.setValue("foo");
+ binder.validate();
+
+ Assert.assertFalse(nameStatus.isVisible());
+ Assert.assertEquals("", nameStatus.getValue());
+ Assert.assertNotNull(event.get());
+ evt = event.get();
+ Assert.assertEquals(ValidationStatus.OK, evt.getStatus());
+ Assert.assertFalse(evt.getMessage().isPresent());
+ Assert.assertEquals(field, evt.getSource());
+ }
+
}
diff --git a/server/src/test/java/com/vaadin/data/BinderTest.java b/server/src/test/java/com/vaadin/data/BinderTest.java
index b8b7d21cd3..a4f8f83425 100644
--- a/server/src/test/java/com/vaadin/data/BinderTest.java
+++ b/server/src/test/java/com/vaadin/data/BinderTest.java
@@ -6,6 +6,7 @@ import static org.junit.Assert.assertSame;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.junit.Assert;
@@ -18,6 +19,7 @@ import com.vaadin.server.AbstractErrorMessage;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.UserError;
import com.vaadin.tests.data.bean.Person;
+import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
public class BinderTest {
@@ -403,4 +405,165 @@ public class BinderTest {
binder.load(bean);
}
+ @Test
+ public void withStatusChangeHandler_handlerGetsEvents() {
+ AtomicReference<ValidationStatusChangeEvent> event = new AtomicReference<>();
+ Binding<Person, String, String> binding = binder.forField(nameField)
+ .withValidator(notEmpty).withStatusChangeHandler(evt -> {
+ Assert.assertNull(event.get());
+ event.set(evt);
+ });
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ nameField.setValue("");
+
+ // First validation fails => should be event with ERROR status and
+ // message
+ binder.validate();
+
+ Assert.assertNotNull(event.get());
+ ValidationStatusChangeEvent evt = event.get();
+ Assert.assertEquals(ValidationStatus.ERROR, evt.getStatus());
+ Assert.assertEquals("Value cannot be empty", evt.getMessage().get());
+ Assert.assertEquals(nameField, evt.getSource());
+
+ nameField.setValue("foo");
+
+ event.set(null);
+ // Second validation succeeds => should be event with OK status and
+ // no message
+ binder.validate();
+
+ evt = event.get();
+ Assert.assertNotNull(evt);
+ Assert.assertEquals(ValidationStatus.OK, evt.getStatus());
+ Assert.assertFalse(evt.getMessage().isPresent());
+ Assert.assertEquals(nameField, evt.getSource());
+ }
+
+ @Test
+ public void withStatusChangeHandler_defaultStatusChangeHandlerIsReplaced() {
+ Binding<Person, String, String> binding = binder.forField(nameField)
+ .withValidator(notEmpty).withStatusChangeHandler(evt -> {
+ });
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ Assert.assertNull(nameField.getComponentError());
+
+ nameField.setValue("");
+
+ // First validation fails => should be event with ERROR status and
+ // message
+ binder.validate();
+
+ // default behavior should update component error for the nameField
+ Assert.assertNull(nameField.getComponentError());
+ }
+
+ @Test
+ public void withStatusLabel_labelIsUpdatedAccordingStatus() {
+ Label label = new Label();
+
+ Binding<Person, String, String> binding = binder.forField(nameField)
+ .withValidator(notEmpty).withStatusLabel(label);
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ nameField.setValue("");
+
+ // First validation fails => should be event with ERROR status and
+ // message
+ binder.validate();
+
+ Assert.assertTrue(label.isVisible());
+ Assert.assertEquals("Value cannot be empty", label.getValue());
+
+ nameField.setValue("foo");
+
+ // Second validation succeeds => should be event with OK status and
+ // no message
+ binder.validate();
+
+ Assert.assertFalse(label.isVisible());
+ Assert.assertEquals("", label.getValue());
+ }
+
+ @Test
+ public void withStatusLabel_defaultStatusChangeHandlerIsReplaced() {
+ Label label = new Label();
+
+ Binding<Person, String, String> binding = binder.forField(nameField)
+ .withValidator(notEmpty).withStatusLabel(label);
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ Assert.assertNull(nameField.getComponentError());
+
+ nameField.setValue("");
+
+ // First validation fails => should be event with ERROR status and
+ // message
+ binder.validate();
+
+ // default behavior should update component error for the nameField
+ Assert.assertNull(nameField.getComponentError());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void withStatusChangeHandler_addAfterBound() {
+ Binding<Person, String, String> binding = binder.forField(nameField)
+ .withValidator(notEmpty);
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ binding.withStatusChangeHandler(evt -> Assert.fail());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void withStatusLabel_addAfterBound() {
+ Label label = new Label();
+
+ Binding<Person, String, String> binding = binder.forField(nameField)
+ .withValidator(notEmpty);
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ binding.withStatusLabel(label);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void withStatusLabel_setAfterHandler() {
+ Label label = new Label();
+
+ Binding<Person, String, String> binding = binder.forField(nameField);
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ binding.withStatusChangeHandler(event -> {
+ });
+
+ binding.withStatusLabel(label);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void withStatusChangeHandler_setAfterLabel() {
+ Label label = new Label();
+
+ Binding<Person, String, String> binding = binder.forField(nameField);
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ binding.withStatusLabel(label);
+
+ binding.withStatusChangeHandler(event -> {
+ });
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void withStatusChangeHandler_setAfterOtherHandler() {
+
+ Binding<Person, String, String> binding = binder.forField(nameField);
+ binding.bind(Person::getFirstName, Person::setLastName);
+
+ binding.withStatusChangeHandler(event -> {
+ });
+
+ binding.withStatusChangeHandler(event -> {
+ });
+ }
+
}