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.

DateField.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.ui;
  5. import java.text.ParseException;
  6. import java.text.SimpleDateFormat;
  7. import java.util.Calendar;
  8. import java.util.Date;
  9. import java.util.Locale;
  10. import java.util.Map;
  11. import com.vaadin.data.Property;
  12. import com.vaadin.terminal.PaintException;
  13. import com.vaadin.terminal.PaintTarget;
  14. /**
  15. * <p>
  16. * A date editor component that can be bound to any bindable Property. that is
  17. * compatible with <code>java.util.Date</code>.
  18. * </p>
  19. * <p>
  20. * Since <code>DateField</code> extends <code>AbstractField</code> it implements
  21. * the {@link com.vaadin.data.Buffered}interface. A
  22. * <code>DateField</code> is in write-through mode by default, so
  23. * {@link com.vaadin.ui.AbstractField#setWriteThrough(boolean)}must be
  24. * called to enable buffering.
  25. * </p>
  26. *
  27. * @author IT Mill Ltd.
  28. * @version
  29. * @VERSION@
  30. * @since 3.0
  31. */
  32. @SuppressWarnings("serial")
  33. public class DateField extends AbstractField {
  34. /* Private members */
  35. /**
  36. * Resolution identifier: milliseconds.
  37. */
  38. public static final int RESOLUTION_MSEC = 0;
  39. /**
  40. * Resolution identifier: seconds.
  41. */
  42. public static final int RESOLUTION_SEC = 1;
  43. /**
  44. * Resolution identifier: minutes.
  45. */
  46. public static final int RESOLUTION_MIN = 2;
  47. /**
  48. * Resolution identifier: hours.
  49. */
  50. public static final int RESOLUTION_HOUR = 3;
  51. /**
  52. * Resolution identifier: days.
  53. */
  54. public static final int RESOLUTION_DAY = 4;
  55. /**
  56. * Resolution identifier: months.
  57. */
  58. public static final int RESOLUTION_MONTH = 5;
  59. /**
  60. * Resolution identifier: years.
  61. */
  62. public static final int RESOLUTION_YEAR = 6;
  63. /**
  64. * Popup date selector (calendar).
  65. */
  66. protected static final String TYPE_POPUP = "popup";
  67. /**
  68. * Inline date selector (calendar).
  69. */
  70. protected static final String TYPE_INLINE = "inline";
  71. /**
  72. * Specified widget type.
  73. */
  74. protected String type = TYPE_POPUP;
  75. /**
  76. * Specified smallest modifiable unit.
  77. */
  78. private int resolution = RESOLUTION_MSEC;
  79. /**
  80. * Specified largest modifiable unit.
  81. */
  82. private static final int largestModifiable = RESOLUTION_YEAR;
  83. /**
  84. * The internal calendar to be used in java.utl.Date conversions.
  85. */
  86. private Calendar calendar;
  87. /**
  88. * Overridden format string
  89. */
  90. private String dateFormat;
  91. /**
  92. * Read-only content of an VTextualDate field - null for other types of date
  93. * fields.
  94. */
  95. private String dateString;
  96. /* Constructors */
  97. /**
  98. * Constructs an empty <code>DateField</code> with no caption.
  99. */
  100. public DateField() {
  101. }
  102. /**
  103. * Constructs an empty <code>DateField</code> with caption.
  104. *
  105. * @param caption
  106. * the caption of the datefield.
  107. */
  108. public DateField(String caption) {
  109. setCaption(caption);
  110. }
  111. /**
  112. * Constructs a new <code>DateField</code> that's bound to the specified
  113. * <code>Property</code> and has the given caption <code>String</code>.
  114. *
  115. * @param caption
  116. * the caption <code>String</code> for the editor.
  117. * @param dataSource
  118. * the Property to be edited with this editor.
  119. */
  120. public DateField(String caption, Property dataSource) {
  121. this(dataSource);
  122. setCaption(caption);
  123. }
  124. /**
  125. * Constructs a new <code>DateField</code> that's bound to the specified
  126. * <code>Property</code> and has no caption.
  127. *
  128. * @param dataSource
  129. * the Property to be edited with this editor.
  130. */
  131. public DateField(Property dataSource) throws IllegalArgumentException {
  132. if (!Date.class.isAssignableFrom(dataSource.getType())) {
  133. throw new IllegalArgumentException("Can't use "
  134. + dataSource.getType().getName()
  135. + " typed property as datasource");
  136. }
  137. setPropertyDataSource(dataSource);
  138. }
  139. /**
  140. * Constructs a new <code>DateField</code> with the given caption and
  141. * initial text contents. The editor constructed this way will not be bound
  142. * to a Property unless
  143. * {@link com.vaadin.data.Property.Viewer#setPropertyDataSource(Property)}
  144. * is called to bind it.
  145. *
  146. * @param caption
  147. * the caption <code>String</code> for the editor.
  148. * @param value
  149. * the Date value.
  150. */
  151. public DateField(String caption, Date value) {
  152. setValue(value);
  153. setCaption(caption);
  154. }
  155. /* Component basic features */
  156. /*
  157. * Paints this component. Don't add a JavaDoc comment here, we use the
  158. * default documentation from implemented interface.
  159. */
  160. @Override
  161. public void paintContent(PaintTarget target) throws PaintException {
  162. super.paintContent(target);
  163. // Adds the locale as attribute
  164. final Locale l = getLocale();
  165. if (l != null) {
  166. target.addAttribute("locale", l.toString());
  167. }
  168. if (getDateFormat() != null) {
  169. target.addAttribute("format", dateFormat);
  170. }
  171. target.addAttribute("type", type);
  172. // Gets the calendar
  173. final Calendar calendar = getCalendar();
  174. final Date currentDate = (Date) getValue();
  175. for (int r = resolution; r <= largestModifiable; r++) {
  176. switch (r) {
  177. case RESOLUTION_MSEC:
  178. target.addVariable(this, "msec", currentDate != null ? calendar
  179. .get(Calendar.MILLISECOND) : -1);
  180. break;
  181. case RESOLUTION_SEC:
  182. target.addVariable(this, "sec", currentDate != null ? calendar
  183. .get(Calendar.SECOND) : -1);
  184. break;
  185. case RESOLUTION_MIN:
  186. target.addVariable(this, "min", currentDate != null ? calendar
  187. .get(Calendar.MINUTE) : -1);
  188. break;
  189. case RESOLUTION_HOUR:
  190. target.addVariable(this, "hour", currentDate != null ? calendar
  191. .get(Calendar.HOUR_OF_DAY) : -1);
  192. break;
  193. case RESOLUTION_DAY:
  194. target.addVariable(this, "day", currentDate != null ? calendar
  195. .get(Calendar.DAY_OF_MONTH) : -1);
  196. break;
  197. case RESOLUTION_MONTH:
  198. target.addVariable(this, "month",
  199. currentDate != null ? calendar.get(Calendar.MONTH) + 1
  200. : -1);
  201. break;
  202. case RESOLUTION_YEAR:
  203. target.addVariable(this, "year", currentDate != null ? calendar
  204. .get(Calendar.YEAR) : -1);
  205. break;
  206. }
  207. }
  208. }
  209. /*
  210. * Gets the components UIDL tag string. Don't add a JavaDoc comment here, we
  211. * use the default documentation from implemented interface.
  212. */
  213. @Override
  214. public String getTag() {
  215. return "datefield";
  216. }
  217. /*
  218. * Invoked when a variable of the component changes. Don't add a JavaDoc
  219. * comment here, we use the default documentation from implemented
  220. * interface.
  221. */
  222. @Override
  223. public void changeVariables(Object source, Map variables) {
  224. super.changeVariables(source, variables);
  225. if (!isReadOnly()
  226. && (variables.containsKey("year")
  227. || variables.containsKey("month")
  228. || variables.containsKey("day")
  229. || variables.containsKey("hour")
  230. || variables.containsKey("min")
  231. || variables.containsKey("sec")
  232. || variables.containsKey("msec") || variables
  233. .containsKey("dateString"))) {
  234. // Old and new dates
  235. final Date oldDate = (Date) getValue();
  236. final String oldDateString = dateString;
  237. Date newDate = null;
  238. // this enables analyzing invalid input on the server
  239. Object o = variables.get("dateString");
  240. if (o != null) {
  241. dateString = o.toString();
  242. } else {
  243. dateString = null;
  244. }
  245. // Gets the new date in parts
  246. // Null values are converted to negative values.
  247. int year = variables.containsKey("year") ? (variables.get("year") == null ? -1
  248. : ((Integer) variables.get("year")).intValue())
  249. : -1;
  250. int month = variables.containsKey("month") ? (variables
  251. .get("month") == null ? -1 : ((Integer) variables
  252. .get("month")).intValue() - 1) : -1;
  253. int day = variables.containsKey("day") ? (variables.get("day") == null ? -1
  254. : ((Integer) variables.get("day")).intValue())
  255. : -1;
  256. int hour = variables.containsKey("hour") ? (variables.get("hour") == null ? -1
  257. : ((Integer) variables.get("hour")).intValue())
  258. : -1;
  259. int min = variables.containsKey("min") ? (variables.get("min") == null ? -1
  260. : ((Integer) variables.get("min")).intValue())
  261. : -1;
  262. int sec = variables.containsKey("sec") ? (variables.get("sec") == null ? -1
  263. : ((Integer) variables.get("sec")).intValue())
  264. : -1;
  265. int msec = variables.containsKey("msec") ? (variables.get("msec") == null ? -1
  266. : ((Integer) variables.get("msec")).intValue())
  267. : -1;
  268. // If all of the components is < 0 use the previous value
  269. if (year < 0 && month < 0 && day < 0 && hour < 0 && min < 0
  270. && sec < 0 && msec < 0) {
  271. newDate = null;
  272. } else {
  273. // Clone the calendar for date operation
  274. final Calendar cal = getCalendar();
  275. // Make sure that meaningful values exists
  276. // Use the previous value if some of the variables
  277. // have not been changed.
  278. year = year < 0 ? cal.get(Calendar.YEAR) : year;
  279. month = month < 0 ? cal.get(Calendar.MONTH) : month;
  280. day = day < 0 ? cal.get(Calendar.DAY_OF_MONTH) : day;
  281. hour = hour < 0 ? cal.get(Calendar.HOUR_OF_DAY) : hour;
  282. min = min < 0 ? cal.get(Calendar.MINUTE) : min;
  283. sec = sec < 0 ? cal.get(Calendar.SECOND) : sec;
  284. msec = msec < 0 ? cal.get(Calendar.MILLISECOND) : msec;
  285. // Sets the calendar fields
  286. cal.set(Calendar.YEAR, year);
  287. cal.set(Calendar.MONTH, month);
  288. cal.set(Calendar.DAY_OF_MONTH, day);
  289. cal.set(Calendar.HOUR_OF_DAY, hour);
  290. cal.set(Calendar.MINUTE, min);
  291. cal.set(Calendar.SECOND, sec);
  292. cal.set(Calendar.MILLISECOND, msec);
  293. // Assigns the date
  294. newDate = cal.getTime();
  295. }
  296. if (newDate != oldDate
  297. && (newDate == null || !newDate.equals(oldDate))) {
  298. setValue(newDate, true); // Don't require a repaint, client
  299. // updates itself
  300. } else if (dateString != null && !"".equals(dateString)
  301. && !dateString.equals(oldDateString)) {
  302. setValue(handleUnparsableDateString(dateString));
  303. }
  304. }
  305. }
  306. /**
  307. * This method is called to handle the date string from the client if the
  308. * client could not parse it as a Date.
  309. *
  310. * By default, null is returned. If an exception is thrown, the current
  311. * value will not be modified.
  312. *
  313. * This can be overridden to handle conversions or to throw an exception, or
  314. * to fire an event.
  315. *
  316. * The default behavior is likely to change in the next major version of the
  317. * toolkit - a Property.ConversionException will be thrown.
  318. *
  319. * @param dateString
  320. * @return parsed Date
  321. * @throws Property.ConversionException
  322. * to keep the old value and indicate an error
  323. */
  324. protected Date handleUnparsableDateString(String dateString)
  325. throws Property.ConversionException {
  326. // TODO in the next major version, this should throw an exception to be
  327. // consistent with other fields
  328. // throw new Property.ConversionException();
  329. return null;
  330. }
  331. /* Property features */
  332. /*
  333. * Gets the edited property's type. Don't add a JavaDoc comment here, we use
  334. * the default documentation from implemented interface.
  335. */
  336. @Override
  337. public Class getType() {
  338. return Date.class;
  339. }
  340. /*
  341. * Returns the value of the property in human readable textual format. Don't
  342. * add a JavaDoc comment here, we use the default documentation from
  343. * implemented interface.
  344. */
  345. @Override
  346. public String toString() {
  347. final Date value = (Date) getValue();
  348. if (value != null) {
  349. return value.toString();
  350. }
  351. return null;
  352. }
  353. /*
  354. * Sets the value of the property. Don't add a JavaDoc comment here, we use
  355. * the default documentation from implemented interface.
  356. */
  357. @Override
  358. public void setValue(Object newValue) throws Property.ReadOnlyException,
  359. Property.ConversionException {
  360. setValue(newValue, false);
  361. }
  362. @Override
  363. public void setValue(Object newValue, boolean repaintIsNotNeeded)
  364. throws Property.ReadOnlyException, Property.ConversionException {
  365. // Allows setting dates directly
  366. if (newValue == null || newValue instanceof Date) {
  367. super.setValue(newValue, repaintIsNotNeeded);
  368. } else {
  369. // Try to parse as string
  370. try {
  371. final SimpleDateFormat parser = new SimpleDateFormat();
  372. final Date val = parser.parse(newValue.toString());
  373. super.setValue(val, repaintIsNotNeeded);
  374. } catch (final ParseException e) {
  375. throw new Property.ConversionException(e.getMessage());
  376. }
  377. }
  378. }
  379. /**
  380. * Sets the DateField datasource. Datasource type must assignable to Date.
  381. *
  382. * @see com.vaadin.data.Property.Viewer#setPropertyDataSource(Property)
  383. */
  384. @Override
  385. public void setPropertyDataSource(Property newDataSource) {
  386. if (newDataSource == null
  387. || Date.class.isAssignableFrom(newDataSource.getType())) {
  388. super.setPropertyDataSource(newDataSource);
  389. } else {
  390. throw new IllegalArgumentException(
  391. "DateField only supports Date properties");
  392. }
  393. }
  394. /**
  395. * Gets the resolution.
  396. *
  397. * @return int
  398. */
  399. public int getResolution() {
  400. return resolution;
  401. }
  402. /**
  403. * Sets the resolution of the DateField.
  404. *
  405. * @param resolution
  406. * the resolution to set.
  407. */
  408. public void setResolution(int resolution) {
  409. this.resolution = resolution;
  410. }
  411. /**
  412. * Returns new instance calendar used in Date conversions.
  413. *
  414. * Returns new clone of the calendar object initialized using the the
  415. * current date (if available)
  416. *
  417. * If this is no calendar is assigned the <code>Calendar.getInstance</code>
  418. * is used.
  419. *
  420. * @return the Calendar.
  421. * @see #setCalendar(Calendar)
  422. */
  423. private Calendar getCalendar() {
  424. // Makes sure we have an calendar instance
  425. if (calendar == null) {
  426. calendar = Calendar.getInstance();
  427. }
  428. // Clone the instance
  429. final Calendar newCal = (Calendar) calendar.clone();
  430. // Assigns the current time tom calendar.
  431. final Date currentDate = (Date) getValue();
  432. if (currentDate != null) {
  433. newCal.setTime(currentDate);
  434. }
  435. return newCal;
  436. }
  437. /**
  438. * Sets formatting used by some component implementations. See
  439. * {@link SimpleDateFormat} for format details.
  440. *
  441. * By default it is encouraged to used default formatting defined by Locale,
  442. * but due some JVM bugs it is sometimes necessary to use this method to
  443. * override formatting. See Toolkit issue #2200.
  444. *
  445. * @param dateFormat
  446. * the dateFormat to set
  447. *
  448. * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
  449. */
  450. public void setDateFormat(String dateFormat) {
  451. this.dateFormat = dateFormat;
  452. }
  453. /**
  454. * Reterns a format string used to format date value on client side or null
  455. * if default formatting from {@link Component#getLocale()} is used.
  456. *
  457. * @return the dateFormat
  458. */
  459. public String getDateFormat() {
  460. return dateFormat;
  461. }
  462. }