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 33KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  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 com.googlecode.gentyref.GenericTypeReflector;
  40. import org.jsoup.nodes.Element;
  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. * Value of the field.
  163. */
  164. private T value;
  165. /**
  166. * Default value of the field, displayed when nothing has been selected.
  167. *
  168. * @since 8.1.2
  169. */
  170. private T defaultValue;
  171. /**
  172. * Specified smallest modifiable unit for the date field.
  173. */
  174. private R resolution;
  175. private ZoneId zoneId;
  176. private String dateString = "";
  177. private String currentErrorMessage;
  178. private String defaultParseErrorMessage = "Date format not recognized";
  179. private String dateOutOfRangeMessage = "Date is out of allowed range";
  180. /* Constructors */
  181. /**
  182. * Constructs an empty {@code AbstractDateField} with no caption and
  183. * specified {@code resolution}.
  184. *
  185. * @param resolution
  186. * initial resolution for the field, not {@code null}
  187. */
  188. public AbstractDateField(R resolution) {
  189. registerRpc(rpc);
  190. setResolution(resolution);
  191. }
  192. /**
  193. * Constructs an empty {@code AbstractDateField} with caption.
  194. *
  195. * @param caption
  196. * the caption of the datefield
  197. * @param resolution
  198. * initial resolution for the field, not {@code null}
  199. */
  200. public AbstractDateField(String caption, R resolution) {
  201. this(resolution);
  202. setCaption(caption);
  203. }
  204. /**
  205. * Constructs a new {@code AbstractDateField} with the given caption and
  206. * initial text contents.
  207. *
  208. * @param caption
  209. * the caption {@code String} for the editor.
  210. * @param value
  211. * the date/time value.
  212. * @param resolution
  213. * initial resolution for the field, not {@code null}
  214. */
  215. public AbstractDateField(String caption, T value, R resolution) {
  216. this(caption, resolution);
  217. setValue(value);
  218. }
  219. /* Component basic features */
  220. @Override
  221. public void beforeClientResponse(boolean initial) {
  222. super.beforeClientResponse(initial);
  223. Locale locale = getLocale();
  224. getState().locale = locale == null ? null : locale.toString();
  225. }
  226. /**
  227. * Construct a date object from the individual field values received from
  228. * the client.
  229. *
  230. * @param resolutions
  231. * map of time unit (resolution) name and value, the key is the
  232. * resolution name e.g. "HOUR", "MINUTE", the value can be
  233. * {@code null}
  234. * @param oldDate
  235. * used as a fallback to get needed values if they are not
  236. * defined in the specified {@code resolutions}
  237. *
  238. * @return the date object built from the specified resolutions
  239. * @since 8.2
  240. */
  241. protected T reconstructDateFromFields(Map<String, Integer> resolutions,
  242. T oldDate) {
  243. Map<R, Integer> calendarFields = new HashMap<>();
  244. for (R resolution : getResolutionsHigherOrEqualTo(getResolution())) {
  245. // Only handle what the client is allowed to send. The same
  246. // resolutions that are painted
  247. String resolutionName = resolution.name();
  248. Integer newValue = resolutions.get(resolutionName);
  249. if (newValue == null) {
  250. newValue = getDatePart(oldDate, resolution);
  251. }
  252. calendarFields.put(resolution, newValue);
  253. }
  254. return buildDate(calendarFields);
  255. }
  256. /**
  257. * Sets the start range for this component. If the value is set before this
  258. * date (taking the resolution into account), the component will not
  259. * validate. If {@code startDate} is set to {@code null}, any value before
  260. * {@code endDate} will be accepted by the range
  261. *
  262. * @param startDate
  263. * - the allowed range's start date
  264. */
  265. public void setRangeStart(T startDate) {
  266. if (afterDate(startDate, convertFromDateString(getState().rangeEnd))) {
  267. throw new IllegalStateException(
  268. "startDate cannot be later than endDate");
  269. }
  270. getState().rangeStart = convertToDateString(startDate);
  271. }
  272. /**
  273. * Sets the current error message if the range validation fails.
  274. *
  275. * @param dateOutOfRangeMessage
  276. * - Localizable message which is shown when value (the date) is
  277. * set outside allowed range
  278. */
  279. public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
  280. this.dateOutOfRangeMessage = dateOutOfRangeMessage;
  281. }
  282. /**
  283. * Returns current date-out-of-range error message.
  284. *
  285. * @see #setDateOutOfRangeMessage(String)
  286. * @return Current error message for dates out of range.
  287. */
  288. public String getDateOutOfRangeMessage() {
  289. return dateOutOfRangeMessage;
  290. }
  291. /**
  292. * Gets the resolution.
  293. *
  294. * @return the date/time field resolution
  295. */
  296. public R getResolution() {
  297. return resolution;
  298. }
  299. /**
  300. * Sets the resolution of the DateField.
  301. *
  302. * The default resolution is {@link DateResolution#DAY} since Vaadin 7.0.
  303. *
  304. * @param resolution
  305. * the resolution to set, not {@code null}
  306. */
  307. public void setResolution(R resolution) {
  308. this.resolution = resolution;
  309. updateResolutions();
  310. }
  311. /**
  312. * Sets the end range for this component. If the value is set after this
  313. * date (taking the resolution into account), the component will not
  314. * validate. If {@code endDate} is set to {@code null}, any value after
  315. * {@code startDate} will be accepted by the range.
  316. *
  317. * @param endDate
  318. * the allowed range's end date (inclusive, based on the current
  319. * resolution)
  320. */
  321. public void setRangeEnd(T endDate) {
  322. String date = convertToDateString(endDate);
  323. if (afterDate(convertFromDateString(getState().rangeStart), endDate)) {
  324. throw new IllegalStateException(
  325. "endDate cannot be earlier than startDate");
  326. }
  327. getState().rangeEnd = date;
  328. }
  329. /**
  330. * Returns the precise rangeStart used.
  331. *
  332. * @return the precise rangeStart used, may be {@code null}.
  333. */
  334. public T getRangeStart() {
  335. return convertFromDateString(getState(false).rangeStart);
  336. }
  337. /**
  338. * Parses string representaion of date range limit into date type
  339. *
  340. * @param temporalStr
  341. * the string representation
  342. * @return parsed value
  343. * @see AbstractDateFieldState#rangeStart
  344. * @see AbstractDateFieldState#rangeEnd
  345. * @since 8.4
  346. */
  347. protected T convertFromDateString(String temporalStr) {
  348. if (temporalStr == null) {
  349. return null;
  350. }
  351. return toType(RANGE_FORMATTER.parse(temporalStr));
  352. }
  353. /**
  354. * Converts a temporal value into field-specific data type.
  355. *
  356. * @param temporalAccessor
  357. * - source value
  358. * @return conversion result.
  359. * @since 8.4
  360. */
  361. protected abstract T toType(TemporalAccessor temporalAccessor);
  362. /**
  363. * Converts date range limit into string representation.
  364. *
  365. * @param temporal
  366. * the value
  367. * @return textual representation
  368. * @see AbstractDateFieldState#rangeStart
  369. * @see AbstractDateFieldState#rangeEnd
  370. * @since 8.4
  371. */
  372. protected String convertToDateString(T temporal) {
  373. if (temporal == null) {
  374. return null;
  375. }
  376. return RANGE_FORMATTER.format(temporal);
  377. }
  378. /**
  379. * Checks if {@code value} is after {@code base} or not.
  380. *
  381. * @param value
  382. * temporal value
  383. * @param base
  384. * temporal value to compare to
  385. * @return {@code true} if {@code value} is after {@code base},
  386. * {@code false} otherwise
  387. */
  388. protected boolean afterDate(T value, T base) {
  389. if (value == null || base == null) {
  390. return false;
  391. }
  392. return value.compareTo(base) > 0;
  393. }
  394. /**
  395. * Returns the precise rangeEnd used.
  396. *
  397. * @return the precise rangeEnd used, may be {@code null}.
  398. */
  399. public T getRangeEnd() {
  400. return convertFromDateString(getState(false).rangeEnd);
  401. }
  402. /**
  403. * Sets formatting used by some component implementations. See
  404. * {@link SimpleDateFormat} for format details.
  405. *
  406. * By default it is encouraged to used default formatting defined by Locale,
  407. * but due some JVM bugs it is sometimes necessary to use this method to
  408. * override formatting. See Vaadin issue #2200.
  409. *
  410. * @param dateFormat
  411. * the dateFormat to set, can be {@code null}
  412. *
  413. * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
  414. */
  415. public void setDateFormat(String dateFormat) {
  416. getState().format = dateFormat;
  417. }
  418. /**
  419. * Returns a format string used to format date value on client side or
  420. * {@code null} if default formatting from {@link Component#getLocale()} is
  421. * used.
  422. *
  423. * @return the dateFormat
  424. */
  425. public String getDateFormat() {
  426. return getState(false).format;
  427. }
  428. /**
  429. * Sets the {@link ZoneId}, which is used when {@code z} is included inside
  430. * the {@link #setDateFormat(String)}.
  431. *
  432. * @param zoneId
  433. * the zone id
  434. * @since 8.2
  435. */
  436. public void setZoneId(ZoneId zoneId) {
  437. if (zoneId != this.zoneId
  438. || (zoneId != null && !zoneId.equals(this.zoneId))) {
  439. updateTimeZoneJSON(zoneId, getLocale());
  440. }
  441. this.zoneId = zoneId;
  442. }
  443. private void updateTimeZoneJSON(ZoneId zoneId, Locale locale) {
  444. String timeZoneJSON;
  445. if (zoneId != null && locale != null) {
  446. timeZoneJSON = TimeZoneUtil.toJSON(zoneId, locale);
  447. } else {
  448. timeZoneJSON = null;
  449. }
  450. getState().timeZoneJSON = timeZoneJSON;
  451. }
  452. @Override
  453. public void setLocale(Locale locale) {
  454. Locale oldLocale = getLocale();
  455. if (locale != oldLocale
  456. || (locale != null && !locale.equals(oldLocale))) {
  457. updateTimeZoneJSON(getZoneId(), locale);
  458. }
  459. super.setLocale(locale);
  460. }
  461. private void updateResolutions() {
  462. final T currentDate = getValue();
  463. Map<String, Integer> resolutions = getState().resolutions;
  464. resolutions.clear();
  465. // Only paint variables for the resolution and up, e.g. Resolution DAY
  466. // paints DAY,MONTH,YEAR
  467. for (R resolution : getResolutionsHigherOrEqualTo(getResolution())) {
  468. String resolutionName = resolution.name();
  469. Integer value = getValuePart(currentDate, resolution);
  470. resolutions.put(resolutionName, value);
  471. Integer defaultValuePart = getValuePart(defaultValue, resolution);
  472. resolutions.put("default-" + resolutionName, defaultValuePart);
  473. }
  474. updateDiffstate("resolutions", Json.createObject());
  475. }
  476. private Integer getValuePart(T date, R resolution) {
  477. if (date == null) {
  478. return null;
  479. }
  480. return getDatePart(date, resolution);
  481. }
  482. /**
  483. * Returns the {@link ZoneId}, which is used when {@code z} is included
  484. * inside the {@link #setDateFormat(String)}.
  485. *
  486. * @return the zoneId
  487. * @since 8.2
  488. */
  489. public ZoneId getZoneId() {
  490. return zoneId;
  491. }
  492. /**
  493. * Specifies whether or not date/time interpretation in component is to be
  494. * lenient.
  495. *
  496. * @see Calendar#setLenient(boolean)
  497. * @see #isLenient()
  498. *
  499. * @param lenient
  500. * true if the lenient mode is to be turned on; false if it is to
  501. * be turned off.
  502. */
  503. public void setLenient(boolean lenient) {
  504. getState().lenient = lenient;
  505. }
  506. /**
  507. * Returns whether date/time interpretation is lenient.
  508. *
  509. * @see #setLenient(boolean)
  510. *
  511. * @return {@code true} if the interpretation mode of this calendar is
  512. * lenient; {@code false} otherwise.
  513. */
  514. public boolean isLenient() {
  515. return getState(false).lenient;
  516. }
  517. @Override
  518. public T getValue() {
  519. return value;
  520. }
  521. /**
  522. * Returns the current default value.
  523. *
  524. * @see #setDefaultValue(Temporal)
  525. * @return the default value
  526. * @since 8.1.2
  527. */
  528. public T getDefaultValue() {
  529. return defaultValue;
  530. }
  531. /**
  532. * Sets the default value for the field. The default value is the starting
  533. * point for the date field when nothing has been selected yet. If no
  534. * default value is set, current date/time is used.
  535. *
  536. * @param defaultValue
  537. * the default value, may be {@code null}
  538. * @since 8.1.2
  539. */
  540. public void setDefaultValue(T defaultValue) {
  541. this.defaultValue = defaultValue;
  542. updateResolutions();
  543. }
  544. /**
  545. * Sets the value of this object. If the new value is not equal to
  546. * {@code getValue()}, fires a {@link ValueChangeEvent} .
  547. *
  548. * @param value
  549. * the new value, may be {@code null}
  550. */
  551. @Override
  552. public void setValue(T value) {
  553. currentErrorMessage = null;
  554. /*
  555. * First handle special case when the client side component have a date
  556. * string but value is null (e.g. unparsable date string typed in by the
  557. * user). No value changes should happen, but we need to do some
  558. * internal housekeeping.
  559. */
  560. if (value == null && !getState(false).parsable) {
  561. /*
  562. * Side-effects of doSetValue clears possible previous strings and
  563. * flags about invalid input.
  564. */
  565. doSetValue(null);
  566. markAsDirty();
  567. return;
  568. }
  569. super.setValue(value);
  570. }
  571. /**
  572. * Checks whether ISO 8601 week numbers are shown in the date selector.
  573. *
  574. * @return true if week numbers are shown, false otherwise.
  575. */
  576. public boolean isShowISOWeekNumbers() {
  577. return getState(false).showISOWeekNumbers;
  578. }
  579. /**
  580. * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
  581. * 8601 defines that a week always starts with a Monday so the week numbers
  582. * are only shown if this is the case.
  583. *
  584. * @param showWeekNumbers
  585. * true if week numbers should be shown, false otherwise.
  586. */
  587. public void setShowISOWeekNumbers(boolean showWeekNumbers) {
  588. getState().showISOWeekNumbers = showWeekNumbers;
  589. }
  590. /**
  591. * Return the error message that is shown if the user inputted value can't
  592. * be parsed into a Date object. If
  593. * {@link #handleUnparsableDateString(String)} is overridden and it throws a
  594. * custom exception, the message returned by
  595. * {@link Exception#getLocalizedMessage()} will be used instead of the value
  596. * returned by this method.
  597. *
  598. * @see #setParseErrorMessage(String)
  599. *
  600. * @return the error message that the DateField uses when it can't parse the
  601. * textual input from user to a Date object
  602. */
  603. public String getParseErrorMessage() {
  604. return defaultParseErrorMessage;
  605. }
  606. /**
  607. * Sets the default error message used if the DateField cannot parse the
  608. * text input by user to a Date field. Note that if the
  609. * {@link #handleUnparsableDateString(String)} method is overridden, the
  610. * localized message from its exception is used.
  611. *
  612. * @param parsingErrorMessage
  613. * the default parsing error message
  614. *
  615. * @see #getParseErrorMessage()
  616. * @see #handleUnparsableDateString(String)
  617. */
  618. public void setParseErrorMessage(String parsingErrorMessage) {
  619. defaultParseErrorMessage = parsingErrorMessage;
  620. }
  621. @Override
  622. public Registration addFocusListener(FocusListener listener) {
  623. return addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  624. FocusListener.focusMethod);
  625. }
  626. @Override
  627. public Registration addBlurListener(BlurListener listener) {
  628. return addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  629. BlurListener.blurMethod);
  630. }
  631. @Override
  632. @SuppressWarnings("unchecked")
  633. public void readDesign(Element design, DesignContext designContext) {
  634. super.readDesign(design, designContext);
  635. if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
  636. Type dateType = GenericTypeReflector.getTypeParameter(getClass(),
  637. AbstractDateField.class.getTypeParameters()[0]);
  638. if (dateType instanceof Class<?>) {
  639. Class<?> clazz = (Class<?>) dateType;
  640. T date = (T) DesignAttributeHandler.getFormatter()
  641. .parse(design.attr("value"), clazz);
  642. // formatting will return null if it cannot parse the string
  643. if (date == null) {
  644. Logger.getLogger(AbstractDateField.class.getName())
  645. .info("cannot parse " + design.attr("value")
  646. + " as date");
  647. }
  648. doSetValue(date);
  649. } else {
  650. throw new RuntimeException("Cannot detect resoluton type "
  651. + Optional.ofNullable(dateType).map(Type::getTypeName)
  652. .orElse(null));
  653. }
  654. }
  655. }
  656. /**
  657. * Formats date according to the components locale.
  658. *
  659. * @param value
  660. * the date or {@code null}
  661. * @return textual representation of the date or empty string for
  662. * {@code null}
  663. * @since 8.1.1
  664. */
  665. protected abstract String formatDate(T value);
  666. @Override
  667. public void writeDesign(Element design, DesignContext designContext) {
  668. super.writeDesign(design, designContext);
  669. if (getValue() != null) {
  670. design.attr("value",
  671. DesignAttributeHandler.getFormatter().format(getValue()));
  672. }
  673. }
  674. /**
  675. * This method is called to handle a non-empty date string from the client
  676. * if the client could not parse it as a Date.
  677. *
  678. * By default, an error result is returned whose error message is
  679. * {@link #getParseErrorMessage()}.
  680. *
  681. * This can be overridden to handle conversions, to return a result with
  682. * {@code null} value (equivalent to empty input) or to return a custom
  683. * error.
  684. *
  685. * @param dateString
  686. * date string to handle
  687. * @return result that contains parsed Date as a value or an error
  688. */
  689. protected Result<T> handleUnparsableDateString(String dateString) {
  690. return Result.error(getParseErrorMessage());
  691. }
  692. @Override
  693. protected AbstractDateFieldState getState() {
  694. return (AbstractDateFieldState) super.getState();
  695. }
  696. @Override
  697. protected AbstractDateFieldState getState(boolean markAsDirty) {
  698. return (AbstractDateFieldState) super.getState(markAsDirty);
  699. }
  700. @Override
  701. protected void doSetValue(T value) {
  702. this.value = value;
  703. // Also set the internal dateString
  704. if (value == null) {
  705. value = getEmptyValue();
  706. }
  707. dateString = formatDate(value);
  708. // TODO move range check to internal validator?
  709. RangeValidator<T> validator = getRangeValidator();
  710. ValidationResult result = validator.apply(value,
  711. new ValueContext(this, this));
  712. if (result.isError()) {
  713. currentErrorMessage = getDateOutOfRangeMessage();
  714. }
  715. getState().parsable = currentErrorMessage == null;
  716. ErrorMessage errorMessage;
  717. if (currentErrorMessage == null) {
  718. errorMessage = null;
  719. } else {
  720. errorMessage = new UserError(currentErrorMessage);
  721. }
  722. setComponentError(errorMessage);
  723. updateResolutions();
  724. }
  725. /**
  726. * Returns a date integer value part for the given {@code date} for the
  727. * given {@code resolution}.
  728. *
  729. * @param date
  730. * the given date, can be {@code null}
  731. * @param resolution
  732. * the resolution to extract a value from the date by, not
  733. * {@code null}
  734. * @return the integer value part of the date by the given resolution
  735. */
  736. protected abstract int getDatePart(T date, R resolution);
  737. /**
  738. * Builds date by the given {@code resolutionValues} which is a map whose
  739. * keys are resolution and integer values.
  740. * <p>
  741. * This is the opposite to {@link #getDatePart(Temporal, Enum)}.
  742. *
  743. * @param resolutionValues
  744. * date values to construct a date
  745. * @return date built from the given map of date values
  746. */
  747. protected abstract T buildDate(Map<R, Integer> resolutionValues);
  748. /**
  749. * Returns a custom date range validator which is applicable for the type
  750. * {@code T}.
  751. *
  752. * @return the date range validator
  753. */
  754. protected abstract RangeValidator<T> getRangeValidator();
  755. /**
  756. * Converts {@link Date} to date type {@code T}.
  757. *
  758. * @param date
  759. * a date to convert
  760. * @return object of type {@code T} representing the {@code date}
  761. */
  762. protected abstract T convertFromDate(Date date);
  763. /**
  764. * Converts the object of type {@code T} to {@link Date}.
  765. * <p>
  766. * This is the opposite to {@link #convertFromDate(Date)}.
  767. *
  768. * @param date
  769. * the date of type {@code T}
  770. * @return converted date of type {@code Date}
  771. */
  772. protected abstract Date convertToDate(T date);
  773. @SuppressWarnings("unchecked")
  774. private Stream<R> getResolutions() {
  775. Type resolutionType = GenericTypeReflector.getTypeParameter(getClass(),
  776. AbstractDateField.class.getTypeParameters()[1]);
  777. if (resolutionType instanceof Class<?>) {
  778. Class<?> clazz = (Class<?>) resolutionType;
  779. return Stream.of(clazz.getEnumConstants())
  780. .map(object -> (R) object);
  781. }
  782. throw new RuntimeException("Cannot detect resoluton type "
  783. + Optional.ofNullable(resolutionType).map(Type::getTypeName)
  784. .orElse(null));
  785. }
  786. private Iterable<R> getResolutionsHigherOrEqualTo(R resoution) {
  787. return getResolutions().skip(resolution.ordinal())
  788. .collect(Collectors.toList());
  789. }
  790. @Override
  791. public Validator<T> getDefaultValidator() {
  792. return new Validator<T>() {
  793. @Override
  794. public ValidationResult apply(T value, ValueContext context) {
  795. // currentErrorMessage contains two type of messages, one is
  796. // DateOutOfRangeMessage and the other one is the ParseError
  797. if (currentErrorMessage != null) {
  798. if (currentErrorMessage
  799. .equals(getDateOutOfRangeMessage())) {
  800. // if the currentErrorMessage is DateOutOfRangeMessage,
  801. // then need to double check whether the error message
  802. // has been updated, that is because of #11276.
  803. ValidationResult validationResult = getRangeValidator()
  804. .apply(value, context);
  805. if (validationResult.isError()) {
  806. return ValidationResult.error(currentErrorMessage);
  807. }
  808. } else {
  809. // if the current Error is parsing error, pass it to the
  810. // ValidationResult
  811. return ValidationResult.error(currentErrorMessage);
  812. }
  813. }
  814. // Pass to range validator.
  815. return getRangeValidator().apply(value, context);
  816. }
  817. };
  818. }
  819. /**
  820. * <p>
  821. * Sets a custom style name for the given date's calendar cell. Setting the
  822. * style name will override any previous style names that have been set for
  823. * that date, but can contain several actual style names separated by space.
  824. * Setting the custom style name {@code null} will only remove the previous
  825. * custom style name.
  826. * </p>
  827. * <p>
  828. * This logic is entirely separate from {@link #setStyleName(String)}
  829. * </p>
  830. * <p>
  831. * Usage examples: <br>
  832. * {@code setDateStyle(LocalDate.now(), "teststyle");} <br>
  833. * {@code setDateStyle(LocalDate.now(), "teststyle1 teststyle2");}
  834. * </p>
  835. *
  836. * @param date
  837. * which date cell to modify, not {@code null}
  838. * @param styleName
  839. * the custom style name(s) for given date, {@code null} to clear
  840. * custom style name(s)
  841. *
  842. * @since 8.3
  843. */
  844. public void setDateStyle(LocalDate date, String styleName) {
  845. Objects.requireNonNull(date, "Date cannot be null");
  846. if (styleName != null) {
  847. getState().dateStyles.put(date.toString(), styleName);
  848. } else {
  849. getState().dateStyles.remove(date.toString());
  850. }
  851. }
  852. /**
  853. * Returns the custom style name that corresponds with the given date's
  854. * calendar cell.
  855. *
  856. * @param date
  857. * which date cell's custom style name(s) to return, not
  858. * {@code null}
  859. * @return the corresponding style name(s), if any, {@code null} otherwise
  860. *
  861. * @see #setDateStyle(LocalDate, String)
  862. * @since 8.3
  863. */
  864. public String getDateStyle(LocalDate date) {
  865. Objects.requireNonNull(date, "Date cannot be null");
  866. return getState(false).dateStyles.get(date.toString());
  867. }
  868. /**
  869. * Returns a map from dates to custom style names in each date's calendar
  870. * cell.
  871. *
  872. * @return unmodifiable map from dates to custom style names in each date's
  873. * calendar cell
  874. *
  875. * @see #setDateStyle(LocalDate, String)
  876. * @since 8.3
  877. */
  878. public Map<LocalDate, String> getDateStyles() {
  879. HashMap<LocalDate, String> hashMap = new HashMap<>();
  880. for (Entry<String, String> entry : getState(false).dateStyles
  881. .entrySet()) {
  882. hashMap.put(LocalDate.parse(entry.getKey()), entry.getValue());
  883. }
  884. return Collections.unmodifiableMap(hashMap);
  885. }
  886. /**
  887. * Sets the assistive label for a calendar navigation element. This sets the
  888. * {@code aria-label} attribute for the element which is used by screen
  889. * reading software.
  890. *
  891. * @param element
  892. * the element for which to set the label. Not {@code null}.
  893. * @param label
  894. * the assistive label to set
  895. * @since 8.4
  896. */
  897. public void setAssistiveLabel(AccessibleElement element, String label) {
  898. Objects.requireNonNull(element, "Element cannot be null");
  899. getState().assistiveLabels.put(element, label);
  900. }
  901. /**
  902. * Gets the assistive label of a calendar navigation element.
  903. *
  904. * @param element
  905. * the element of which to get the assistive label
  906. * @since 8.4
  907. */
  908. public void getAssistiveLabel(AccessibleElement element) {
  909. getState(false).assistiveLabels.get(element);
  910. }
  911. }