001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.gwt.client.ui.input.datebox; 029 030import org.opencms.gwt.client.I_CmsHasInit; 031import org.opencms.gwt.client.Messages; 032import org.opencms.gwt.client.ui.CmsPopup; 033import org.opencms.gwt.client.ui.I_CmsAutoHider; 034import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 035import org.opencms.gwt.client.ui.input.CmsRadioButton; 036import org.opencms.gwt.client.ui.input.CmsRadioButtonGroup; 037import org.opencms.gwt.client.ui.input.CmsTextBox; 038import org.opencms.gwt.client.ui.input.I_CmsFormWidget; 039import org.opencms.gwt.client.ui.input.form.CmsWidgetFactoryRegistry; 040import org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory; 041import org.opencms.util.CmsStringUtil; 042 043import java.util.Date; 044import java.util.Map; 045 046import com.google.common.base.Optional; 047import com.google.gwt.core.client.GWT; 048import com.google.gwt.core.client.Scheduler; 049import com.google.gwt.core.client.Scheduler.ScheduledCommand; 050import com.google.gwt.dom.client.Element; 051import com.google.gwt.dom.client.EventTarget; 052import com.google.gwt.dom.client.Style.Unit; 053import com.google.gwt.event.dom.client.BlurEvent; 054import com.google.gwt.event.dom.client.BlurHandler; 055import com.google.gwt.event.dom.client.ClickEvent; 056import com.google.gwt.event.dom.client.ClickHandler; 057import com.google.gwt.event.dom.client.FocusEvent; 058import com.google.gwt.event.dom.client.FocusHandler; 059import com.google.gwt.event.dom.client.HasKeyPressHandlers; 060import com.google.gwt.event.dom.client.KeyCodes; 061import com.google.gwt.event.dom.client.KeyPressEvent; 062import com.google.gwt.event.dom.client.KeyPressHandler; 063import com.google.gwt.event.dom.client.KeyUpEvent; 064import com.google.gwt.event.dom.client.KeyUpHandler; 065import com.google.gwt.event.logical.shared.CloseEvent; 066import com.google.gwt.event.logical.shared.CloseHandler; 067import com.google.gwt.event.logical.shared.ValueChangeEvent; 068import com.google.gwt.event.logical.shared.ValueChangeHandler; 069import com.google.gwt.event.shared.HandlerRegistration; 070import com.google.gwt.uibinder.client.UiBinder; 071import com.google.gwt.uibinder.client.UiField; 072import com.google.gwt.user.client.Command; 073import com.google.gwt.user.client.DOM; 074import com.google.gwt.user.client.Event; 075import com.google.gwt.user.client.Event.NativePreviewEvent; 076import com.google.gwt.user.client.Event.NativePreviewHandler; 077import com.google.gwt.user.client.ui.Composite; 078import com.google.gwt.user.client.ui.FlowPanel; 079import com.google.gwt.user.client.ui.HasValue; 080import com.google.gwt.user.client.ui.PopupPanel; 081import com.google.gwt.user.client.ui.RootPanel; 082import com.google.gwt.user.client.ui.UIObject; 083import com.google.gwt.user.datepicker.client.CalendarUtil; 084import com.google.gwt.user.datepicker.client.DatePicker; 085 086/** 087 * A text box that shows a date time picker widget when the user clicks on it. 088 */ 089public class CmsDateBox extends Composite 090implements HasValue<Date>, I_CmsFormWidget, I_CmsHasInit, HasKeyPressHandlers, I_CmsHasDateBoxEventHandlers { 091 092 /** 093 * Drag and drop event preview handler.<p> 094 * 095 * To be used while dragging.<p> 096 */ 097 protected class CloseEventPreviewHandler implements NativePreviewHandler { 098 099 /** 100 * @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent) 101 */ 102 public void onPreviewNativeEvent(NativePreviewEvent event) { 103 104 Event nativeEvent = Event.as(event.getNativeEvent()); 105 switch (DOM.eventGetType(nativeEvent)) { 106 case Event.ONMOUSEMOVE: 107 break; 108 case Event.ONMOUSEUP: 109 break; 110 case Event.ONKEYDOWN: 111 break; 112 case Event.ONMOUSEWHEEL: 113 hidePopup(); 114 break; 115 default: 116 // do nothing 117 } 118 } 119 120 } 121 122 /** 123 * This inner class implements the handler for the date box widget.<p> 124 */ 125 protected class CmsDateBoxHandler 126 implements ClickHandler, FocusHandler, BlurHandler, KeyUpHandler, ValueChangeHandler<Date>, 127 CloseHandler<PopupPanel> { 128 129 /** 130 * @see com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event.dom.client.BlurEvent) 131 */ 132 public void onBlur(BlurEvent event) { 133 134 UIObject source = (UIObject)event.getSource(); 135 if (m_box.getElement().isOrHasChild(source.getElement())) { 136 onDateBoxBlur(); 137 } else if (m_time.getElement().isOrHasChild(source.getElement())) { 138 onTimeBlur(); 139 } 140 } 141 142 /** 143 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) 144 */ 145 public void onClick(ClickEvent event) { 146 147 if (event.getSource() == m_box) { 148 onDateBoxClick(); 149 } else if (event.getSource() == m_time) { 150 onTimeClick(); 151 } else if ((event.getSource() == m_am) || (event.getSource() == m_pm)) { 152 onAmPmClick(); 153 } 154 } 155 156 /** 157 * @see com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt.event.logical.shared.CloseEvent) 158 */ 159 public void onClose(CloseEvent<PopupPanel> event) { 160 161 m_box.setPreventShowError(false); 162 163 } 164 165 /** 166 * @see com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event.dom.client.FocusEvent) 167 */ 168 public void onFocus(FocusEvent event) { 169 170 UIObject source = (UIObject)event.getSource(); 171 if (m_time.getElement().isOrHasChild(source.getElement())) { 172 onFocusTimeBox(); 173 } 174 } 175 176 /** 177 * @see com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent) 178 */ 179 public void onKeyUp(KeyUpEvent event) { 180 181 if (event.getSource() == m_box) { 182 onDateBoxKeyPress(event); 183 } else if (event.getSource() == m_time) { 184 onTimeKeyPressed(event); 185 } 186 } 187 188 /** 189 * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent) 190 */ 191 public void onValueChange(ValueChangeEvent<Date> event) { 192 193 onPickerValueChanged(); 194 } 195 } 196 197 /** The ui-binder interface for this composite. */ 198 interface I_CmsDateBoxUiBinder extends UiBinder<FlowPanel, CmsDateBox> { 199 // GWT interface, nothing to do here 200 } 201 202 /** Dummy value used for invalid dates. */ 203 public static final Date INVALID_DATE = new Date(Integer.MIN_VALUE + 37); // can't use Long.MIN_VALUE since this leads to an invalid Date object in GWT 204 205 /** The widget type identifier for this widget. */ 206 public static final String WIDGET_TYPE = "datebox"; 207 208 /** The ui-binder instance. */ 209 private static I_CmsDateBoxUiBinder uiBinder = GWT.create(I_CmsDateBoxUiBinder.class); 210 211 /** The am radio button. */ 212 @UiField 213 protected CmsRadioButton m_am; 214 215 /** The radio button group for am/pm selection. */ 216 protected CmsRadioButtonGroup m_ampmGroup; 217 218 /** The auto hide parent. */ 219 protected I_CmsAutoHider m_autoHideParent; 220 221 /** The input field to show the result of picking a date. */ 222 @UiField 223 protected CmsTextBox m_box; 224 225 /** The panel for the date time picker. */ 226 @UiField 227 protected FlowPanel m_dateTimePanel; 228 229 /** The gwt date picker. */ 230 @UiField 231 protected DatePicker m_picker; 232 233 /** The pm radio button. */ 234 @UiField 235 protected CmsRadioButton m_pm; 236 237 /** Event preview handler registration. */ 238 protected HandlerRegistration m_previewHandlerRegistration; 239 240 /** The text box to input the time. */ 241 @UiField 242 protected CmsTextBox m_time; 243 244 /** The panel for the time selection. */ 245 @UiField 246 protected FlowPanel m_timeField; 247 248 /** True if invalid values should be allowed. */ 249 private boolean m_allowInvalidValue; 250 251 /** The value for show date only. */ 252 private boolean m_dateOnly; 253 254 /** The initial date shown, when the date picker is opened and no date was set before. */ 255 private Date m_initialDate; 256 257 /** Signals whether the date box is valid or not. */ 258 private boolean m_isValidDateBox; 259 260 /** Signals whether the time field is valid or not. */ 261 private boolean m_isValidTime; 262 263 /** The old value for fire event decision. */ 264 private Date m_oldValue; 265 266 /** The popup panel to show the the date time picker widget in. */ 267 private CmsPopup m_popup; 268 269 /** 270 * The event preview handler.<p> 271 * 272 * Blurs the time box if the user clicks outside of it.<p> 273 */ 274 private NativePreviewHandler m_previewHandler = new NativePreviewHandler() { 275 276 /** 277 * @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent) 278 */ 279 public void onPreviewNativeEvent(NativePreviewEvent event) { 280 281 previewNativeEvent(event); 282 } 283 }; 284 285 /** Stores the preview handler. */ 286 private HandlerRegistration m_previewRegistration; 287 288 /** The date used for enable and disable the text box. */ 289 private Date m_tmpValue; 290 291 /** 292 * Create a new date box widget with the date time picker. 293 */ 294 public CmsDateBox() { 295 296 initWidget(uiBinder.createAndBindUi(this)); 297 m_box.colorWhite(); 298 299 m_popup = new CmsPopup(Messages.get().key(Messages.GUI_DATEBOX_TITLE_0)); 300 m_ampmGroup = new CmsRadioButtonGroup(); 301 302 m_am.setText(Messages.get().key(Messages.GUI_DATE_AM_0)); 303 m_am.setGroup(m_ampmGroup); 304 m_pm.setText(Messages.get().key(Messages.GUI_DATE_PM_0)); 305 m_pm.setGroup(m_ampmGroup); 306 307 if (!CmsDateConverter.is12HourPresentation()) { 308 m_pm.setVisible(false); 309 m_am.setVisible(false); 310 } 311 312 CmsDateBoxHandler dateBoxHandler = new CmsDateBoxHandler(); 313 m_picker.addValueChangeHandler(dateBoxHandler); 314 315 m_box.addBlurHandler(dateBoxHandler); 316 m_box.addClickHandler(dateBoxHandler); 317 m_box.addKeyUpHandler(dateBoxHandler); 318 m_am.addClickHandler(dateBoxHandler); 319 m_pm.addClickHandler(dateBoxHandler); 320 m_time.addClickHandler(dateBoxHandler); 321 m_time.addBlurHandler(dateBoxHandler); 322 m_time.addKeyUpHandler(dateBoxHandler); 323 m_time.addFocusHandler(dateBoxHandler); 324 325 m_popup.add(m_dateTimePanel); 326 m_popup.setWidth(0); 327 m_popup.setModal(true); 328 m_popup.removePadding(); 329 m_popup.setBackgroundColor(I_CmsLayoutBundle.INSTANCE.constants().css().backgroundColorDialog()); 330 m_popup.addDialogClose(new Command() { 331 332 public void execute() { 333 334 m_box.setPreventShowError(false); 335 } 336 }); 337 m_popup.addCloseHandler(dateBoxHandler); 338 m_popup.addAutoHidePartner(m_box.getElement()); 339 m_popup.setAutoHideEnabled(true); 340 } 341 342 /** 343 * Initializes this class.<p> 344 */ 345 public static void initClass() { 346 347 // registers a factory for creating new instances of this widget 348 CmsWidgetFactoryRegistry.instance().registerFactory(WIDGET_TYPE, new I_CmsFormWidgetFactory() { 349 350 /** 351 * @see org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory#createWidget(java.util.Map, com.google.common.base.Optional) 352 */ 353 public I_CmsFormWidget createWidget(Map<String, String> widgetParams, Optional<String> defaultValue) { 354 355 return new CmsDateBox(); 356 } 357 }); 358 } 359 360 /** 361 * @see org.opencms.gwt.client.ui.input.datebox.I_CmsHasDateBoxEventHandlers#addCmsDateBoxEventHandler(org.opencms.gwt.client.ui.input.datebox.I_CmsDateBoxEventHandler) 362 */ 363 public HandlerRegistration addCmsDateBoxEventHandler(I_CmsDateBoxEventHandler handler) { 364 365 return addHandler(handler, CmsDateBoxEvent.TYPE); 366 } 367 368 /** 369 * @see com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler(com.google.gwt.event.dom.client.KeyPressHandler) 370 */ 371 public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) { 372 373 return m_box.addHandler(handler, KeyPressEvent.getType()); 374 } 375 376 /** 377 * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler) 378 */ 379 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Date> handler) { 380 381 return addHandler(handler, ValueChangeEvent.getType()); 382 } 383 384 /** 385 * Returns true if invalid values should be allowed.<p> 386 * 387 * If invalid values are allowed, methods returning a Date will return a special dummy date in case the date is invalid. 388 * 389 * @return true if invalid values should be allowed 390 */ 391 public boolean allowInvalidValue() { 392 393 return m_allowInvalidValue; 394 } 395 396 /** 397 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getApparentValue() 398 */ 399 public String getApparentValue() { 400 401 return getFormValueAsString(); 402 } 403 404 /** 405 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFieldType() 406 */ 407 public FieldType getFieldType() { 408 409 return I_CmsFormWidget.FieldType.DATE; 410 } 411 412 /** 413 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValue() 414 */ 415 public Object getFormValue() { 416 417 return getValue(); 418 } 419 420 /** 421 * Returns the value of the date box as String in form of a long.<p> 422 * 423 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValueAsString() 424 */ 425 public String getFormValueAsString() { 426 427 Date value = getValue(); 428 if (value == null) { 429 return ""; 430 } else if (allowInvalidValue() && value.equals(INVALID_DATE)) { 431 return "INVALID_DATE"; 432 } 433 return String.valueOf(getValue().getTime()); 434 } 435 436 /** 437 * Returns the text box of this widget.<p> 438 * 439 * @return the CmsText Box 440 */ 441 public CmsTextBox getTextField() { 442 443 return m_box; 444 } 445 446 /** 447 * @see com.google.gwt.user.client.ui.HasValue#getValue() 448 */ 449 public Date getValue() { 450 451 Date date = null; 452 if (isEnabled()) { 453 try { 454 if (m_dateOnly) { 455 date = CmsDateConverter.toDayDate(m_box.getText()); 456 } else { 457 date = CmsDateConverter.toDate(m_box.getText()); 458 } 459 setErrorMessage(null); 460 } catch (Exception e) { 461 setErrorMessage(Messages.get().key(Messages.ERR_DATEBOX_INVALID_DATE_FORMAT_0)); 462 if (allowInvalidValue()) { 463 // empty is actually a valid value for date fields, so we have to return a different value to distinguish between the cases 464 return INVALID_DATE; 465 } 466 } 467 } 468 469 return date; 470 } 471 472 /** 473 * Returns the date value as formated String or an empty String if the date value is null.<p> 474 * 475 * @return the date value as formated String 476 */ 477 public String getValueAsFormatedString() { 478 479 return CmsDateConverter.toString(getValue()); 480 } 481 482 /** 483 * Returns <code>true</code> if the box and the time input fields don't have any errors.<p> 484 * 485 * @return <code>true</code> if the box and the time input fields don't have any errors 486 */ 487 public boolean hasErrors() { 488 489 if (m_box.hasError() || m_time.hasError()) { 490 return true; 491 } 492 return false; 493 } 494 495 /** 496 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#isEnabled() 497 */ 498 public boolean isEnabled() { 499 500 return m_box.isEnabled(); 501 } 502 503 /** 504 * Checks if the String in the date box input field is a valid date format.<p> 505 * 506 * @return <code>true</code> if the String in the date box input field is a valid date format 507 */ 508 public boolean isValideDateBox() { 509 510 try { 511 CmsDateConverter.toDate(m_box.getText()); 512 return true; 513 } catch (Exception e) { 514 return false; 515 } 516 } 517 518 /** 519 * Updates the date box when the user has clicked on the time field.<p> 520 */ 521 public void onTimeClick() { 522 523 updateFromPicker(); 524 } 525 526 /** 527 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#reset() 528 */ 529 public void reset() { 530 531 setValue(null); 532 } 533 534 /** 535 * Enables or disables whether invalid values are allowed.<p> 536 * 537 * If invalid values are allowed, they will be returned as a special dummy date (INVALID_DATE). 538 * 539 * @param allowInvalidValue true if invalid values should be allowed 540 */ 541 public void setAllowInvalidValue(boolean allowInvalidValue) { 542 543 m_allowInvalidValue = allowInvalidValue; 544 } 545 546 /** 547 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setAutoHideParent(org.opencms.gwt.client.ui.I_CmsAutoHider) 548 */ 549 public void setAutoHideParent(I_CmsAutoHider autoHideParent) { 550 551 m_autoHideParent = autoHideParent; 552 } 553 554 /** 555 * Sets the value if the date only should be shown. 556 * @param dateOnly if the date only should be shown 557 */ 558 public void setDateOnly(boolean dateOnly) { 559 560 if (m_dateOnly != dateOnly) { 561 m_dateOnly = dateOnly; 562 if (m_dateOnly) { 563 m_time.removeFromParent(); 564 m_am.removeFromParent(); 565 m_pm.removeFromParent(); 566 } else { 567 m_timeField.add(m_time); 568 m_timeField.add(m_am); 569 m_timeField.add(m_pm); 570 } 571 } 572 } 573 574 /** 575 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setEnabled(boolean) 576 */ 577 public void setEnabled(boolean enabled) { 578 579 if (!enabled) { 580 m_tmpValue = getValue(); 581 m_box.setFormValueAsString(Messages.get().key(Messages.GUI_INPUT_NOT_USED_0)); 582 } else { 583 setValue(m_tmpValue); 584 } 585 m_box.setEnabled(enabled); 586 } 587 588 /** 589 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setErrorMessage(java.lang.String) 590 */ 591 public void setErrorMessage(String errorMessage) { 592 593 m_box.setErrorMessage(errorMessage); 594 } 595 596 /** 597 * Expects the value as String in form of a long.<p> 598 * 599 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setFormValueAsString(java.lang.String) 600 */ 601 public void setFormValueAsString(String value) { 602 603 if (!CmsStringUtil.isEmpty(value)) { 604 try { 605 long time = Long.parseLong(value); 606 setValue(new Date(time)); 607 } catch (NumberFormatException e) { 608 // if the String value is none long number make the field empty 609 setValue(null); 610 } 611 } else { 612 // if the value is <code>null</code> make the field empty 613 setValue(null); 614 } 615 } 616 617 /** 618 * Sets the initial date shown, when the date picker is opened and no date was set before.<p> 619 * 620 * @param initialDate the initial date 621 */ 622 public void setInitialDate(Date initialDate) { 623 624 m_initialDate = initialDate; 625 } 626 627 /** 628 * Sets the name of the input field.<p> 629 * 630 * @param name of the input field 631 */ 632 public void setName(String name) { 633 634 m_time.setName(name); 635 636 } 637 638 /** 639 * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object) 640 */ 641 public void setValue(Date value) { 642 643 setValue(value, false); 644 } 645 646 /** 647 * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, boolean) 648 */ 649 public void setValue(Date value, boolean fireEvents) { 650 651 m_tmpValue = value; 652 if (fireEvents) { 653 fireChange(value, false); 654 } 655 if (value == null) { 656 m_box.setFormValueAsString(""); 657 } else if (m_dateOnly) { 658 m_box.setFormValueAsString(CmsDateConverter.toDateString(value)); 659 } else { 660 m_box.setFormValueAsString(CmsDateConverter.toString(value)); 661 } 662 } 663 664 /** 665 * Updates the updates the close behavior and sets the value of the date box to the value from the picker.<p> 666 */ 667 protected void executeTimeAction() { 668 669 if (isValidTime()) { 670 updateFromPicker(); 671 } 672 updateCloseBehavior(); 673 } 674 675 /** 676 * Fires the value change event if needed.<p> 677 * 678 * @param newValue the new value 679 * @param isTyping true if the user is currently typing 680 */ 681 protected void fireChange(Date newValue, boolean isTyping) { 682 683 ValueChangeEvent.<Date> fireIfNotEqual(this, m_oldValue, CalendarUtil.copyDate(newValue)); 684 CmsDateBoxEvent.fire(this, newValue, isTyping); 685 m_oldValue = newValue; 686 } 687 688 /** 689 * Hides the date time popup.<p> 690 */ 691 protected void hidePopup() { 692 693 if (CmsDateConverter.validateTime(getTimeText())) { 694 // before hiding the date picker remove the date box popup from the auto hide partners of the parent popup 695 if (m_autoHideParent != null) { 696 m_autoHideParent.removeAutoHidePartner(getElement()); 697 } 698 m_popup.hide(); 699 if (m_previewHandlerRegistration != null) { 700 m_previewHandlerRegistration.removeHandler(); 701 } 702 m_previewHandlerRegistration = null; 703 } 704 } 705 706 /** 707 * If the am or pm radio button is clicked update the date box from the date time picker.<p> 708 */ 709 protected void onAmPmClick() { 710 711 updateFromPicker(); 712 } 713 714 /** 715 * The date box on blur action.<p> 716 * 717 * If the date box loses the focus the date time picker should be updated from the date box value.<p> 718 */ 719 protected void onDateBoxBlur() { 720 721 if (!m_popup.isShowing()) { 722 updateFromTextBox(false); 723 } 724 updateCloseBehavior(); 725 } 726 727 /** 728 * The date box on click action.<p> 729 * 730 * If the date box is clicked the time date picker should be shown.<p> 731 */ 732 protected void onDateBoxClick() { 733 734 if (!m_popup.isShowing()) { 735 showPopup(); 736 } 737 } 738 739 /** 740 * The date box on key down action.<p> 741 * <ul> 742 * <li>If enter or tab is pressed in the date box the date time 743 * picker should be updated with the value from the date box.</li> 744 * <li>If the escape key is pressed the picker should be hided.</li> 745 * <li>If the up key is pressed the value should be taken from the date box.</li> 746 * <li>If the down key is pressed the picker should be hided.</li> 747 * </ul> 748 * 749 * @param event the key down event 750 */ 751 protected void onDateBoxKeyPress(KeyUpEvent event) { 752 753 switch (event.getNativeEvent().getKeyCode()) { 754 case KeyCodes.KEY_CTRL: 755 case KeyCodes.KEY_SHIFT: 756 case KeyCodes.KEY_ALT: 757 case KeyCodes.KEY_LEFT: 758 case KeyCodes.KEY_RIGHT: 759 break; 760 case KeyCodes.KEY_ENTER: 761 case KeyCodes.KEY_TAB: 762 case KeyCodes.KEY_ESCAPE: 763 case KeyCodes.KEY_UP: 764 updateFromTextBox(false); 765 hidePopup(); 766 break; 767 case KeyCodes.KEY_DOWN: 768 showPopup(); 769 break; 770 default: 771 hidePopup(); 772 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 773 774 public void execute() { 775 776 updateCloseBehavior(); 777 if (isValideDateBox() || allowInvalidValue()) { 778 setErrorMessage(null); 779 fireChange(getValue(), true); 780 } 781 } 782 }); 783 break; 784 } 785 } 786 787 /** 788 * Adds the preview handler.<p> 789 */ 790 protected void onFocusTimeBox() { 791 792 m_previewRegistration = Event.addNativePreviewHandler(m_previewHandler); 793 } 794 795 /** 796 * If the value of the picker changes, the value of the date time picker should be updated.<p> 797 */ 798 protected void onPickerValueChanged() { 799 800 setErrorMessage(null); 801 updateFromPicker(); 802 } 803 804 /** 805 * If the time field loses the focus the entered time should be checked.<p> 806 */ 807 protected void onTimeBlur() { 808 809 if (m_previewRegistration != null) { 810 m_previewRegistration.removeHandler(); 811 } 812 checkTime(); 813 } 814 815 /** 816 * If the user presses enter in the time field the value of the 817 * picker should be updated and the the popup should be closed.<p> 818 * 819 * In any other case the popup should be prevented to being closed.<p> 820 * 821 * @param event the key pressed event 822 */ 823 protected void onTimeKeyPressed(KeyUpEvent event) { 824 825 switch (event.getNativeEvent().getKeyCode()) { 826 case KeyCodes.KEY_CTRL: 827 case KeyCodes.KEY_SHIFT: 828 case KeyCodes.KEY_ALT: 829 case KeyCodes.KEY_LEFT: 830 case KeyCodes.KEY_RIGHT: 831 break; 832 case KeyCodes.KEY_ENTER: 833 updateFromPicker(); 834 hidePopup(); 835 break; 836 default: 837 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 838 839 public void execute() { 840 841 executeTimeAction(); 842 fireChange(getValue(), false); 843 } 844 }); 845 break; 846 } 847 } 848 849 /** 850 * Blurs the time box if the user clicks outside of it.<p> 851 * 852 * @param event the native preview event 853 */ 854 protected void previewNativeEvent(NativePreviewEvent event) { 855 856 Event nativeEvent = Event.as(event.getNativeEvent()); 857 if ((nativeEvent.getTypeInt() == Event.ONCLICK)) { 858 EventTarget target = nativeEvent.getEventTarget(); 859 if (!Element.is(target)) { 860 return; 861 } 862 Element element = Element.as(target); 863 if (!m_time.getElement().isOrHasChild(element)) { 864 m_time.blur(); 865 } 866 } 867 } 868 869 /** 870 * Updates the auto hide partner from the parent widget.<p> 871 * 872 * If there is any invalid user input the parent widget should not be closed automatically.<p> 873 */ 874 protected void updateCloseBehavior() { 875 876 if (isEnabled()) { 877 if (!m_isValidTime && isValidTime()) { 878 m_isValidTime = true; 879 m_popup.setAutoHideEnabled(true); 880 } else if (m_isValidTime && !isValidTime()) { 881 m_isValidTime = false; 882 m_popup.setAutoHideEnabled(false); 883 } 884 885 if (!m_isValidDateBox && isValideDateBox()) { 886 m_isValidDateBox = true; 887 if (m_autoHideParent != null) { 888 m_autoHideParent.removeAutoHidePartner(RootPanel.getBodyElement().getParentElement()); 889 } 890 } else if (m_isValidDateBox && !isValideDateBox()) { 891 m_isValidDateBox = false; 892 if (m_autoHideParent != null) { 893 m_autoHideParent.addAutoHidePartner(RootPanel.getBodyElement().getParentElement()); 894 } 895 } 896 } 897 } 898 899 /** 900 * Validates the time and prints out an error message if the time format is incorrect.<p> 901 */ 902 private void checkTime() { 903 904 if (!isValidTime()) { 905 m_time.setErrorMessageWidth((m_popup.getOffsetWidth() - 32) + Unit.PX.toString()); 906 m_time.setErrorMessage(Messages.get().key(Messages.ERR_DATEBOX_INVALID_TIME_FORMAT_0)); 907 } else { 908 m_time.setErrorMessage(null); 909 } 910 updateCloseBehavior(); 911 } 912 913 /** 914 * Returns the time text field value as string.<p> 915 * 916 * @return the time text field value as string 917 */ 918 private String getTimeText() { 919 920 String timeAsString = m_time.getText().trim(); 921 if (CmsDateConverter.is12HourPresentation()) { 922 if (!(timeAsString.contains(CmsDateConverter.AM) || timeAsString.contains(CmsDateConverter.PM))) { 923 if (m_am.isChecked()) { 924 timeAsString = timeAsString + " " + CmsDateConverter.AM; 925 } else { 926 timeAsString = timeAsString + " " + CmsDateConverter.PM; 927 } 928 } 929 } 930 return timeAsString; 931 } 932 933 /** 934 * Checks if the String in the time input field is a valid time format.<p> 935 * 936 * @return <code>true</code> if the String in the time input field is a valid time format 937 */ 938 private boolean isValidTime() { 939 940 return m_dateOnly || CmsDateConverter.validateTime(getTimeText()); 941 } 942 943 /** 944 * Sets the value of the date picker.<p> 945 * 946 * @param date the value to set 947 * @param fireEvents signals whether the value changed event should be fired or not 948 */ 949 private void setPickerValue(Date date, boolean fireEvents) { 950 951 if (date == null) { 952 date = new Date(); 953 } 954 m_picker.setValue(date, fireEvents); 955 m_picker.setCurrentMonth(date); 956 m_time.setFormValueAsString(CmsDateConverter.cutSuffix(CmsDateConverter.getTime(date)).trim()); 957 if (CmsDateConverter.isAm(date)) { 958 m_ampmGroup.selectButton(m_am); 959 } else { 960 m_ampmGroup.selectButton(m_pm); 961 } 962 } 963 964 /** 965 * Shows the date picker popup.<p> 966 */ 967 private void showPopup() { 968 969 updateFromTextBox(true); 970 m_box.setPreventShowError(true); 971 m_popup.showRelativeTo(m_box); 972 if (m_previewHandlerRegistration != null) { 973 m_previewHandlerRegistration.removeHandler(); 974 } 975 m_previewHandlerRegistration = Event.addNativePreviewHandler(new CloseEventPreviewHandler()); 976 } 977 978 /** 979 * Sets the value of the date box.<p> 980 */ 981 private void updateFromPicker() { 982 983 checkTime(); 984 Date date = m_picker.getValue(); 985 if (!m_dateOnly) { 986 String timeAsString = getTimeText(); 987 date = CmsDateConverter.getDateWithTime(date, timeAsString); 988 } 989 setValue(date); 990 setErrorMessage(null); 991 fireChange(date, false); 992 } 993 994 /** 995 * Updates the picker if the user manually modified the date of the text box.<p> 996 * 997 * @param initial flag indicating if the date box is being initialized 998 */ 999 private void updateFromTextBox(boolean initial) { 1000 1001 Date date = getValue(); 1002 if (initial && (date == null)) { 1003 date = m_initialDate; 1004 } 1005 setPickerValue(date, false); 1006 m_time.setErrorMessage(null); 1007 fireChange(getValue(), false); 1008 } 1009}