You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractDateField.java 38KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Type;
  19. import java.text.SimpleDateFormat;
  20. import java.time.LocalDate;
  21. import java.time.ZoneId;
  22. import java.time.format.DateTimeFormatter;
  23. import java.time.temporal.Temporal;
  24. import java.time.temporal.TemporalAccessor;
  25. import java.time.temporal.TemporalAdjuster;
  26. import java.util.Calendar;
  27. import java.util.Collections;
  28. import java.util.Date;
  29. import java.util.HashMap;
  30. import java.util.Locale;
  31. import java.util.Map;
  32. import java.util.Map.Entry;
  33. import java.util.Objects;
  34. import java.util.Optional;
  35. import java.util.Set;
  36. import java.util.logging.Logger;
  37. import java.util.stream.Collectors;
  38. import java.util.stream.Stream;
  39. import org.jsoup.nodes.Element;
  40. import com.googlecode.gentyref.GenericTypeReflector;
  41. import com.vaadin.data.Result;
  42. import com.vaadin.data.ValidationResult;
  43. import com.vaadin.data.Validator;
  44. import com.vaadin.data.ValueContext;
  45. import com.vaadin.data.validator.RangeValidator;
  46. import com.vaadin.event.FieldEvents.BlurEvent;
  47. import com.vaadin.event.FieldEvents.BlurListener;
  48. import com.vaadin.event.FieldEvents.BlurNotifier;
  49. import com.vaadin.event.FieldEvents.FocusEvent;
  50. import com.vaadin.event.FieldEvents.FocusListener;
  51. import com.vaadin.event.FieldEvents.FocusNotifier;
  52. import com.vaadin.server.ErrorMessage;
  53. import com.vaadin.server.UserError;
  54. import com.vaadin.shared.Registration;
  55. import com.vaadin.shared.ui.datefield.AbstractDateFieldServerRpc;
  56. import com.vaadin.shared.ui.datefield.AbstractDateFieldState;
  57. import com.vaadin.shared.ui.datefield.AbstractDateFieldState.AccessibleElement;
  58. import com.vaadin.shared.ui.datefield.DateResolution;
  59. import com.vaadin.ui.declarative.DesignAttributeHandler;
  60. import com.vaadin.ui.declarative.DesignContext;
  61. import com.vaadin.util.TimeZoneUtil;
  62. import elemental.json.Json;
  63. /**
  64. * A date editor component with {@link LocalDate} as an input value.
  65. *
  66. * @author Vaadin Ltd
  67. *
  68. * @since 8.0
  69. *
  70. * @param <T>
  71. * type of date ({@code LocalDate} or {@code LocalDateTime}).
  72. * @param <R>
  73. * resolution enumeration type
  74. *
  75. */
  76. public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & Serializable & Comparable<? super T>, R extends Enum<R>>
  77. extends AbstractField<T> implements FocusNotifier, BlurNotifier {
  78. private static final DateTimeFormatter RANGE_FORMATTER = DateTimeFormatter
  79. .ofPattern("yyyy-MM-dd[ HH:mm:ss]", Locale.ENGLISH);
  80. private AbstractDateFieldServerRpc rpc = new AbstractDateFieldServerRpc() {
  81. @Override
  82. public void update(String newDateString,
  83. Map<String, Integer> resolutions) {
  84. valueUpdate(newDateString, resolutions);
  85. }
  86. @Override
  87. public void updateValueWithDelay(String newDateString,
  88. Map<String, Integer> resolutions) {
  89. valueUpdate(newDateString, resolutions);
  90. }
  91. private void valueUpdate(String newDateString,
  92. Map<String, Integer> resolutions) {
  93. Set<String> resolutionNames = getResolutions().map(Enum::name)
  94. .collect(Collectors.toSet());
  95. resolutionNames.retainAll(resolutions.keySet());
  96. if (!isReadOnly()
  97. && (!resolutionNames.isEmpty() || newDateString != null)) {
  98. // Old and new dates
  99. final T oldDate = getValue();
  100. T newDate;
  101. boolean hasChanges = false;
  102. if ("".equals(newDateString)) {
  103. newDate = null;
  104. } else {
  105. newDate = reconstructDateFromFields(resolutions, oldDate);
  106. }
  107. boolean parseErrorWasSet = currentErrorMessage != null;
  108. hasChanges |= !Objects.equals(dateString, newDateString)
  109. || !Objects.equals(oldDate, newDate)
  110. || parseErrorWasSet;
  111. if (hasChanges) {
  112. dateString = newDateString;
  113. currentErrorMessage = null;
  114. if (newDateString == null || newDateString.isEmpty()) {
  115. boolean valueChanged = setValue(newDate, true);
  116. if (!valueChanged && parseErrorWasSet) {
  117. doSetValue(newDate);
  118. }
  119. } else {
  120. // invalid date string
  121. if (resolutions.isEmpty()) {
  122. Result<T> parsedDate = handleUnparsableDateString(
  123. dateString);
  124. // If handleUnparsableDateString returns the same
  125. // date as current, force update state to display
  126. // correct representation
  127. parsedDate.ifOk(v -> {
  128. if (!setValue(v, true)
  129. && !isDifferentValue(v)) {
  130. updateDiffstate("resolutions",
  131. Json.createObject());
  132. doSetValue(v);
  133. }
  134. });
  135. if (parsedDate.isError()) {
  136. dateString = null;
  137. currentErrorMessage = parsedDate.getMessage()
  138. .orElse("Parsing error");
  139. if (!isDifferentValue(null)) {
  140. doSetValue(null);
  141. } else {
  142. setValue(null, true);
  143. }
  144. }
  145. } else {
  146. setValue(newDate, true);
  147. }
  148. }
  149. }
  150. }
  151. }
  152. @Override
  153. public void focus() {
  154. fireEvent(new FocusEvent(AbstractDateField.this));
  155. }
  156. @Override
  157. public void blur() {
  158. fireEvent(new BlurEvent(AbstractDateField.this));
  159. }
  160. };
  161. /**
  162. * The default start year (inclusive) from which to calculate the
  163. * daylight-saving time zone transition dates.
  164. */
  165. private static final int DEFAULT_START_YEAR = 1980;
  166. /**
  167. * The default value of the number of future years from the current date for
  168. * which the daylight-saving time zone transition dates are calculated.
  169. */
  170. private static final int DEFAULT_YEARS_FROM_NOW = 20;
  171. /**
  172. * The optional user-supplied start year (inclusive) from which to calculate
  173. * the daylight-saving time zone transition dates.
  174. */
  175. private Integer startYear;
  176. /**
  177. * The optional user-supplied end year (inclusive) until which to calculate
  178. * the daylight-saving time zone transition dates.
  179. */
  180. private Integer endYear;
  181. /**
  182. * Value of the field.
  183. */
  184. private T value;
  185. /**
  186. * Default value of the field, displayed when nothing has been selected.
  187. *
  188. * @since 8.1.2
  189. */
  190. private T defaultValue;
  191. /**
  192. * Specified smallest modifiable unit for the date field.
  193. */
  194. private R resolution;
  195. private ZoneId zoneId;
  196. private String dateString = "";
  197. private String currentErrorMessage;
  198. private String defaultParseErrorMessage = "Date format not recognized";
  199. private String dateOutOfRangeMessage = "Date is out of allowed range";
  200. /* Constructors */
  201. /**
  202. * Constructs an empty {@code AbstractDateField} with no caption and
  203. * specified {@code resolution}.
  204. *
  205. * @param resolution
  206. * initial resolution for the field, not {@code null}
  207. */
  208. public AbstractDateField(R resolution) {
  209. registerRpc(rpc);
  210. setResolution(resolution);
  211. }
  212. /**
  213. * Constructs an empty {@code AbstractDateField} with caption.
  214. *
  215. * @param caption
  216. * the caption of the datefield
  217. * @param resolution
  218. * initial resolution for the field, not {@code null}
  219. */
  220. public AbstractDateField(String caption, R resolution) {
  221. this(resolution);
  222. setCaption(caption);
  223. }
  224. /**
  225. * Constructs a new {@code AbstractDateField} with the given caption and
  226. * initial text contents.
  227. *
  228. * @param caption
  229. * the caption {@code String} for the editor.
  230. * @param value
  231. * the date/time value.
  232. * @param resolution
  233. * initial resolution for the field, not {@code null}
  234. */
  235. public AbstractDateField(String caption, T value, R resolution) {
  236. this(caption, resolution);
  237. setValue(value);
  238. }
  239. /* Component basic features */
  240. @Override
  241. public void beforeClientResponse(boolean initial) {
  242. super.beforeClientResponse(initial);
  243. Locale locale = getLocale();
  244. getState().locale = locale == null ? null : locale.toString();
  245. }
  246. /**
  247. * Construct a date object from the individual field values received from
  248. * the client.
  249. *
  250. * @param resolutions
  251. * map of time unit (resolution) name and value, the key is the
  252. * resolution name e.g. "HOUR", "MINUTE", the value can be
  253. * {@code null}
  254. * @param oldDate
  255. * used as a fallback to get needed values if they are not
  256. * defined in the specified {@code resolutions}
  257. *
  258. * @return the date object built from the specified resolutions
  259. * @since 8.2
  260. */
  261. protected T reconstructDateFromFields(Map<String, Integer> resolutions,
  262. T oldDate) {
  263. Map<R, Integer> calendarFields = new HashMap<>();
  264. for (R resolution : getResolutionsHigherOrEqualTo(getResolution())) {
  265. // Only handle what the client is allowed to send. The same
  266. // resolutions that are painted
  267. String resolutionName = resolution.name();
  268. Integer newValue = resolutions.get(resolutionName);
  269. if (newValue == null) {
  270. newValue = getDatePart(oldDate, resolution);
  271. }
  272. calendarFields.put(resolution, newValue);
  273. }
  274. return buildDate(calendarFields);
  275. }
  276. /**
  277. * Sets the start range for this component. If the value is set before this
  278. * date (taking the resolution into account), the component will not
  279. * validate. If {@code startDate} is set to {@code null}, any value before
  280. * {@code endDate} will be accepted by the range
  281. * <p>
  282. * Note: Negative, i.e. BC dates are not supported.
  283. * <p>
  284. * Note: It's usually recommended to use only one of the following at the
  285. * same time: Range validator with Binder or DateField's setRangeStart
  286. * check.
  287. *
  288. * @param startDate
  289. * - the allowed range's start date
  290. */
  291. public void setRangeStart(T startDate) {
  292. if (afterDate(startDate, convertFromDateString(getState().rangeEnd))) {
  293. throw new IllegalStateException(
  294. "startDate cannot be later than endDate");
  295. }
  296. getState().rangeStart = convertToDateString(startDate);
  297. }
  298. /**
  299. * Sets the current error message if the range validation fails.
  300. *
  301. * @param dateOutOfRangeMessage
  302. * - Localizable message which is shown when value (the date) is
  303. * set outside allowed range
  304. */
  305. public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
  306. this.dateOutOfRangeMessage = dateOutOfRangeMessage;
  307. }
  308. /**
  309. * Returns current date-out-of-range error message.
  310. *
  311. * @see #setDateOutOfRangeMessage(String)
  312. * @return Current error message for dates out of range.
  313. */
  314. public String getDateOutOfRangeMessage() {
  315. return dateOutOfRangeMessage;
  316. }
  317. /**
  318. * Gets the resolution.
  319. *
  320. * @return the date/time field resolution
  321. */
  322. public R getResolution() {
  323. return resolution;
  324. }
  325. /**
  326. * Sets the resolution of the DateField.
  327. *
  328. * The default resolution is {@link DateResolution#DAY} since Vaadin 7.0.
  329. *
  330. * @param resolution
  331. * the resolution to set, not {@code null}
  332. */
  333. public void setResolution(R resolution) {
  334. if (!resolution.equals(this.resolution)) {
  335. this.resolution = resolution;
  336. setValue(adjustToResolution(getValue(), resolution));
  337. updateResolutions();
  338. }
  339. }
  340. /**
  341. * Sets the end range for this component. If the value is set after this
  342. * date (taking the resolution into account), the component will not
  343. * validate. If {@code endDate} is set to {@code null}, any value after
  344. * {@code startDate} will be accepted by the range.
  345. * <p>
  346. * Note: It's usually recommended to use only one of the following at the
  347. * same time: Range validator with Binder or DateField's setRangeEnd check.
  348. *
  349. * @param endDate
  350. * the allowed range's end date (inclusive, based on the current
  351. * resolution)
  352. */
  353. public void setRangeEnd(T endDate) {
  354. String date = convertToDateString(endDate);
  355. if (afterDate(convertFromDateString(getState().rangeStart), endDate)) {
  356. throw new IllegalStateException(
  357. "endDate cannot be earlier than startDate");
  358. }
  359. getState().rangeEnd = date;
  360. }
  361. /**
  362. * Returns the precise rangeStart used.
  363. *
  364. * @return the precise rangeStart used, may be {@code null}.
  365. */
  366. public T getRangeStart() {
  367. return convertFromDateString(getState(false).rangeStart);
  368. }
  369. /**
  370. * Parses string representaion of date range limit into date type
  371. *
  372. * @param temporalStr
  373. * the string representation
  374. * @return parsed value
  375. * @see AbstractDateFieldState#rangeStart
  376. * @see AbstractDateFieldState#rangeEnd
  377. * @since 8.4
  378. */
  379. protected T convertFromDateString(String temporalStr) {
  380. if (temporalStr == null) {
  381. return null;
  382. }
  383. return toType(RANGE_FORMATTER.parse(temporalStr));
  384. }
  385. /**
  386. * Converts a temporal value into field-specific data type.
  387. *
  388. * @param temporalAccessor
  389. * - source value
  390. * @return conversion result.
  391. * @since 8.4
  392. */
  393. protected abstract T toType(TemporalAccessor temporalAccessor);
  394. /**
  395. * Converts date range limit into string representation.
  396. *
  397. * @param temporal
  398. * the value
  399. * @return textual representation
  400. * @see AbstractDateFieldState#rangeStart
  401. * @see AbstractDateFieldState#rangeEnd
  402. * @since 8.4
  403. */
  404. protected String convertToDateString(T temporal) {
  405. if (temporal == null) {
  406. return null;
  407. }
  408. return RANGE_FORMATTER.format(temporal);
  409. }
  410. /**
  411. * Checks if {@code value} is after {@code base} or not.
  412. *
  413. * @param value
  414. * temporal value
  415. * @param base
  416. * temporal value to compare to
  417. * @return {@code true} if {@code value} is after {@code base},
  418. * {@code false} otherwise
  419. */
  420. protected boolean afterDate(T value, T base) {
  421. if (value == null || base == null) {
  422. return false;
  423. }
  424. return value.compareTo(base) > 0;
  425. }
  426. /**
  427. * Returns the precise rangeEnd used.
  428. *
  429. * @return the precise rangeEnd used, may be {@code null}.
  430. */
  431. public T getRangeEnd() {
  432. return convertFromDateString(getState(false).rangeEnd);
  433. }
  434. /**
  435. * Sets formatting used by some component implementations. See
  436. * {@link SimpleDateFormat} for format details.
  437. *
  438. * By default it is encouraged to used default formatting defined by Locale,
  439. * but due some JVM bugs it is sometimes necessary to use this method to
  440. * override formatting. See Vaadin issue #2200.
  441. *
  442. * @param dateFormat
  443. * the dateFormat to set, can be {@code null}
  444. *
  445. * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
  446. */
  447. public void setDateFormat(String dateFormat) {
  448. getState().format = dateFormat;
  449. }
  450. /**
  451. * Returns a format string used to format date value on client side or
  452. * {@code null} if default formatting from {@link Component#getLocale()} is
  453. * used.
  454. *
  455. * @return the dateFormat
  456. */
  457. public String getDateFormat() {
  458. return getState(false).format;
  459. }
  460. /**
  461. * Sets the {@link ZoneId}, which is used when {@code z} is included inside
  462. * the {@link #setDateFormat(String)} .
  463. *
  464. * @param zoneId
  465. * the zone id
  466. * @since 8.2
  467. */
  468. public void setZoneId(ZoneId zoneId) {
  469. if (zoneId != this.zoneId
  470. || (zoneId != null && !zoneId.equals(this.zoneId))) {
  471. updateTimeZoneJSON(zoneId, getLocale(), getStartYear(),
  472. getEndYear());
  473. }
  474. this.zoneId = zoneId;
  475. }
  476. private void updateTimeZoneJSON(ZoneId zoneId, Locale locale, int startYear,
  477. int endYear) {
  478. String timeZoneJSON;
  479. if (zoneId != null && locale != null) {
  480. timeZoneJSON = TimeZoneUtil.toJSON(zoneId, locale, startYear,
  481. endYear);
  482. } else {
  483. timeZoneJSON = null;
  484. }
  485. getState().timeZoneJSON = timeZoneJSON;
  486. }
  487. /**
  488. * Sets {@link startYear} and {@link endYear}: the start and end years (both
  489. * inclusive) between which to calculate the daylight-saving time zone
  490. * transition dates. Both parameters are used when '{@code z}' is included
  491. * inside the {@link #setDateFormat(String)}, they would have no effect
  492. * otherwise. Specifically, these parameters determine the range of years in
  493. * which zone names are are adjusted to show the daylight saving names.
  494. *
  495. * If no values are provided, by default {@link startYear} is set to
  496. * {@value #DEFAULT_START_YEAR}, and {@link endYear} is set to
  497. * {@value #DEFAULT_YEARS_FROM_NOW} years into the future from the current
  498. * date.
  499. *
  500. * @param startYear
  501. * the start year of DST transitions
  502. * @param endYear
  503. * the end year of DST transitions
  504. * @since 8.11
  505. */
  506. public void setDaylightSavingTimeRange(int startYear, int endYear) {
  507. if (startYear > endYear) {
  508. throw new IllegalArgumentException(
  509. "The start year from which to begin calculating the "
  510. + "daylight-saving time zone transition dates must"
  511. + " be less than or equal to the end year.\n"
  512. + startYear + " is greater than " + endYear);
  513. }
  514. if (this.startYear == null || this.endYear == null
  515. || startYear != this.startYear || endYear != this.endYear) {
  516. updateTimeZoneJSON(getZoneId(), getLocale(), startYear, endYear);
  517. }
  518. this.startYear = startYear;
  519. this.endYear = endYear;
  520. }
  521. private int getStartYear() {
  522. if (startYear == null) {
  523. return DEFAULT_START_YEAR;
  524. } else {
  525. return startYear;
  526. }
  527. }
  528. private int getEndYear() {
  529. if (endYear == null) {
  530. return LocalDate.now().getYear() + DEFAULT_YEARS_FROM_NOW;
  531. } else {
  532. return endYear;
  533. }
  534. }
  535. @Override
  536. public void setLocale(Locale locale) {
  537. Locale oldLocale = getLocale();
  538. if (locale != oldLocale
  539. || (locale != null && !locale.equals(oldLocale))) {
  540. updateTimeZoneJSON(getZoneId(), locale, getStartYear(),
  541. getEndYear());
  542. }
  543. super.setLocale(locale);
  544. }
  545. private void updateResolutions() {
  546. final T currentDate = getValue();
  547. Map<String, Integer> resolutions = getState().resolutions;
  548. resolutions.clear();
  549. // Only paint variables for the resolution and up, e.g. Resolution DAY
  550. // paints DAY,MONTH,YEAR
  551. for (R resolution : getResolutionsHigherOrEqualTo(getResolution())) {
  552. String resolutionName = resolution.name();
  553. Integer value = getValuePart(currentDate, resolution);
  554. resolutions.put(resolutionName, value);
  555. Integer defaultValuePart = getValuePart(defaultValue, resolution);
  556. resolutions.put("default-" + resolutionName, defaultValuePart);
  557. }
  558. updateDiffstate("resolutions", Json.createObject());
  559. }
  560. private Integer getValuePart(T date, R resolution) {
  561. if (date == null) {
  562. return null;
  563. }
  564. return getDatePart(date, resolution);
  565. }
  566. /**
  567. * Returns the {@link ZoneId}, which is used when {@code z} is included
  568. * inside the {@link #setDateFormat(String)}.
  569. *
  570. * @return the zoneId
  571. * @since 8.2
  572. */
  573. public ZoneId getZoneId() {
  574. return zoneId;
  575. }
  576. /**
  577. * Specifies whether or not date/time interpretation in component is to be
  578. * lenient.
  579. *
  580. * @see Calendar#setLenient(boolean)
  581. * @see #isLenient()
  582. *
  583. * @param lenient
  584. * true if the lenient mode is to be turned on; false if it is to
  585. * be turned off.
  586. */
  587. public void setLenient(boolean lenient) {
  588. getState().lenient = lenient;
  589. }
  590. /**
  591. * Returns whether date/time interpretation is lenient.
  592. *
  593. * @see #setLenient(boolean)
  594. *
  595. * @return {@code true} if the interpretation mode of this calendar is
  596. * lenient; {@code false} otherwise.
  597. */
  598. public boolean isLenient() {
  599. return getState(false).lenient;
  600. }
  601. @Override
  602. public T getValue() {
  603. return value;
  604. }
  605. /**
  606. * Returns the current default value.
  607. *
  608. * @see #setDefaultValue(Temporal)
  609. * @return the default value
  610. * @since 8.1.2
  611. */
  612. public T getDefaultValue() {
  613. return defaultValue;
  614. }
  615. /**
  616. * Sets the default value for the field. The default value is the starting
  617. * point for the date field when nothing has been selected yet. If no
  618. * default value is set, current date/time is used.
  619. *
  620. * @param defaultValue
  621. * the default value, may be {@code null}
  622. * @since 8.1.2
  623. */
  624. public void setDefaultValue(T defaultValue) {
  625. this.defaultValue = defaultValue;
  626. updateResolutions();
  627. }
  628. /**
  629. * Sets the value of this object. If the new value is not equal to
  630. * {@code getValue()}, fires a {@link ValueChangeEvent} .
  631. *
  632. * @param value
  633. * the new value, may be {@code null}
  634. * @throws IllegalArgumentException
  635. * if the value is not within range bounds
  636. */
  637. @Override
  638. public void setValue(T value) {
  639. T adjusted = adjustToResolution(value, getResolution());
  640. RangeValidator<T> validator = getRangeValidator();
  641. ValidationResult result = validator.apply(adjusted,
  642. new ValueContext(this, this));
  643. if (result.isError()) {
  644. throw new IllegalArgumentException(
  645. "value is not within acceptable range");
  646. } else {
  647. currentErrorMessage = null;
  648. /*
  649. * First handle special case when the client side component has a
  650. * date string but value is null (e.g. unparsable date string typed
  651. * in by the user). No value changes should happen, but we need to
  652. * do some internal housekeeping.
  653. */
  654. if (adjusted == null && !getState(false).parsable) {
  655. /*
  656. * Side-effects of doSetValue clears possible previous strings
  657. * and flags about invalid input.
  658. */
  659. doSetValue(null);
  660. markAsDirty();
  661. return;
  662. }
  663. super.setValue(adjusted);
  664. }
  665. }
  666. /**
  667. * Adjusts the given date to the given resolution. Any values that are more
  668. * specific than the given resolution are truncated to their default values.
  669. *
  670. * @param date
  671. * the date to adjust, can be {@code null}
  672. * @param resolution
  673. * the resolution to be used in the adjustment, can be
  674. * {@code null}
  675. * @return an adjusted date that matches the given resolution, or
  676. * {@code null} if the given date, resolution, or both were
  677. * {@code null}
  678. */
  679. protected abstract T adjustToResolution(T date, R resolution);
  680. /**
  681. * Checks whether ISO 8601 week numbers are shown in the date selector.
  682. *
  683. * @return true if week numbers are shown, false otherwise.
  684. */
  685. public boolean isShowISOWeekNumbers() {
  686. return getState(false).showISOWeekNumbers;
  687. }
  688. /**
  689. * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
  690. * 8601 defines that a week always starts with a Monday so the week numbers
  691. * are only shown if this is the case.
  692. *
  693. * @param showWeekNumbers
  694. * true if week numbers should be shown, false otherwise.
  695. */
  696. public void setShowISOWeekNumbers(boolean showWeekNumbers) {
  697. getState().showISOWeekNumbers = showWeekNumbers;
  698. }
  699. /**
  700. * Return the error message that is shown if the user inputted value can't
  701. * be parsed into a Date object. If
  702. * {@link #handleUnparsableDateString(String)} is overridden and it throws a
  703. * custom exception, the message returned by
  704. * {@link Exception#getLocalizedMessage()} will be used instead of the value
  705. * returned by this method.
  706. *
  707. * @see #setParseErrorMessage(String)
  708. *
  709. * @return the error message that the DateField uses when it can't parse the
  710. * textual input from user to a Date object
  711. */
  712. public String getParseErrorMessage() {
  713. return defaultParseErrorMessage;
  714. }
  715. /**
  716. * Sets the default error message used if the DateField cannot parse the
  717. * text input by user to a Date field. Note that if the
  718. * {@link #handleUnparsableDateString(String)} method is overridden, the
  719. * localized message from its exception is used.
  720. *
  721. * @param parsingErrorMessage
  722. * the default parsing error message
  723. *
  724. * @see #getParseErrorMessage()
  725. * @see #handleUnparsableDateString(String)
  726. */
  727. public void setParseErrorMessage(String parsingErrorMessage) {
  728. defaultParseErrorMessage = parsingErrorMessage;
  729. }
  730. @Override
  731. public Registration addFocusListener(FocusListener listener) {
  732. return addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  733. FocusListener.focusMethod);
  734. }
  735. @Override
  736. public Registration addBlurListener(BlurListener listener) {
  737. return addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  738. BlurListener.blurMethod);
  739. }
  740. @Override
  741. @SuppressWarnings("unchecked")
  742. public void readDesign(Element design, DesignContext designContext) {
  743. super.readDesign(design, designContext);
  744. if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
  745. Type dateType = GenericTypeReflector.getTypeParameter(getClass(),
  746. AbstractDateField.class.getTypeParameters()[0]);
  747. if (dateType instanceof Class<?>) {
  748. Class<?> clazz = (Class<?>) dateType;
  749. T date = (T) DesignAttributeHandler.getFormatter()
  750. .parse(design.attr("value"), clazz);
  751. // formatting will return null if it cannot parse the string
  752. if (date == null) {
  753. Logger.getLogger(AbstractDateField.class.getName())
  754. .info("cannot parse " + design.attr("value")
  755. + " as date");
  756. }
  757. doSetValue(adjustToResolution(date, getResolution()));
  758. } else {
  759. throw new RuntimeException("Cannot detect resoluton type "
  760. + Optional.ofNullable(dateType).map(Type::getTypeName)
  761. .orElse(null));
  762. }
  763. }
  764. }
  765. /**
  766. * Formats date according to the components locale.
  767. *
  768. * @param value
  769. * the date or {@code null}
  770. * @return textual representation of the date or empty string for
  771. * {@code null}
  772. * @since 8.1.1
  773. */
  774. protected abstract String formatDate(T value);
  775. @Override
  776. public void writeDesign(Element design, DesignContext designContext) {
  777. super.writeDesign(design, designContext);
  778. if (getValue() != null) {
  779. design.attr("value",
  780. DesignAttributeHandler.getFormatter().format(getValue()));
  781. }
  782. }
  783. /**
  784. * This method is called to handle a non-empty date string from the client
  785. * if the client could not parse it as a Date.
  786. *
  787. * By default, an error result is returned whose error message is
  788. * {@link #getParseErrorMessage()}.
  789. *
  790. * This can be overridden to handle conversions, to return a result with
  791. * {@code null} value (equivalent to empty input) or to return a custom
  792. * error.
  793. *
  794. * @param dateString
  795. * date string to handle
  796. * @return result that contains parsed Date as a value or an error
  797. */
  798. protected Result<T> handleUnparsableDateString(String dateString) {
  799. return Result.error(getParseErrorMessage());
  800. }
  801. @Override
  802. protected AbstractDateFieldState getState() {
  803. return (AbstractDateFieldState) super.getState();
  804. }
  805. @Override
  806. protected AbstractDateFieldState getState(boolean markAsDirty) {
  807. return (AbstractDateFieldState) super.getState(markAsDirty);
  808. }
  809. @Override
  810. protected void doSetValue(T value) {
  811. // Also set the internal dateString
  812. this.value = value;
  813. if (value == null) {
  814. value = getEmptyValue();
  815. }
  816. dateString = formatDate(value);
  817. // TODO move range check to internal validator?
  818. RangeValidator<T> validator = getRangeValidator();
  819. ValidationResult result = validator.apply(value,
  820. new ValueContext(this, this));
  821. if (result.isError()) {
  822. currentErrorMessage = getDateOutOfRangeMessage();
  823. }
  824. getState().parsable = currentErrorMessage == null;
  825. ErrorMessage errorMessage;
  826. if (currentErrorMessage == null) {
  827. errorMessage = null;
  828. } else {
  829. errorMessage = new UserError(currentErrorMessage);
  830. }
  831. setComponentError(errorMessage);
  832. updateResolutions();
  833. }
  834. /**
  835. * Returns a date integer value part for the given {@code date} for the
  836. * given {@code resolution}.
  837. *
  838. * @param date
  839. * the given date, can be {@code null}
  840. * @param resolution
  841. * the resolution to extract a value from the date by, not
  842. * {@code null}
  843. * @return the integer value part of the date by the given resolution
  844. */
  845. protected abstract int getDatePart(T date, R resolution);
  846. /**
  847. * Builds date by the given {@code resolutionValues} which is a map whose
  848. * keys are resolution and integer values.
  849. * <p>
  850. * This is the opposite to {@link #getDatePart(Temporal, Enum)}.
  851. *
  852. * @param resolutionValues
  853. * date values to construct a date
  854. * @return date built from the given map of date values
  855. */
  856. protected abstract T buildDate(Map<R, Integer> resolutionValues);
  857. /**
  858. * Returns a custom date range validator which is applicable for the type
  859. * {@code T}.
  860. *
  861. * @return the date range validator
  862. */
  863. protected abstract RangeValidator<T> getRangeValidator();
  864. /**
  865. * Converts {@link Date} to date type {@code T}.
  866. *
  867. * @param date
  868. * a date to convert
  869. * @return object of type {@code T} representing the {@code date}
  870. */
  871. protected abstract T convertFromDate(Date date);
  872. /**
  873. * Converts the object of type {@code T} to {@link Date}.
  874. * <p>
  875. * This is the opposite to {@link #convertFromDate(Date)}.
  876. *
  877. * @param date
  878. * the date of type {@code T}
  879. * @return converted date of type {@code Date}
  880. */
  881. protected abstract Date convertToDate(T date);
  882. @SuppressWarnings("unchecked")
  883. private Stream<R> getResolutions() {
  884. Type resolutionType = GenericTypeReflector.getTypeParameter(getClass(),
  885. AbstractDateField.class.getTypeParameters()[1]);
  886. if (resolutionType instanceof Class<?>) {
  887. Class<?> clazz = (Class<?>) resolutionType;
  888. return Stream.of(clazz.getEnumConstants())
  889. .map(object -> (R) object);
  890. }
  891. throw new RuntimeException("Cannot detect resoluton type "
  892. + Optional.ofNullable(resolutionType).map(Type::getTypeName)
  893. .orElse(null));
  894. }
  895. private Iterable<R> getResolutionsHigherOrEqualTo(R resoution) {
  896. return getResolutions().skip(resolution.ordinal())
  897. .collect(Collectors.toList());
  898. }
  899. @Override
  900. public Validator<T> getDefaultValidator() {
  901. return new Validator<T>() {
  902. @Override
  903. public ValidationResult apply(T value, ValueContext context) {
  904. // currentErrorMessage contains two type of messages, one is
  905. // DateOutOfRangeMessage and the other one is the ParseError
  906. if (currentErrorMessage != null) {
  907. if (currentErrorMessage
  908. .equals(getDateOutOfRangeMessage())) {
  909. // if the currentErrorMessage is DateOutOfRangeMessage,
  910. // then need to double check whether the error message
  911. // has been updated, that is because of #11276.
  912. ValidationResult validationResult = getRangeValidator()
  913. .apply(value, context);
  914. if (validationResult.isError()) {
  915. return ValidationResult.error(currentErrorMessage);
  916. }
  917. } else {
  918. // if the current Error is parsing error, pass it to the
  919. // ValidationResult
  920. return ValidationResult.error(currentErrorMessage);
  921. }
  922. }
  923. // Pass to range validator.
  924. return getRangeValidator().apply(value, context);
  925. }
  926. };
  927. }
  928. /**
  929. * <p>
  930. * Sets a custom style name for the given date's calendar cell. Setting the
  931. * style name will override any previous style names that have been set for
  932. * that date, but can contain several actual style names separated by space.
  933. * Setting the custom style name {@code null} will only remove the previous
  934. * custom style name.
  935. * </p>
  936. * <p>
  937. * This logic is entirely separate from {@link #setStyleName(String)}
  938. * </p>
  939. * <p>
  940. * Usage examples: <br>
  941. * {@code setDateStyle(LocalDate.now(), "teststyle");} <br>
  942. * {@code setDateStyle(LocalDate.now(), "teststyle1 teststyle2");}
  943. * </p>
  944. *
  945. * @param date
  946. * which date cell to modify, not {@code null}
  947. * @param styleName
  948. * the custom style name(s) for given date, {@code null} to clear
  949. * custom style name(s)
  950. *
  951. * @since 8.3
  952. */
  953. public void setDateStyle(LocalDate date, String styleName) {
  954. Objects.requireNonNull(date, "Date cannot be null");
  955. if (styleName != null) {
  956. getState().dateStyles.put(date.toString(), styleName);
  957. } else {
  958. getState().dateStyles.remove(date.toString());
  959. }
  960. }
  961. /**
  962. * Returns the custom style name that corresponds with the given date's
  963. * calendar cell.
  964. *
  965. * @param date
  966. * which date cell's custom style name(s) to return, not
  967. * {@code null}
  968. * @return the corresponding style name(s), if any, {@code null} otherwise
  969. *
  970. * @see #setDateStyle(LocalDate, String)
  971. * @since 8.3
  972. */
  973. public String getDateStyle(LocalDate date) {
  974. Objects.requireNonNull(date, "Date cannot be null");
  975. return getState(false).dateStyles.get(date.toString());
  976. }
  977. /**
  978. * Returns a map from dates to custom style names in each date's calendar
  979. * cell.
  980. *
  981. * @return unmodifiable map from dates to custom style names in each date's
  982. * calendar cell
  983. *
  984. * @see #setDateStyle(LocalDate, String)
  985. * @since 8.3
  986. */
  987. public Map<LocalDate, String> getDateStyles() {
  988. HashMap<LocalDate, String> hashMap = new HashMap<>();
  989. for (Entry<String, String> entry : getState(false).dateStyles
  990. .entrySet()) {
  991. hashMap.put(LocalDate.parse(entry.getKey()), entry.getValue());
  992. }
  993. return Collections.unmodifiableMap(hashMap);
  994. }
  995. /**
  996. * Sets the assistive label for a calendar navigation element. This sets the
  997. * {@code aria-label} attribute for the element which is used by screen
  998. * reading software.
  999. *
  1000. * @param element
  1001. * the element for which to set the label. Not {@code null}.
  1002. * @param label
  1003. * the assistive label to set
  1004. * @since 8.4
  1005. */
  1006. public void setAssistiveLabel(AccessibleElement element, String label) {
  1007. Objects.requireNonNull(element, "Element cannot be null");
  1008. getState().assistiveLabels.put(element, label);
  1009. }
  1010. /**
  1011. * Gets the assistive label of a calendar navigation element.
  1012. *
  1013. * @param element
  1014. * the element of which to get the assistive label
  1015. * @since 8.4
  1016. */
  1017. public void getAssistiveLabel(AccessibleElement element) {
  1018. getState(false).assistiveLabels.get(element);
  1019. }
  1020. }