Fixes #9792tags/8.2.0.alpha3
@@ -115,6 +115,7 @@ | |||
<li><tt>Button</tt> has a new constructor that may cause constructor calls with null as first parameter to be ambiguous.</li> | |||
<li><tt>DataCommunicator</tt> methods <tt>getDataProviderSize</tt> and <tt>fetchItemsWithRange</tt> are now <tt>public</tt>, not <tt>protected</tt>.</li> | |||
<li><tt>Binder</tt> method <tt>getBindings</tt> now returns a Collection, not a Set.</li> | |||
<li><tt>Binder</tt> protected method <tt>handleError</tt> now takes a <tt>ValidationResult</tt>, not <tt>String</tt>.</li> | |||
<li><tt>BindingBuilder</tt> now works like a proper builder. Adding a converter will not mark Binding as <tt>bound</tt> allowing chaining to the same object.</li> | |||
<li><tt>ErrorLevel</tt> is removed from <tt>ErrorMessage</tt> and now <tt>com.vaadin.shared.ui.ErrorLevel</tt> should be used.</li> | |||
<li>Error indicators are now <tt><span class="v-errorindicator"></span></tt> elements.</li> |
@@ -43,12 +43,14 @@ import com.vaadin.data.HasValue.ValueChangeListener; | |||
import com.vaadin.data.converter.StringToIntegerConverter; | |||
import com.vaadin.data.validator.BeanValidator; | |||
import com.vaadin.event.EventRouter; | |||
import com.vaadin.server.AbstractErrorMessage.ContentMode; | |||
import com.vaadin.server.ErrorMessage; | |||
import com.vaadin.server.SerializableFunction; | |||
import com.vaadin.server.SerializablePredicate; | |||
import com.vaadin.server.Setter; | |||
import com.vaadin.server.UserError; | |||
import com.vaadin.shared.Registration; | |||
import com.vaadin.shared.ui.ErrorLevel; | |||
import com.vaadin.ui.AbstractComponent; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.Label; | |||
@@ -286,6 +288,7 @@ public class Binder<BEAN> implements Serializable { | |||
* failure, the property value is not updated. | |||
* | |||
* @see #withValidator(Validator) | |||
* @see #withValidator(SerializablePredicate, String, ErrorLevel) | |||
* @see #withValidator(SerializablePredicate, ErrorMessageProvider) | |||
* @see Validator#from(SerializablePredicate, String) | |||
* | |||
@@ -303,6 +306,40 @@ public class Binder<BEAN> implements Serializable { | |||
return withValidator(Validator.from(predicate, message)); | |||
} | |||
/** | |||
* A convenience method to add a validator to this binding using the | |||
* {@link Validator#from(SerializablePredicate, String, ErrorLevel)} | |||
* factory method. | |||
* <p> | |||
* Validators are applied, in registration order, when the field value | |||
* is written to the backing property. If any validator returns a | |||
* failure, the property value is not updated. | |||
* | |||
* @see #withValidator(Validator) | |||
* @see #withValidator(SerializablePredicate, String) | |||
* @see #withValidator(SerializablePredicate, ErrorMessageProvider, | |||
* ErrorLevel) | |||
* @see Validator#from(SerializablePredicate, String) | |||
* | |||
* @param predicate | |||
* the predicate performing validation, not null | |||
* @param message | |||
* the error message to report in case validation failure | |||
* @param errorLevel | |||
* the error level for failures from this validator, not null | |||
* @return this binding, for chaining | |||
* @throws IllegalStateException | |||
* if {@code bind} has already been called | |||
* | |||
* @since 8.2 | |||
*/ | |||
public default BindingBuilder<BEAN, TARGET> withValidator( | |||
SerializablePredicate<? super TARGET> predicate, String message, | |||
ErrorLevel errorLevel) { | |||
return withValidator( | |||
Validator.from(predicate, message, errorLevel)); | |||
} | |||
/** | |||
* A convenience method to add a validator to this binding using the | |||
* {@link Validator#from(SerializablePredicate, ErrorMessageProvider)} | |||
@@ -314,6 +351,8 @@ public class Binder<BEAN> implements Serializable { | |||
* | |||
* @see #withValidator(Validator) | |||
* @see #withValidator(SerializablePredicate, String) | |||
* @see #withValidator(SerializablePredicate, ErrorMessageProvider, | |||
* ErrorLevel) | |||
* @see Validator#from(SerializablePredicate, ErrorMessageProvider) | |||
* | |||
* @param predicate | |||
@@ -331,6 +370,41 @@ public class Binder<BEAN> implements Serializable { | |||
Validator.from(predicate, errorMessageProvider)); | |||
} | |||
/** | |||
* A convenience method to add a validator to this binding using the | |||
* {@link Validator#from(SerializablePredicate, ErrorMessageProvider, ErrorLevel)} | |||
* factory method. | |||
* <p> | |||
* Validators are applied, in registration order, when the field value | |||
* is written to the backing property. If any validator returns a | |||
* failure, the property value is not updated. | |||
* | |||
* @see #withValidator(Validator) | |||
* @see #withValidator(SerializablePredicate, String, ErrorLevel) | |||
* @see #withValidator(SerializablePredicate, ErrorMessageProvider) | |||
* @see Validator#from(SerializablePredicate, ErrorMessageProvider, | |||
* ErrorLevel) | |||
* | |||
* @param predicate | |||
* the predicate performing validation, not null | |||
* @param errorMessageProvider | |||
* the provider to generate error messages, not null | |||
* @param errorLevel | |||
* the error level for failures from this validator, not null | |||
* @return this binding, for chaining | |||
* @throws IllegalStateException | |||
* if {@code bind} has already been called | |||
* | |||
* @since 8.2 | |||
*/ | |||
public default BindingBuilder<BEAN, TARGET> withValidator( | |||
SerializablePredicate<? super TARGET> predicate, | |||
ErrorMessageProvider errorMessageProvider, | |||
ErrorLevel errorLevel) { | |||
return withValidator(Validator.from(predicate, errorMessageProvider, | |||
errorLevel)); | |||
} | |||
/** | |||
* Maps the binding to another data type using the given | |||
* {@link Converter}. | |||
@@ -905,10 +979,7 @@ public class Binder<BEAN> implements Serializable { | |||
private BindingValidationStatus<TARGET> toValidationStatus( | |||
Result<TARGET> result) { | |||
return new BindingValidationStatus<>(this, | |||
result.isError() | |||
? ValidationResult.error(result.getMessage().get()) | |||
: ValidationResult.ok()); | |||
return new BindingValidationStatus<>(result, this); | |||
} | |||
/** | |||
@@ -1031,11 +1102,7 @@ public class Binder<BEAN> implements Serializable { | |||
@Override | |||
public Result<T> convertToModel(T value, ValueContext context) { | |||
ValidationResult validationResult = validator.apply(value, context); | |||
if (validationResult.isError()) { | |||
return Result.error(validationResult.getErrorMessage()); | |||
} else { | |||
return Result.ok(value); | |||
} | |||
return new ValidationResultWrap<>(value, validationResult); | |||
} | |||
@Override | |||
@@ -2012,14 +2079,18 @@ public class Binder<BEAN> implements Serializable { | |||
* | |||
* @param field | |||
* the field with the invalid value | |||
* @param error | |||
* the error message to set | |||
* @param result | |||
* the validation error result | |||
* | |||
* @since 8.2 | |||
*/ | |||
protected void handleError(HasValue<?> field, String error) { | |||
if (field instanceof AbstractComponent) { | |||
((AbstractComponent) field).setComponentError(new UserError(error)); | |||
} | |||
protected void handleError(HasValue<?> field, ValidationResult result) { | |||
result.getErrorLevel().ifPresent(level -> { | |||
if (field instanceof AbstractComponent) { | |||
((AbstractComponent) field).setComponentError(new UserError( | |||
result.getErrorMessage(), ContentMode.TEXT, level)); | |||
} | |||
}); | |||
} | |||
/** | |||
@@ -2033,7 +2104,23 @@ public class Binder<BEAN> implements Serializable { | |||
HasValue<?> source = status.getField(); | |||
clearError(source); | |||
if (status.isError()) { | |||
handleError(source, status.getMessage().get()); | |||
Optional<ValidationResult> firstError = status | |||
.getValidationResults().stream() | |||
.filter(ValidationResult::isError).findFirst(); | |||
if (firstError.isPresent()) { | |||
// Failed with a Validation error | |||
handleError(source, firstError.get()); | |||
} else { | |||
// Conversion error | |||
status.getResult() | |||
.ifPresent(result -> handleError(source, result)); | |||
} | |||
} else { | |||
// Show first non-error ValidationResult message. | |||
status.getValidationResults().stream() | |||
.filter(result -> result.getErrorLevel().isPresent()) | |||
.findFirst() | |||
.ifPresent(result -> handleError(source, result)); | |||
} | |||
} | |||
@@ -16,6 +16,8 @@ | |||
package com.vaadin.data; | |||
import java.io.Serializable; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
@@ -24,8 +26,8 @@ import com.vaadin.data.Binder.BindingBuilder; | |||
/** | |||
* Represents the status of field validation. Status can be {@code Status.OK}, | |||
* {@code Status.ERROR} or {@code Status.RESET}. Status OK and ERROR are always | |||
* associated with a ValidationResult {@link #getResult}. | |||
* {@code Status.ERROR} or {@code Status.UNRESOLVED}. Status OK and ERROR are | |||
* always associated with a ValidationResult {@link #getResult}. | |||
* <p> | |||
* Use | |||
* {@link BindingBuilder#withValidationStatusHandler(BindingValidationStatusHandler)} | |||
@@ -70,8 +72,9 @@ public class BindingValidationStatus<TARGET> implements Serializable { | |||
} | |||
private final Status status; | |||
private final ValidationResult result; | |||
private final List<ValidationResult> results; | |||
private final Binding<?, TARGET> binding; | |||
private Result<TARGET> result; | |||
/** | |||
* Convenience method for creating a {@link Status#UNRESOLVED} validation | |||
@@ -86,7 +89,7 @@ public class BindingValidationStatus<TARGET> implements Serializable { | |||
*/ | |||
public static <TARGET> BindingValidationStatus<TARGET> createUnresolvedStatus( | |||
Binding<?, TARGET> source) { | |||
return new BindingValidationStatus<>(source, Status.UNRESOLVED, null); | |||
return new BindingValidationStatus<TARGET>(null, source); | |||
} | |||
/** | |||
@@ -98,6 +101,7 @@ public class BindingValidationStatus<TARGET> implements Serializable { | |||
* @param result | |||
* the result of the validation | |||
*/ | |||
@Deprecated | |||
public BindingValidationStatus(Binding<?, TARGET> source, | |||
ValidationResult result) { | |||
this(source, result.isError() ? Status.ERROR : Status.OK, result); | |||
@@ -116,20 +120,43 @@ public class BindingValidationStatus<TARGET> implements Serializable { | |||
* @param result | |||
* the related result, may be {@code null} | |||
*/ | |||
@Deprecated | |||
public BindingValidationStatus(Binding<?, TARGET> source, Status status, | |||
ValidationResult result) { | |||
this(result.isError() ? Result.error(result.getErrorMessage()) | |||
: Result.ok(null), source); | |||
} | |||
/** | |||
* Creates a new status change event. | |||
* <p> | |||
* If {@code result} is {@code null}, the {@code status} is | |||
* {@link Status#UNRESOLVED}. | |||
* | |||
* @param result | |||
* the related result object, may be {@code null} | |||
* @param source | |||
* field whose status has changed, not {@code null} | |||
* | |||
* @since 8.2 | |||
*/ | |||
public BindingValidationStatus(Result<TARGET> result, | |||
Binding<?, TARGET> source) { | |||
Objects.requireNonNull(source, "Event source may not be null"); | |||
Objects.requireNonNull(status, "Status may not be null"); | |||
if (Objects.equals(status, Status.OK) && result.isError() | |||
|| Objects.equals(status, Status.ERROR) && !result.isError() | |||
|| Objects.equals(status, Status.UNRESOLVED) | |||
&& result != null) { | |||
throw new IllegalStateException( | |||
"Invalid validation status " + status + " for given result " | |||
+ (result == null ? "null" : result.toString())); | |||
} | |||
binding = source; | |||
this.status = status; | |||
if (result != null) { | |||
this.status = result.isError() ? Status.ERROR : Status.OK; | |||
if (result instanceof ValidationResultWrap) { | |||
results = ((ValidationResultWrap<TARGET>) result) | |||
.getValidationResults(); | |||
} else { | |||
results = Collections.emptyList(); | |||
} | |||
} else { | |||
this.status = Status.UNRESOLVED; | |||
results = Collections.emptyList(); | |||
} | |||
this.result = result; | |||
} | |||
@@ -159,8 +186,10 @@ public class BindingValidationStatus<TARGET> implements Serializable { | |||
* status is not an error | |||
*/ | |||
public Optional<String> getMessage() { | |||
return Optional.ofNullable(result).filter(ValidationResult::isError) | |||
.map(ValidationResult::getErrorMessage); | |||
if (getStatus() == Status.OK || result == null) { | |||
return Optional.empty(); | |||
} | |||
return result.getMessage(); | |||
} | |||
/** | |||
@@ -171,7 +200,24 @@ public class BindingValidationStatus<TARGET> implements Serializable { | |||
* @return the validation result | |||
*/ | |||
public Optional<ValidationResult> getResult() { | |||
return Optional.ofNullable(result); | |||
if (result == null) { | |||
return Optional.empty(); | |||
} | |||
return Optional.of(result.isError() | |||
? ValidationResult.error(result.getMessage().orElse("")) | |||
: ValidationResult.ok()); | |||
} | |||
/** | |||
* Gets all the validation results related to this binding validation | |||
* status. | |||
* | |||
* @return list of validation results | |||
* | |||
* @since 8.2 | |||
*/ | |||
public List<ValidationResult> getValidationResults() { | |||
return Collections.unmodifiableList(results); | |||
} | |||
/** |
@@ -17,6 +17,9 @@ package com.vaadin.data; | |||
import java.io.Serializable; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import com.vaadin.shared.ui.ErrorLevel; | |||
/** | |||
* Represents the result of a validation. A result may be either successful or | |||
@@ -35,26 +38,30 @@ public interface ValidationResult extends Serializable { | |||
class SimpleValidationResult implements ValidationResult { | |||
private final String error; | |||
private final ErrorLevel errorLevel; | |||
SimpleValidationResult(String error) { | |||
SimpleValidationResult(String error, ErrorLevel errorLevel) { | |||
if (error != null && errorLevel == null) { | |||
throw new IllegalStateException("ValidationResult has an " | |||
+ "error message, but no ErrorLevel is provided."); | |||
} | |||
this.error = error; | |||
this.errorLevel = errorLevel; | |||
} | |||
@Override | |||
public String getErrorMessage() { | |||
if (error == null) { | |||
if (!getErrorLevel().isPresent()) { | |||
throw new IllegalStateException("The result is not an error. " | |||
+ "It cannot contain error message"); | |||
} else { | |||
return error; | |||
return error != null ? error : ""; | |||
} | |||
} | |||
@Override | |||
public boolean isError() { | |||
return error != null; | |||
public Optional<ErrorLevel> getErrorLevel() { | |||
return Optional.ofNullable(errorLevel); | |||
} | |||
} | |||
/** | |||
@@ -68,13 +75,37 @@ public interface ValidationResult extends Serializable { | |||
*/ | |||
String getErrorMessage(); | |||
/** | |||
* Returns optional error level for this validation result. Error level is | |||
* not present for successful validation results. | |||
* <p> | |||
* <strong>Note:</strong> By default {@link ErrorLevel#INFO} and | |||
* {@link ErrorLevel#WARNING} are not considered to be blocking the | |||
* validation and conversion chain. | |||
* | |||
* @see #isError() | |||
* | |||
* @return optional error level; error level is present for validation | |||
* results that have not passed validation | |||
* | |||
* @since 8.2 | |||
*/ | |||
Optional<ErrorLevel> getErrorLevel(); | |||
/** | |||
* Checks if the result denotes an error. | |||
* <p> | |||
* <strong>Note:</strong> By default {@link ErrorLevel#INFO} and | |||
* {@link ErrorLevel#WARNING} are not considered to be errors. | |||
* | |||
* @return <code>true</code> if the result denotes an error, | |||
* <code>false</code> otherwise | |||
*/ | |||
boolean isError(); | |||
default boolean isError() { | |||
ErrorLevel errorLevel = getErrorLevel().orElse(null); | |||
return errorLevel != null && errorLevel != ErrorLevel.INFO | |||
&& errorLevel != ErrorLevel.WARNING; | |||
} | |||
/** | |||
* Returns a successful result. | |||
@@ -82,7 +113,7 @@ public interface ValidationResult extends Serializable { | |||
* @return the successful result | |||
*/ | |||
public static ValidationResult ok() { | |||
return new SimpleValidationResult(null); | |||
return new SimpleValidationResult(null, null); | |||
} | |||
/** | |||
@@ -98,6 +129,32 @@ public interface ValidationResult extends Serializable { | |||
*/ | |||
public static ValidationResult error(String errorMessage) { | |||
Objects.requireNonNull(errorMessage); | |||
return new SimpleValidationResult(errorMessage); | |||
return create(errorMessage, ErrorLevel.ERROR); | |||
} | |||
/** | |||
* Creates the validation result with the given {@code errorMessage} and | |||
* {@code errorLevel}. Results with {@link ErrorLevel} of {@code INFO} or | |||
* {@code WARNING} are not errors by default. | |||
* | |||
* @see #ok() | |||
* @see #error(String) | |||
* | |||
* @param errorMessage | |||
* error message, not {@code null} | |||
* @param errorLevel | |||
* error level, not {@code null} | |||
* @return validation result with the given {@code errorMessage} and | |||
* {@code errorLevel} | |||
* @throws NullPointerException | |||
* if {@code errorMessage} or {@code errorLevel} is {@code null} | |||
* | |||
* @since 8.2 | |||
*/ | |||
public static ValidationResult create(String errorMessage, | |||
ErrorLevel errorLevel) { | |||
Objects.requireNonNull(errorMessage); | |||
Objects.requireNonNull(errorLevel); | |||
return new SimpleValidationResult(errorMessage, errorLevel); | |||
} | |||
} |
@@ -0,0 +1,102 @@ | |||
/* | |||
* 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.ArrayList; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import com.vaadin.server.SerializableConsumer; | |||
import com.vaadin.server.SerializableFunction; | |||
/** | |||
* Internal implementation of a {@code Result} that collects all possible | |||
* ValidationResults into one list. This class intercepts the normal chaining of | |||
* Converters and Validators, catching and collecting results. | |||
* | |||
* @param <R> | |||
* the result data type | |||
* | |||
* @since 8.2 | |||
*/ | |||
class ValidationResultWrap<R> implements Result<R> { | |||
private final List<ValidationResult> resultList; | |||
private final Result<R> wrappedResult; | |||
ValidationResultWrap(Result<R> result, List<ValidationResult> resultList) { | |||
this.resultList = resultList; | |||
this.wrappedResult = result; | |||
} | |||
ValidationResultWrap(R value, ValidationResult result) { | |||
if (result.isError()) { | |||
wrappedResult = new SimpleResult<>(null, result.getErrorMessage()); | |||
} else { | |||
wrappedResult = new SimpleResult<>(value, null); | |||
} | |||
this.resultList = new ArrayList<>(); | |||
this.resultList.add(result); | |||
} | |||
List<ValidationResult> getValidationResults() { | |||
return Collections.unmodifiableList(resultList); | |||
} | |||
Result<R> getWrappedResult() { | |||
return wrappedResult; | |||
} | |||
@Override | |||
public <S> Result<S> flatMap(SerializableFunction<R, Result<S>> mapper) { | |||
Result<S> result = wrappedResult.flatMap(mapper); | |||
if (!(result instanceof ValidationResultWrap)) { | |||
return new ValidationResultWrap<S>(result, resultList); | |||
} | |||
List<ValidationResult> currentResults = new ArrayList<>(resultList); | |||
ValidationResultWrap<S> resultWrap = (ValidationResultWrap<S>) result; | |||
currentResults.addAll(resultWrap.getValidationResults()); | |||
return new ValidationResultWrap<>(resultWrap.getWrappedResult(), | |||
currentResults); | |||
} | |||
@Override | |||
public void handle(SerializableConsumer<R> ifOk, | |||
SerializableConsumer<String> ifError) { | |||
wrappedResult.handle(ifOk, ifError); | |||
} | |||
@Override | |||
public boolean isError() { | |||
return wrappedResult.isError(); | |||
} | |||
@Override | |||
public Optional<String> getMessage() { | |||
return wrappedResult.getMessage(); | |||
} | |||
@Override | |||
public <X extends Throwable> R getOrThrow( | |||
SerializableFunction<String, ? extends X> exceptionProvider) | |||
throws X { | |||
return wrappedResult.getOrThrow(exceptionProvider); | |||
} | |||
} |
@@ -21,6 +21,7 @@ import java.util.Objects; | |||
import java.util.function.BiFunction; | |||
import com.vaadin.server.SerializablePredicate; | |||
import com.vaadin.shared.ui.ErrorLevel; | |||
/** | |||
* A functional interface for validating user input or other potentially invalid | |||
@@ -80,8 +81,8 @@ public interface Validator<T> | |||
* Builds a validator out of a conditional function and an error message. If | |||
* the function returns true, the validator returns {@code Result.ok()}; if | |||
* it returns false or throws an exception, | |||
* {@link ValidationResult#error(String)} is returned with the given | |||
* message. | |||
* {@link ValidationResult#error(String)} is returned with the given message | |||
* and error level {@link ErrorLevel#ERROR}. | |||
* <p> | |||
* For instance, the following validator checks if a number is between 0 and | |||
* 10, inclusive: | |||
@@ -101,11 +102,43 @@ public interface Validator<T> | |||
*/ | |||
public static <T> Validator<T> from(SerializablePredicate<T> guard, | |||
String errorMessage) { | |||
Objects.requireNonNull(guard, "guard cannot be null"); | |||
Objects.requireNonNull(errorMessage, "errorMessage cannot be null"); | |||
return from(guard, ctx -> errorMessage); | |||
} | |||
/** | |||
* Builds a validator out of a conditional function and an error message. If | |||
* the function returns true, the validator returns {@code Result.ok()}; if | |||
* it returns false or throws an exception, | |||
* {@link ValidationResult#error(String)} is returned with the given message | |||
* and error level. | |||
* <p> | |||
* For instance, the following validator checks if a number is between 0 and | |||
* 10, inclusive: | |||
* | |||
* <pre> | |||
* Validator<Integer> v = Validator.from(num -> num >= 0 && num <= 10, | |||
* "number must be between 0 and 10", ErrorLevel.ERROR); | |||
* </pre> | |||
* | |||
* @param <T> | |||
* the value type | |||
* @param guard | |||
* the function used to validate, not null | |||
* @param errorMessage | |||
* the message returned if validation fails, not null | |||
* @param errorLevel | |||
* the error level for failures from this validator, not null | |||
* @return the new validator using the function | |||
* | |||
* @since 8.2 | |||
*/ | |||
public static <T> Validator<T> from(SerializablePredicate<T> guard, | |||
String errorMessage, ErrorLevel errorLevel) { | |||
Objects.requireNonNull(errorMessage, "errorMessage cannot be null"); | |||
return from(guard, ctx -> errorMessage, errorLevel); | |||
} | |||
/** | |||
* Builds a validator out of a conditional function and an error message | |||
* provider. If the function returns true, the validator returns | |||
@@ -122,20 +155,44 @@ public interface Validator<T> | |||
*/ | |||
public static <T> Validator<T> from(SerializablePredicate<T> guard, | |||
ErrorMessageProvider errorMessageProvider) { | |||
return from(guard, errorMessageProvider, ErrorLevel.ERROR); | |||
} | |||
/** | |||
* Builds a validator out of a conditional function and an error message | |||
* provider. If the function returns true, the validator returns | |||
* {@code Result.ok()}; if it returns false or throws an exception, | |||
* {@code Result.error()} is returned with the message from the provider. | |||
* | |||
* @param <T> | |||
* the value type | |||
* @param guard | |||
* the function used to validate, not null | |||
* @param errorMessageProvider | |||
* the provider to generate error messages, not null | |||
* @param errorLevel | |||
* the error level for failures from this validator, not null | |||
* @return the new validator using the function | |||
* | |||
* @since 8.2 | |||
*/ | |||
public static <T> Validator<T> from(SerializablePredicate<T> guard, | |||
ErrorMessageProvider errorMessageProvider, ErrorLevel errorLevel) { | |||
Objects.requireNonNull(guard, "guard cannot be null"); | |||
Objects.requireNonNull(errorMessageProvider, | |||
"errorMessageProvider cannot be null"); | |||
Objects.requireNonNull(errorLevel, "errorLevel cannot be null"); | |||
return (value, context) -> { | |||
try { | |||
if (guard.test(value)) { | |||
return ValidationResult.ok(); | |||
} else { | |||
return ValidationResult | |||
.error(errorMessageProvider.apply(context)); | |||
return ValidationResult.create( | |||
errorMessageProvider.apply(context), errorLevel); | |||
} | |||
} catch (Exception e) { | |||
return ValidationResult | |||
.error(errorMessageProvider.apply(context)); | |||
return ValidationResult.create( | |||
errorMessageProvider.apply(context), errorLevel); | |||
} | |||
}; | |||
} |
@@ -14,6 +14,7 @@ import java.util.Objects; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
import java.util.stream.Stream; | |||
import org.junit.Assert; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
@@ -24,6 +25,7 @@ import com.vaadin.data.validator.IntegerRangeValidator; | |||
import com.vaadin.data.validator.NotEmptyValidator; | |||
import com.vaadin.data.validator.StringLengthValidator; | |||
import com.vaadin.server.ErrorMessage; | |||
import com.vaadin.shared.ui.ErrorLevel; | |||
import com.vaadin.tests.data.bean.Person; | |||
import com.vaadin.tests.data.bean.Sex; | |||
import com.vaadin.ui.TextField; | |||
@@ -942,6 +944,24 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> { | |||
ageBeforeUnbind, String.valueOf(item.getAge())); | |||
} | |||
@Test | |||
public void info_validator_not_considered_error() { | |||
String infoMessage = "Young"; | |||
binder.forField(ageField) | |||
.withConverter(new StringToIntegerConverter("Can't convert")) | |||
.withValidator(i -> i > 5, infoMessage, ErrorLevel.INFO) | |||
.bind(Person::getAge, Person::setAge); | |||
binder.setBean(item); | |||
ageField.setValue("3"); | |||
Assert.assertEquals(infoMessage, | |||
ageField.getComponentError().getFormattedHtmlMessage()); | |||
Assert.assertEquals(ErrorLevel.INFO, | |||
ageField.getComponentError().getErrorLevel()); | |||
Assert.assertEquals(3, item.getAge()); | |||
} | |||
@Test | |||
public void two_asRequired_fields_without_initial_values() { | |||
binder.forField(nameField).asRequired("Empty name").bind(p -> "", |
@@ -1 +1 @@ | |||
Subproject commit 93a7b5d5a00209767b54a69395b1a7cc30ee970d | |||
Subproject commit a20d0108ad315b1b5a4626619cd157e03cfa7b7f |
@@ -0,0 +1,29 @@ | |||
package com.vaadin.tests.binder; | |||
import com.vaadin.annotations.Widgetset; | |||
import com.vaadin.data.Binder; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.shared.ui.ErrorLevel; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.TextField; | |||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||
public class BinderValidatorErrorLevel extends AbstractTestUI { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
Binder<Object> binder = new Binder<>(); | |||
for (ErrorLevel l : ErrorLevel.values()) { | |||
TextField field = new TextField(l.name()); | |||
binder.forField(field) | |||
.withValidator(s -> s.length() > 3, | |||
"ErrorLevel: " + l.name(), l) | |||
.bind(t -> "", (t, s) -> { | |||
}); | |||
addComponent(field); | |||
} | |||
binder.validate(); | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
package com.vaadin.tests.binder; | |||
import java.io.IOException; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.shared.ui.ErrorLevel; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.testbench.elements.TextFieldElement; | |||
import com.vaadin.tests.tb3.SingleBrowserTest; | |||
public class BinderValidatorErrorLevelTest extends SingleBrowserTest { | |||
@Test | |||
public void testErrorLevelStyleNames() throws IOException { | |||
openTestURL(); | |||
for (ErrorLevel l : ErrorLevel.values()) { | |||
TextFieldElement textField = $(TextFieldElement.class) | |||
.caption(l.name()).first(); | |||
// Screenshot the whole slot | |||
compareScreen(textField.findElement(By.xpath("..")), | |||
l.name().toLowerCase()); | |||
Assert.assertTrue("Error style for " + l.name() + " not present", | |||
textField.getAttribute("class").contains( | |||
"v-textfield-error-" + l.name().toLowerCase())); | |||
textField.setValue("long enough text"); | |||
Assert.assertFalse("Error style for " + l.name() + " still present", | |||
textField.getAttribute("class").contains( | |||
"v-textfield-error-" + l.name().toLowerCase())); | |||
textField.setValue("foo"); | |||
Assert.assertTrue( | |||
"Error style for " + l.name() + " should be present again.", | |||
textField.getAttribute("class").contains( | |||
"v-textfield-error-" + l.name().toLowerCase())); | |||
} | |||
} | |||
} |