Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

VAbstractPopupCalendar.java 25KB

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