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.

VAbstractTextualDate.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /*
  2. * Copyright 2000-2016 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.client.ui;
  17. import java.util.Date;
  18. import com.google.gwt.aria.client.Roles;
  19. import com.google.gwt.dom.client.Element;
  20. import com.google.gwt.event.dom.client.ChangeEvent;
  21. import com.google.gwt.event.dom.client.ChangeHandler;
  22. import com.google.gwt.event.dom.client.DomEvent;
  23. import com.google.gwt.event.dom.client.KeyCodes;
  24. import com.google.gwt.event.dom.client.KeyDownEvent;
  25. import com.google.gwt.event.dom.client.KeyDownHandler;
  26. import com.google.gwt.i18n.client.DateTimeFormat;
  27. import com.google.gwt.i18n.client.TimeZone;
  28. import com.google.gwt.user.client.ui.TextBox;
  29. import com.vaadin.client.BrowserInfo;
  30. import com.vaadin.client.Focusable;
  31. import com.vaadin.client.LocaleNotLoadedException;
  32. import com.vaadin.client.LocaleService;
  33. import com.vaadin.client.VConsole;
  34. import com.vaadin.client.ui.aria.AriaHelper;
  35. import com.vaadin.client.ui.aria.HandlesAriaCaption;
  36. import com.vaadin.client.ui.aria.HandlesAriaInvalid;
  37. import com.vaadin.client.ui.aria.HandlesAriaRequired;
  38. import com.vaadin.shared.EventId;
  39. /**
  40. * Abstract textual date field base implementation. Provides a text box as an
  41. * editor for a date. The class is parameterized by the date resolution
  42. * enumeration type.
  43. *
  44. * @author Vaadin Ltd
  45. *
  46. * @param <R>
  47. * the resolution type which this field is based on (day, month, ...)
  48. * @since 8.0
  49. */
  50. public abstract class VAbstractTextualDate<R extends Enum<R>>
  51. extends VDateField<R> implements Field, ChangeHandler, Focusable,
  52. SubPartAware, HandlesAriaCaption, HandlesAriaInvalid,
  53. HandlesAriaRequired, KeyDownHandler {
  54. private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
  55. private static final String ISO_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
  56. private static final String ISO_DATE_PATTERN = "yyyy-MM-dd";
  57. /** For internal use only. May be removed or replaced in the future. */
  58. public final TextBox text;
  59. /** For internal use only. May be removed or replaced in the future. */
  60. public boolean lenient;
  61. private static final String TEXTFIELD_ID = "field";
  62. /** For internal use only. May be removed or replaced in the future. */
  63. private String formatStr;
  64. /** For internal use only. May be removed or replaced in the future. */
  65. private TimeZone timeZone;
  66. public VAbstractTextualDate(R resoluton) {
  67. super(resoluton);
  68. text = new TextBox();
  69. text.addChangeHandler(this);
  70. text.addFocusHandler(
  71. event -> fireBlurFocusEvent(event, true, EventId.FOCUS));
  72. text.addBlurHandler(
  73. event -> fireBlurFocusEvent(event, false, EventId.BLUR));
  74. if (BrowserInfo.get().isIE()) {
  75. addDomHandler(this, KeyDownEvent.getType());
  76. }
  77. add(text);
  78. publishJSHelpers(getElement());
  79. }
  80. /**
  81. * Updates style names for the widget (and its children).
  82. */
  83. protected void updateStyleNames() {
  84. if (text != null) {
  85. text.setStyleName(VTextField.CLASSNAME);
  86. text.addStyleName(getStylePrimaryName() + "-textfield");
  87. }
  88. }
  89. /**
  90. * Gets the date format string for the current locale.
  91. *
  92. * @return the format string
  93. */
  94. public String getFormatString() {
  95. if (formatStr == null) {
  96. setFormatString(createFormatString());
  97. }
  98. return formatStr;
  99. }
  100. /**
  101. * Create a format string suitable for the widget in its current state.
  102. *
  103. * @return a date format string to use when formatting and parsing the text
  104. * in the input field
  105. * @since 8.1
  106. */
  107. protected String createFormatString() {
  108. if (isYear(getCurrentResolution())) {
  109. return "yyyy"; // force full year
  110. }
  111. try {
  112. String frmString = LocaleService.getDateFormat(currentLocale);
  113. return cleanFormat(frmString);
  114. } catch (LocaleNotLoadedException e) {
  115. // TODO should die instead? Can the component survive
  116. // without format string?
  117. VConsole.error(e);
  118. return null;
  119. }
  120. }
  121. /**
  122. * Sets the date format string to use for the text field.
  123. *
  124. * @param formatString
  125. * the format string to use, or {@code null} to force re-creating
  126. * the format string from the locale the next time it is needed
  127. * @since 8.1
  128. */
  129. public void setFormatString(String formatString) {
  130. this.formatStr = formatString;
  131. }
  132. @Override
  133. public void bindAriaCaption(
  134. com.google.gwt.user.client.Element captionElement) {
  135. AriaHelper.bindCaption(text, captionElement);
  136. }
  137. @Override
  138. public void setAriaRequired(boolean required) {
  139. AriaHelper.handleInputRequired(text, required);
  140. }
  141. @Override
  142. public void setAriaInvalid(boolean invalid) {
  143. AriaHelper.handleInputInvalid(text, invalid);
  144. }
  145. /**
  146. * Updates the text field according to the current date (provided by
  147. * {@link #getDate()}). Takes care of updating text, enabling and disabling
  148. * the field, setting/removing readonly status and updating readonly styles.
  149. * <p>
  150. * For internal use only. May be removed or replaced in the future.
  151. * <p>
  152. * TODO: Split part of this into a method that only updates the text as this
  153. * is what usually is needed except for updateFromUIDL.
  154. */
  155. public void buildDate() {
  156. removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
  157. // Create the initial text for the textfield
  158. String dateText;
  159. Date currentDate = getDate();
  160. // Always call this to ensure the format ends up in the element
  161. String formatString = getFormatString();
  162. if (currentDate != null) {
  163. dateText = getDateTimeService().formatDate(currentDate,
  164. formatString, timeZone);
  165. } else {
  166. dateText = "";
  167. }
  168. setText(dateText);
  169. text.setEnabled(enabled);
  170. text.setReadOnly(readonly);
  171. if (readonly) {
  172. text.addStyleName("v-readonly");
  173. Roles.getTextboxRole().setAriaReadonlyProperty(text.getElement(),
  174. true);
  175. } else {
  176. text.removeStyleName("v-readonly");
  177. Roles.getTextboxRole()
  178. .removeAriaReadonlyProperty(text.getElement());
  179. }
  180. }
  181. /**
  182. * Sets the time zone for the field.
  183. *
  184. * @param timeZone
  185. * the new time zone to use
  186. * @since 8.2
  187. */
  188. public void setTimeZone(TimeZone timeZone) {
  189. this.timeZone = timeZone;
  190. }
  191. @Override
  192. public void setEnabled(boolean enabled) {
  193. super.setEnabled(enabled);
  194. text.setEnabled(enabled);
  195. }
  196. @Override
  197. @SuppressWarnings("deprecation")
  198. public void onChange(ChangeEvent event) {
  199. if (!text.getText().isEmpty()) {
  200. try {
  201. String enteredDate = text.getText();
  202. setDate(getDateTimeService().parseDate(enteredDate,
  203. getFormatString(), lenient));
  204. if (lenient) {
  205. // If date value was leniently parsed, normalize text
  206. // presentation.
  207. // FIXME: Add a description/example here of when this is
  208. // needed
  209. text.setValue(getDateTimeService().formatDate(getDate(),
  210. getFormatString(), timeZone), false);
  211. }
  212. // remove possibly added invalid value indication
  213. removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
  214. } catch (final Exception e) {
  215. VConsole.log(e);
  216. addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
  217. setDate(null);
  218. }
  219. } else {
  220. setDate(null);
  221. // remove possibly added invalid value indication
  222. removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
  223. }
  224. // always send the date string
  225. bufferedDateString = text.getText();
  226. updateAndSendBufferedValues();
  227. }
  228. /**
  229. * Updates the {@link VDateField#bufferedResolutions bufferedResolutions},
  230. * then {@link #sendBufferedValues() sends} the values to the server.
  231. *
  232. * @since
  233. */
  234. protected final void updateAndSendBufferedValues() {
  235. updateBufferedResolutions();
  236. sendBufferedValues();
  237. }
  238. /**
  239. * Updates {@link VDateField#bufferedResolutions bufferedResolutions} before
  240. * sending a response to the server.
  241. * <p>
  242. * The method can be overridden by subclasses to provide a custom logic for
  243. * date variables to avoid overriding the {@link #onChange(ChangeEvent)}
  244. * method.
  245. *
  246. * <p>
  247. * Note that this method should not send the buffered values, but use
  248. * {@link #updateAndSendBufferedValues()} instead
  249. *
  250. * @since
  251. */
  252. protected void updateBufferedResolutions() {
  253. Date currentDate = getDate();
  254. if (currentDate != null) {
  255. bufferedResolutions.put(
  256. getResolutions().filter(this::isYear).findFirst().get(),
  257. currentDate.getYear() + 1900);
  258. }
  259. }
  260. /**
  261. * Clean date format string to make it suitable for
  262. * {@link #getFormatString()}.
  263. *
  264. * @see #getFormatString()
  265. *
  266. * @param format
  267. * date format string
  268. * @return cleaned up string
  269. */
  270. protected String cleanFormat(String format) {
  271. // Remove unsupported patterns
  272. // TODO support for 'G', era designator (used at least in Japan)
  273. format = format.replaceAll("[GzZwWkK]", "");
  274. // Remove extra delimiters ('/' and '.')
  275. while (format.startsWith("/") || format.startsWith(".")
  276. || format.startsWith("-")) {
  277. format = format.substring(1);
  278. }
  279. while (format.endsWith("/") || format.endsWith(".")
  280. || format.endsWith("-")) {
  281. format = format.substring(0, format.length() - 1);
  282. }
  283. // Remove duplicate delimiters
  284. format = format.replaceAll("//", "/");
  285. format = format.replaceAll("\\.\\.", ".");
  286. format = format.replaceAll("--", "-");
  287. return format.trim();
  288. }
  289. @Override
  290. public void focus() {
  291. text.setFocus(true);
  292. }
  293. /**
  294. * Sets the placeholder for this textual date input.
  295. *
  296. * @param placeholder
  297. * the placeholder to set, or {@code null} to clear
  298. */
  299. public void setPlaceholder(String placeholder) {
  300. if (placeholder != null) {
  301. text.getElement().setAttribute("placeholder", placeholder);
  302. } else {
  303. text.getElement().removeAttribute("placeholder");
  304. }
  305. }
  306. /**
  307. * Gets the set placeholder this textual date input, or an empty string if
  308. * none is set.
  309. *
  310. * @return the placeholder or an empty string if none set
  311. */
  312. public String getPlaceHolder() {
  313. return text.getElement().getAttribute("placeholder");
  314. }
  315. protected String getText() {
  316. return text.getText();
  317. }
  318. protected void setText(String text) {
  319. this.text.setText(text);
  320. }
  321. @Override
  322. public com.google.gwt.user.client.Element getSubPartElement(
  323. String subPart) {
  324. if (subPart.equals(TEXTFIELD_ID)) {
  325. return text.getElement();
  326. }
  327. return null;
  328. }
  329. @Override
  330. public String getSubPartName(
  331. com.google.gwt.user.client.Element subElement) {
  332. if (text.getElement().isOrHasChild(subElement)) {
  333. return TEXTFIELD_ID;
  334. }
  335. return null;
  336. }
  337. @Override
  338. public void onKeyDown(KeyDownEvent event) {
  339. if (BrowserInfo.get().isIE()
  340. && event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
  341. // IE does not send change events when pressing enter in a text
  342. // input so we handle it using a key listener instead
  343. onChange(null);
  344. }
  345. }
  346. private void fireBlurFocusEvent(DomEvent<?> event,
  347. boolean addFocusStyleName, String eventId) {
  348. String styleName = VTextField.CLASSNAME + "-"
  349. + VTextField.CLASSNAME_FOCUS;
  350. if (addFocusStyleName) {
  351. text.addStyleName(styleName);
  352. } else {
  353. text.removeStyleName(styleName);
  354. }
  355. if (getClient() != null && getClient()
  356. .hasEventListeners(VAbstractTextualDate.this, eventId)) {
  357. // may excessively send events if if focus went to another
  358. // sub-component
  359. if (EventId.FOCUS.equals(eventId)) {
  360. rpc.focus();
  361. } else {
  362. rpc.blur();
  363. }
  364. sendBufferedValues();
  365. }
  366. // Needed for tooltip event handling
  367. fireEvent(event);
  368. }
  369. /**
  370. * Publish methods/properties on the element to be used from JavaScript.
  371. *
  372. * @since 8.1
  373. */
  374. private native void publishJSHelpers(Element root)
  375. /*-{
  376. var self = this;
  377. root.setISOValue = $entry(function (value) {
  378. self.@VAbstractTextualDate::setISODate(*)(value);
  379. });
  380. root.getISOValue = $entry(function () {
  381. return self.@VAbstractTextualDate::getISODate()();
  382. });
  383. }-*/;
  384. /**
  385. * Sets the value of the date field as a locale independent ISO date
  386. * (yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd depending on whether this is a date
  387. * field or a date and time field).
  388. *
  389. * @param isoDate
  390. * the date to set in ISO8601 format, or null to clear the date
  391. * value
  392. * @since 8.1
  393. */
  394. public void setISODate(String isoDate) {
  395. Date date = null;
  396. if (isoDate != null) {
  397. date = getIsoFormatter().parse(isoDate);
  398. }
  399. setDate(date);
  400. updateAndSendBufferedValues();
  401. }
  402. /**
  403. * Gets the value of the date field as a locale independent ISO date
  404. * (yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd depending on whether this is a date
  405. * field or a date and time field).
  406. *
  407. * @return the current date in ISO8601 format, or null if no date is set
  408. *
  409. * @since 8.1
  410. */
  411. public String getISODate() {
  412. Date date = getDate();
  413. if (date == null) {
  414. return null;
  415. }
  416. return getIsoFormatter().format(date);
  417. }
  418. private DateTimeFormat getIsoFormatter() {
  419. if (supportsTime()) {
  420. return DateTimeFormat.getFormat(ISO_DATE_TIME_PATTERN);
  421. }
  422. return DateTimeFormat.getFormat(ISO_DATE_PATTERN);
  423. }
  424. }