diff options
author | Denis Anisimov <denis@vaadin.com> | 2016-08-16 12:47:55 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2016-08-17 19:30:05 +0000 |
commit | 7a1adf6b3b9878600759eaed26d679c69b603375 (patch) | |
tree | 0e4195b11cb3dc8dc831687ab5d4f1770eb8c72f | |
parent | ea3894e96d615c2ee006cb550703bc3e7efd4721 (diff) | |
download | vaadin-framework-7a1adf6b3b9878600759eaed26d679c69b603375.tar.gz vaadin-framework-7a1adf6b3b9878600759eaed26d679c69b603375.zip |
Binding.withStatusChangeHandler and Binding.withStatusLabel (#30).
Change-Id: Iecd8bd88d94b98829dfaec43b8635b1e93df330f
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 -> { + }); + } + } |