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

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