Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  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.ui;
  17. import java.text.SimpleDateFormat;
  18. import java.util.Calendar;
  19. import java.util.Collection;
  20. import java.util.Date;
  21. import java.util.HashMap;
  22. import java.util.Locale;
  23. import java.util.Map;
  24. import java.util.TimeZone;
  25. import java.util.logging.Logger;
  26. import org.jsoup.nodes.Element;
  27. import com.vaadin.event.FieldEvents.BlurEvent;
  28. import com.vaadin.event.FieldEvents.BlurListener;
  29. import com.vaadin.event.FieldEvents.FocusEvent;
  30. import com.vaadin.event.FieldEvents.FocusListener;
  31. import com.vaadin.server.PaintException;
  32. import com.vaadin.server.PaintTarget;
  33. import com.vaadin.ui.Component;
  34. import com.vaadin.ui.LegacyComponent;
  35. import com.vaadin.ui.declarative.DesignAttributeHandler;
  36. import com.vaadin.ui.declarative.DesignContext;
  37. import com.vaadin.v7.data.Property;
  38. import com.vaadin.v7.data.Validator;
  39. import com.vaadin.v7.data.Validator.InvalidValueException;
  40. import com.vaadin.v7.data.util.converter.Converter;
  41. import com.vaadin.v7.data.validator.DateRangeValidator;
  42. import com.vaadin.v7.event.FieldEvents;
  43. import com.vaadin.v7.shared.ui.datefield.DateFieldConstants;
  44. import com.vaadin.v7.shared.ui.datefield.Resolution;
  45. import com.vaadin.v7.shared.ui.datefield.TextualDateFieldState;
  46. /**
  47. * <p>
  48. * A date editor component that can be bound to any {@link Property} that is
  49. * compatible with <code>java.util.Date</code>.
  50. * </p>
  51. * <p>
  52. * Since <code>DateField</code> extends <code>LegacyAbstractField</code> it
  53. * implements the {@link com.vaadin.v7.data.Buffered}interface.
  54. * </p>
  55. * <p>
  56. * A <code>DateField</code> is in write-through mode by default, so
  57. * {@link com.vaadin.v7.ui.AbstractField#setWriteThrough(boolean)}must be called
  58. * to enable buffering.
  59. * </p>
  60. *
  61. * @author Vaadin Ltd.
  62. * @since 3.0
  63. */
  64. @SuppressWarnings("serial")
  65. @Deprecated
  66. public class DateField extends AbstractField<Date> implements
  67. FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, LegacyComponent {
  68. /**
  69. * Resolution identifier: seconds.
  70. *
  71. * @deprecated As of 7.0, use {@link Resolution#SECOND}
  72. */
  73. @Deprecated
  74. public static final Resolution RESOLUTION_SEC = Resolution.SECOND;
  75. /**
  76. * Resolution identifier: minutes.
  77. *
  78. * @deprecated As of 7.0, use {@link Resolution#MINUTE}
  79. */
  80. @Deprecated
  81. public static final Resolution RESOLUTION_MIN = Resolution.MINUTE;
  82. /**
  83. * Resolution identifier: hours.
  84. *
  85. * @deprecated As of 7.0, use {@link Resolution#HOUR}
  86. */
  87. @Deprecated
  88. public static final Resolution RESOLUTION_HOUR = Resolution.HOUR;
  89. /**
  90. * Resolution identifier: days.
  91. *
  92. * @deprecated As of 7.0, use {@link Resolution#DAY}
  93. */
  94. @Deprecated
  95. public static final Resolution RESOLUTION_DAY = Resolution.DAY;
  96. /**
  97. * Resolution identifier: months.
  98. *
  99. * @deprecated As of 7.0, use {@link Resolution#MONTH}
  100. */
  101. @Deprecated
  102. public static final Resolution RESOLUTION_MONTH = Resolution.MONTH;
  103. /**
  104. * Resolution identifier: years.
  105. *
  106. * @deprecated As of 7.0, use {@link Resolution#YEAR}
  107. */
  108. @Deprecated
  109. public static final Resolution RESOLUTION_YEAR = Resolution.YEAR;
  110. /**
  111. * Specified smallest modifiable unit for the date field.
  112. */
  113. private Resolution resolution = Resolution.DAY;
  114. /**
  115. * The internal calendar to be used in java.utl.Date conversions.
  116. */
  117. private transient Calendar calendar;
  118. /**
  119. * Overridden format string
  120. */
  121. private String dateFormat;
  122. private boolean lenient = false;
  123. private String dateString = null;
  124. /**
  125. * Was the last entered string parsable? If this flag is false, datefields
  126. * internal validator does not pass.
  127. */
  128. private boolean uiHasValidDateString = true;
  129. /**
  130. * Determines if week numbers are shown in the date selector.
  131. */
  132. private boolean showISOWeekNumbers = false;
  133. private String currentParseErrorMessage;
  134. private String defaultParseErrorMessage = "Date format not recognized";
  135. private TimeZone timeZone = null;
  136. private static Map<Resolution, String> variableNameForResolution = new HashMap<>();
  137. private String dateOutOfRangeMessage = "Date is out of allowed range";
  138. private DateRangeValidator currentRangeValidator;
  139. /**
  140. * Determines whether the ValueChangeEvent should be fired. Used to prevent
  141. * firing the event when UI has invalid string until uiHasValidDateString
  142. * flag is set
  143. */
  144. private boolean preventValueChangeEvent = false;
  145. static {
  146. variableNameForResolution.put(Resolution.SECOND, "sec");
  147. variableNameForResolution.put(Resolution.MINUTE, "min");
  148. variableNameForResolution.put(Resolution.HOUR, "hour");
  149. variableNameForResolution.put(Resolution.DAY, "day");
  150. variableNameForResolution.put(Resolution.MONTH, "month");
  151. variableNameForResolution.put(Resolution.YEAR, "year");
  152. }
  153. /* Constructors */
  154. /**
  155. * Constructs an empty <code>DateField</code> with no caption.
  156. */
  157. public DateField() {
  158. }
  159. /**
  160. * Constructs an empty <code>DateField</code> with caption.
  161. *
  162. * @param caption
  163. * the caption of the datefield.
  164. */
  165. public DateField(String caption) {
  166. setCaption(caption);
  167. }
  168. /**
  169. * Constructs a new <code>DateField</code> that's bound to the specified
  170. * <code>Property</code> and has the given caption <code>String</code>.
  171. *
  172. * @param caption
  173. * the caption <code>String</code> for the editor.
  174. * @param dataSource
  175. * the Property to be edited with this editor.
  176. */
  177. public DateField(String caption, Property dataSource) {
  178. this(dataSource);
  179. setCaption(caption);
  180. }
  181. /**
  182. * Constructs a new <code>DateField</code> that's bound to the specified
  183. * <code>Property</code> and has no caption.
  184. *
  185. * @param dataSource
  186. * the Property to be edited with this editor.
  187. */
  188. public DateField(Property dataSource) throws IllegalArgumentException {
  189. if (!Date.class.isAssignableFrom(dataSource.getType())) {
  190. throw new IllegalArgumentException(
  191. "Can't use " + dataSource.getType().getName()
  192. + " typed property as datasource");
  193. }
  194. setPropertyDataSource(dataSource);
  195. }
  196. /**
  197. * Constructs a new <code>DateField</code> with the given caption and
  198. * initial text contents. The editor constructed this way will not be bound
  199. * to a Property unless
  200. * {@link com.vaadin.v7.data.Property.Viewer#setPropertyDataSource(Property)}
  201. * is called to bind it.
  202. *
  203. * @param caption
  204. * the caption <code>String</code> for the editor.
  205. * @param value
  206. * the Date value.
  207. */
  208. public DateField(String caption, Date value) {
  209. setValue(value);
  210. setCaption(caption);
  211. }
  212. /* Component basic features */
  213. /*
  214. * Paints this component. Don't add a JavaDoc comment here, we use the
  215. * default documentation from implemented interface.
  216. */
  217. @Override
  218. public void paintContent(PaintTarget target) throws PaintException {
  219. // Adds the locale as attribute
  220. final Locale l = getLocale();
  221. if (l != null) {
  222. target.addAttribute("locale", l.toString());
  223. }
  224. if (getDateFormat() != null) {
  225. target.addAttribute("format", dateFormat);
  226. }
  227. if (!isLenient()) {
  228. target.addAttribute("strict", true);
  229. }
  230. target.addAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS,
  231. isShowISOWeekNumbers());
  232. target.addAttribute("parsable", uiHasValidDateString);
  233. /*
  234. * TODO communicate back the invalid date string? E.g. returning back to
  235. * app or refresh.
  236. */
  237. // Gets the calendar
  238. final Calendar calendar = getCalendar();
  239. final Date currentDate = getValue();
  240. // Only paint variables for the resolution and up, e.g. Resolution DAY
  241. // paints DAY,MONTH,YEAR
  242. for (Resolution res : Resolution
  243. .getResolutionsHigherOrEqualTo(resolution)) {
  244. int value = -1;
  245. if (currentDate != null) {
  246. value = calendar.get(res.getCalendarField());
  247. if (res == Resolution.MONTH) {
  248. // Calendar month is zero based
  249. value++;
  250. }
  251. }
  252. target.addVariable(this, variableNameForResolution.get(res), value);
  253. }
  254. }
  255. @Override
  256. protected boolean shouldHideErrors() {
  257. return super.shouldHideErrors() && uiHasValidDateString;
  258. }
  259. @Override
  260. protected TextualDateFieldState getState() {
  261. return (TextualDateFieldState) super.getState();
  262. }
  263. @Override
  264. protected TextualDateFieldState getState(boolean markAsDirty) {
  265. return (TextualDateFieldState) super.getState(markAsDirty);
  266. }
  267. /**
  268. * Sets the start range for this component. If the value is set before this
  269. * date (taking the resolution into account), the component will not
  270. * validate. If <code>startDate</code> is set to <code>null</code>, any
  271. * value before <code>endDate</code> will be accepted by the range
  272. *
  273. * @param startDate
  274. * - the allowed range's start date
  275. */
  276. public void setRangeStart(Date startDate) {
  277. if (startDate != null && getState().rangeEnd != null
  278. && startDate.after(getState().rangeEnd)) {
  279. throw new IllegalStateException(
  280. "startDate cannot be later than endDate");
  281. }
  282. // Create a defensive copy against issues when using java.sql.Date (and
  283. // also against mutable Date).
  284. getState().rangeStart = startDate != null
  285. ? new Date(startDate.getTime()) : null;
  286. updateRangeValidator();
  287. }
  288. /**
  289. * Sets the current error message if the range validation fails.
  290. *
  291. * @param dateOutOfRangeMessage
  292. * - Localizable message which is shown when value (the date) is
  293. * set outside allowed range
  294. */
  295. public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
  296. this.dateOutOfRangeMessage = dateOutOfRangeMessage;
  297. updateRangeValidator();
  298. }
  299. /**
  300. * Gets the end range for a certain resolution. The range is inclusive, so
  301. * if rangeEnd is set to zero milliseconds past year n and resolution is set
  302. * to YEAR, any date in year n will be accepted. Resolutions lower than DAY
  303. * will be interpreted on a DAY level. That is, everything below DATE is
  304. * cleared
  305. *
  306. * @param forResolution
  307. * - the range conforms to the resolution
  308. * @return
  309. */
  310. private Date getRangeEnd(Resolution forResolution) {
  311. // We need to set the correct resolution for the dates,
  312. // otherwise the range validator will complain
  313. Date rangeEnd = getState(false).rangeEnd;
  314. if (rangeEnd == null) {
  315. return null;
  316. }
  317. Calendar endCal = Calendar.getInstance();
  318. endCal.setTime(rangeEnd);
  319. if (forResolution == Resolution.YEAR) {
  320. // Adding one year (minresolution) and clearing the rest.
  321. endCal.set(endCal.get(Calendar.YEAR) + 1, 0, 1, 0, 0, 0);
  322. } else if (forResolution == Resolution.MONTH) {
  323. // Adding one month (minresolution) and clearing the rest.
  324. endCal.set(endCal.get(Calendar.YEAR),
  325. endCal.get(Calendar.MONTH) + 1, 1, 0, 0, 0);
  326. } else {
  327. endCal.set(endCal.get(Calendar.YEAR), endCal.get(Calendar.MONTH),
  328. endCal.get(Calendar.DATE) + 1, 0, 0, 0);
  329. }
  330. // removing one millisecond will now get the endDate to return to
  331. // current resolution's set time span (year or month)
  332. endCal.set(Calendar.MILLISECOND, -1);
  333. return endCal.getTime();
  334. }
  335. /**
  336. * Gets the start range for a certain resolution. The range is inclusive, so
  337. * if <code>rangeStart</code> is set to one millisecond before year n and
  338. * resolution is set to YEAR, any date in year n - 1 will be accepted.
  339. * Lowest supported resolution is DAY.
  340. *
  341. * @param forResolution
  342. * - the range conforms to the resolution
  343. * @return
  344. */
  345. private Date getRangeStart(Resolution forResolution) {
  346. if (getState(false).rangeStart == null) {
  347. return null;
  348. }
  349. Calendar startCal = Calendar.getInstance();
  350. startCal.setTime(getState(false).rangeStart);
  351. if (forResolution == Resolution.YEAR) {
  352. startCal.set(startCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
  353. } else if (forResolution == Resolution.MONTH) {
  354. startCal.set(startCal.get(Calendar.YEAR),
  355. startCal.get(Calendar.MONTH), 1, 0, 0, 0);
  356. } else {
  357. startCal.set(startCal.get(Calendar.YEAR),
  358. startCal.get(Calendar.MONTH), startCal.get(Calendar.DATE),
  359. 0, 0, 0);
  360. }
  361. startCal.set(Calendar.MILLISECOND, 0);
  362. return startCal.getTime();
  363. }
  364. private void updateRangeValidator() {
  365. if (currentRangeValidator != null) {
  366. removeValidator(currentRangeValidator);
  367. currentRangeValidator = null;
  368. }
  369. if (getRangeStart() != null || getRangeEnd() != null) {
  370. currentRangeValidator = new DateRangeValidator(
  371. dateOutOfRangeMessage, getRangeStart(resolution),
  372. getRangeEnd(resolution), null);
  373. addValidator(currentRangeValidator);
  374. }
  375. }
  376. /**
  377. * Sets the end range for this component. If the value is set after this
  378. * date (taking the resolution into account), the component will not
  379. * validate. If <code>endDate</code> is set to <code>null</code>, any value
  380. * after <code>startDate</code> will be accepted by the range.
  381. *
  382. * @param endDate
  383. * - the allowed range's end date (inclusive, based on the
  384. * current resolution)
  385. */
  386. public void setRangeEnd(Date endDate) {
  387. if (endDate != null && getState().rangeStart != null
  388. && getState().rangeStart.after(endDate)) {
  389. throw new IllegalStateException(
  390. "endDate cannot be earlier than startDate");
  391. }
  392. // Create a defensive copy against issues when using java.sql.Date (and
  393. // also against mutable Date).
  394. getState().rangeEnd = endDate != null ? new Date(endDate.getTime())
  395. : null;
  396. updateRangeValidator();
  397. }
  398. /**
  399. * Returns the precise rangeStart used.
  400. *
  401. * @param startDate
  402. *
  403. */
  404. public Date getRangeStart() {
  405. return getState(false).rangeStart;
  406. }
  407. /**
  408. * Returns the precise rangeEnd used.
  409. *
  410. * @param startDate
  411. */
  412. public Date getRangeEnd() {
  413. return getState(false).rangeEnd;
  414. }
  415. /*
  416. * Invoked when a variable of the component changes. Don't add a JavaDoc
  417. * comment here, we use the default documentation from implemented
  418. * interface.
  419. */
  420. @Override
  421. public void changeVariables(Object source, Map<String, Object> variables) {
  422. if (!isReadOnly() && (variables.containsKey("year")
  423. || variables.containsKey("month")
  424. || variables.containsKey("day") || variables.containsKey("hour")
  425. || variables.containsKey("min") || variables.containsKey("sec")
  426. || variables.containsKey("msec")
  427. || variables.containsKey("dateString"))) {
  428. // Old and new dates
  429. final Date oldDate = getValue();
  430. Date newDate = null;
  431. // this enables analyzing invalid input on the server
  432. final String newDateString = (String) variables.get("dateString");
  433. dateString = newDateString;
  434. // Gets the new date in parts
  435. boolean hasChanges = false;
  436. Map<Resolution, Integer> calendarFieldChanges = new HashMap<>();
  437. for (Resolution r : Resolution
  438. .getResolutionsHigherOrEqualTo(resolution)) {
  439. // Only handle what the client is allowed to send. The same
  440. // resolutions that are painted
  441. String variableName = variableNameForResolution.get(r);
  442. if (variables.containsKey(variableName)) {
  443. Integer value = (Integer) variables.get(variableName);
  444. if (r == Resolution.MONTH) {
  445. // Calendar MONTH is zero based
  446. value--;
  447. }
  448. if (value >= 0) {
  449. hasChanges = true;
  450. calendarFieldChanges.put(r, value);
  451. }
  452. }
  453. }
  454. // If no new variable values were received, use the previous value
  455. if (!hasChanges) {
  456. newDate = null;
  457. } else {
  458. // Clone the calendar for date operation
  459. final Calendar cal = getCalendar();
  460. // Update the value based on the received info
  461. // Must set in this order to avoid invalid dates (or wrong
  462. // dates if lenient is true) in calendar
  463. for (int r = Resolution.YEAR.ordinal(); r >= 0; r--) {
  464. Resolution res = Resolution.values()[r];
  465. if (calendarFieldChanges.containsKey(res)) {
  466. // Field resolution should be included. Others are
  467. // skipped so that client can not make unexpected
  468. // changes (e.g. day change even though resolution is
  469. // year).
  470. Integer newValue = calendarFieldChanges.get(res);
  471. cal.set(res.getCalendarField(), newValue);
  472. }
  473. }
  474. newDate = cal.getTime();
  475. }
  476. if (newDate == null && dateString != null
  477. && !"".equals(dateString)) {
  478. try {
  479. Date parsedDate = handleUnparsableDateString(dateString);
  480. setValue(parsedDate, true);
  481. /*
  482. * Ensure the value is sent to the client if the value is
  483. * set to the same as the previous (#4304). Does not repaint
  484. * if handleUnparsableDateString throws an exception. In
  485. * this case the invalid text remains in the DateField.
  486. */
  487. markAsDirty();
  488. } catch (Converter.ConversionException e) {
  489. /*
  490. * Datefield now contains some text that could't be parsed
  491. * into date. ValueChangeEvent is fired after the value is
  492. * changed and the flags are set
  493. */
  494. if (oldDate != null) {
  495. /*
  496. * Set the logic value to null without firing the
  497. * ValueChangeEvent
  498. */
  499. preventValueChangeEvent = true;
  500. try {
  501. setValue(null);
  502. } finally {
  503. preventValueChangeEvent = false;
  504. }
  505. /*
  506. * Reset the dateString (overridden to null by setValue)
  507. */
  508. dateString = newDateString;
  509. }
  510. /*
  511. * Saves the localized message of parse error. This can be
  512. * overridden in handleUnparsableDateString. The message
  513. * will later be used to show a validation error.
  514. */
  515. currentParseErrorMessage = e.getLocalizedMessage();
  516. /*
  517. * The value of the DateField should be null if an invalid
  518. * value has been given. Not using setValue() since we do
  519. * not want to cause the client side value to change.
  520. */
  521. uiHasValidDateString = false;
  522. /*
  523. * If value was changed fire the ValueChangeEvent
  524. */
  525. if (oldDate != null) {
  526. fireValueChange(false);
  527. }
  528. /*
  529. * Because of our custom implementation of isValid(), that
  530. * also checks the parsingSucceeded flag, we must also
  531. * notify the form (if this is used in one) that the
  532. * validity of this field has changed.
  533. *
  534. * Normally fields validity doesn't change without value
  535. * change and form depends on this implementation detail.
  536. */
  537. notifyFormOfValidityChange();
  538. markAsDirty();
  539. }
  540. } else if (newDate != oldDate
  541. && (newDate == null || !newDate.equals(oldDate))) {
  542. setValue(newDate, true); // Don't require a repaint, client
  543. // updates itself
  544. } else if (!uiHasValidDateString) { // oldDate ==
  545. // newDate == null
  546. // Empty value set, previously contained unparsable date string,
  547. // clear related internal fields
  548. setValue(null);
  549. }
  550. }
  551. if (variables.containsKey(FocusEvent.EVENT_ID)) {
  552. fireEvent(new FocusEvent(this));
  553. }
  554. if (variables.containsKey(BlurEvent.EVENT_ID)) {
  555. fireEvent(new BlurEvent(this));
  556. }
  557. }
  558. @Override
  559. public void discard() {
  560. Property prop = getPropertyDataSource();
  561. if (prop != null) {
  562. Object value = prop.getValue();
  563. if (!isValid() && value == null) {
  564. // If the user entered an invalid value in the date field
  565. // getInternalValue() returns null.
  566. // If the datasource also contains null, then
  567. // updateValueFromDataSource() will then not clear the internal
  568. // state
  569. // and error indicators (ticket #8069).
  570. setInternalValue(null);
  571. } else {
  572. super.discard();
  573. }
  574. }
  575. }
  576. /*
  577. * only fires the event if preventValueChangeEvent flag is false
  578. */
  579. @Override
  580. protected void fireValueChange(boolean repaintIsNotNeeded) {
  581. if (!preventValueChangeEvent) {
  582. super.fireValueChange(repaintIsNotNeeded);
  583. }
  584. }
  585. /**
  586. * This method is called to handle a non-empty date string from the client
  587. * if the client could not parse it as a Date.
  588. *
  589. * By default, a Converter.ConversionException is thrown, and the current
  590. * value is not modified.
  591. *
  592. * This can be overridden to handle conversions, to return null (equivalent
  593. * to empty input), to throw an exception or to fire an event.
  594. *
  595. * @param dateString
  596. * @return parsed Date
  597. * @throws Converter.ConversionException
  598. * to keep the old value and indicate an error
  599. */
  600. protected Date handleUnparsableDateString(String dateString)
  601. throws Converter.ConversionException {
  602. currentParseErrorMessage = null;
  603. throw new Converter.ConversionException(getParseErrorMessage());
  604. }
  605. /* Property features */
  606. /*
  607. * Gets the edited property's type. Don't add a JavaDoc comment here, we use
  608. * the default documentation from implemented interface.
  609. */
  610. @Override
  611. public Class<Date> getType() {
  612. return Date.class;
  613. }
  614. /*
  615. * (non-Javadoc)
  616. *
  617. * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object, boolean)
  618. */
  619. @Override
  620. protected void setValue(Date newValue, boolean repaintIsNotNeeded)
  621. throws Property.ReadOnlyException {
  622. /*
  623. * First handle special case when the client side component have a date
  624. * string but value is null (e.g. unparsable date string typed in by the
  625. * user). No value changes should happen, but we need to do some
  626. * internal housekeeping.
  627. */
  628. if (newValue == null && !uiHasValidDateString) {
  629. /*
  630. * Side-effects of setInternalValue clears possible previous strings
  631. * and flags about invalid input.
  632. */
  633. setInternalValue(null);
  634. markAsDirty();
  635. return;
  636. }
  637. super.setValue(newValue, repaintIsNotNeeded);
  638. }
  639. /**
  640. * Detects if this field is used in a Form (logically) and if so, notifies
  641. * it (by repainting it) that the validity of this field might have changed.
  642. */
  643. private void notifyFormOfValidityChange() {
  644. Component parenOfDateField = getParent();
  645. boolean formFound = false;
  646. while (parenOfDateField != null || formFound) {
  647. if (parenOfDateField instanceof Form) {
  648. Form f = (Form) parenOfDateField;
  649. Collection<?> visibleItemProperties = f.getItemPropertyIds();
  650. for (Object fieldId : visibleItemProperties) {
  651. Field<?> field = f.getField(fieldId);
  652. if (equals(field)) {
  653. /*
  654. * this datefield is logically in a form. Do the same
  655. * thing as form does in its value change listener that
  656. * it registers to all fields.
  657. */
  658. f.markAsDirty();
  659. formFound = true;
  660. break;
  661. }
  662. }
  663. }
  664. if (formFound) {
  665. break;
  666. }
  667. parenOfDateField = parenOfDateField.getParent();
  668. }
  669. }
  670. @Override
  671. protected void setInternalValue(Date newValue) {
  672. // Also set the internal dateString
  673. if (newValue != null) {
  674. dateString = newValue.toString();
  675. } else {
  676. dateString = null;
  677. }
  678. if (!uiHasValidDateString) {
  679. // clear component error and parsing flag
  680. setComponentError(null);
  681. uiHasValidDateString = true;
  682. currentParseErrorMessage = null;
  683. }
  684. super.setInternalValue(newValue);
  685. }
  686. /**
  687. * Gets the resolution.
  688. *
  689. * @return int
  690. */
  691. public Resolution getResolution() {
  692. return resolution;
  693. }
  694. /**
  695. * Sets the resolution of the DateField.
  696. *
  697. * The default resolution is {@link Resolution#DAY} since Vaadin 7.0.
  698. *
  699. * @param resolution
  700. * the resolution to set.
  701. */
  702. public void setResolution(Resolution resolution) {
  703. this.resolution = resolution;
  704. updateRangeValidator();
  705. markAsDirty();
  706. }
  707. /**
  708. * Returns new instance calendar used in Date conversions.
  709. *
  710. * Returns new clone of the calendar object initialized using the the
  711. * current date (if available)
  712. *
  713. * If this is no calendar is assigned the <code>Calendar.getInstance</code>
  714. * is used.
  715. *
  716. * @return the Calendar.
  717. * @see #setCalendar(Calendar)
  718. */
  719. private Calendar getCalendar() {
  720. // Makes sure we have an calendar instance
  721. if (calendar == null) {
  722. calendar = Calendar.getInstance();
  723. // Start by a zeroed calendar to avoid having values for lower
  724. // resolution variables e.g. time when resolution is day
  725. int min, field;
  726. for (Resolution r : Resolution
  727. .getResolutionsLowerThan(resolution)) {
  728. field = r.getCalendarField();
  729. min = calendar.getActualMinimum(field);
  730. calendar.set(field, min);
  731. }
  732. calendar.set(Calendar.MILLISECOND, 0);
  733. }
  734. // Clone the instance
  735. final Calendar newCal = (Calendar) calendar.clone();
  736. final TimeZone currentTimeZone = getTimeZone();
  737. if (currentTimeZone != null) {
  738. newCal.setTimeZone(currentTimeZone);
  739. }
  740. final Date currentDate = getValue();
  741. if (currentDate != null) {
  742. newCal.setTime(currentDate);
  743. }
  744. return newCal;
  745. }
  746. /**
  747. * Sets formatting used by some component implementations. See
  748. * {@link SimpleDateFormat} for format details.
  749. *
  750. * By default it is encouraged to used default formatting defined by Locale,
  751. * but due some JVM bugs it is sometimes necessary to use this method to
  752. * override formatting. See Vaadin issue #2200.
  753. *
  754. * @param dateFormat
  755. * the dateFormat to set
  756. *
  757. * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
  758. */
  759. public void setDateFormat(String dateFormat) {
  760. this.dateFormat = dateFormat;
  761. markAsDirty();
  762. }
  763. /**
  764. * Returns a format string used to format date value on client side or null
  765. * if default formatting from {@link Component#getLocale()} is used.
  766. *
  767. * @return the dateFormat
  768. */
  769. public String getDateFormat() {
  770. return dateFormat;
  771. }
  772. /**
  773. * Specifies whether or not date/time interpretation in component is to be
  774. * lenient.
  775. *
  776. * @see Calendar#setLenient(boolean)
  777. * @see #isLenient()
  778. *
  779. * @param lenient
  780. * true if the lenient mode is to be turned on; false if it is to
  781. * be turned off.
  782. */
  783. public void setLenient(boolean lenient) {
  784. this.lenient = lenient;
  785. markAsDirty();
  786. }
  787. /**
  788. * Returns whether date/time interpretation is to be lenient.
  789. *
  790. * @see #setLenient(boolean)
  791. *
  792. * @return true if the interpretation mode of this calendar is lenient;
  793. * false otherwise.
  794. */
  795. public boolean isLenient() {
  796. return lenient;
  797. }
  798. @Override
  799. public void addFocusListener(FocusListener listener) {
  800. addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  801. FocusListener.focusMethod);
  802. }
  803. @Override
  804. public void removeFocusListener(FocusListener listener) {
  805. removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
  806. }
  807. @Override
  808. public void addBlurListener(BlurListener listener) {
  809. addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  810. BlurListener.blurMethod);
  811. }
  812. @Override
  813. public void removeBlurListener(BlurListener listener) {
  814. removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
  815. }
  816. /**
  817. * Checks whether ISO 8601 week numbers are shown in the date selector.
  818. *
  819. * @return true if week numbers are shown, false otherwise.
  820. */
  821. public boolean isShowISOWeekNumbers() {
  822. return showISOWeekNumbers;
  823. }
  824. /**
  825. * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
  826. * 8601 defines that a week always starts with a Monday so the week numbers
  827. * are only shown if this is the case.
  828. *
  829. * @param showWeekNumbers
  830. * true if week numbers should be shown, false otherwise.
  831. */
  832. public void setShowISOWeekNumbers(boolean showWeekNumbers) {
  833. showISOWeekNumbers = showWeekNumbers;
  834. markAsDirty();
  835. }
  836. /**
  837. * Validates the current value against registered validators if the field is
  838. * not empty. Note that DateField is considered empty (value == null) and
  839. * invalid if it contains text typed in by the user that couldn't be parsed
  840. * into a Date value.
  841. *
  842. * @see com.vaadin.v7.ui.AbstractField#validate()
  843. */
  844. @Override
  845. public void validate() throws InvalidValueException {
  846. /*
  847. * To work properly in form we must throw exception if there is
  848. * currently a parsing error in the datefield. Parsing error is kind of
  849. * an internal validator.
  850. */
  851. if (!uiHasValidDateString) {
  852. throw new UnparsableDateString(currentParseErrorMessage);
  853. }
  854. super.validate();
  855. }
  856. /**
  857. * Return the error message that is shown if the user inputted value can't
  858. * be parsed into a Date object. If
  859. * {@link #handleUnparsableDateString(String)} is overridden and it throws a
  860. * custom exception, the message returned by
  861. * {@link Exception#getLocalizedMessage()} will be used instead of the value
  862. * returned by this method.
  863. *
  864. * @see #setParseErrorMessage(String)
  865. *
  866. * @return the error message that the DateField uses when it can't parse the
  867. * textual input from user to a Date object
  868. */
  869. public String getParseErrorMessage() {
  870. return defaultParseErrorMessage;
  871. }
  872. /**
  873. * Sets the default error message used if the DateField cannot parse the
  874. * text input by user to a Date field. Note that if the
  875. * {@link #handleUnparsableDateString(String)} method is overridden, the
  876. * localized message from its exception is used.
  877. *
  878. * @see #getParseErrorMessage()
  879. * @see #handleUnparsableDateString(String)
  880. * @param parsingErrorMessage
  881. */
  882. public void setParseErrorMessage(String parsingErrorMessage) {
  883. defaultParseErrorMessage = parsingErrorMessage;
  884. }
  885. /**
  886. * Sets the time zone used by this date field. The time zone is used to
  887. * convert the absolute time in a Date object to a logical time displayed in
  888. * the selector and to convert the select time back to a Date object.
  889. *
  890. * If no time zone has been set, the current default time zone returned by
  891. * {@code TimeZone.getDefault()} is used.
  892. *
  893. * @see #getTimeZone()
  894. * @param timeZone
  895. * the time zone to use for time calculations.
  896. */
  897. public void setTimeZone(TimeZone timeZone) {
  898. this.timeZone = timeZone;
  899. markAsDirty();
  900. }
  901. /**
  902. * Gets the time zone used by this field. The time zone is used to convert
  903. * the absolute time in a Date object to a logical time displayed in the
  904. * selector and to convert the select time back to a Date object.
  905. *
  906. * If {@code null} is returned, the current default time zone returned by
  907. * {@code TimeZone.getDefault()} is used.
  908. *
  909. * @return the current time zone
  910. */
  911. public TimeZone getTimeZone() {
  912. return timeZone;
  913. }
  914. @Deprecated
  915. public static class UnparsableDateString
  916. extends Validator.InvalidValueException {
  917. public UnparsableDateString(String message) {
  918. super(message);
  919. }
  920. }
  921. @Override
  922. public void readDesign(Element design, DesignContext designContext) {
  923. super.readDesign(design, designContext);
  924. if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
  925. Date date = DesignAttributeHandler.getFormatter()
  926. .parse(design.attr("value"), Date.class);
  927. // formatting will return null if it cannot parse the string
  928. if (date == null) {
  929. Logger.getLogger(DateField.class.getName()).info(
  930. "cannot parse " + design.attr("value") + " as date");
  931. }
  932. this.setValue(date, false, true);
  933. }
  934. }
  935. @Override
  936. public void writeDesign(Element design, DesignContext designContext) {
  937. super.writeDesign(design, designContext);
  938. if (getValue() != null) {
  939. design.attr("value",
  940. DesignAttributeHandler.getFormatter().format(getValue()));
  941. }
  942. }
  943. /**
  944. * Returns current date-out-of-range error message.
  945. *
  946. * @see #setDateOutOfRangeMessage(String)
  947. * @since 7.4
  948. * @return Current error message for dates out of range.
  949. */
  950. public String getDateOutOfRangeMessage() {
  951. return dateOutOfRangeMessage;
  952. }
  953. }