選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

VPopupCalendar.java 25KB

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