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.

VPopupCalendar.java 25KB

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