com.vaadin.v7.ui.DateField Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-compatibility-server Show documentation
Show all versions of vaadin-compatibility-server Show documentation
Vaadin 7 compatibility package for Vaadin 8
The newest version!
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.v7.ui;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Logger;
import org.jsoup.nodes.Element;
import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.BlurListener;
import com.vaadin.event.FieldEvents.FocusEvent;
import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.DateTimeField;
import com.vaadin.ui.LegacyComponent;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.v7.data.Buffered;
import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.Validator;
import com.vaadin.v7.data.Validator.InvalidValueException;
import com.vaadin.v7.data.util.converter.Converter;
import com.vaadin.v7.data.validator.DateRangeValidator;
import com.vaadin.v7.event.FieldEvents;
import com.vaadin.v7.shared.ui.datefield.DateFieldConstants;
import com.vaadin.v7.shared.ui.datefield.Resolution;
import com.vaadin.v7.shared.ui.datefield.TextualDateFieldState;
/**
*
* A date editor component that can be bound to any {@link Property} that is
* compatible with java.util.Date
.
*
*
* Since DateField
extends AbstractField
it implements
* the {@link Buffered}interface.
*
*
* A DateField
is in write-through mode by default, so
* {@link AbstractField#setBuffered(boolean)} must be called to enable
* buffering.
*
*
* @author Vaadin Ltd.
* @since 3.0
*
* @deprecated As of 8.0, use {@link com.vaadin.ui.DateField} or
* {@link DateTimeField} instead.
*/
@SuppressWarnings("serial")
@Deprecated
public class DateField extends AbstractField implements
FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, LegacyComponent {
/**
* Resolution identifier: seconds.
*
* @deprecated As of 7.0, use {@link Resolution#SECOND}
*/
@Deprecated
public static final Resolution RESOLUTION_SEC = Resolution.SECOND;
/**
* Resolution identifier: minutes.
*
* @deprecated As of 7.0, use {@link Resolution#MINUTE}
*/
@Deprecated
public static final Resolution RESOLUTION_MIN = Resolution.MINUTE;
/**
* Resolution identifier: hours.
*
* @deprecated As of 7.0, use {@link Resolution#HOUR}
*/
@Deprecated
public static final Resolution RESOLUTION_HOUR = Resolution.HOUR;
/**
* Resolution identifier: days.
*
* @deprecated As of 7.0, use {@link Resolution#DAY}
*/
@Deprecated
public static final Resolution RESOLUTION_DAY = Resolution.DAY;
/**
* Resolution identifier: months.
*
* @deprecated As of 7.0, use {@link Resolution#MONTH}
*/
@Deprecated
public static final Resolution RESOLUTION_MONTH = Resolution.MONTH;
/**
* Resolution identifier: years.
*
* @deprecated As of 7.0, use {@link Resolution#YEAR}
*/
@Deprecated
public static final Resolution RESOLUTION_YEAR = Resolution.YEAR;
/**
* Specified smallest modifiable unit for the date field.
*/
private Resolution resolution = Resolution.DAY;
/**
* The internal calendar to be used in java.utl.Date conversions.
*/
private transient Calendar calendar;
/**
* Overridden format string
*/
private String dateFormat;
private boolean lenient = false;
private String dateString = null;
/**
* Was the last entered string parsable? If this flag is false, datefields
* internal validator does not pass.
*/
private boolean uiHasValidDateString = true;
/**
* Determines if week numbers are shown in the date selector.
*/
private boolean showISOWeekNumbers = false;
private String currentParseErrorMessage;
private String defaultParseErrorMessage = "Date format not recognized";
private TimeZone timeZone = null;
private static Map variableNameForResolution = new HashMap();
private String dateOutOfRangeMessage = "Date is out of allowed range";
private DateRangeValidator currentRangeValidator;
/**
* Determines whether the ValueChangeEvent should be fired. Used to prevent
* firing the event when UI has invalid string until uiHasValidDateString
* flag is set
*/
private boolean preventValueChangeEvent = false;
static {
variableNameForResolution.put(Resolution.SECOND, "sec");
variableNameForResolution.put(Resolution.MINUTE, "min");
variableNameForResolution.put(Resolution.HOUR, "hour");
variableNameForResolution.put(Resolution.DAY, "day");
variableNameForResolution.put(Resolution.MONTH, "month");
variableNameForResolution.put(Resolution.YEAR, "year");
}
/* Constructors */
/**
* Constructs an empty DateField
with no caption.
*/
public DateField() {
}
/**
* Constructs an empty DateField
with caption.
*
* @param caption
* the caption of the datefield.
*/
public DateField(String caption) {
setCaption(caption);
}
/**
* Constructs a new DateField
that's bound to the specified
* Property
and has the given caption String
.
*
* @param caption
* the caption String
for the editor.
* @param dataSource
* the Property to be edited with this editor.
*/
public DateField(String caption, Property dataSource) {
this(dataSource);
setCaption(caption);
}
/**
* Constructs a new DateField
that's bound to the specified
* Property
and has no caption.
*
* @param dataSource
* the Property to be edited with this editor.
*/
public DateField(Property dataSource) throws IllegalArgumentException {
if (!Date.class.isAssignableFrom(dataSource.getType())) {
throw new IllegalArgumentException(
"Can't use " + dataSource.getType().getName()
+ " typed property as datasource");
}
setPropertyDataSource(dataSource);
}
/**
* Constructs a new DateField
with the given caption and
* initial text contents. The editor constructed this way will not be bound
* to a Property unless
* {@link Property.Viewer#setPropertyDataSource(Property)} is called to bind
* it.
*
* @param caption
* the caption String
for the editor.
* @param value
* the Date value.
*/
public DateField(String caption, Date value) {
setValue(value);
setCaption(caption);
}
/* Component basic features */
/*
* Paints this component. Don't add a JavaDoc comment here, we use the
* default documentation from implemented interface.
*/
@Override
public void paintContent(PaintTarget target) throws PaintException {
// Adds the locale as attribute
final Locale l = getLocale();
if (l != null) {
target.addAttribute("locale", l.toString());
}
if (getDateFormat() != null) {
target.addAttribute("format", dateFormat);
}
if (!isLenient()) {
target.addAttribute("strict", true);
}
target.addAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS,
isShowISOWeekNumbers());
target.addAttribute("parsable", uiHasValidDateString);
/*
* TODO communicate back the invalid date string? E.g. returning back to
* app or refresh.
*/
// Gets the calendar
final Calendar calendar = getCalendar();
final Date currentDate = getValue();
// Only paint variables for the resolution and up, e.g. Resolution DAY
// paints DAY,MONTH,YEAR
for (Resolution res : Resolution
.getResolutionsHigherOrEqualTo(resolution)) {
int value = -1;
if (currentDate != null) {
value = calendar.get(res.getCalendarField());
if (res == Resolution.MONTH) {
// Calendar month is zero based
value++;
}
}
target.addVariable(this, variableNameForResolution.get(res), value);
}
}
@Override
protected boolean shouldHideErrors() {
return super.shouldHideErrors() && uiHasValidDateString;
}
@Override
protected TextualDateFieldState getState() {
return (TextualDateFieldState) super.getState();
}
@Override
protected TextualDateFieldState getState(boolean markAsDirty) {
return (TextualDateFieldState) super.getState(markAsDirty);
}
/**
* Sets the start range for this component. If the value is set before this
* date (taking the resolution into account), the component will not
* validate. If startDate
is set to null
, any
* value before endDate
will be accepted by the range
*
* @param startDate
* - the allowed range's start date
*/
public void setRangeStart(Date startDate) {
if (startDate != null && getState().rangeEnd != null
&& startDate.after(getState().rangeEnd)) {
throw new IllegalStateException(
"startDate cannot be later than endDate");
}
// Create a defensive copy against issues when using java.sql.Date (and
// also against mutable Date).
getState().rangeStart = startDate != null
? new Date(startDate.getTime()) : null;
updateRangeValidator();
}
/**
* Sets the current error message if the range validation fails.
*
* @param dateOutOfRangeMessage
* - Localizable message which is shown when value (the date) is
* set outside allowed range
*/
public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
this.dateOutOfRangeMessage = dateOutOfRangeMessage;
updateRangeValidator();
}
/**
* Gets the end range for a certain resolution. The range is inclusive, so
* if rangeEnd is set to zero milliseconds past year n and resolution is set
* to YEAR, any date in year n will be accepted. Resolutions lower than DAY
* will be interpreted on a DAY level. That is, everything below DATE is
* cleared
*
* @param forResolution
* - the range conforms to the resolution
* @return
*/
private Date getRangeEnd(Resolution forResolution) {
// We need to set the correct resolution for the dates,
// otherwise the range validator will complain
Date rangeEnd = getState(false).rangeEnd;
if (rangeEnd == null) {
return null;
}
Calendar endCal = Calendar.getInstance();
endCal.setTime(rangeEnd);
if (forResolution == Resolution.YEAR) {
// Adding one year (minresolution) and clearing the rest.
endCal.set(endCal.get(Calendar.YEAR) + 1, 0, 1, 0, 0, 0);
} else if (forResolution == Resolution.MONTH) {
// Adding one month (minresolution) and clearing the rest.
endCal.set(endCal.get(Calendar.YEAR),
endCal.get(Calendar.MONTH) + 1, 1, 0, 0, 0);
} else {
endCal.set(endCal.get(Calendar.YEAR), endCal.get(Calendar.MONTH),
endCal.get(Calendar.DATE) + 1, 0, 0, 0);
}
// removing one millisecond will now get the endDate to return to
// current resolution's set time span (year or month)
endCal.set(Calendar.MILLISECOND, -1);
return endCal.getTime();
}
/**
* Gets the start range for a certain resolution. The range is inclusive, so
* if rangeStart
is set to one millisecond before year n and
* resolution is set to YEAR, any date in year n - 1 will be accepted.
* Lowest supported resolution is DAY.
*
* @param forResolution
* - the range conforms to the resolution
* @return
*/
private Date getRangeStart(Resolution forResolution) {
if (getState(false).rangeStart == null) {
return null;
}
Calendar startCal = Calendar.getInstance();
startCal.setTime(getState(false).rangeStart);
if (forResolution == Resolution.YEAR) {
startCal.set(startCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
} else if (forResolution == Resolution.MONTH) {
startCal.set(startCal.get(Calendar.YEAR),
startCal.get(Calendar.MONTH), 1, 0, 0, 0);
} else {
startCal.set(startCal.get(Calendar.YEAR),
startCal.get(Calendar.MONTH), startCal.get(Calendar.DATE),
0, 0, 0);
}
startCal.set(Calendar.MILLISECOND, 0);
return startCal.getTime();
}
private void updateRangeValidator() {
if (currentRangeValidator != null) {
removeValidator(currentRangeValidator);
currentRangeValidator = null;
}
if (getRangeStart() != null || getRangeEnd() != null) {
currentRangeValidator = new DateRangeValidator(
dateOutOfRangeMessage, getRangeStart(resolution),
getRangeEnd(resolution), null);
addValidator(currentRangeValidator);
}
}
/**
* Sets the end range for this component. If the value is set after this
* date (taking the resolution into account), the component will not
* validate. If endDate
is set to null
, any value
* after startDate
will be accepted by the range.
*
* @param endDate
* - the allowed range's end date (inclusive, based on the
* current resolution)
*/
public void setRangeEnd(Date endDate) {
if (endDate != null && getState().rangeStart != null
&& getState().rangeStart.after(endDate)) {
throw new IllegalStateException(
"endDate cannot be earlier than startDate");
}
// Create a defensive copy against issues when using java.sql.Date (and
// also against mutable Date).
getState().rangeEnd = endDate != null ? new Date(endDate.getTime())
: null;
updateRangeValidator();
}
/**
* Returns the precise rangeStart used.
*/
public Date getRangeStart() {
return getState(false).rangeStart;
}
/**
* Returns the precise rangeEnd used.
*/
public Date getRangeEnd() {
return getState(false).rangeEnd;
}
/*
* Invoked when a variable of the component changes. Don't add a JavaDoc
* comment here, we use the default documentation from implemented
* interface.
*/
@Override
public void changeVariables(Object source, Map variables) {
if (!isReadOnly() && (variables.containsKey("year")
|| variables.containsKey("month")
|| variables.containsKey("day") || variables.containsKey("hour")
|| variables.containsKey("min") || variables.containsKey("sec")
|| variables.containsKey("msec")
|| variables.containsKey("dateString"))) {
// Old and new dates
final Date oldDate = getValue();
Date newDate = null;
// this enables analyzing invalid input on the server
final String newDateString = (String) variables.get("dateString");
dateString = newDateString;
// Gets the new date in parts
boolean hasChanges = false;
Map calendarFieldChanges = new HashMap();
for (Resolution r : Resolution
.getResolutionsHigherOrEqualTo(resolution)) {
// Only handle what the client is allowed to send. The same
// resolutions that are painted
String variableName = variableNameForResolution.get(r);
if (variables.containsKey(variableName)) {
Integer value = (Integer) variables.get(variableName);
if (r == Resolution.MONTH) {
// Calendar MONTH is zero based
value--;
}
if (value >= 0) {
hasChanges = true;
calendarFieldChanges.put(r, value);
}
}
}
// If no new variable values were received, use the previous value
if (!hasChanges) {
newDate = null;
} else {
// Clone the calendar for date operation
final Calendar cal = getCalendar();
// Update the value based on the received info
// Must set in this order to avoid invalid dates (or wrong
// dates if lenient is true) in calendar
for (int r = Resolution.YEAR.ordinal(); r >= 0; r--) {
Resolution res = Resolution.values()[r];
if (calendarFieldChanges.containsKey(res)) {
// Field resolution should be included. Others are
// skipped so that client can not make unexpected
// changes (e.g. day change even though resolution is
// year).
Integer newValue = calendarFieldChanges.get(res);
cal.set(res.getCalendarField(), newValue);
}
}
newDate = cal.getTime();
}
if (newDate == null && dateString != null
&& !"".equals(dateString)) {
try {
Date parsedDate = handleUnparsableDateString(dateString);
setValue(parsedDate, true);
/*
* Ensure the value is sent to the client if the value is
* set to the same as the previous (#4304). Does not repaint
* if handleUnparsableDateString throws an exception. In
* this case the invalid text remains in the DateField.
*/
markAsDirty();
} catch (Converter.ConversionException e) {
/*
* Datefield now contains some text that could't be parsed
* into date. ValueChangeEvent is fired after the value is
* changed and the flags are set
*/
if (oldDate != null) {
/*
* Set the logic value to null without firing the
* ValueChangeEvent
*/
preventValueChangeEvent = true;
try {
setValue(null);
} finally {
preventValueChangeEvent = false;
}
/*
* Reset the dateString (overridden to null by setValue)
*/
dateString = newDateString;
}
/*
* Saves the localized message of parse error. This can be
* overridden in handleUnparsableDateString. The message
* will later be used to show a validation error.
*/
currentParseErrorMessage = e.getLocalizedMessage();
/*
* The value of the DateField should be null if an invalid
* value has been given. Not using setValue() since we do
* not want to cause the client side value to change.
*/
uiHasValidDateString = false;
/*
* If value was changed fire the ValueChangeEvent
*/
if (oldDate != null) {
fireValueChange(false);
}
/*
* Because of our custom implementation of isValid(), that
* also checks the parsingSucceeded flag, we must also
* notify the form (if this is used in one) that the
* validity of this field has changed.
*
* Normally fields validity doesn't change without value
* change and form depends on this implementation detail.
*/
notifyFormOfValidityChange();
markAsDirty();
}
} else if (newDate != oldDate
&& (newDate == null || !newDate.equals(oldDate))) {
// Don't require a repaint, client updates itself
setValue(newDate, true);
} else if (!uiHasValidDateString) { // oldDate ==
// newDate == null
// Empty value set, previously contained unparsable date string,
// clear related internal fields
setValue(null);
}
}
if (variables.containsKey(FocusEvent.EVENT_ID)) {
fireEvent(new FocusEvent(this));
}
if (variables.containsKey(BlurEvent.EVENT_ID)) {
fireEvent(new BlurEvent(this));
}
}
@Override
public void discard() {
Property prop = getPropertyDataSource();
if (prop != null) {
Object value = prop.getValue();
if (!isValid() && value == null) {
// If the user entered an invalid value in the date field
// getInternalValue() returns null.
// If the datasource also contains null, then
// updateValueFromDataSource() will then not clear the internal
// state
// and error indicators (ticket #8069).
setInternalValue(null);
} else {
super.discard();
}
}
}
/*
* only fires the event if preventValueChangeEvent flag is false
*/
@Override
protected void fireValueChange(boolean repaintIsNotNeeded) {
if (!preventValueChangeEvent) {
super.fireValueChange(repaintIsNotNeeded);
}
}
/**
* This method is called to handle a non-empty date string from the client
* if the client could not parse it as a Date.
*
* By default, a Converter.ConversionException is thrown, and the current
* value is not modified.
*
* This can be overridden to handle conversions, to return null (equivalent
* to empty input), to throw an exception or to fire an event.
*
* @param dateString
* @return parsed Date
* @throws Converter.ConversionException
* to keep the old value and indicate an error
*/
protected Date handleUnparsableDateString(String dateString)
throws Converter.ConversionException {
currentParseErrorMessage = null;
throw new Converter.ConversionException(getParseErrorMessage());
}
/* Property features */
/*
* Gets the edited property's type. Don't add a JavaDoc comment here, we use
* the default documentation from implemented interface.
*/
@Override
public Class getType() {
return Date.class;
}
@Override
protected void setValue(Date newValue, boolean repaintIsNotNeeded)
throws Property.ReadOnlyException {
/*
* 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 (newValue == null && !uiHasValidDateString) {
/*
* Side-effects of setInternalValue clears possible previous strings
* and flags about invalid input.
*/
setInternalValue(null);
/*
* Due to DateField's special implementation of isValid(),
* datefields validity may change although the logical value does
* not change. This is an issue for Form which expects that validity
* of Fields cannot change unless actual value changes.
*
* So we check if this field is inside a form and the form has
* registered this as a field. In this case we repaint the form.
* Without this hacky solution the form might not be able to clean
* validation errors etc. We could avoid this by firing an extra
* value change event, but feels like at least as bad solution as
* this.
*/
notifyFormOfValidityChange();
markAsDirty();
return;
}
super.setValue(newValue, repaintIsNotNeeded);
}
/**
* Detects if this field is used in a Form (logically) and if so, notifies
* it (by repainting it) that the validity of this field might have changed.
*/
private void notifyFormOfValidityChange() {
Component parenOfDateField = getParent();
boolean formFound = false;
while (parenOfDateField != null || formFound) {
if (parenOfDateField instanceof Form) {
Form f = (Form) parenOfDateField;
Collection> visibleItemProperties = f.getItemPropertyIds();
for (Object fieldId : visibleItemProperties) {
Field> field = f.getField(fieldId);
if (equals(field)) {
/*
* this datefield is logically in a form. Do the same
* thing as form does in its value change listener that
* it registers to all fields.
*/
f.markAsDirty();
formFound = true;
break;
}
}
}
if (formFound) {
break;
}
parenOfDateField = parenOfDateField.getParent();
}
}
@Override
protected void setInternalValue(Date newValue) {
// Also set the internal dateString
if (newValue != null) {
dateString = newValue.toString();
} else {
dateString = null;
}
if (!uiHasValidDateString) {
// clear component error and parsing flag
setComponentError(null);
uiHasValidDateString = true;
currentParseErrorMessage = null;
}
super.setInternalValue(newValue);
}
/**
* Gets the resolution.
*
* @return int
*/
public Resolution getResolution() {
return resolution;
}
/**
* Sets the resolution of the DateField.
*
* The default resolution is {@link Resolution#DAY} since Vaadin 7.0.
*
* @param resolution
* the resolution to set.
*/
public void setResolution(Resolution resolution) {
this.resolution = resolution;
updateRangeValidator();
markAsDirty();
}
/**
* Returns new instance calendar used in Date conversions.
*
* Returns new clone of the calendar object initialized using the the
* current date (if available)
*
* If this is no calendar is assigned the Calendar.getInstance
* is used.
*
* @return the Calendar.
* @see #setCalendar(Calendar)
*/
private Calendar getCalendar() {
// Makes sure we have an calendar instance
if (calendar == null) {
calendar = Calendar.getInstance();
// Start by a zeroed calendar to avoid having values for lower
// resolution variables e.g. time when resolution is day
int min, field;
for (Resolution r : Resolution
.getResolutionsLowerThan(resolution)) {
field = r.getCalendarField();
min = calendar.getActualMinimum(field);
calendar.set(field, min);
}
calendar.set(Calendar.MILLISECOND, 0);
}
// Clone the instance
final Calendar newCal = (Calendar) calendar.clone();
final TimeZone currentTimeZone = getTimeZone();
if (currentTimeZone != null) {
newCal.setTimeZone(currentTimeZone);
}
final Date currentDate = getValue();
if (currentDate != null) {
newCal.setTime(currentDate);
}
return newCal;
}
/**
* Sets formatting used by some component implementations. See
* {@link SimpleDateFormat} for format details.
*
* By default it is encouraged to used default formatting defined by Locale,
* but due some JVM bugs it is sometimes necessary to use this method to
* override formatting. See Vaadin issue #2200.
*
* @param dateFormat
* the dateFormat to set
*
* @see AbstractComponent#setLocale(Locale)
*/
public void setDateFormat(String dateFormat) {
this.dateFormat = dateFormat;
markAsDirty();
}
/**
* Returns a format string used to format date value on client side or null
* if default formatting from {@link Component#getLocale()} is used.
*
* @return the dateFormat
*/
public String getDateFormat() {
return dateFormat;
}
/**
* Specifies whether or not date/time interpretation in component is to be
* lenient.
*
* @see Calendar#setLenient(boolean)
* @see #isLenient()
*
* @param lenient
* true if the lenient mode is to be turned on; false if it is to
* be turned off.
*/
public void setLenient(boolean lenient) {
this.lenient = lenient;
markAsDirty();
}
/**
* Returns whether date/time interpretation is to be lenient.
*
* @see #setLenient(boolean)
*
* @return true if the interpretation mode of this calendar is lenient;
* false otherwise.
*/
public boolean isLenient() {
return lenient;
}
@Override
public void addFocusListener(FocusListener listener) {
addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
FocusListener.focusMethod);
}
@Override
public void removeFocusListener(FocusListener listener) {
removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
}
@Override
public void addBlurListener(BlurListener listener) {
addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
BlurListener.blurMethod);
}
@Override
public void removeBlurListener(BlurListener listener) {
removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
}
/**
* Checks whether ISO 8601 week numbers are shown in the date selector.
*
* @return true if week numbers are shown, false otherwise.
*/
public boolean isShowISOWeekNumbers() {
return showISOWeekNumbers;
}
/**
* Sets the visibility of ISO 8601 week numbers in the date selector. ISO
* 8601 defines that a week always starts with a Monday so the week numbers
* are only shown if this is the case.
*
* @param showWeekNumbers
* true if week numbers should be shown, false otherwise.
*/
public void setShowISOWeekNumbers(boolean showWeekNumbers) {
showISOWeekNumbers = showWeekNumbers;
markAsDirty();
}
/**
* Validates the current value against registered validators if the field is
* not empty. Note that DateField is considered empty (value == null) and
* invalid if it contains text typed in by the user that couldn't be parsed
* into a Date value.
*
* @see AbstractField#validate()
*/
@Override
public void validate() throws InvalidValueException {
/*
* To work properly in form we must throw exception if there is
* currently a parsing error in the datefield. Parsing error is kind of
* an internal validator.
*/
if (!uiHasValidDateString) {
throw new UnparsableDateString(currentParseErrorMessage);
}
super.validate();
}
/**
* Return the error message that is shown if the user inputted value can't
* be parsed into a Date object. If
* {@link #handleUnparsableDateString(String)} is overridden and it throws a
* custom exception, the message returned by
* {@link Exception#getLocalizedMessage()} will be used instead of the value
* returned by this method.
*
* @see #setParseErrorMessage(String)
*
* @return the error message that the DateField uses when it can't parse the
* textual input from user to a Date object
*/
public String getParseErrorMessage() {
return defaultParseErrorMessage;
}
/**
* Sets the default error message used if the DateField cannot parse the
* text input by user to a Date field. Note that if the
* {@link #handleUnparsableDateString(String)} method is overridden, the
* localized message from its exception is used.
*
* @see #getParseErrorMessage()
* @see #handleUnparsableDateString(String)
* @param parsingErrorMessage
*/
public void setParseErrorMessage(String parsingErrorMessage) {
defaultParseErrorMessage = parsingErrorMessage;
}
/**
* Sets the time zone used by this date field. The time zone is used to
* convert the absolute time in a Date object to a logical time displayed in
* the selector and to convert the select time back to a Date object.
*
* If no time zone has been set, the current default time zone returned by
* {@code TimeZone.getDefault()} is used.
*
* @see #getTimeZone()
* @param timeZone
* the time zone to use for time calculations.
*/
public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
markAsDirty();
}
/**
* Gets the time zone used by this field. The time zone is used to convert
* the absolute time in a Date object to a logical time displayed in the
* selector and to convert the select time back to a Date object.
*
* If {@code null} is returned, the current default time zone returned by
* {@code TimeZone.getDefault()} is used.
*
* @return the current time zone
*/
public TimeZone getTimeZone() {
return timeZone;
}
@Deprecated
public static class UnparsableDateString
extends Validator.InvalidValueException {
public UnparsableDateString(String message) {
super(message);
}
}
@Override
public void readDesign(Element design, DesignContext designContext) {
super.readDesign(design, designContext);
if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
Date date = DesignAttributeHandler.getFormatter()
.parse(design.attr("value"), Date.class);
// formatting will return null if it cannot parse the string
if (date == null) {
Logger.getLogger(DateField.class.getName()).info(
"cannot parse " + design.attr("value") + " as date");
}
this.setValue(date, false, true);
}
}
@Override
public void writeDesign(Element design, DesignContext designContext) {
super.writeDesign(design, designContext);
if (getValue() != null) {
design.attr("value",
DesignAttributeHandler.getFormatter().format(getValue()));
}
}
/**
* Returns current date-out-of-range error message.
*
* @see #setDateOutOfRangeMessage(String)
* @since 7.4
* @return Current error message for dates out of range.
*/
public String getDateOutOfRangeMessage() {
return dateOutOfRangeMessage;
}
}