From 3a69a723e689b047325ed88392d012ac96f4f62d Mon Sep 17 00:00:00 2001 From: Denis Anisimov Date: Mon, 3 Oct 2016 15:42:10 +0300 Subject: Add support for binder status change events (#208). Change-Id: Ic8dee407569ee310f007ebe32660a1d2922e9493 --- server/src/main/java/com/vaadin/data/Binder.java | 123 +++++++++++++++++---- .../java/com/vaadin/data/StatusChangeEvent.java | 86 ++++++++++++++ .../java/com/vaadin/data/StatusChangeListener.java | 37 +++++++ 3 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 server/src/main/java/com/vaadin/data/StatusChangeEvent.java create mode 100644 server/src/main/java/com/vaadin/data/StatusChangeListener.java (limited to 'server/src/main/java/com/vaadin/data') 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 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 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 implements Serializable { */ private ValidationStatus doValidation() { FIELDVALUE fieldValue = field.getValue(); - Result dataValue = converterValidatorChain.convertToModel( - fieldValue, findLocale()); + Result dataValue = converterValidatorChain + .convertToModel(fieldValue, findLocale()); return new ValidationStatus<>(this, dataValue); } @@ -566,8 +569,8 @@ public class Binder 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 implements Serializable { * the new value */ private void handleFieldValueChange(BEAN bean) { - binder.setHasChanges(true); + getBinder().setHasChanges(true); // store field value if valid ValidationStatus fieldValidationStatus = storeFieldValue( bean); @@ -585,14 +588,15 @@ public class Binder 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 status = new BinderValidationStatus<>( + binder, Arrays.asList(fieldValidationStatus), + binderValidationResults); + getBinder().getValidationStatusHandler().accept(status); + getBinder().fireStatusChangeEvent(status.hasErrors()); } /** @@ -668,6 +672,8 @@ public class Binder implements Serializable { private final List> validators = new ArrayList<>(); + private EventRouter eventRouter; + private Label statusLabel; private BinderValidationStatusHandler statusHandler; @@ -742,9 +748,9 @@ public class Binder implements Serializable { @Override public Registration addValueChangeListener( ValueChangeListener 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 implements Serializable { *
      * 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 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 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 implements Serializable {
 
         getValidationStatusHandler()
                 .accept(BinderValidationStatus.createUnresolvedStatus(this));
+        fireStatusChangeEvent(false);
     }
 
     /**
@@ -1073,6 +1075,7 @@ public class Binder implements Serializable {
      * @return a list of field validation errors if such occur, otherwise a list
      *         of bean validation errors.
      */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     private BinderValidationStatus doSaveIfValid(BEAN bean) {
         Objects.requireNonNull(bean, "bean cannot be null");
         // First run fields level validation
@@ -1080,6 +1083,7 @@ public class Binder 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 implements Serializable {
         bindings.forEach(binding -> binding.storeFieldValue(bean));
         // Now run bean level validation against the updated bean
         List> 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 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 implements Serializable {
                     bindingStatuses, validateItem(bean));
         }
         getValidationStatusHandler().accept(validationStatus);
+        fireStatusChangeEvent(validationStatus.hasErrors());
         return validationStatus;
     }
 
@@ -1273,6 +1280,43 @@ public class Binder implements Serializable {
                 .orElse(this::handleBinderValidationStatus);
     }
 
+    /**
+     * Adds status change listener to the binder.
+     * 

+ * The {@link Binder} status is changed whenever any of the following + * happens: + *

    + *
  • if it's bound and any of its bound field or select has been changed + *
  • {@link #save(Object)} or {@link #saveIfValid(Object)} is called + *
  • {@link #load(Object)} is called + *
  • {@link #bind(Object)} is called + *
  • {@link #unbind(Object)} is called + *
  • {@link Binding#bind(Function, BiConsumer)} is called + *
  • {@link Binder#validate()} or {@link Binding#validate()} is called + *
+ * + * @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. * @@ -1400,4 +1444,35 @@ public class Binder 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. + *

+ * The {@link Binder} status is changed whenever any of the following happens: + *

    + *
  • if it's bound and any of its bound field or select has been changed + *
  • {@link #save(Object)} or {@link #saveIfValid(Object)} is called + *
  • {@link #load(Object)} is called + *
  • {@link #bind(Object)} is called + *
  • {@link #unbind(Object)} is called + *
  • {@link Binding#bind(Function, BiConsumer)} is called + *
  • {@link Binder#validate()} or {@link Binding#validate()} is called + *
+ * + * @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); +} -- cgit v1.2.3