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.

VAbstractPopupCalendar.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. /*
  2. * Copyright 2000-2018 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 java.util.logging.Logger;
  19. import com.google.gwt.aria.client.Id;
  20. import com.google.gwt.aria.client.LiveValue;
  21. import com.google.gwt.aria.client.Roles;
  22. import com.google.gwt.dom.client.Element;
  23. import com.google.gwt.event.dom.client.ClickEvent;
  24. import com.google.gwt.event.dom.client.ClickHandler;
  25. import com.google.gwt.event.dom.client.KeyCodes;
  26. import com.google.gwt.event.dom.client.MouseOutEvent;
  27. import com.google.gwt.event.dom.client.MouseOverEvent;
  28. import com.google.gwt.event.logical.shared.CloseEvent;
  29. import com.google.gwt.event.logical.shared.CloseHandler;
  30. import com.google.gwt.i18n.client.DateTimeFormat;
  31. import com.google.gwt.user.client.DOM;
  32. import com.google.gwt.user.client.Event;
  33. import com.google.gwt.user.client.Timer;
  34. import com.google.gwt.user.client.Window;
  35. import com.google.gwt.user.client.ui.Button;
  36. import com.google.gwt.user.client.ui.FlowPanel;
  37. import com.google.gwt.user.client.ui.Label;
  38. import com.google.gwt.user.client.ui.PopupPanel;
  39. import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
  40. import com.google.gwt.user.client.ui.RootPanel;
  41. import com.google.gwt.user.client.ui.Widget;
  42. import com.vaadin.client.BrowserInfo;
  43. import com.vaadin.client.ComputedStyle;
  44. import com.vaadin.client.ui.VAbstractCalendarPanel.SubmitListener;
  45. import com.vaadin.client.ui.aria.AriaHelper;
  46. import com.vaadin.shared.ui.datefield.TextualDateFieldState;
  47. /**
  48. * Represents a date selection component with a text field and a popup date/time
  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.VAbstractCalendarPanel</code> and
  53. * then pass set it by calling the
  54. * <code>setCalendarPanel(VAbstractCalendarPanel panel)</code> method.
  55. *
  56. * @since 8.0
  57. */
  58. public abstract class VAbstractPopupCalendar<PANEL extends VAbstractCalendarPanel<R>, R extends Enum<R>>
  59. extends VAbstractTextualDate<R>
  60. implements ClickHandler, CloseHandler<PopupPanel> {
  61. /** For internal use only. May be removed or replaced in the future. */
  62. public final Button calendarToggle = new Button();
  63. /** For internal use only. May be removed or replaced in the future. */
  64. public PANEL calendar;
  65. /** For internal use only. May be removed or replaced in the future. */
  66. public final VOverlay popup;
  67. /** For internal use only. May be removed or replaced in the future. */
  68. public boolean parsable = true;
  69. private boolean open;
  70. /*
  71. * #14857: If calendarToggle button is clicked when calendar popup is
  72. * already open we should prevent calling openCalendarPanel() in onClick,
  73. * since we don't want to reopen it again right after it closes.
  74. */
  75. private boolean preventOpenPopupCalendar;
  76. private boolean cursorOverCalendarToggleButton;
  77. private boolean toggleButtonClosesWithGuarantee;
  78. private boolean textFieldEnabled = true;
  79. private String captionId;
  80. private Label selectedDate;
  81. private Element descriptionForAssistiveDevicesElement;
  82. private static final String CALENDAR_TOGGLE_ID = "popupButton";
  83. public VAbstractPopupCalendar(PANEL calendarPanel, R resolution) {
  84. super(resolution);
  85. calendarToggle.setText("");
  86. calendarToggle.addClickHandler(this);
  87. calendarToggle.addDomHandler(
  88. event -> cursorOverCalendarToggleButton = true,
  89. MouseOverEvent.getType());
  90. calendarToggle.addDomHandler(
  91. event -> cursorOverCalendarToggleButton = false,
  92. MouseOutEvent.getType());
  93. // -2 instead of -1 to avoid FocusWidget.onAttach to reset it
  94. calendarToggle.getElement().setTabIndex(-2);
  95. Roles.getButtonRole().set(calendarToggle.getElement());
  96. Roles.getButtonRole().setAriaHiddenState(calendarToggle.getElement(),
  97. true);
  98. add(calendarToggle);
  99. // Description of the usage of the widget for assistive device users
  100. descriptionForAssistiveDevicesElement = DOM.createDiv();
  101. descriptionForAssistiveDevicesElement.setInnerText(
  102. TextualDateFieldState.DESCRIPTION_FOR_ASSISTIVE_DEVICES);
  103. AriaHelper.ensureHasId(descriptionForAssistiveDevicesElement);
  104. Roles.getTextboxRole().setAriaDescribedbyProperty(text.getElement(),
  105. Id.of(descriptionForAssistiveDevicesElement));
  106. AriaHelper.setVisibleForAssistiveDevicesOnly(
  107. descriptionForAssistiveDevicesElement, true);
  108. calendar = calendarPanel;
  109. calendar.setParentField(this);
  110. calendar.setFocusOutListener(event -> {
  111. event.preventDefault();
  112. closeCalendarPanel();
  113. return true;
  114. });
  115. // FIXME: Problem is, that the element with the provided id does not
  116. // exist yet in html. This is the same problem as with the context menu.
  117. // Apply here the same fix (#11795)
  118. Roles.getTextboxRole().setAriaControlsProperty(text.getElement(),
  119. Id.of(calendar.getElement()));
  120. Roles.getButtonRole().setAriaControlsProperty(
  121. calendarToggle.getElement(), Id.of(calendar.getElement()));
  122. calendar.setSubmitListener(new SubmitListener() {
  123. @Override
  124. public void onSubmit() {
  125. // Update internal value and send valuechange event if immediate
  126. updateValue(calendar.getDate());
  127. // Update text field (a must when not immediate).
  128. buildDate(true);
  129. closeCalendarPanel();
  130. }
  131. @Override
  132. public void onCancel() {
  133. closeCalendarPanel();
  134. }
  135. });
  136. popup = new VOverlay(true, false);
  137. popup.setOwner(this);
  138. FlowPanel wrapper = new FlowPanel();
  139. selectedDate = new Label();
  140. selectedDate.setStyleName(getStylePrimaryName() + "-selecteddate");
  141. AriaHelper.setVisibleForAssistiveDevicesOnly(selectedDate.getElement(),
  142. true);
  143. Roles.getTextboxRole().setAriaLiveProperty(selectedDate.getElement(),
  144. LiveValue.ASSERTIVE);
  145. Roles.getTextboxRole().setAriaAtomicProperty(selectedDate.getElement(),
  146. true);
  147. wrapper.add(selectedDate);
  148. wrapper.add(calendar);
  149. popup.setWidget(wrapper);
  150. popup.addCloseHandler(this);
  151. DOM.setElementProperty(calendar.getElement(), "id",
  152. "PID_VAADIN_POPUPCAL");
  153. sinkEvents(Event.ONKEYDOWN);
  154. updateStyleNames();
  155. }
  156. @Override
  157. protected void onAttach() {
  158. super.onAttach();
  159. DOM.appendChild(RootPanel.get().getElement(),
  160. descriptionForAssistiveDevicesElement);
  161. }
  162. @Override
  163. protected void onDetach() {
  164. super.onDetach();
  165. descriptionForAssistiveDevicesElement.removeFromParent();
  166. closeCalendarPanel();
  167. }
  168. /**
  169. * Changes the current date, and updates the
  170. * {@link VDateField#bufferedResolutions}, possibly
  171. * {@link VDateField#sendBufferedValues()} to the server if needed
  172. *
  173. * @param newDate
  174. * the new {@code Date} to update
  175. */
  176. @SuppressWarnings("deprecation")
  177. public void updateValue(Date newDate) {
  178. Date currentDate = getCurrentDate();
  179. R resolution = getCurrentResolution();
  180. if (currentDate == null || newDate.getTime() != currentDate.getTime()) {
  181. setCurrentDate((Date) newDate.clone());
  182. bufferedResolutions.put(calendar.getResolution(calendar::isYear),
  183. newDate.getYear() + 1900);
  184. if (!calendar.isYear(resolution)) {
  185. bufferedResolutions.put(
  186. calendar.getResolution(calendar::isMonth),
  187. newDate.getMonth() + 1);
  188. if (!calendar.isMonth(resolution)) {
  189. bufferedResolutions.put(
  190. calendar.getResolution(calendar::isDay),
  191. newDate.getDate());
  192. }
  193. }
  194. }
  195. }
  196. /**
  197. * Checks whether the text field is enabled.
  198. *
  199. * @see VAbstractPopupCalendar#setTextFieldEnabled(boolean)
  200. * @return The current state of the text field.
  201. */
  202. public boolean isTextFieldEnabled() {
  203. return textFieldEnabled;
  204. }
  205. /**
  206. * Sets the state of the text field of this component. By default the text
  207. * field is enabled. Disabling it causes only the button for date selection
  208. * to be active, thus preventing the user from entering invalid dates. See
  209. * <a href="http://dev.vaadin.com/ticket/6790>#6790</a>.
  210. *
  211. * @param textFieldEnabled
  212. */
  213. public void setTextFieldEnabled(boolean textFieldEnabled) {
  214. this.textFieldEnabled = textFieldEnabled;
  215. updateTextFieldEnabled();
  216. }
  217. protected void updateTextFieldEnabled() {
  218. boolean reallyEnabled = isEnabled() && isTextFieldEnabled();
  219. // IE has a non input disabled themeing that can not be overridden so we
  220. // must fake the functionality using readonly and unselectable
  221. if (BrowserInfo.get().isIE()) {
  222. if (!reallyEnabled) {
  223. text.getElement().setAttribute("unselectable", "on");
  224. text.getElement().setAttribute("readonly", "");
  225. text.setTabIndex(-2);
  226. } else if (reallyEnabled
  227. && text.getElement().hasAttribute("unselectable")) {
  228. text.getElement().removeAttribute("unselectable");
  229. text.getElement().removeAttribute("readonly");
  230. text.setTabIndex(0);
  231. }
  232. } else {
  233. text.setEnabled(reallyEnabled);
  234. }
  235. if (reallyEnabled) {
  236. calendarToggle.setTabIndex(-1);
  237. Roles.getButtonRole()
  238. .setAriaHiddenState(calendarToggle.getElement(), true);
  239. } else {
  240. calendarToggle.setTabIndex(0);
  241. Roles.getButtonRole()
  242. .setAriaHiddenState(calendarToggle.getElement(), false);
  243. }
  244. handleAriaAttributes();
  245. }
  246. /**
  247. * Set correct tab index for disabled text field in IE as the value set in
  248. * setTextFieldEnabled(...) gets overridden in
  249. * TextualDateConnection.updateFromUIDL(...).
  250. *
  251. * @since 7.3.1
  252. */
  253. public void setTextFieldTabIndex() {
  254. if (BrowserInfo.get().isIE() && !textFieldEnabled) {
  255. // index needs to be -2 because FocusWidget updates -1 to 0 onAttach
  256. text.setTabIndex(-2);
  257. }
  258. }
  259. @Override
  260. public void bindAriaCaption(
  261. com.google.gwt.user.client.Element captionElement) {
  262. if (captionElement == null) {
  263. captionId = null;
  264. } else {
  265. captionId = captionElement.getId();
  266. }
  267. if (isTextFieldEnabled()) {
  268. super.bindAriaCaption(captionElement);
  269. } else {
  270. AriaHelper.bindCaption(calendarToggle, captionElement);
  271. }
  272. handleAriaAttributes();
  273. }
  274. private void handleAriaAttributes() {
  275. Widget removeFromWidget;
  276. Widget setForWidget;
  277. if (isTextFieldEnabled()) {
  278. setForWidget = text;
  279. removeFromWidget = calendarToggle;
  280. } else {
  281. setForWidget = calendarToggle;
  282. removeFromWidget = text;
  283. }
  284. Roles.getFormRole()
  285. .removeAriaLabelledbyProperty(removeFromWidget.getElement());
  286. if (captionId == null) {
  287. Roles.getFormRole()
  288. .removeAriaLabelledbyProperty(setForWidget.getElement());
  289. } else {
  290. Roles.getFormRole().setAriaLabelledbyProperty(
  291. setForWidget.getElement(), Id.of(captionId));
  292. }
  293. }
  294. /*
  295. * (non-Javadoc)
  296. *
  297. * @see
  298. * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
  299. */
  300. @Override
  301. public void setStyleName(String style) {
  302. super.setStyleName(style);
  303. updateStyleNames();
  304. }
  305. @Override
  306. public void setStylePrimaryName(String style) {
  307. removeStyleName(getStylePrimaryName() + "-popupcalendar");
  308. super.setStylePrimaryName(style);
  309. updateStyleNames();
  310. }
  311. @Override
  312. protected void updateStyleNames() {
  313. super.updateStyleNames();
  314. if (getStylePrimaryName() != null && calendarToggle != null) {
  315. addStyleName(getStylePrimaryName() + "-popupcalendar");
  316. calendarToggle.setStyleName(getStylePrimaryName() + "-button");
  317. popup.setStyleName(getStylePrimaryName() + "-popup");
  318. calendar.setStyleName(getStylePrimaryName() + "-calendarpanel");
  319. }
  320. }
  321. /**
  322. * Opens the calendar panel popup.
  323. */
  324. public void openCalendarPanel() {
  325. if (!open && !readonly && isEnabled()) {
  326. open = true;
  327. if (getCurrentDate() != null) {
  328. calendar.setDate((Date) getCurrentDate().clone());
  329. } else if (getDefaultDate() != null) {
  330. calendar.setDate(getDefaultDate());
  331. } else {
  332. calendar.setDate(new Date());
  333. }
  334. // clear previous values
  335. popup.setWidth("");
  336. popup.setHeight("");
  337. popup.setPopupPositionAndShow(new PopupPositionCallback());
  338. checkGroupFocus(true);
  339. } else {
  340. getLogger().severe("Cannot reopen popup, it is already open!");
  341. }
  342. }
  343. /*
  344. * (non-Javadoc)
  345. *
  346. * @see
  347. * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
  348. * .dom.client.ClickEvent)
  349. */
  350. @Override
  351. public void onClick(ClickEvent event) {
  352. if (event.getSource() == calendarToggle && isEnabled()) {
  353. if (open) {
  354. closeCalendarPanel();
  355. } else if (!preventOpenPopupCalendar) {
  356. openCalendarPanel();
  357. }
  358. preventOpenPopupCalendar = false;
  359. }
  360. }
  361. /*
  362. * (non-Javadoc)
  363. *
  364. * @see
  365. * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt
  366. * .event.logical.shared.CloseEvent)
  367. */
  368. @Override
  369. public void onClose(CloseEvent<PopupPanel> event) {
  370. if (event.getSource() == popup) {
  371. buildDate();
  372. if (!BrowserInfo.get().isTouchDevice() && textFieldEnabled) {
  373. /*
  374. * Move focus to textbox, unless on touch device (avoids opening
  375. * virtual keyboard) or if textField is disabled.
  376. */
  377. focus();
  378. }
  379. open = false;
  380. if (cursorOverCalendarToggleButton
  381. && !toggleButtonClosesWithGuarantee) {
  382. preventOpenPopupCalendar = true;
  383. }
  384. toggleButtonClosesWithGuarantee = false;
  385. }
  386. }
  387. /**
  388. * Sets focus to Calendar panel.
  389. *
  390. * @param focus
  391. * {@code true} for {@code focus}, {@code false} for {@code blur}
  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. updateTextFieldEnabled();
  400. calendarToggle.setEnabled(enabled);
  401. Roles.getButtonRole().setAriaDisabledState(calendarToggle.getElement(),
  402. !enabled);
  403. }
  404. /**
  405. * Sets the content of a special field for assistive devices, so that they
  406. * can recognize the change and inform the user (reading out in case of
  407. * screen reader).
  408. *
  409. * @param selectedDate
  410. * Date that is currently selected
  411. */
  412. public void setFocusedDate(Date selectedDate) {
  413. this.selectedDate.setText(DateTimeFormat.getFormat("dd, MMMM, yyyy")
  414. .format(selectedDate));
  415. }
  416. /**
  417. * For internal use only. May be removed or replaced in the future.
  418. *
  419. * @see com.vaadin.client.ui.VAbstractTextualDate#buildDate()
  420. */
  421. @Override
  422. public void buildDate() {
  423. // Save previous value
  424. String previousValue = getText();
  425. super.buildDate();
  426. // Restore previous value if the input could not be parsed
  427. if (!parsable) {
  428. setText(previousValue);
  429. }
  430. updateTextFieldEnabled();
  431. bufferedDateString = text.getText();
  432. updateBufferedResolutions();
  433. // send the Time changes with delayed rpc.
  434. sendBufferedValuesWithDelay();
  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. toggleButtonClosesWithGuarantee = true;
  479. popup.hide(true);
  480. }
  481. }
  482. @Override
  483. public com.google.gwt.user.client.Element getSubPartElement(
  484. String subPart) {
  485. if (subPart.equals(CALENDAR_TOGGLE_ID)) {
  486. return calendarToggle.getElement();
  487. }
  488. return super.getSubPartElement(subPart);
  489. }
  490. @Override
  491. public String getSubPartName(
  492. com.google.gwt.user.client.Element subElement) {
  493. if (calendarToggle.getElement().isOrHasChild(subElement)) {
  494. return CALENDAR_TOGGLE_ID;
  495. }
  496. return super.getSubPartName(subElement);
  497. }
  498. /**
  499. * Set a description that explains the usage of the Widget for users of
  500. * assistive devices.
  501. *
  502. * @param descriptionForAssistiveDevices
  503. * String with the description
  504. */
  505. public void setDescriptionForAssistiveDevices(
  506. String descriptionForAssistiveDevices) {
  507. descriptionForAssistiveDevicesElement
  508. .setInnerText(descriptionForAssistiveDevices);
  509. }
  510. /**
  511. * Get the description that explains the usage of the Widget for users of
  512. * assistive devices.
  513. *
  514. * @return String with the description
  515. */
  516. public String getDescriptionForAssistiveDevices() {
  517. return descriptionForAssistiveDevicesElement.getInnerText();
  518. }
  519. /**
  520. * Sets the start range for this component. The start range is inclusive,
  521. * and it depends on the current resolution, what is considered inside the
  522. * range.
  523. *
  524. * @param rangeStart
  525. * - the allowed range's start date
  526. */
  527. public void setRangeStart(String rangeStart) {
  528. calendar.setRangeStart(rangeStart);
  529. }
  530. /**
  531. * Sets the end range for this component. The end range is inclusive, and it
  532. * depends on the current resolution, what is considered inside the range.
  533. *
  534. * @param rangeEnd
  535. * - the allowed range's end date
  536. */
  537. public void setRangeEnd(String rangeEnd) {
  538. calendar.setRangeEnd(rangeEnd);
  539. }
  540. private class PopupPositionCallback implements PositionCallback {
  541. @Override
  542. public void setPosition(int offsetWidth, int offsetHeight) {
  543. final int width = offsetWidth;
  544. final int height = offsetHeight;
  545. final int browserWindowWidth = Window.getClientWidth()
  546. + Window.getScrollLeft();
  547. final int windowHeight = Window.getClientHeight()
  548. + Window.getScrollTop();
  549. int left = calendarToggle.getAbsoluteLeft();
  550. // Add a little extra space to the right to avoid
  551. // problems with IE7 scrollbars and to make it look
  552. // nicer.
  553. int extraSpace = 30;
  554. boolean overflow = left + width + extraSpace > browserWindowWidth;
  555. if (overflow) {
  556. // Part of the popup is outside the browser window
  557. // (to the right)
  558. left = browserWindowWidth - width - extraSpace;
  559. }
  560. int top = calendarToggle.getAbsoluteTop();
  561. int extraHeight = 2;
  562. boolean verticallyRepositioned = false;
  563. ComputedStyle style = new ComputedStyle(popup.getElement());
  564. int[] margins = style.getMargin();
  565. int desiredPopupBottom = top + height
  566. + calendarToggle.getOffsetHeight() + margins[0]
  567. + margins[2];
  568. if (desiredPopupBottom > windowHeight) {
  569. int updatedLeft = left;
  570. left = getLeftPosition(left, width, style, overflow);
  571. // if position has not been changed then it means there is no
  572. // space to make popup fully visible
  573. if (updatedLeft == left) {
  574. // let's try to show popup on the top of the field
  575. int updatedTop = top - extraHeight - height - margins[0]
  576. - margins[2];
  577. verticallyRepositioned = updatedTop >= 0;
  578. if (verticallyRepositioned) {
  579. top = updatedTop;
  580. }
  581. }
  582. // Part of the popup is outside the browser window
  583. // (below)
  584. if (!verticallyRepositioned) {
  585. verticallyRepositioned = true;
  586. top = windowHeight - height - extraSpace + extraHeight;
  587. }
  588. }
  589. if (verticallyRepositioned) {
  590. popup.setPopupPosition(left, top);
  591. } else {
  592. popup.setPopupPosition(left,
  593. top + calendarToggle.getOffsetHeight() + extraHeight);
  594. }
  595. doSetFocus();
  596. }
  597. private int getLeftPosition(int left, int width, ComputedStyle style,
  598. boolean overflow) {
  599. if (positionRightSide()) {
  600. // Show to the right of the popup button unless we
  601. // are in the lower right corner of the screen
  602. if (overflow) {
  603. return left;
  604. }
  605. return left + calendarToggle.getOffsetWidth();
  606. }
  607. int[] margins = style.getMargin();
  608. int desiredLeftPosition = calendarToggle.getAbsoluteLeft() - width
  609. - margins[1] - margins[3];
  610. if (desiredLeftPosition >= 0) {
  611. return desiredLeftPosition;
  612. }
  613. return left;
  614. }
  615. private boolean positionRightSide() {
  616. int buttonRightSide = calendarToggle.getAbsoluteLeft()
  617. + calendarToggle.getOffsetWidth();
  618. int textRightSide = text.getAbsoluteLeft() + text.getOffsetWidth();
  619. return buttonRightSide >= textRightSide;
  620. }
  621. private void doSetFocus() {
  622. /*
  623. * We have to wait a while before focusing since the popup needs to
  624. * be opened before we can focus
  625. */
  626. Timer focusTimer = new Timer() {
  627. @Override
  628. public void run() {
  629. setFocus(true);
  630. }
  631. };
  632. focusTimer.schedule(100);
  633. }
  634. }
  635. @Override
  636. protected boolean hasChildFocus() {
  637. return open;
  638. }
  639. private static Logger getLogger() {
  640. return Logger.getLogger(VAbstractPopupCalendar.class.getName());
  641. }
  642. }