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

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