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

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