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.

VTextualDate.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  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.v7.client.ui;
  17. import java.util.Date;
  18. import java.util.logging.Level;
  19. import java.util.logging.Logger;
  20. import com.google.gwt.aria.client.Roles;
  21. import com.google.gwt.event.dom.client.BlurEvent;
  22. import com.google.gwt.event.dom.client.BlurHandler;
  23. import com.google.gwt.event.dom.client.ChangeEvent;
  24. import com.google.gwt.event.dom.client.ChangeHandler;
  25. import com.google.gwt.event.dom.client.FocusEvent;
  26. import com.google.gwt.event.dom.client.FocusHandler;
  27. import com.google.gwt.event.dom.client.KeyCodes;
  28. import com.google.gwt.event.dom.client.KeyDownEvent;
  29. import com.google.gwt.event.dom.client.KeyDownHandler;
  30. import com.google.gwt.user.client.ui.TextBox;
  31. import com.vaadin.client.BrowserInfo;
  32. import com.vaadin.client.Focusable;
  33. import com.vaadin.client.LocaleNotLoadedException;
  34. import com.vaadin.client.LocaleService;
  35. import com.vaadin.client.ui.SubPartAware;
  36. import com.vaadin.client.ui.aria.AriaHelper;
  37. import com.vaadin.client.ui.aria.HandlesAriaCaption;
  38. import com.vaadin.client.ui.aria.HandlesAriaInvalid;
  39. import com.vaadin.client.ui.aria.HandlesAriaRequired;
  40. import com.vaadin.shared.EventId;
  41. import com.vaadin.v7.shared.ui.datefield.Resolution;
  42. public class VTextualDate extends VDateField
  43. implements ChangeHandler,
  44. Focusable, SubPartAware, HandlesAriaCaption, HandlesAriaInvalid,
  45. HandlesAriaRequired, KeyDownHandler {
  46. private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
  47. /** For internal use only. May be removed or replaced in the future. */
  48. public final TextBox text;
  49. /** For internal use only. May be removed or replaced in the future. */
  50. public String formatStr;
  51. /** For internal use only. May be removed or replaced in the future. */
  52. public boolean lenient;
  53. private static final String CLASSNAME_PROMPT = "prompt";
  54. /** For internal use only. May be removed or replaced in the future. */
  55. public static final String ATTR_INPUTPROMPT = "prompt";
  56. /** For internal use only. May be removed or replaced in the future. */
  57. public String inputPrompt = "";
  58. private boolean prompting = false;
  59. public VTextualDate() {
  60. super();
  61. text = new TextBox();
  62. text.addChangeHandler(this);
  63. text.addFocusHandler(new FocusHandler() {
  64. @Override
  65. public void onFocus(FocusEvent event) {
  66. text.addStyleName(VTextField.CLASSNAME + "-"
  67. + VTextField.CLASSNAME_FOCUS);
  68. if (prompting) {
  69. text.setText("");
  70. setPrompting(false);
  71. }
  72. if (getClient() != null && getClient()
  73. .hasEventListeners(VTextualDate.this, EventId.FOCUS)) {
  74. getClient().updateVariable(getId(), EventId.FOCUS, "",
  75. true);
  76. }
  77. // Needed for tooltip event handling
  78. VTextualDate.this.fireEvent(event);
  79. }
  80. });
  81. text.addBlurHandler(new BlurHandler() {
  82. @Override
  83. public void onBlur(BlurEvent event) {
  84. text.removeStyleName(VTextField.CLASSNAME + "-"
  85. + VTextField.CLASSNAME_FOCUS);
  86. String value = getText();
  87. setPrompting(inputPrompt != null
  88. && (value == null || "".equals(value)));
  89. if (prompting) {
  90. text.setText(readonly ? "" : inputPrompt);
  91. }
  92. if (getClient() != null && getClient()
  93. .hasEventListeners(VTextualDate.this, EventId.BLUR)) {
  94. getClient().updateVariable(getId(), EventId.BLUR, "", true);
  95. }
  96. // Needed for tooltip event handling
  97. VTextualDate.this.fireEvent(event);
  98. }
  99. });
  100. if (BrowserInfo.get().isIE()) {
  101. addDomHandler(this, KeyDownEvent.getType());
  102. }
  103. add(text);
  104. }
  105. protected void updateStyleNames() {
  106. if (text != null) {
  107. text.setStyleName(VTextField.CLASSNAME);
  108. text.addStyleName(getStylePrimaryName() + "-textfield");
  109. }
  110. }
  111. protected String getFormatString() {
  112. if (formatStr == null) {
  113. if (currentResolution == Resolution.YEAR) {
  114. formatStr = "yyyy"; // force full year
  115. } else {
  116. try {
  117. String frmString = LocaleService
  118. .getDateFormat(currentLocale);
  119. frmString = cleanFormat(frmString);
  120. // String delim = LocaleService
  121. // .getClockDelimiter(currentLocale);
  122. if (currentResolution.getCalendarField() >= Resolution.HOUR
  123. .getCalendarField()) {
  124. if (dts.isTwelveHourClock()) {
  125. frmString += " hh";
  126. } else {
  127. frmString += " HH";
  128. }
  129. if (currentResolution
  130. .getCalendarField() >= Resolution.MINUTE
  131. .getCalendarField()) {
  132. frmString += ":mm";
  133. if (currentResolution
  134. .getCalendarField() >= Resolution.SECOND
  135. .getCalendarField()) {
  136. frmString += ":ss";
  137. }
  138. }
  139. if (dts.isTwelveHourClock()) {
  140. frmString += " aaa";
  141. }
  142. }
  143. formatStr = frmString;
  144. } catch (LocaleNotLoadedException e) {
  145. // TODO should die instead? Can the component survive
  146. // without format string?
  147. getLogger().log(Level.SEVERE,
  148. e.getMessage() == null ? "" : e.getMessage(), e);
  149. }
  150. }
  151. }
  152. return formatStr;
  153. }
  154. @Override
  155. public void bindAriaCaption(
  156. com.google.gwt.user.client.Element captionElement) {
  157. AriaHelper.bindCaption(text, captionElement);
  158. }
  159. @Override
  160. public void setAriaRequired(boolean required) {
  161. AriaHelper.handleInputRequired(text, required);
  162. }
  163. @Override
  164. public void setAriaInvalid(boolean invalid) {
  165. AriaHelper.handleInputInvalid(text, invalid);
  166. }
  167. /**
  168. * Updates the text field according to the current date (provided by
  169. * {@link #getDate()}). Takes care of updating text, enabling and disabling
  170. * the field, setting/removing readonly status and updating readonly styles.
  171. * <p>
  172. * For internal use only. May be removed or replaced in the future.
  173. * <p>
  174. * TODO: Split part of this into a method that only updates the text as this
  175. * is what usually is needed except for updateFromUIDL.
  176. */
  177. public void buildDate() {
  178. removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
  179. // Create the initial text for the textfield
  180. String dateText;
  181. Date currentDate = getDate();
  182. if (currentDate != null) {
  183. dateText = getDateTimeService().formatDate(currentDate,
  184. getFormatString());
  185. } else {
  186. dateText = "";
  187. }
  188. setText(dateText);
  189. text.setEnabled(enabled);
  190. text.setReadOnly(readonly);
  191. if (readonly) {
  192. text.addStyleName("v-readonly");
  193. Roles.getTextboxRole().setAriaReadonlyProperty(text.getElement(),
  194. true);
  195. } else {
  196. text.removeStyleName("v-readonly");
  197. Roles.getTextboxRole()
  198. .removeAriaReadonlyProperty(text.getElement());
  199. }
  200. }
  201. @Override
  202. public void setEnabled(boolean enabled) {
  203. super.setEnabled(enabled);
  204. text.setEnabled(enabled);
  205. }
  206. protected void setPrompting(boolean prompting) {
  207. this.prompting = prompting;
  208. if (prompting) {
  209. addStyleDependentName(CLASSNAME_PROMPT);
  210. } else {
  211. removeStyleDependentName(CLASSNAME_PROMPT);
  212. }
  213. }
  214. @Override
  215. @SuppressWarnings("deprecation")
  216. public void onChange(ChangeEvent event) {
  217. if (!text.getText().equals("")) {
  218. try {
  219. String enteredDate = text.getText();
  220. setDate(getDateTimeService().parseDate(enteredDate,
  221. getFormatString(), lenient));
  222. if (lenient) {
  223. // If date value was leniently parsed, normalize text
  224. // presentation.
  225. // FIXME: Add a description/example here of when this is
  226. // needed
  227. text.setValue(getDateTimeService().formatDate(getDate(),
  228. getFormatString()), false);
  229. }
  230. // remove possibly added invalid value indication
  231. removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
  232. } catch (final Exception e) {
  233. getLogger().log(Level.INFO,
  234. e.getMessage() == null ? "" : e.getMessage(), e);
  235. addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
  236. // this is a hack that may eventually be removed
  237. getClient().updateVariable(getId(), "lastInvalidDateString",
  238. text.getText(), false);
  239. setDate(null);
  240. }
  241. } else {
  242. setDate(null);
  243. // remove possibly added invalid value indication
  244. removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
  245. }
  246. // always send the date string
  247. getClient().updateVariable(getId(), "dateString", text.getText(),
  248. false);
  249. // Update variables
  250. // (only the smallest defining resolution needs to be
  251. // immediate)
  252. Date currentDate = getDate();
  253. getClient().updateVariable(getId(), "year",
  254. currentDate != null ? currentDate.getYear() + 1900 : -1,
  255. currentResolution == Resolution.YEAR && immediate);
  256. if (currentResolution.getCalendarField() >= Resolution.MONTH
  257. .getCalendarField()) {
  258. getClient().updateVariable(getId(), "month",
  259. currentDate != null ? currentDate.getMonth() + 1 : -1,
  260. currentResolution == Resolution.MONTH && immediate);
  261. }
  262. if (currentResolution.getCalendarField() >= Resolution.DAY
  263. .getCalendarField()) {
  264. getClient().updateVariable(getId(), "day",
  265. currentDate != null ? currentDate.getDate() : -1,
  266. currentResolution == Resolution.DAY && immediate);
  267. }
  268. if (currentResolution.getCalendarField() >= Resolution.HOUR
  269. .getCalendarField()) {
  270. getClient().updateVariable(getId(), "hour",
  271. currentDate != null ? currentDate.getHours() : -1,
  272. currentResolution == Resolution.HOUR && immediate);
  273. }
  274. if (currentResolution.getCalendarField() >= Resolution.MINUTE
  275. .getCalendarField()) {
  276. getClient().updateVariable(getId(), "min",
  277. currentDate != null ? currentDate.getMinutes() : -1,
  278. currentResolution == Resolution.MINUTE && immediate);
  279. }
  280. if (currentResolution.getCalendarField() >= Resolution.SECOND
  281. .getCalendarField()) {
  282. getClient().updateVariable(getId(), "sec",
  283. currentDate != null ? currentDate.getSeconds() : -1,
  284. currentResolution == Resolution.SECOND && immediate);
  285. }
  286. }
  287. private String cleanFormat(String format) {
  288. // Remove unnecessary d & M if resolution is too low
  289. if (currentResolution.getCalendarField() < Resolution.DAY
  290. .getCalendarField()) {
  291. format = format.replaceAll("d", "");
  292. }
  293. if (currentResolution.getCalendarField() < Resolution.MONTH
  294. .getCalendarField()) {
  295. format = format.replaceAll("M", "");
  296. }
  297. // Remove unsupported patterns
  298. // TODO support for 'G', era designator (used at least in Japan)
  299. format = format.replaceAll("[GzZwWkK]", "");
  300. // Remove extra delimiters ('/' and '.')
  301. while (format.startsWith("/") || format.startsWith(".")
  302. || format.startsWith("-")) {
  303. format = format.substring(1);
  304. }
  305. while (format.endsWith("/") || format.endsWith(".")
  306. || format.endsWith("-")) {
  307. format = format.substring(0, format.length() - 1);
  308. }
  309. // Remove duplicate delimiters
  310. format = format.replaceAll("//", "/");
  311. format = format.replaceAll("\\.\\.", ".");
  312. format = format.replaceAll("--", "-");
  313. return format.trim();
  314. }
  315. @Override
  316. public void focus() {
  317. text.setFocus(true);
  318. }
  319. protected String getText() {
  320. if (prompting) {
  321. return "";
  322. }
  323. return text.getText();
  324. }
  325. protected void setText(String text) {
  326. if (inputPrompt != null && (text == null || "".equals(text))
  327. && !this.text.getStyleName().contains(VTextField.CLASSNAME + "-"
  328. + VTextField.CLASSNAME_FOCUS)) {
  329. text = readonly ? "" : inputPrompt;
  330. setPrompting(true);
  331. } else {
  332. setPrompting(false);
  333. }
  334. this.text.setText(text);
  335. }
  336. private static final String TEXTFIELD_ID = "field";
  337. @Override
  338. public com.google.gwt.user.client.Element getSubPartElement(
  339. String subPart) {
  340. if (subPart.equals(TEXTFIELD_ID)) {
  341. return text.getElement();
  342. }
  343. return null;
  344. }
  345. @Override
  346. public String getSubPartName(
  347. com.google.gwt.user.client.Element subElement) {
  348. if (text.getElement().isOrHasChild(subElement)) {
  349. return TEXTFIELD_ID;
  350. }
  351. return null;
  352. }
  353. @Override
  354. public void onKeyDown(KeyDownEvent event) {
  355. if (BrowserInfo.get().isIE()
  356. && event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
  357. // IE does not send change events when pressing enter in a text
  358. // input so we handle it using a key listener instead
  359. onChange(null);
  360. }
  361. }
  362. private static Logger getLogger() {
  363. return Logger.getLogger(VTextualDate.class.getName());
  364. }
  365. }