diff options
author | Denis Anisimov <denis@vaadin.com> | 2016-10-03 15:42:10 +0300 |
---|---|---|
committer | Aleksi Hietanen <aleksi@vaadin.com> | 2016-10-06 08:44:50 +0000 |
commit | 3a69a723e689b047325ed88392d012ac96f4f62d (patch) | |
tree | 0e0034dcc7bd78e403aaad517fe242021ef1ca69 /server/src/main/java/com/vaadin/data | |
parent | 6abb9c5c64c60db47e8f2dde87afaf0e76b2228c (diff) | |
download | vaadin-framework-3a69a723e689b047325ed88392d012ac96f4f62d.tar.gz vaadin-framework-3a69a723e689b047325ed88392d012ac96f4f62d.zip |
Add support for binder status change events (#208).
Change-Id: Ic8dee407569ee310f007ebe32660a1d2922e9493
Diffstat (limited to 'server/src/main/java/com/vaadin/data')
3 files changed, 222 insertions, 24 deletions
diff --git a/server/src/main/java/com/vaadin/data/Binder.java b/server/src/main/java/com/vaadin/data/Binder.java index d2114e8120..7a59b90946 100644 --- a/server/src/main/java/com/vaadin/data/Binder.java +++ b/server/src/main/java/com/vaadin/data/Binder.java @@ -34,6 +34,7 @@ import java.util.stream.Collectors; 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; @@ -435,6 +436,7 @@ public class Binder<BEAN> implements Serializable { this.setter = setter; getBinder().bindings.add(this); getBinder().getBean().ifPresent(this::bind); + getBinder().fireStatusChangeEvent(false); } @Override @@ -533,6 +535,7 @@ public class Binder<BEAN> implements Serializable { getBinder().getValidationStatusHandler() .accept(new BinderValidationStatus<>(getBinder(), Arrays.asList(status), Collections.emptyList())); + getBinder().fireStatusChangeEvent(status.isError()); return status; } @@ -544,8 +547,8 @@ public class Binder<BEAN> implements Serializable { */ 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); } @@ -566,8 +569,8 @@ public class Binder<BEAN> implements Serializable { } private FIELDVALUE convertDataToFieldType(BEAN bean) { - return converterValidatorChain.convertToPresentation( - getter.apply(bean), findLocale()); + return converterValidatorChain + .convertToPresentation(getter.apply(bean), findLocale()); } /** @@ -577,7 +580,7 @@ public class Binder<BEAN> implements Serializable { * the new value */ private void handleFieldValueChange(BEAN bean) { - binder.setHasChanges(true); + getBinder().setHasChanges(true); // store field value if valid ValidationStatus<TARGET> fieldValidationStatus = storeFieldValue( bean); @@ -585,14 +588,15 @@ public class Binder<BEAN> implements Serializable { // 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()); } /** @@ -668,6 +672,8 @@ public class Binder<BEAN> implements Serializable { private final List<Validator<? super BEAN>> validators = new ArrayList<>(); + private EventRouter eventRouter; + private Label statusLabel; private BinderValidationStatusHandler statusHandler; @@ -742,9 +748,9 @@ public class Binder<BEAN> implements Serializable { @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()))); } }); } @@ -916,7 +922,7 @@ public class Binder<BEAN> implements Serializable { * <pre> * class Feature { * public enum Browser { CHROME, EDGE, FIREFOX, IE, OPERA, SAFARI } - + * public Set<Browser> getSupportedBrowsers() { ... } * public void setSupportedBrowsers(Set<Browser> title) { ... } * } @@ -964,13 +970,14 @@ public class Binder<BEAN> implements Serializable { */ 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); } /** @@ -978,13 +985,7 @@ public class Binder<BEAN> implements Serializable { * nothing. */ public void unbind() { - setHasChanges(false); - if (bean != null) { - bean = null; - bindings.forEach(BindingImpl::unbind); - } - getValidationStatusHandler() - .accept(BinderValidationStatus.createUnresolvedStatus(this)); + doUnbind(true); } /** @@ -1009,6 +1010,7 @@ public class Binder<BEAN> implements Serializable { getValidationStatusHandler() .accept(BinderValidationStatus.createUnresolvedStatus(this)); + fireStatusChangeEvent(false); } /** @@ -1073,6 +1075,7 @@ public class Binder<BEAN> implements Serializable { * @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 @@ -1080,6 +1083,7 @@ public class Binder<BEAN> implements Serializable { // If no validation errors then update bean if (bindingStatuses.stream().filter(ValidationStatus::isError).findAny() .isPresent()) { + fireStatusChangeEvent(true); return new BinderValidationStatus<>(this, bindingStatuses, Collections.emptyList()); } @@ -1092,8 +1096,9 @@ public class Binder<BEAN> implements Serializable { 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))); @@ -1101,6 +1106,7 @@ public class Binder<BEAN> implements Serializable { // Save successful, reset hasChanges to false setHasChanges(false); } + fireStatusChangeEvent(hasErrors); return new BinderValidationStatus<>(this, bindingStatuses, binderResults); } @@ -1150,6 +1156,7 @@ public class Binder<BEAN> implements Serializable { bindingStatuses, validateItem(bean)); } getValidationStatusHandler().accept(validationStatus); + fireStatusChangeEvent(validationStatus.hasErrors()); return validationStatus; } @@ -1274,6 +1281,43 @@ public class Binder<BEAN> implements Serializable { } /** + * 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. * * @param <FIELDVALUE> @@ -1400,4 +1444,35 @@ public class Binder<BEAN> implements Serializable { 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)); + } + } diff --git a/server/src/main/java/com/vaadin/data/StatusChangeEvent.java b/server/src/main/java/com/vaadin/data/StatusChangeEvent.java new file mode 100644 index 0000000000..7a73fb5150 --- /dev/null +++ b/server/src/main/java/com/vaadin/data/StatusChangeEvent.java @@ -0,0 +1,86 @@ +/* + * 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(); + } + +} diff --git a/server/src/main/java/com/vaadin/data/StatusChangeListener.java b/server/src/main/java/com/vaadin/data/StatusChangeListener.java new file mode 100644 index 0000000000..cb6afead24 --- /dev/null +++ b/server/src/main/java/com/vaadin/data/StatusChangeListener.java @@ -0,0 +1,37 @@ +/* + * 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); +} |