if (hasChanges) { | if (hasChanges) { | ||||
dateString = newDateString; | dateString = newDateString; | ||||
if (newDateString == null || newDateString.isEmpty()) { | if (newDateString == null || newDateString.isEmpty()) { | ||||
setValue(newDate, true); | |||||
uiHasValidDateString = true; | uiHasValidDateString = true; | ||||
currentParseErrorMessage = null; | currentParseErrorMessage = null; | ||||
setValue(newDate, true); | |||||
setComponentError(null); | setComponentError(null); | ||||
} else { | } else { | ||||
if (variables.get("lastInvalidDateString") != null) { | if (variables.get("lastInvalidDateString") != null) { | ||||
Result<T> parsedDate = handleUnparsableDateString(dateString); | Result<T> parsedDate = handleUnparsableDateString(dateString); | ||||
parsedDate.ifOk(this::setValue); | |||||
parsedDate.ifOk(v-> { | |||||
uiHasValidDateString = true; | |||||
currentParseErrorMessage = null; | |||||
setValue(v,true); | |||||
}); | |||||
if (parsedDate.isError()) { | if (parsedDate.isError()) { | ||||
doSetValue(null); | |||||
dateString = null; | dateString = null; | ||||
uiHasValidDateString = false; | uiHasValidDateString = false; | ||||
currentParseErrorMessage = parsedDate.getMessage().orElse("Parsing error"); | currentParseErrorMessage = parsedDate.getMessage().orElse("Parsing error"); | ||||
setComponentError(new UserError(getParseErrorMessage())); | setComponentError(new UserError(getParseErrorMessage())); | ||||
setValue(null,true); | |||||
} | } | ||||
} else { | } else { | ||||
uiHasValidDateString = true; | |||||
currentParseErrorMessage = null; | |||||
setValue(newDate, true); | setValue(newDate, true); | ||||
} | } | ||||
} | } | ||||
return value; | return value; | ||||
} | } | ||||
/** | |||||
* Sets the value of this object. If the new value is not equal to | |||||
* {@code getValue()}, fires a {@link ValueChangeEvent} . | |||||
* | |||||
* @param value | |||||
* the new value, may be {@code null} | |||||
*/ | |||||
@Override | |||||
public void setValue(T value) { | |||||
/* | |||||
* 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 | |||||
* user). No value changes should happen, but we need to do some | |||||
* internal housekeeping. | |||||
*/ | |||||
if (value == null && !uiHasValidDateString) { | |||||
/* | |||||
* Side-effects of doSetValue clears possible previous strings and | |||||
* flags about invalid input. | |||||
*/ | |||||
doSetValue(null); | |||||
markAsDirty(); | |||||
return; | |||||
} | |||||
super.setValue(value); | |||||
} | |||||
/** | /** | ||||
* Checks whether ISO 8601 week numbers are shown in the date selector. | * Checks whether ISO 8601 week numbers are shown in the date selector. | ||||
* | * | ||||
/** | /** | ||||
* Formats date according to the components locale. | * Formats date according to the components locale. | ||||
* To be reimplemented in subclasses. | |||||
* | * | ||||
* @param value the date or {@code null} | * @param value the date or {@code null} | ||||
* @return textual representation of the date or empty string for {@code null} | * @return textual representation of the date or empty string for {@code null} | ||||
* @since 8.1 | |||||
*/ | */ | ||||
protected abstract String formatDate(T value); | |||||
protected String formatDate(T value) { | |||||
return Objects.toString(value, ""); | |||||
} | |||||
@Override | @Override | ||||
public void writeDesign(Element design, DesignContext designContext) { | public void writeDesign(Element design, DesignContext designContext) { | ||||
@Override | @Override | ||||
protected void doSetValue(T value) { | protected void doSetValue(T value) { | ||||
uiHasValidDateString = true; | |||||
currentParseErrorMessage = null; | |||||
this.value = value; | this.value = value; | ||||
// Also set the internal dateString | // Also set the internal dateString |
return null; | return null; | ||||
} | } | ||||
@Override | |||||
protected String formatDate(T value) { | |||||
return value.toString(); | |||||
} | |||||
} | } | ||||
@Test | @Test |
layout.addComponent(errorLabel); | layout.addComponent(errorLabel); | ||||
Binder<Void> binder = new Binder<>(); | Binder<Void> binder = new Binder<>(); | ||||
binder.forField(dateField).withStatusLabel(errorLabel).bind(o -> dateField.getEmptyValue(), null); | |||||
binder.forField(dateField).withStatusLabel(errorLabel).bind(o -> dateField.getValue(), null); | |||||
Button button = new Button("Validate!"); | Button button = new Button("Validate!"); | ||||
button.addClickListener(event1 -> { | button.addClickListener(event1 -> { |
assertLogText("2. buttonClick: value: 01/01/01, is valid: true"); | assertLogText("2. buttonClick: value: 01/01/01, is valid: true"); | ||||
dateTextbox.sendKeys("lala", Keys.TAB); | dateTextbox.sendKeys("lala", Keys.TAB); | ||||
assertLogText("3. valueChange: value: null, is valid: false"); | |||||
button.click(); | button.click(); | ||||
assertLogText("3. buttonClick: value: null, is valid: false"); | |||||
assertLogText("4. buttonClick: value: null, is valid: false"); | |||||
dateTextbox.clear(); | dateTextbox.clear(); | ||||
dateTextbox.sendKeys("02/02/02", Keys.TAB); | dateTextbox.sendKeys("02/02/02", Keys.TAB); | ||||
assertLogText("4. valueChange: value: 02/02/02, is valid: true"); | |||||
assertLogText("5. valueChange: value: 02/02/02, is valid: true"); | |||||
button.click(); | button.click(); | ||||
assertLogText("5. buttonClick: value: 02/02/02, is valid: true"); | |||||
assertLogText("6. buttonClick: value: 02/02/02, is valid: true"); | |||||
} | } | ||||
private void assertLogText(String expected) throws Exception { | private void assertLogText(String expected) throws Exception { |
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
public class DateTextHandlingTest extends SingleBrowserTest { | public class DateTextHandlingTest extends SingleBrowserTest { | ||||
public static final String Y2K_GB_LOCALE = "01-Jan-2000"; | |||||
@Test | @Test | ||||
public void testSpecialValue() throws InterruptedException { | public void testSpecialValue() throws InterruptedException { | ||||
openTestURL(); | openTestURL(); | ||||
DateFieldElement dateFieldElement = $(DateFieldElement.class).first(); | DateFieldElement dateFieldElement = $(DateFieldElement.class).first(); | ||||
ButtonElement validate = $(ButtonElement.class).first(); | ButtonElement validate = $(ButtonElement.class).first(); | ||||
LabelElement validateResult = $(LabelElement.class).first(); | |||||
WebElement dateTextbox = dateFieldElement | WebElement dateTextbox = dateFieldElement | ||||
.findElement(com.vaadin.testbench.By.className("v-textfield")); | .findElement(com.vaadin.testbench.By.className("v-textfield")); | ||||
dateTextbox.sendKeys("Y2K",Keys.TAB); | dateTextbox.sendKeys("Y2K",Keys.TAB); | ||||
validate.click(); | validate.click(); | ||||
assertNotification("Y2K Sould be converted to 1-JAN-2000", "01-Jan-2000"); | |||||
assertNotification("Y2K Should be converted to " + Y2K_GB_LOCALE, Y2K_GB_LOCALE); | |||||
dateTextbox.clear(); | dateTextbox.clear(); | ||||
validate.click(); | validate.click(); |
LabelElement eventCount = $(LabelElement.class).get(4); | LabelElement eventCount = $(LabelElement.class).get(4); | ||||
// we can type any string in date field element | // we can type any string in date field element | ||||
elem.setValue(TYPED_STRING); | elem.setValue(TYPED_STRING); | ||||
// invalid values are cleared stays | |||||
// invalid values should stay unchanged | |||||
Assert.assertEquals(TYPED_STRING, elem.getValue()); | Assert.assertEquals(TYPED_STRING, elem.getValue()); | ||||
} | } | ||||