Fixes #11276 Refactor the code and correct the logic for validation Rename an internal variable, as it contains more than one type of messagestags/8.6.1
@@ -38,10 +38,9 @@ import java.util.logging.Logger; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.Stream; | |||
import elemental.json.Json; | |||
import com.googlecode.gentyref.GenericTypeReflector; | |||
import org.jsoup.nodes.Element; | |||
import com.googlecode.gentyref.GenericTypeReflector; | |||
import com.vaadin.data.Result; | |||
import com.vaadin.data.ValidationResult; | |||
import com.vaadin.data.Validator; | |||
@@ -64,6 +63,8 @@ import com.vaadin.ui.declarative.DesignAttributeHandler; | |||
import com.vaadin.ui.declarative.DesignContext; | |||
import com.vaadin.util.TimeZoneUtil; | |||
import elemental.json.Json; | |||
/** | |||
* A date editor component with {@link LocalDate} as an input value. | |||
* | |||
@@ -107,14 +108,14 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & | |||
newDate = reconstructDateFromFields(resolutions, oldDate); | |||
} | |||
boolean parseErrorWasSet = currentParseErrorMessage != null; | |||
boolean parseErrorWasSet = currentErrorMessage != null; | |||
hasChanges |= !Objects.equals(dateString, newDateString) | |||
|| !Objects.equals(oldDate, newDate) | |||
|| parseErrorWasSet; | |||
if (hasChanges) { | |||
dateString = newDateString; | |||
currentParseErrorMessage = null; | |||
currentErrorMessage = null; | |||
if (newDateString == null || newDateString.isEmpty()) { | |||
boolean valueChanged = setValue(newDate, true); | |||
if (!valueChanged && parseErrorWasSet) { | |||
@@ -138,8 +139,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & | |||
}); | |||
if (parsedDate.isError()) { | |||
dateString = null; | |||
currentParseErrorMessage = parsedDate | |||
.getMessage().orElse("Parsing error"); | |||
currentErrorMessage = parsedDate.getMessage() | |||
.orElse("Parsing error"); | |||
if (!isDifferentValue(null)) { | |||
doSetValue(null); | |||
@@ -187,7 +188,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & | |||
private String dateString = ""; | |||
private String currentParseErrorMessage; | |||
private String currentErrorMessage; | |||
private String defaultParseErrorMessage = "Date format not recognized"; | |||
@@ -607,7 +608,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & | |||
*/ | |||
@Override | |||
public void setValue(T value) { | |||
currentParseErrorMessage = null; | |||
currentErrorMessage = null; | |||
/* | |||
* First handle special case when the client side component have a date | |||
* string but value is null (e.g. unparsable date string typed in by the | |||
@@ -783,16 +784,16 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & | |||
new ValueContext(this, this)); | |||
if (result.isError()) { | |||
currentParseErrorMessage = getDateOutOfRangeMessage(); | |||
currentErrorMessage = getDateOutOfRangeMessage(); | |||
} | |||
getState().parsable = currentParseErrorMessage == null; | |||
getState().parsable = currentErrorMessage == null; | |||
ErrorMessage errorMessage; | |||
if (currentParseErrorMessage == null) { | |||
if (currentErrorMessage == null) { | |||
errorMessage = null; | |||
} else { | |||
errorMessage = new UserError(currentParseErrorMessage); | |||
errorMessage = new UserError(currentErrorMessage); | |||
} | |||
setComponentError(errorMessage); | |||
@@ -876,9 +877,27 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & | |||
return new Validator<T>() { | |||
@Override | |||
public ValidationResult apply(T value, ValueContext context) { | |||
if (currentParseErrorMessage != null) { | |||
return ValidationResult.error(currentParseErrorMessage); | |||
// currentErrorMessage contains two type of messages, one is | |||
// DateOutOfRangeMessage and the other one is the ParseError | |||
if (currentErrorMessage != null) { | |||
if (currentErrorMessage | |||
.equals(getDateOutOfRangeMessage())) { | |||
// if the currentErrorMessage is DateOutOfRangeMessage, | |||
// then need to double check whether the error message | |||
// has been updated, that is because of #11276. | |||
ValidationResult validationResult = getRangeValidator() | |||
.apply(value, context); | |||
if (validationResult.isError()) { | |||
return ValidationResult.error(currentErrorMessage); | |||
} | |||
} else { | |||
// if the current Error is parsing error, pass it to the | |||
// ValidationResult | |||
return ValidationResult.error(currentErrorMessage); | |||
} | |||
} | |||
// Pass to range validator. | |||
return getRangeValidator().apply(value, context); | |||
} |
@@ -0,0 +1,103 @@ | |||
package com.vaadin.tests.components.datefield; | |||
import java.time.LocalDate; | |||
import java.util.Objects; | |||
import com.gargoylesoftware.htmlunit.javascript.host.html.FormField; | |||
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.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.DateField; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.TextField; | |||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||
public class DateFieldBinderCrossValidation extends AbstractTestUI { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
HorizontalLayout horizontalLayout = new HorizontalLayout(); | |||
Label label = new Label(); | |||
label.setId("status"); | |||
Binder<FromToModel> fromToModelBinder = new Binder<>(); | |||
DateField fromField = new DateField(); | |||
fromField.setId("from-field"); | |||
fromField.setDateFormat("yyyy/MM/dd"); | |||
final Binder.Binding<FromToModel, LocalDate> fromBinding = fromToModelBinder | |||
.forField(fromField).asRequired() | |||
.bind(FromToModel::getFromDate, FromToModel::setFromDate); | |||
DateField toField = new DateField(); | |||
toField.setId("to-field"); | |||
toField.setDateFormat("yyyy/MM/dd"); | |||
final Binder.Binding<FromToModel, LocalDate> toBinding = fromToModelBinder | |||
.forField(toField).asRequired() | |||
.bind(FromToModel::getToDate, FromToModel::setToDate); | |||
fromField.addValueChangeListener(e -> { | |||
toField.setRangeStart(e.getValue()); | |||
if (toField.getValue() != null) { | |||
toBinding.validate(); | |||
} | |||
label.setValue("from field is " + fromField.getErrorMessage() | |||
+ ". To field is " + toField.getErrorMessage()); | |||
}); | |||
toField.addValueChangeListener(e -> { | |||
fromField.setRangeEnd(e.getValue()); | |||
if (fromField.getValue() != null) { | |||
fromBinding.validate(); | |||
} | |||
label.setValue("from field is " + fromField.getErrorMessage() | |||
+ ". To field is " + toField.getErrorMessage()); | |||
}); | |||
horizontalLayout.addComponents(fromField, toField, label); | |||
addComponents(horizontalLayout); | |||
} | |||
private static class FromToModel { | |||
private LocalDate fromDate; | |||
private LocalDate toDate; | |||
public LocalDate getFromDate() { | |||
return fromDate; | |||
} | |||
public void setFromDate(LocalDate fromDate) { | |||
this.fromDate = fromDate; | |||
} | |||
public LocalDate getToDate() { | |||
return toDate; | |||
} | |||
public void setToDate(LocalDate toDate) { | |||
this.toDate = toDate; | |||
} | |||
@Override | |||
public boolean equals(Object o) { | |||
if (this == o) | |||
return true; | |||
if (!(o instanceof FromToModel)) | |||
return false; | |||
FromToModel that = (FromToModel) o; | |||
return Objects.equals(getFromDate(), that.getFromDate()) | |||
&& Objects.equals(getToDate(), that.getToDate()); | |||
} | |||
@Override | |||
public int hashCode() { | |||
return Objects.hash(getFromDate(), getToDate()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
package com.vaadin.tests.components.datefield; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.Keys; | |||
import org.openqa.selenium.WebElement; | |||
import com.vaadin.testbench.elements.DateFieldElement; | |||
import com.vaadin.testbench.elements.LabelElement; | |||
import com.vaadin.tests.tb3.SingleBrowserTest; | |||
import static org.junit.Assert.assertEquals; | |||
public class DateFieldBinderCrossValidationTest extends SingleBrowserTest { | |||
private final static String EXPECTED_ERROR = "from field is Date is out of allowed range. To field is Date is out of allowed range"; | |||
private final static String EXPECTED_NULL_ERROR = "from field is null. To field is null"; | |||
@Test | |||
public void makeBothFieldInvalidThenValid() { | |||
openTestURL(); | |||
DateFieldElement fromField = $(DateFieldElement.class).id("from-field"); | |||
WebElement fromFieldText = fromField.findElement(By.tagName("input")); | |||
DateFieldElement toField = $(DateFieldElement.class).id("to-field"); | |||
WebElement toFieldText = toField.findElement(By.tagName("input")); | |||
LabelElement label = $(LabelElement.class).id("status"); | |||
fromFieldText.sendKeys("2019/01/01", Keys.ENTER); | |||
toFieldText.sendKeys("2018/02/02", Keys.ENTER); | |||
assertEquals("Error message should contain the information", | |||
EXPECTED_ERROR, label.getText()); | |||
fromFieldText.clear(); | |||
fromFieldText.sendKeys("2018/01/01", Keys.ENTER); | |||
assertEquals("Error message should be null", EXPECTED_NULL_ERROR, | |||
label.getText()); | |||
} | |||
} |