Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

VPopupCalendar.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. /*
  2. * Copyright 2000-2013 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.client.ui;
  17. import java.util.Date;
  18. import com.google.gwt.aria.client.Id;
  19. import com.google.gwt.aria.client.LiveValue;
  20. import com.google.gwt.aria.client.Roles;
  21. import com.google.gwt.core.client.GWT;
  22. import com.google.gwt.event.dom.client.ClickEvent;
  23. import com.google.gwt.event.dom.client.ClickHandler;
  24. import com.google.gwt.event.dom.client.DomEvent;
  25. import com.google.gwt.event.dom.client.KeyCodes;
  26. import com.google.gwt.event.logical.shared.CloseEvent;
  27. import com.google.gwt.event.logical.shared.CloseHandler;
  28. import com.google.gwt.i18n.client.DateTimeFormat;
  29. import com.google.gwt.user.client.DOM;
  30. import com.google.gwt.user.client.Event;
  31. import com.google.gwt.user.client.Timer;
  32. import com.google.gwt.user.client.Window;
  33. import com.google.gwt.user.client.ui.Button;
  34. import com.google.gwt.user.client.ui.FlowPanel;
  35. import com.google.gwt.user.client.ui.Label;
  36. import com.google.gwt.user.client.ui.PopupPanel;
  37. import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
  38. import com.google.gwt.user.client.ui.RootPanel;
  39. import com.google.gwt.user.client.ui.Widget;
  40. import com.vaadin.client.BrowserInfo;
  41. import com.vaadin.client.VConsole;
  42. import com.vaadin.client.ui.VCalendarPanel.FocusOutListener;
  43. import com.vaadin.client.ui.VCalendarPanel.SubmitListener;
  44. import com.vaadin.client.ui.aria.AriaHelper;
  45. import com.vaadin.shared.ui.datefield.PopupDateFieldState;
  46. import com.vaadin.shared.ui.datefield.Resolution;
  47. /**
  48. * Represents a date selection component with a text field and a popup date
  49. * selector.
  50. *
  51. * <b>Note:</b> To change the keyboard assignments used in the popup dialog you
  52. * should extend <code>com.vaadin.client.ui.VCalendarPanel</code> and then pass
  53. * set it by calling the <code>setCalendarPanel(VCalendarPanel panel)</code>
  54. * method.
  55. *
  56. */
  57. public class VPopupCalendar extends VTextualDate implements Field,
  58. ClickHandler, CloseHandler<PopupPanel>, SubPartAware {
  59. /** For internal use only. May be removed or replaced in the future. */
  60. public final Button calendarToggle = new Button();
  61. /** For internal use only. May be removed or replaced in the future. */
  62. public VCalendarPanel calendar;
  63. /** For internal use only. May be removed or replaced in the future. */
  64. public final VOverlay popup;
  65. /** For internal use only. May be removed or replaced in the future. */
  66. public boolean parsable = true;
  67. private boolean open = false;
  68. private boolean textFieldEnabled = true;
  69. private String captionId;
  70. private Label selectedDate;
  71. private com.google.gwt.user.client.Element descriptionForAssisitveDevicesElement;
  72. public VPopupCalendar() {
  73. super();
  74. calendarToggle.setText("");
  75. calendarToggle.addClickHandler(this);
  76. // -2 instead of -1 to avoid FocusWidget.onAttach to reset it
  77. calendarToggle.getElement().setTabIndex(-2);
  78. Roles.getButtonRole().set(calendarToggle.getElement());
  79. Roles.getButtonRole().setAriaHiddenState(calendarToggle.getElement(),
  80. true);
  81. add(calendarToggle);
  82. // Description of the usage of the widget for assisitve device users
  83. descriptionForAssisitveDevicesElement = DOM.createDiv();
  84. descriptionForAssisitveDevicesElement
  85. .setInnerText(PopupDateFieldState.DESCRIPTION_FOR_ASSISTIVE_DEVICES);
  86. AriaHelper.ensureHasId(descriptionForAssisitveDevicesElement);
  87. Roles.getTextboxRole().setAriaDescribedbyProperty(text.getElement(),
  88. Id.of(descriptionForAssisitveDevicesElement));
  89. AriaHelper.setVisibleForAssistiveDevicesOnly(
  90. descriptionForAssisitveDevicesElement, true);
  91. calendar = GWT.create(VCalendarPanel.class);
  92. calendar.setParentField(this);
  93. calendar.setFocusOutListener(new FocusOutListener() {
  94. @Override
  95. public boolean onFocusOut(DomEvent<?> event) {
  96. event.preventDefault();
  97. closeCalendarPanel();
  98. return true;
  99. }
  100. });
  101. // FIXME: Problem is, that the element with the provided id does not
  102. // exist yet in html. This is the same problem as with the context menu.
  103. // Apply here the same fix (#11795)
  104. Roles.getTextboxRole().setAriaControlsProperty(text.getElement(),
  105. Id.of(calendar.getElement()));
  106. Roles.getButtonRole().setAriaControlsProperty(
  107. calendarToggle.getElement(), Id.of(calendar.getElement()));
  108. calendar.setSubmitListener(new SubmitListener() {
  109. @Override
  110. public void onSubmit() {
  111. // Update internal value and send valuechange event if immediate
  112. updateValue(calendar.getDate());
  113. // Update text field (a must when not immediate).
  114. buildDate(true);
  115. closeCalendarPanel();
  116. }
  117. @Override
  118. public void onCancel() {
  119. closeCalendarPanel();
  120. }
  121. });
  122. popup = new VOverlay(true, true, true);
  123. popup.setOwner(this);
  124. FlowPanel wrapper = new FlowPanel();
  125. selectedDate = new Label();
  126. selectedDate.setStyleName(getStylePrimaryName() + "-selecteddate");
  127. AriaHelper.setVisibleForAssistiveDevicesOnly(selectedDate.getElement(),
  128. true);
  129. Roles.getTextboxRole().setAriaLiveProperty(selectedDate.getElement(),
  130. LiveValue.ASSERTIVE);
  131. Roles.getTextboxRole().setAriaAtomicProperty(selectedDate.getElement(),
  132. true);
  133. wrapper.add(selectedDate);
  134. wrapper.add(calendar);
  135. popup.setWidget(wrapper);
  136. popup.addCloseHandler(this);
  137. DOM.setElementProperty(calendar.getElement(), "id",
  138. "PID_VAADIN_POPUPCAL");
  139. sinkEvents(Event.ONKEYDOWN);
  140. updateStyleNames();
  141. }
  142. @Override
  143. protected void onAttach() {
  144. super.onAttach();
  145. DOM.appendChild(RootPanel.get().getElement(),
  146. descriptionForAssisitveDevicesElement);
  147. }
  148. @Override
  149. protected void onDetach() {
  150. super.onDetach();
  151. descriptionForAssisitveDevicesElement.removeFromParent();
  152. }
  153. @SuppressWarnings("deprecation")
  154. public void updateValue(Date newDate) {
  155. Date currentDate = getCurrentDate();
  156. if (currentDate == null || newDate.getTime() != currentDate.getTime()) {
  157. setCurrentDate((Date) newDate.clone());
  158. getClient().updateVariable(getId(), "year",
  159. newDate.getYear() + 1900, false);
  160. if (getCurrentResolution().getCalendarField() > Resolution.YEAR
  161. .getCalendarField()) {
  162. getClient().updateVariable(getId(), "month",
  163. newDate.getMonth() + 1, false);
  164. if (getCurrentResolution().getCalendarField() > Resolution.MONTH
  165. .getCalendarField()) {
  166. getClient().updateVariable(getId(), "day",
  167. newDate.getDate(), false);
  168. if (getCurrentResolution().getCalendarField() > Resolution.DAY
  169. .getCalendarField()) {
  170. getClient().updateVariable(getId(), "hour",
  171. newDate.getHours(), false);
  172. if (getCurrentResolution().getCalendarField() > Resolution.HOUR
  173. .getCalendarField()) {
  174. getClient().updateVariable(getId(), "min",
  175. newDate.getMinutes(), false);
  176. if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
  177. .getCalendarField()) {
  178. getClient().updateVariable(getId(), "sec",
  179. newDate.getSeconds(), false);
  180. }
  181. }
  182. }
  183. }
  184. }
  185. }
  186. }
  187. /**
  188. * Checks whether the text field is enabled.
  189. *
  190. * @see VPopupCalendar#setTextFieldEnabled(boolean)
  191. * @return The current state of the text field.
  192. */
  193. public boolean isTextFieldEnabled() {
  194. return textFieldEnabled;
  195. }
  196. /**
  197. * Sets the state of the text field of this component. By default the text
  198. * field is enabled. Disabling it causes only the button for date selection
  199. * to be active, thus preventing the user from entering invalid dates. See
  200. * {@link http://dev.vaadin.com/ticket/6790}.
  201. *
  202. * @param state
  203. */
  204. public void setTextFieldEnabled(boolean textFieldEnabled) {
  205. this.textFieldEnabled = textFieldEnabled;
  206. text.setEnabled(textFieldEnabled);
  207. if (textFieldEnabled) {
  208. calendarToggle.setTabIndex(-1);
  209. Roles.getButtonRole().setAriaHiddenState(
  210. calendarToggle.getElement(), true);
  211. } else {
  212. calendarToggle.setTabIndex(0);
  213. Roles.getButtonRole().setAriaHiddenState(
  214. calendarToggle.getElement(), false);
  215. }
  216. handleAriaAttributes();
  217. }
  218. @Override
  219. public void bindAriaCaption(
  220. com.google.gwt.user.client.Element captionElement) {
  221. if (captionElement == null) {
  222. captionId = null;
  223. } else {
  224. captionId = captionElement.getId();
  225. }
  226. if (isTextFieldEnabled()) {
  227. super.bindAriaCaption(captionElement);
  228. } else {
  229. AriaHelper.bindCaption(calendarToggle, captionElement);
  230. }
  231. handleAriaAttributes();
  232. }
  233. private void handleAriaAttributes() {
  234. Widget removeFromWidget;
  235. Widget setForWidget;
  236. if (isTextFieldEnabled()) {
  237. setForWidget = text;
  238. removeFromWidget = calendarToggle;
  239. } else {
  240. setForWidget = calendarToggle;
  241. removeFromWidget = text;
  242. }
  243. Roles.getFormRole().removeAriaLabelledbyProperty(
  244. removeFromWidget.getElement());
  245. if (captionId == null) {
  246. Roles.getFormRole().removeAriaLabelledbyProperty(
  247. setForWidget.getElement());
  248. } else {
  249. Roles.getFormRole().setAriaLabelledbyProperty(
  250. setForWidget.getElement(), Id.of(captionId));
  251. }
  252. }
  253. /*
  254. * (non-Javadoc)
  255. *
  256. * @see
  257. * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
  258. */
  259. @Override
  260. public void setStyleName(String style) {
  261. super.setStyleName(style);
  262. updateStyleNames();
  263. }
  264. @Override
  265. public void setStylePrimaryName(String style) {
  266. removeStyleName(getStylePrimaryName() + "-popupcalendar");
  267. super.setStylePrimaryName(style);
  268. updateStyleNames();
  269. }
  270. @Override
  271. protected void updateStyleNames() {
  272. super.updateStyleNames();
  273. if (getStylePrimaryName() != null && calendarToggle != null) {
  274. addStyleName(getStylePrimaryName() + "-popupcalendar");
  275. calendarToggle.setStyleName(getStylePrimaryName() + "-button");
  276. popup.setStyleName(getStylePrimaryName() + "-popup");
  277. calendar.setStyleName(getStylePrimaryName() + "-calendarpanel");
  278. }
  279. }
  280. /**
  281. * Opens the calendar panel popup
  282. */
  283. public void openCalendarPanel() {
  284. if (!open && !readonly) {
  285. open = true;
  286. if (getCurrentDate() != null) {
  287. calendar.setDate((Date) getCurrentDate().clone());
  288. } else {
  289. calendar.setDate(new Date());
  290. }
  291. // clear previous values
  292. popup.setWidth("");
  293. popup.setHeight("");
  294. popup.setPopupPositionAndShow(new PositionCallback() {
  295. @Override
  296. public void setPosition(int offsetWidth, int offsetHeight) {
  297. final int w = offsetWidth;
  298. final int h = offsetHeight;
  299. final int browserWindowWidth = Window.getClientWidth()
  300. + Window.getScrollLeft();
  301. final int browserWindowHeight = Window.getClientHeight()
  302. + Window.getScrollTop();
  303. int t = calendarToggle.getAbsoluteTop();
  304. int l = calendarToggle.getAbsoluteLeft();
  305. // Add a little extra space to the right to avoid
  306. // problems with IE7 scrollbars and to make it look
  307. // nicer.
  308. int extraSpace = 30;
  309. boolean overflowRight = false;
  310. if (l + +w + extraSpace > browserWindowWidth) {
  311. overflowRight = true;
  312. // Part of the popup is outside the browser window
  313. // (to the right)
  314. l = browserWindowWidth - w - extraSpace;
  315. }
  316. if (t + h + calendarToggle.getOffsetHeight() + 30 > browserWindowHeight) {
  317. // Part of the popup is outside the browser window
  318. // (below)
  319. t = browserWindowHeight - h
  320. - calendarToggle.getOffsetHeight() - 30;
  321. if (!overflowRight) {
  322. // Show to the right of the popup button unless we
  323. // are in the lower right corner of the screen
  324. l += calendarToggle.getOffsetWidth();
  325. }
  326. }
  327. popup.setPopupPosition(l,
  328. t + calendarToggle.getOffsetHeight() + 2);
  329. /*
  330. * We have to wait a while before focusing since the popup
  331. * needs to be opened before we can focus
  332. */
  333. Timer focusTimer = new Timer() {
  334. @Override
  335. public void run() {
  336. setFocus(true);
  337. }
  338. };
  339. focusTimer.schedule(100);
  340. }
  341. });
  342. } else {
  343. VConsole.error("Cannot reopen popup, it is already open!");
  344. }
  345. }
  346. /*
  347. * (non-Javadoc)
  348. *
  349. * @see
  350. * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
  351. * .dom.client.ClickEvent)
  352. */
  353. @Override
  354. public void onClick(ClickEvent event) {
  355. if (event.getSource() == calendarToggle && isEnabled()) {
  356. openCalendarPanel();
  357. }
  358. }
  359. /*
  360. * (non-Javadoc)
  361. *
  362. * @see
  363. * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt
  364. * .event.logical.shared.CloseEvent)
  365. */
  366. @Override
  367. public void onClose(CloseEvent<PopupPanel> event) {
  368. if (event.getSource() == popup) {
  369. buildDate();
  370. if (!BrowserInfo.get().isTouchDevice()) {
  371. /*
  372. * Move focus to textbox, unless on touch device (avoids opening
  373. * virtual keyboard).
  374. */
  375. focus();
  376. }
  377. // TODO resolve what the "Sigh." is all about and document it here
  378. // Sigh.
  379. Timer t = new Timer() {
  380. @Override
  381. public void run() {
  382. open = false;
  383. }
  384. };
  385. t.schedule(100);
  386. }
  387. }
  388. /**
  389. * Sets focus to Calendar panel.
  390. *
  391. * @param focus
  392. */
  393. public void setFocus(boolean focus) {
  394. calendar.setFocus(focus);
  395. }
  396. @Override
  397. public void setEnabled(boolean enabled) {
  398. super.setEnabled(enabled);
  399. if (enabled) {
  400. Roles.getButtonRole().setAriaDisabledState(
  401. calendarToggle.getElement(), true);
  402. } else {
  403. Roles.getButtonRole().setAriaDisabledState(
  404. calendarToggle.getElement(), false);
  405. }
  406. }
  407. /**
  408. * Sets the content of a special field for assistive devices, so that they
  409. * can recognize the change and inform the user (reading out in case of
  410. * screen reader)
  411. *
  412. * @param selectedDate
  413. * Date that is currently selected
  414. */
  415. public void setFocusedDate(Date selectedDate) {
  416. this.selectedDate.setText(DateTimeFormat.getFormat("dd, MMMM, yyyy")
  417. .format(selectedDate));
  418. }
  419. /**
  420. * For internal use only. May be removed or replaced in the future.
  421. *
  422. * @see com.vaadin.client.ui.VTextualDate#buildDate()
  423. */
  424. @Override
  425. public void buildDate() {
  426. // Save previous value
  427. String previousValue = getText();
  428. super.buildDate();
  429. // Restore previous value if the input could not be parsed
  430. if (!parsable) {
  431. setText(previousValue);
  432. }
  433. // superclass sets the text field independently when building date
  434. text.setEnabled(isEnabled() && isTextFieldEnabled());
  435. }
  436. /**
  437. * Update the text field contents from the date. See {@link #buildDate()}.
  438. *
  439. * @param forceValid
  440. * true to force the text field to be updated, false to only
  441. * update if the parsable flag is true.
  442. */
  443. protected void buildDate(boolean forceValid) {
  444. if (forceValid) {
  445. parsable = true;
  446. }
  447. buildDate();
  448. }
  449. /*
  450. * (non-Javadoc)
  451. *
  452. * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google
  453. * .gwt.user.client.Event)
  454. */
  455. @Override
  456. public void onBrowserEvent(com.google.gwt.user.client.Event event) {
  457. super.onBrowserEvent(event);
  458. if (DOM.eventGetType(event) == Event.ONKEYDOWN
  459. && event.getKeyCode() == getOpenCalenderPanelKey()) {
  460. openCalendarPanel();
  461. event.preventDefault();
  462. }
  463. }
  464. /**
  465. * Get the key code that opens the calendar panel. By default it is the down
  466. * key but you can override this to be whatever you like
  467. *
  468. * @return
  469. */
  470. protected int getOpenCalenderPanelKey() {
  471. return KeyCodes.KEY_DOWN;
  472. }
  473. /**
  474. * Closes the open popup panel
  475. */
  476. public void closeCalendarPanel() {
  477. if (open) {
  478. popup.hide(true);
  479. }
  480. }
  481. private final String CALENDAR_TOGGLE_ID = "popupButton";
  482. @Override
  483. public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
  484. if (subPart.equals(CALENDAR_TOGGLE_ID)) {
  485. return calendarToggle.getElement();
  486. }
  487. return super.getSubPartElement(subPart);
  488. }
  489. @Override
  490. public String getSubPartName(com.google.gwt.user.client.Element subElement) {
  491. if (calendarToggle.getElement().isOrHasChild(subElement)) {
  492. return CALENDAR_TOGGLE_ID;
  493. }
  494. return super.getSubPartName(subElement);
  495. }
  496. /**
  497. * Set a description that explains the usage of the Widget for users of
  498. * assistive devices.
  499. *
  500. * @param descriptionForAssistiveDevices
  501. * String with the description
  502. */
  503. public void setDescriptionForAssistiveDevices(
  504. String descriptionForAssistiveDevices) {
  505. descriptionForAssisitveDevicesElement
  506. .setInnerText(descriptionForAssistiveDevices);
  507. }
  508. /**
  509. * Get the description that explains the usage of the Widget for users of
  510. * assistive devices.
  511. *
  512. * @return String with the description
  513. */
  514. public String getDescriptionForAssistiveDevices() {
  515. return descriptionForAssisitveDevicesElement.getInnerText();
  516. }
  517. /**
  518. * Sets the start range for this component. The start range is inclusive,
  519. * and it depends on the current resolution, what is considered inside the
  520. * range.
  521. *
  522. * @param startDate
  523. * - the allowed range's start date
  524. */
  525. public void setRangeStart(Date rangeStart) {
  526. calendar.setRangeStart(rangeStart);
  527. }
  528. /**
  529. * Sets the end range for this component. The end range is inclusive, and it
  530. * depends on the current resolution, what is considered inside the range.
  531. *
  532. * @param endDate
  533. * - the allowed range's end date
  534. */
  535. public void setRangeEnd(Date rangeEnd) {
  536. calendar.setRangeEnd(rangeEnd);
  537. }
  538. }