import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.StringToIntegerConverter;
+import com.vaadin.event.EventRouter;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.UserError;
import com.vaadin.shared.Registration;
this.setter = setter;
getBinder().bindings.add(this);
getBinder().getBean().ifPresent(this::bind);
+ getBinder().fireStatusChangeEvent(false);
}
@Override
getBinder().getValidationStatusHandler()
.accept(new BinderValidationStatus<>(getBinder(),
Arrays.asList(status), Collections.emptyList()));
+ getBinder().fireStatusChangeEvent(status.isError());
return status;
}
*/
private ValidationStatus<TARGET> doValidation() {
FIELDVALUE fieldValue = field.getValue();
- Result<TARGET> dataValue = converterValidatorChain.convertToModel(
- fieldValue, findLocale());
+ Result<TARGET> dataValue = converterValidatorChain
+ .convertToModel(fieldValue, findLocale());
return new ValidationStatus<>(this, dataValue);
}
}
private FIELDVALUE convertDataToFieldType(BEAN bean) {
- return converterValidatorChain.convertToPresentation(
- getter.apply(bean), findLocale());
+ return converterValidatorChain
+ .convertToPresentation(getter.apply(bean), findLocale());
}
/**
* the new value
*/
private void handleFieldValueChange(BEAN bean) {
- binder.setHasChanges(true);
+ getBinder().setHasChanges(true);
// store field value if valid
ValidationStatus<TARGET> fieldValidationStatus = storeFieldValue(
bean);
// if all field level validations pass, run bean level validation
if (!getBinder().bindings.stream().map(BindingImpl::doValidation)
.anyMatch(ValidationStatus::isError)) {
- binderValidationResults = binder.validateItem(bean);
+ binderValidationResults = getBinder().validateItem(bean);
} else {
binderValidationResults = Collections.emptyList();
}
- binder.getValidationStatusHandler()
- .accept(new BinderValidationStatus<>(binder,
- Arrays.asList(fieldValidationStatus),
- binderValidationResults));
+ BinderValidationStatus<BEAN> status = new BinderValidationStatus<>(
+ binder, Arrays.asList(fieldValidationStatus),
+ binderValidationResults);
+ getBinder().getValidationStatusHandler().accept(status);
+ getBinder().fireStatusChangeEvent(status.hasErrors());
}
/**
private final List<Validator<? super BEAN>> validators = new ArrayList<>();
+ private EventRouter eventRouter;
+
private Label statusLabel;
private BinderValidationStatusHandler statusHandler;
@Override
public Registration addValueChangeListener(
ValueChangeListener<? super SELECTVALUE> listener) {
- return select.addSelectionListener(e -> listener.accept(
- new ValueChange<>(select, getValue(), e
- .isUserOriginated())));
+ return select.addSelectionListener(
+ e -> listener.accept(new ValueChange<>(select,
+ getValue(), e.isUserOriginated())));
}
});
}
* <pre>
* class Feature {
* public enum Browser { CHROME, EDGE, FIREFOX, IE, OPERA, SAFARI }
-
+
* public Set<Browser> getSupportedBrowsers() { ... }
* public void setSupportedBrowsers(Set<Browser> title) { ... }
* }
*/
public void bind(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null");
- unbind();
+ doUnbind(false);
this.bean = bean;
bindings.forEach(b -> b.bind(bean));
// if there has been field value change listeners that trigger
// validation, need to make sure the validation errors are cleared
getValidationStatusHandler()
.accept(BinderValidationStatus.createUnresolvedStatus(this));
+ fireStatusChangeEvent(false);
}
/**
* nothing.
*/
public void unbind() {
- setHasChanges(false);
- if (bean != null) {
- bean = null;
- bindings.forEach(BindingImpl::unbind);
- }
- getValidationStatusHandler()
- .accept(BinderValidationStatus.createUnresolvedStatus(this));
+ doUnbind(true);
}
/**
getValidationStatusHandler()
.accept(BinderValidationStatus.createUnresolvedStatus(this));
+ fireStatusChangeEvent(false);
}
/**
* @return a list of field validation errors if such occur, otherwise a list
* of bean validation errors.
*/
+ @SuppressWarnings({ "rawtypes", "unchecked" })
private BinderValidationStatus<BEAN> doSaveIfValid(BEAN bean) {
Objects.requireNonNull(bean, "bean cannot be null");
// First run fields level validation
// If no validation errors then update bean
if (bindingStatuses.stream().filter(ValidationStatus::isError).findAny()
.isPresent()) {
+ fireStatusChangeEvent(true);
return new BinderValidationStatus<>(this, bindingStatuses,
Collections.emptyList());
}
bindings.forEach(binding -> binding.storeFieldValue(bean));
// Now run bean level validation against the updated bean
List<Result<?>> binderResults = validateItem(bean);
- if (binderResults.stream().filter(Result::isError).findAny()
- .isPresent()) {
+ boolean hasErrors = binderResults.stream().filter(Result::isError)
+ .findAny().isPresent();
+ if (hasErrors) {
// Item validator failed, revert values
bindings.forEach((BindingImpl binding) -> binding.setBeanValue(bean,
oldValues.get(binding)));
// Save successful, reset hasChanges to false
setHasChanges(false);
}
+ fireStatusChangeEvent(hasErrors);
return new BinderValidationStatus<>(this, bindingStatuses,
binderResults);
}
bindingStatuses, validateItem(bean));
}
getValidationStatusHandler().accept(validationStatus);
+ fireStatusChangeEvent(validationStatus.hasErrors());
return validationStatus;
}
.orElse(this::handleBinderValidationStatus);
}
+ /**
+ * Adds status change listener to the binder.
+ * <p>
+ * The {@link Binder} status is changed whenever any of the following
+ * happens:
+ * <ul>
+ * <li>if it's bound and any of its bound field or select has been changed
+ * <li>{@link #save(Object)} or {@link #saveIfValid(Object)} is called
+ * <li>{@link #load(Object)} is called
+ * <li>{@link #bind(Object)} is called
+ * <li>{@link #unbind(Object)} is called
+ * <li>{@link Binding#bind(Function, BiConsumer)} is called
+ * <li>{@link Binder#validate()} or {@link Binding#validate()} is called
+ * </ul>
+ *
+ * @see #load(Object)
+ * @see #save(Object)
+ * @see #saveIfValid(Object)
+ * @see #bind(Object)
+ * @see #unbind()
+ * @see #forField(HasValue)
+ * @see #forSelect(AbstractMultiSelect)
+ * @See {@link #validate()}
+ * @see Binding#validate()
+ * @see Binding#bind(Object)
+ *
+ * @param listener
+ * status change listener to add, not null
+ * @return a registration for the listener
+ */
+ public Registration addStatusChangeListener(StatusChangeListener listener) {
+ getEventRouter().addListener(StatusChangeEvent.class, listener,
+ StatusChangeListener.class.getDeclaredMethods()[0]);
+ return () -> getEventRouter().removeListener(StatusChangeEvent.class,
+ listener);
+ }
+
/**
* Creates a new binding with the given field.
*
public boolean hasChanges() {
return hasChanges;
}
+
+ /**
+ * Returns the event router for this binder.
+ *
+ * @return the event router, not null
+ */
+ protected EventRouter getEventRouter() {
+ if (eventRouter == null) {
+ eventRouter = new EventRouter();
+ }
+ return eventRouter;
+ }
+
+ private void doUnbind(boolean fireStatusEvent) {
+ setHasChanges(false);
+ if (bean != null) {
+ bean = null;
+ bindings.forEach(BindingImpl::unbind);
+ }
+ getValidationStatusHandler()
+ .accept(BinderValidationStatus.createUnresolvedStatus(this));
+ if (fireStatusEvent) {
+ fireStatusChangeEvent(false);
+ }
+ }
+
+ private void fireStatusChangeEvent(boolean hasValidationErrors) {
+ getEventRouter()
+ .fireEvent(new StatusChangeEvent(this, hasValidationErrors));
+ }
+
}
--- /dev/null
+/*
+ * 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 java.util.EventObject;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+import com.vaadin.data.Binder.Binding;
+
+/**
+ * Binder status change event.
+ * <p>
+ * The {@link Binder} status is changed whenever any of the following happens:
+ * <ul>
+ * <li>if it's bound and any of its bound field or select has been changed
+ * <li>{@link #save(Object)} or {@link #saveIfValid(Object)} is called
+ * <li>{@link #load(Object)} is called
+ * <li>{@link #bind(Object)} is called
+ * <li>{@link #unbind(Object)} is called
+ * <li>{@link Binding#bind(Function, BiConsumer)} is called
+ * <li>{@link Binder#validate()} or {@link Binding#validate()} is called
+ * </ul>
+ *
+ * @see StatusChangeListener#statusChange(StatusChangeEvent)
+ * @see Binder#addStatusChangeListener(StatusChangeListener)
+ *
+ * @author Vaadin Ltd
+ *
+ */
+public class StatusChangeEvent extends EventObject {
+
+ private final boolean hasValidationErrors;
+
+ /**
+ * Create a new status change event for given {@code binder} using its
+ * current validation status.
+ *
+ * @param binder
+ * the event source binder
+ * @param hasValidationErrors
+ * the binder validation status
+ */
+ public StatusChangeEvent(Binder<?> binder, boolean hasValidationErrors) {
+ super(binder);
+ this.hasValidationErrors = hasValidationErrors;
+ }
+
+ /**
+ * Gets the binder validation status.
+ *
+ * @return {@code true} if the binder has validation errors, {@code false}
+ * otherwise
+ */
+ public boolean hasValidationErrors() {
+ return hasValidationErrors;
+ }
+
+ @Override
+ public Binder<?> getSource() {
+ return (Binder<?>) super.getSource();
+ }
+
+ /**
+ * Gets the binder.
+ *
+ * @return the binder
+ */
+ public Binder<?> getBinder() {
+ return getSource();
+ }
+
+}
--- /dev/null
+/*
+ * 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 java.io.Serializable;
+
+/**
+ * Listener interface for {@link StatusChangeEvent}s.
+ *
+ * @see StatusChangeEvent
+ * @author Vaadin Ltd
+ *
+ */
+@FunctionalInterface
+public interface StatusChangeListener extends Serializable {
+
+ /**
+ * Notifies the listener about status change {@code event}.
+ *
+ * @param event
+ * a status change event, not null
+ */
+ void statusChange(StatusChangeEvent event);
+}
import java.time.LocalDate;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import com.vaadin.data.Binder.Binding;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.StringToIntegerConverter;
import com.vaadin.data.validator.EmailValidator;
+import com.vaadin.data.validator.StringLengthValidator;
import com.vaadin.server.AbstractErrorMessage;
import com.vaadin.ui.Button;
import com.vaadin.ui.DateField;
formStatusLabel.getValue());
}
+
+ @Test
+ @Ignore
+ public void statusChangeListener_binderIsNotBound() {
+ Button saveButton = new Button();
+ Button resetButton = new Button();
+
+ AtomicBoolean eventIsFired = new AtomicBoolean(false);
+
+ binder.addStatusChangeListener(event -> {
+ boolean isValid = !event.hasValidationErrors();
+ boolean hasChanges = event.getBinder().hasChanges();
+ eventIsFired.set(true);
+
+ saveButton.setEnabled(hasChanges && isValid);
+ resetButton.setEnabled(hasChanges);
+ });
+ binder.forField(field)
+ .withValidator(new StringLengthValidator("", 1, 3))
+ .bind(BookPerson::getLastName, BookPerson::setLastName);
+ // no changes
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertFalse(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+
+ BookPerson person = new BookPerson(2000, 1);
+ binder.load(person);
+ // no changes
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertFalse(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+
+ field.setValue("a");
+ // binder is not bound, no event fired
+ // no changes: see #375. There should be a change and enabled state
+ Assert.assertTrue(saveButton.isEnabled());
+ Assert.assertTrue(resetButton.isEnabled());
+ Assert.assertTrue(eventIsFired.get());
+
+ binder.saveIfValid(person);
+ // no changes
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertFalse(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+
+ binder.validate();
+ // no changes
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertFalse(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+
+ field.setValue("");
+ // binder is not bound, no event fired
+ // no changes: see #375. There should be a change and disabled state for
+ // save button because of failed validation
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertTrue(resetButton.isEnabled());
+ Assert.assertTrue(eventIsFired.get());
+ }
+
+ @Test
+ public void statusChangeListener_binderIsBound() {
+ Button saveButton = new Button();
+ Button resetButton = new Button();
+
+ AtomicBoolean eventIsFired = new AtomicBoolean(false);
+
+ binder.addStatusChangeListener(event -> {
+ boolean isValid = !event.hasValidationErrors();
+ boolean hasChanges = event.getBinder().hasChanges();
+ eventIsFired.set(true);
+
+ saveButton.setEnabled(hasChanges && isValid);
+ resetButton.setEnabled(hasChanges);
+ });
+ binder.forField(field)
+ .withValidator(new StringLengthValidator("", 1, 3))
+ .bind(BookPerson::getLastName, BookPerson::setLastName);
+ // no changes
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertFalse(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+
+ BookPerson person = new BookPerson(2000, 1);
+ binder.bind(person);
+ // no changes
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertFalse(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+
+ field.setValue("a");
+ // there are valid changes
+ Assert.assertTrue(saveButton.isEnabled());
+ Assert.assertTrue(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+
+ field.setValue("");
+ // there are invalid changes
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertTrue(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+
+ // set valid value
+ field.setValue("a");
+ verifyEventIsFired(eventIsFired);
+ binder.saveIfValid(person);
+ // there are no changes.
+ Assert.assertFalse(saveButton.isEnabled());
+ Assert.assertFalse(resetButton.isEnabled());
+ verifyEventIsFired(eventIsFired);
+ }
+
+ private void verifyEventIsFired(AtomicBoolean flag) {
+ Assert.assertTrue(flag.get());
+ flag.set(false);
+ }
}
--- /dev/null
+/*
+ * 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 java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.Binder.Binding;
+import com.vaadin.data.util.converter.StringToIntegerConverter;
+import com.vaadin.tests.data.bean.Person;
+
+/**
+ * @author Vaadin Ltd
+ *
+ */
+public class BinderStatusChangeTest
+ extends BinderTestBase<Binder<Person>, Person> {
+
+ private AtomicReference<StatusChangeEvent> event;
+
+ @Before
+ public void setUp() {
+ binder = new Binder<>();
+ item = new Person();
+ event = new AtomicReference<>();
+ }
+
+ @Test
+ public void bindBinding_unbound_eventWhenBoundEndnoEventsBeforeBound() {
+ binder.addStatusChangeListener(this::statusChanged);
+
+ Binding<Person, String, String> binding = binder.forField(nameField);
+
+ nameField.setValue("");
+ Assert.assertNull(event.get());
+
+ binding.bind(Person::getFirstName, Person::setFirstName);
+ verifyEvent();
+ }
+
+ @Test
+ public void bindBinder_unbound_singleEventWhenBound() {
+ binder.addStatusChangeListener(this::statusChanged);
+
+ Assert.assertNull(event.get());
+
+ binder.bind(item);
+
+ verifyEvent();
+ }
+
+ @Test
+ public void unbindBinder_bound_singleEventWhenBound() {
+ binder.bind(item);
+
+ unbindBinder_unbound_singleEventWhenBound();
+ }
+
+ @Test
+ public void unbindBinder_unbound_singleEventWhenBound() {
+ binder.addStatusChangeListener(this::statusChanged);
+
+ Assert.assertNull(event.get());
+ binder.unbind();
+ verifyEvent();
+ }
+
+ @Test
+ public void setValue_bound_singleEventOnSetValue() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.bind(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+
+ Assert.assertNull(event.get());
+ nameField.setValue("foo");
+ verifyEvent();
+ }
+
+ @Test
+ public void setValue_severalBoundFieldsAndBoundBinder_singleEventOnSetValue() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.bind(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+
+ Assert.assertNull(event.get());
+ nameField.setValue("foo");
+ verifyEvent();
+ }
+
+ @Test
+ public void setInvalidValue_bound_singleEventOnSetValue() {
+ binder.forField(nameField).withValidator(name -> false, "")
+ .bind(Person::getFirstName, Person::setFirstName);
+ binder.bind(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+
+ Assert.assertNull(event.get());
+ nameField.setValue("foo");
+ verifyEvent(true);
+ }
+
+ @Test
+ public void setInvalidBean_bound_singleEventOnSetValue() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.bind(item);
+
+ binder.withValidator(Validator.from(bean -> false, ""));
+
+ binder.addStatusChangeListener(this::statusChanged);
+
+ Assert.assertNull(event.get());
+ nameField.setValue("foo");
+ verifyEvent(true);
+ }
+
+ @Test
+ public void load_hasBindings_singleEventOnLoad() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.load(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void load_hasSeveralBindings_singleEventOnLoad() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.load(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void load_hasNoBindings_singleEvent() {
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.load(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void save_hasNoBindings_singleEvent() throws ValidationException {
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.save(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void saveIfValid_hasNoBindings_singleEvent() {
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.saveIfValid(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void save_hasBindings_singleEvent() throws ValidationException {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.load(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.save(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void save_hasSeveralBindings_singleEvent()
+ throws ValidationException {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.load(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.save(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void saveIfValid_hasBindings_singleEvent() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.load(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.saveIfValid(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void saveIfValid_hasSeveralBindings_singleEvent() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.load(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.saveIfValid(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void saveInvalidValue_hasBindings_singleEvent() {
+ binder.forField(nameField).withValidator(name -> false, "")
+ .bind(Person::getFirstName, Person::setFirstName);
+ binder.load(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ try {
+ binder.save(item);
+ } catch (ValidationException ignore) {
+ }
+ verifyEvent(true);
+ }
+
+ @Test
+ public void saveIfValid_invalidValueAndBinderHasBindings_singleEvent() {
+ binder.forField(nameField).withValidator(name -> false, "")
+ .bind(Person::getFirstName, Person::setFirstName);
+ binder.load(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.saveIfValid(item);
+ verifyEvent(true);
+ }
+
+ @Test
+ public void saveIfValid_invalidValueAndBinderHasSeveralBindings_singleEvent() {
+ binder.forField(nameField).withValidator(name -> false, "")
+ .bind(Person::getFirstName, Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.load(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.saveIfValid(item);
+ verifyEvent(true);
+ }
+
+ @Test
+ public void saveInvalidBean_hasBindings_singleEvent() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.load(item);
+ binder.withValidator(Validator.from(person -> false, ""));
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ try {
+ binder.save(item);
+ } catch (ValidationException ignore) {
+ }
+ verifyEvent(true);
+ }
+
+ @Test
+ public void saveIfValid_invalidBeanAndBinderHasBindings_singleEvent() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.load(item);
+ binder.withValidator(Validator.from(person -> false, ""));
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.saveIfValid(item);
+ verifyEvent(true);
+ }
+
+ @Test
+ public void saveValidBean_hasBindings_singleEvent()
+ throws ValidationException {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.load(item);
+ binder.withValidator(Validator.from(person -> true, ""));
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.save(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void saveIfValid_validBeanAndBinderHasBindings_singleEvent() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.load(item);
+ binder.withValidator(Validator.from(person -> true, ""));
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+ binder.saveIfValid(item);
+ verifyEvent();
+ }
+
+ @Test
+ public void validateBinder_noValidationErrors_statusEventWithoutErrors() {
+ binder.forField(nameField).bind(Person::getFirstName,
+ Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.bind(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+
+ binder.validate();
+ verifyEvent();
+ }
+
+ @Test
+ public void validateBinder_validationErrors_statusEventWithError() {
+ binder.forField(nameField).withValidator(name -> false, "")
+ .bind(Person::getFirstName, Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.bind(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+
+ binder.validate();
+ verifyEvent(true);
+ }
+
+ @Test
+ public void validateBinding_noValidationErrors_statusEventWithoutErrors() {
+ Binding<Person, String, String> binding = binder.forField(nameField);
+ binding.bind(Person::getFirstName, Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.bind(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+
+ binding.validate();
+ verifyEvent();
+ }
+
+ @Test
+ public void validateBinding_validationErrors_statusEventWithError() {
+ Binding<Person, String, String> binding = binder.forField(nameField)
+ .withValidator(name -> false, "");
+ binding.bind(Person::getFirstName, Person::setFirstName);
+ binder.forField(ageField)
+ .withConverter(new StringToIntegerConverter(""))
+ .bind(Person::getAge, Person::setAge);
+ binder.bind(item);
+
+ binder.addStatusChangeListener(this::statusChanged);
+ Assert.assertNull(event.get());
+
+ binding.validate();
+ verifyEvent(true);
+ }
+
+ private void verifyEvent() {
+ verifyEvent(false);
+ }
+
+ private void verifyEvent(boolean validationErrors) {
+ StatusChangeEvent statusChangeEvent = event.get();
+ Assert.assertNotNull(statusChangeEvent);
+ Assert.assertEquals(binder, statusChangeEvent.getBinder());
+ Assert.assertEquals(binder, statusChangeEvent.getSource());
+ Assert.assertEquals(validationErrors,
+ statusChangeEvent.hasValidationErrors());
+ }
+
+ private void statusChanged(StatusChangeEvent evt) {
+ Assert.assertNull(event.get());
+ event.set(evt);
+ }
+}