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

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