diff options
6 files changed, 235 insertions, 21 deletions
diff --git a/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java b/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java index a8c356a07c..9d9862ceae 100644 --- a/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java +++ b/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java @@ -391,6 +391,8 @@ public abstract class VAbstractPopupCalendar<PANEL extends VAbstractCalendarPane if (getCurrentDate() != null) { calendar.setDate((Date) getCurrentDate().clone()); + } else if (getDefaultDate() != null) { + calendar.setDate(getDefaultDate()); } else { calendar.setDate(new Date()); } diff --git a/client/src/main/java/com/vaadin/client/ui/VDateField.java b/client/src/main/java/com/vaadin/client/ui/VDateField.java index 40fc9a8f8c..772f72c897 100644 --- a/client/src/main/java/com/vaadin/client/ui/VDateField.java +++ b/client/src/main/java/com/vaadin/client/ui/VDateField.java @@ -54,6 +54,12 @@ public abstract class VDateField<R extends Enum<R>> extends FlowPanel protected boolean enabled; /** + * The date that is displayed the date field before a value is selected. If + * null, display the current date. + */ + private Date defaultDate = null; + + /** * The date that is selected in the date field. Null if an invalid date is * specified. */ @@ -95,6 +101,18 @@ public abstract class VDateField<R extends Enum<R>> extends FlowPanel } /** + * Set the default date to open popup when no date is selected. + * + * @param date + * default date to show as the initial (non-selected) value when + * opening a popup with no value selected + * @since 8.1.2 + */ + public void setDefaultDate(Date date) { + this.defaultDate = date; + } + + /** * Set the current date using a map with date values. * <p> * The map contains integer representation of values per resolution. The @@ -108,6 +126,27 @@ public abstract class VDateField<R extends Enum<R>> extends FlowPanel setCurrentDate(getDate(dateValues)); } + /** + * Set the default date using a map with date values. + * + * @see #setCurrentDate(Map) + * @param defaultValues + * @since 8.1.2 + */ + public void setDefaultDate(Map<R, Integer> defaultValues) { + setDefaultDate(getDate(defaultValues)); + } + + /** + * Sets the default date when no date is selected. + * + * @return the default date + * @since 8.1.2 + */ + public Date getDefaultDate() { + return defaultDate; + } + public boolean isReadonly() { return readonly; } diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java index c851c13679..21e85773de 100644 --- a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java @@ -77,6 +77,7 @@ public abstract class AbstractDateFieldConnector<R extends Enum<R>> + getWidget().resolutionAsString(), true); getWidget().setCurrentDate(getTimeValues(uidl)); + getWidget().setDefaultDate(getDefaultValues(uidl)); } private void updateResolution(UIDL uidl) { @@ -98,6 +99,25 @@ public abstract class AbstractDateFieldConnector<R extends Enum<R>> : -1)); } + /** + * Returns the default date (when no date is selected) components as a map + * from Resolution to the corresponding value. + * + * @param uidl + * UIDL with corresponding variables + * @return default date component map + * @since 8.1.2 + */ + protected Map<R, Integer> getDefaultValues(UIDL uidl) { + Stream<R> resolutions = getWidget().getResolutions(); + R resolution = getWidget().getCurrentResolution(); + return resolutions.collect(Collectors.toMap(Function.identity(), + res -> (resolution.compareTo(res) <= 0) + ? uidl.getIntVariable("default-" + + getWidget().getResolutionVariable(res)) + : -1)); + } + @SuppressWarnings("unchecked") @Override public VDateField<R> getWidget() { diff --git a/server/src/main/java/com/vaadin/ui/AbstractDateField.java b/server/src/main/java/com/vaadin/ui/AbstractDateField.java index 9d063af5ec..2ad976a7fe 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractDateField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractDateField.java @@ -80,6 +80,12 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & private T value; /** + * Default value of the field, displayed when nothing has been selected. + * + * @since 8.1.2 + */ + private T defaultValue = null; + /** * Specified smallest modifiable unit for the date field. */ private R resolution; @@ -187,7 +193,15 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & if (currentDate != null) { value = getDatePart(currentDate, res); } - target.addVariable(this, getResolutionVariable(res), value); + String variableName = getResolutionVariable(res); + target.addVariable(this, variableName, value); + if (defaultValue != null) { + int defaultValuePart = getDatePart(defaultValue, res); + target.addVariable(this, "default-" + variableName, + defaultValuePart); + } else { + target.addVariable(this, "default-" + variableName, -1); + } } } @@ -227,8 +241,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & newDate = reconstructDateFromFields(variables, oldDate); } - hasChanges |= !Objects.equals(dateString, newDateString) || - !Objects.equals(oldDate, newDate); + hasChanges |= !Objects.equals(dateString, newDateString) + || !Objects.equals(oldDate, newDate); if (hasChanges) { dateString = newDateString; @@ -239,18 +253,21 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & setComponentError(null); } else { if (variables.get("lastInvalidDateString") != null) { - Result<T> parsedDate = handleUnparsableDateString(dateString); - parsedDate.ifOk(v-> { + Result<T> parsedDate = handleUnparsableDateString( + dateString); + parsedDate.ifOk(v -> { uiHasValidDateString = true; currentParseErrorMessage = null; - setValue(v,true); + setValue(v, true); }); if (parsedDate.isError()) { dateString = null; uiHasValidDateString = false; - currentParseErrorMessage = parsedDate.getMessage().orElse("Parsing error"); - setComponentError(new UserError(getParseErrorMessage())); - setValue(null,true); + currentParseErrorMessage = parsedDate.getMessage() + .orElse("Parsing error"); + setComponentError( + new UserError(getParseErrorMessage())); + setValue(null, true); } } else { uiHasValidDateString = true; @@ -272,16 +289,16 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & } /** - * Construct a date object from the individual field values received from the - * client. + * Construct a date object from the individual field values received from + * the client. * * @since 8.1.1 */ - protected T reconstructDateFromFields(Map<String, Object> variables, T oldDate) { + protected T reconstructDateFromFields(Map<String, Object> variables, + T oldDate) { Map<R, Integer> calendarFields = new HashMap<>(); - for (R resolution : getResolutionsHigherOrEqualTo( - getResolution())) { + for (R resolution : getResolutionsHigherOrEqualTo(getResolution())) { // Only handle what the client is allowed to send. The same // resolutions that are painted String variableName = getResolutionVariable(resolution); @@ -290,7 +307,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & if (newValue != null && newValue >= 0) { calendarFields.put(resolution, newValue); } else { - calendarFields.put(resolution, getDatePart(oldDate, resolution)); + calendarFields.put(resolution, + getDatePart(oldDate, resolution)); } } return buildDate(calendarFields); @@ -460,6 +478,57 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & } /** + * Returns the current default value. + * + * @see #setDefaultValue(Temporal) + * @return the default value + * @since 8.1.2 + */ + public T getDefaultValue() { + return defaultValue; + } + + /** + * Sets the default value for the field. The default value is the starting + * point for the date field when nothing has been selected yet. If no + * default value is set, current date/time is used. + * + * @param defaultValue + * @since 8.1.2 + */ + public void setDefaultValue(T defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * 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. * * @return true if week numbers are shown, false otherwise. @@ -545,17 +614,19 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & } else { throw new RuntimeException("Cannot detect resoluton type " + Optional.ofNullable(dateType).map(Type::getTypeName) - .orElse(null)); + .orElse(null)); } } } /** - * Formats date according to the components locale. - * To be reimplemented in subclasses. + * Formats date according to the components locale. To be reimplemented in + * subclasses. * - * @param value the date or {@code null} - * @return textual representation of the date or empty string for {@code null} + * @param value + * the date or {@code null} + * @return textual representation of the date or empty string for + * {@code null} * @since 8.1.1 */ protected String formatDate(T value) { @@ -610,7 +681,9 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & } else { dateString = formatDate(getEmptyValue()); } - RangeValidator<T> validator = getRangeValidator();// TODO move range check to internal validator? + RangeValidator<T> validator = getRangeValidator();// TODO move range + // check to internal + // validator? ValidationResult result = validator.apply(value, new ValueContext(this, this)); if (result.isError()) { diff --git a/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldWithDefaultValue.java b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldWithDefaultValue.java new file mode 100755 index 0000000000..e422fc991e --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/datefield/DateFieldWithDefaultValue.java @@ -0,0 +1,34 @@ +package com.vaadin.tests.components.datefield; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.DateField; + +@Widgetset("com.vaadin.DefaultWidgetSet") +public class DateFieldWithDefaultValue extends AbstractTestUI { + + static final String DATEFIELD_HAS_DEFAULT = "hasdefault"; + + static final String DATEFIELD_REGULAR = "regular"; + + @Override + protected void setup(VaadinRequest request) { + DateField dfWithDefault = new DateField( + "Date field with default value 2010-10-01"); + dfWithDefault.setId(DATEFIELD_HAS_DEFAULT); + LocalDate defaultValue = LocalDate.parse("2010-10-01", + DateTimeFormatter.ISO_DATE); + dfWithDefault.setDefaultValue(defaultValue); + addComponent(dfWithDefault); + + DateField regularDF = new DateField("Regular datefield"); + regularDF.setId(DATEFIELD_REGULAR); + addComponent(regularDF); + + } + +} diff --git a/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldWithDefaultValueTest.java b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldWithDefaultValueTest.java new file mode 100755 index 0000000000..964b6bbecb --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/datefield/DateFieldWithDefaultValueTest.java @@ -0,0 +1,46 @@ +package com.vaadin.tests.components.datefield; + +import static com.vaadin.tests.components.datefield.DateFieldWithDefaultValue.DATEFIELD_HAS_DEFAULT; +import static com.vaadin.tests.components.datefield.DateFieldWithDefaultValue.DATEFIELD_REGULAR; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.DateFieldElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class DateFieldWithDefaultValueTest extends MultiBrowserTest { + + @Test + public void testDateFieldDefaultValue() { + openTestURL(); + String datePickerId = DATEFIELD_HAS_DEFAULT; + getDateFieldElement(datePickerId).openPopup(); + WebElement monthSpanElement = getMonthSpanFromVisibleCalendarPanel(); + // Can't check for "October 2010", since IE11 translates October -> + // lokakuu + assert (monthSpanElement.getText().contains("2010")); + } + + @Test + public void testDateFieldWithNoDefault() { + openTestURL(); + String datePickerId = DATEFIELD_REGULAR; + getDateFieldElement(datePickerId).openPopup(); + WebElement monthSpanElement = getMonthSpanFromVisibleCalendarPanel(); + assert (!monthSpanElement.getText().contains("2010")); + } + + private WebElement getMonthSpanFromVisibleCalendarPanel() { + return getDriver() + .findElements( + By.cssSelector(".v-datefield-calendarpanel-month")) + .get(0); + } + + private DateFieldElement getDateFieldElement(String id) { + return $(DateFieldElement.class).id(id); + } + +} |