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 36KB

Migrating 7.7.1, 7.7.2, 7.7.3 to V8. commit 11c3f8bd9ea65f7a7b8da9a282c31a127bd475a6 - Test and its UI class are added (both V8 and V7). Required functionality should be available via modern GWT version. commit 729dbf96fe76e7627168ab2c9d1d71c4eb7214c8 - About update release notes. No need to be included. commit 675f38349c43ac45dae40cf33a7b1fd0f8f261ca - V8 already contains correct Import-Packages section which uses osgi.javax.servlet.version variable whise version is 3.0.0 at the moment. commit 5da7c052f55cb4703b74f38f5bb19fc3f3fa2a76 - Use Vaadin plugin 7.7.0 from 7.7.0.alpha1. Is not applicable. commit 1df80001ab6c916effa917781dba652d09d01056 - Updated tutorial to Vaadin 7.7.0. Is not applicable. The tutorial already contains correct links and updated source code snippets. commit 8b4f0ed8a894b04902a5d4258119dcdc8e76d1e0 - set-property-fallback name="user.agent" value="safari". Is already there. commit 28ed04e827669cc4dd329331dac9699bd93f70bc - Fix animation end listeners so they are always removed. Is already there. commit 408253bc3f8bd3975f0525ce6832be214a3552e9 - Use servlet context classloader when finding servlet class for websockets. Is already there. commit 7a6f250d89474849648ed2ee96a6bfb78c3b9ca8 - Fire actions before removing menu from the DOM. Is already there. commit 9b66c6eb9bebf657d3f2def8c767e0e9d51cc92c - Do not run test on IE8 as IE8 is broken. Transplanted. commit 3faa43ff39ecda56587b93f0c5e262a2907871a7 - Discard for DateField when the data source contains null. It is not applicable for V8 (There is no anymore discard method in DateField (and no datasource suport in field)). Transplanted for DateField in compatibility. commit e0c1f91a3d6d1884e07ce8d1ba957aff6a9bf29a - Fix ComboBox paging when number of items equals page length. It's already done by another fix which replaced ComboBox in compatibility package to the V7 version. commit 83a1b8a0961cc9b2d43e01757530cefd035b0a22 - Update DOM and update escalator row count in the correct order. Transplanted. commit 45f2fba8ff7a4b62680618a325d4afcebfb7a1e9 - Prevent editor from being canceled while it is being saved. Transplanted to compatibility package. Is not applicable to modern Grid. commit ad67f7f43afb0feec5e029aea90297f2abe4f2c1 - Delete broken stylesheet and revert to default style until a new stylesheet is created. Is already there. commit c970a78d42a2d8f1745df7a11a74f3731f8be9a5 - Always show loading indicator for JavaScript RPC. Transplanted. commit 2aad3416061586f7e2649160bd832eefe03702ad - Make test independent of any converters present in the factory. It's already there. commit c9ad48430be135d18fe9f30868e091dd51c57b94 - Do not include yuicompressor for Sass compiler. Transplanted. Exclusion is added into vaadin/pom.xml commit 52d01a68e91ce73306b3a1747af97e928048ecdf - Test for Firefox download disconnecting push channel. Transplanted. commit 4bc375d1d21f468e6433da3a183150e0bfe0cae4 - Handle encoded URL characters correctly when constructing widget set name. Transplanted. commit 17ba88eaf87e15e6f3c729e5c7f8e875d5f86d8d - Update version to 7.7-SNAPSHOT. Is not applicable. commit 47b7b13e5c959de3bd925693b074d85e7625a87e - Ensure Firefox always updates the grid scrollbar. Transplanted. Made changes in the logic to the test for modern Grid component. commit 4d851ba21d1b8f35685b631d2845731f8fb33252 - Calculate column widths immediately if there is data. Transplanted to both client side modules. commit 8f0b1a1dd026a756912c9f21bd2b34ea46897c7f - Skip Maven enforcer plugin during demo validation. Transplanted (one build file is affected). commit 62815353e1b9d3cd126809f5c818ad35bf913807 - Build demos from 7.7 branch (now for master branch). FW8 demos are added (one build file is affected). commit 815d72115d5aaf3676daefd5642115577e4151ef - Make test pass on all browsers. Transplanted to both V7 and V8 version tests. commit 516c428ca127e3c31b7b4d74220e4b7eed4571be - Use widget set specified by init parameter. Transplanted to the one UIProvider class. commit b00c580ed70f682a42afbfa91f978921bb86c2cd - Use correct column index when calculating min width during resize. Transplanted into both client side classes (main and compatibility) as is. Test for V7 is transplanted as is. Test for V8 is written from scratch based on V7 version. commit 7dd91cf057eb06a09009096a8278f34aad9bd8d9 - Fix regression that broke widget set compilation in 7.7.1. It's already there. commit c665731b0b97b697e80c47955d3558c19f0c81cb - Ensure temporary layout manager state is cleared at the end of a layout phase. Transplanted to the one LayoutManager class. commit 57a965251afdb5ee9ac1913a0101d854d8215aa6 - Fix assertion error when column widths are calculated. Transplanted to both versions of the client Grid widget. commit c5c52684eb30d924cb75a632b526a0f879d5a33c - Format Java files using Eclipse Neon and Vaadin settings. Only formatting changes. Is not transplanted. f5d06d877165bf413ec71c4fc88cf46c8c57a372 - Change javadoc to a style Eclipse formatter can handle. Transplanted to both versions of the client Grid widget. commit 6033e13c20b3d6e8b6f5add0f786d5ab2e1bb3fe - Make initially disabled grid work when enabled. Transplanted to both client side modules. commit a2d6e4fb4b1fd13e9a1b88f2ab1b78d14d8b64a9 - Use requestAnimationFrame when scrolling in Grid. Transplanted to both client side modules. commit fe9438e7b77c606855cfd739dd7e30b3f8cd4165 - Specify branch also for Sampler. Is not applicable for master branch. commit 1ec5d8ef7cb8bbd82bae1c9b79a376a5dca28f48 - Update to Chrome 53. Is already there. commit 961851bfbc4844474299433c34af6c9e4323d891 - Updated link to new step 1 video in tutorial. Is already there. commit 41dc2fe1611adc70d00e6f77debb2a6d4dcdefb0 - Revert "Use widget set specified by init parameter. Transplanted to the one UIProvider class. commit 092b4f7f3192555fe3ae22ac03a89ac2ada2a2dd - Use widget set specified by init parameter. Transplanted to the common server side classes. commit 977cec7e3107c2da306d46449dbf32f6544313be - Fix widget set builder to create widget set in correct location. Transplanted to the one ClassPathExplorer class file. commit 6c12ad89ea1064cd4cc0456baca5ee00ae76d032 - Format project pom files using correct settings. Is not transplanted: only formatting changes for POM files. commit 0aad93ecc1ce743dffc093ce7ae2ef88831f6073 - Add tests for widgetset compilation in different modes. Transplanted. New test projects. commit 0a3a1ef8321ed421be2337034fdb1cae2c434c3d - Use versions-maven-plugin 2.3 to avoid NPE while setting project version. Is already there. Change-Id: Ie3a5088f25de1772f01ea30c4a5eba0b169ee0ab
7 years ago
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. }