/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.gwt.client.ui.input.datebox;
import org.opencms.gwt.client.I_CmsHasInit;
import org.opencms.gwt.client.Messages;
import org.opencms.gwt.client.ui.CmsPopup;
import org.opencms.gwt.client.ui.I_CmsAutoHider;
import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
import org.opencms.gwt.client.ui.input.CmsRadioButton;
import org.opencms.gwt.client.ui.input.CmsRadioButtonGroup;
import org.opencms.gwt.client.ui.input.CmsTextBox;
import org.opencms.gwt.client.ui.input.I_CmsFormWidget;
import org.opencms.gwt.client.ui.input.form.CmsWidgetFactoryRegistry;
import org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory;
import java.util.Date;
import java.util.Map;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.HasKeyPressHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.datepicker.client.CalendarUtil;
import com.google.gwt.user.datepicker.client.DatePicker;
/**
* A text box that shows a date time picker widget when the user clicks on it.
*/
public class CmsDateBox extends Composite implements HasValue<Date>, I_CmsFormWidget, I_CmsHasInit, HasKeyPressHandlers {
/**
* This inner class implements the handler for the date box widget.<p>
*/
protected class CmsDateBoxHandler
implements ClickHandler, FocusHandler, BlurHandler, KeyPressHandler, ValueChangeHandler<Date>,
CloseHandler<PopupPanel> {
/**
* @see com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event.dom.client.BlurEvent)
*/
public void onBlur(BlurEvent event) {
UIObject source = (UIObject)event.getSource();
if (m_box.getElement().isOrHasChild(source.getElement())) {
onDateBoxBlur();
} else if (m_time.getElement().isOrHasChild(source.getElement())) {
onTimeBlur();
}
}
/**
* @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
*/
public void onClick(ClickEvent event) {
if (event.getSource() == m_box) {
onDateBoxClick();
} else if (event.getSource() == m_time) {
onTimeClick();
} else if ((event.getSource() == m_am) || (event.getSource() == m_pm)) {
onAmPmClick();
}
}
/**
* @see com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt.event.logical.shared.CloseEvent)
*/
public void onClose(CloseEvent<PopupPanel> event) {
m_box.setPreventShowError(false);
}
/**
* @see com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event.dom.client.FocusEvent)
*/
public void onFocus(FocusEvent event) {
UIObject source = (UIObject)event.getSource();
if (m_time.getElement().isOrHasChild(source.getElement())) {
onFocusTimeBox();
}
}
/**
* @see com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google.gwt.event.dom.client.KeyPressEvent)
*/
public void onKeyPress(KeyPressEvent event) {
if (event.getSource() == m_box) {
onDateBoxKeyPress(event);
} else if (event.getSource() == m_time) {
onTimeKeyPressed(event);
}
}
/**
* @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
*/
public void onValueChange(ValueChangeEvent<Date> event) {
onPickerValueChanged();
}
}
/** The ui-binder interface for this composite. */
interface I_CmsDateBoxUiBinder extends UiBinder<FlowPanel, CmsDateBox> {
// GWT interface, nothing to do here
}
/** The widget type identifier for this widget. */
public static final String WIDGET_TYPE = "datebox";
/** The ui-binder instance. */
private static I_CmsDateBoxUiBinder uiBinder = GWT.create(I_CmsDateBoxUiBinder.class);
/** The am radio button. */
@UiField
protected CmsRadioButton m_am;
/** The radio button group for am/pm selection. */
protected CmsRadioButtonGroup m_ampmGroup;
/** The auto hide parent. */
protected I_CmsAutoHider m_autoHideParent;
/** The input field to show the result of picking a date. */
@UiField
protected CmsTextBox m_box;
/** The panel for the date time picker. */
@UiField
protected FlowPanel m_dateTimePanel;
/** The gwt date picker. */
@UiField
protected DatePicker m_picker;
/** The pm radio button. */
@UiField
protected CmsRadioButton m_pm;
/** The text box to input the time. */
@UiField
protected CmsTextBox m_time;
/** The initial date shown, when the date picker is opened and no date was set before. */
private Date m_initialDate;
/** Signals whether the date box is valid or not. */
private boolean m_isValidDateBox;
/** Signals whether the time field is valid or not. */
private boolean m_isValidTime;
/** The old value for fire event decision. */
private Date m_oldValue;
/** The popup panel to show the the date time picker widget in. */
private CmsPopup m_popup;
/**
* The event preview handler.<p>
*
* Blurs the time box if the user clicks outside of it.<p>
*/
private NativePreviewHandler m_previewHandler = new NativePreviewHandler() {
/**
* @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
*/
public void onPreviewNativeEvent(NativePreviewEvent event) {
previewNativeEvent(event);
}
};
/** Stores the preview handler. */
private HandlerRegistration m_previewRegistration;
/** The date used for enable and disable the text box. */
private Date m_tmpValue;
/**
* Create a new date box widget with the date time picker.
*/
public CmsDateBox() {
initWidget(uiBinder.createAndBindUi(this));
m_popup = new CmsPopup();
m_ampmGroup = new CmsRadioButtonGroup();
m_am.setText(Messages.get().key(Messages.GUI_DATE_AM_0));
m_am.setGroup(m_ampmGroup);
m_pm.setText(Messages.get().key(Messages.GUI_DATE_PM_0));
m_pm.setGroup(m_ampmGroup);
if (!CmsDateConverter.is12HourPresentation()) {
m_pm.setVisible(false);
m_am.setVisible(false);
}
CmsDateBoxHandler dateBoxHandler = new CmsDateBoxHandler();
m_picker.addValueChangeHandler(dateBoxHandler);
m_box.addBlurHandler(dateBoxHandler);
m_box.addClickHandler(dateBoxHandler);
m_box.addKeyPressHandler(dateBoxHandler);
m_am.addClickHandler(dateBoxHandler);
m_pm.addClickHandler(dateBoxHandler);
m_time.addClickHandler(dateBoxHandler);
m_time.addBlurHandler(dateBoxHandler);
m_time.addKeyPressHandler(dateBoxHandler);
m_time.addFocusHandler(dateBoxHandler);
m_popup.add(m_dateTimePanel);
m_popup.setWidth(0);
m_popup.setModal(true);
m_popup.removePadding();
m_popup.setBackgroundColor(I_CmsLayoutBundle.INSTANCE.constants().css().backgroundColorDialog());
m_popup.addCloseHandler(dateBoxHandler);
m_popup.addAutoHidePartner(m_box.getElement());
m_popup.setAutoHideEnabled(true);
}
/**
* Initializes this class.<p>
*/
public static void initClass() {
// registers a factory for creating new instances of this widget
CmsWidgetFactoryRegistry.instance().registerFactory(WIDGET_TYPE, new I_CmsFormWidgetFactory() {
/**
* @see org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory#createWidget(java.util.Map)
*/
public I_CmsFormWidget createWidget(Map<String, String> widgetParams) {
return new CmsDateBox();
}
});
}
/**
* @see com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler(com.google.gwt.event.dom.client.KeyPressHandler)
*/
public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
return m_box.addHandler(handler, KeyPressEvent.getType());
}
/**
* @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
*/
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Date> handler) {
return addHandler(handler, ValueChangeEvent.getType());
}
/**
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getApparentValue()
*/
public String getApparentValue() {
return getFormValueAsString();
}
/**
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFieldType()
*/
public FieldType getFieldType() {
return I_CmsFormWidget.FieldType.DATE;
}
/**
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValue()
*/
public Object getFormValue() {
return getValue();
}
/**
* Returns the value of the date box as String in form of a long.<p>
*
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValueAsString()
*/
public String getFormValueAsString() {
Date value = getValue();
if (value == null) {
return null;
}
return String.valueOf(getValue().getTime());
}
/**
* @see com.google.gwt.user.client.ui.HasValue#getValue()
*/
public Date getValue() {
Date date = null;
if (isEnabled()) {
try {
date = CmsDateConverter.toDate(m_box.getText());
setErrorMessage(null);
} catch (Exception e) {
setErrorMessage(Messages.get().key(Messages.ERR_DATEBOX_INVALID_DATE_FORMAT_0));
}
}
return date;
}
/**
* Returns the date value as formated String or an empty String if the date value is null.<p>
*
* @return the date value as formated String
*/
public String getValueAsFormatedString() {
return CmsDateConverter.toString(getValue());
}
/**
* Returns <code>true</code> if the box and the time input fields don't have any errors.<p>
*
* @return <code>true</code> if the box and the time input fields don't have any errors
*/
public boolean hasErrors() {
if (m_box.hasError() || m_time.hasError()) {
return true;
}
return false;
}
/**
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#isEnabled()
*/
public boolean isEnabled() {
return m_box.isEnabled();
}
/**
* Checks if the String in the date box input field is a valid date format.<p>
*
* @return <code>true</code> if the String in the date box input field is a valid date format
*/
public boolean isValideDateBox() {
try {
CmsDateConverter.toDate(m_box.getText());
return true;
} catch (Exception e) {
return false;
}
}
/**
* Updates the date box when the user has clicked on the time field.<p>
*/
public void onTimeClick() {
updateFromPicker();
}
/**
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#reset()
*/
public void reset() {
setValue(null);
}
/**
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setAutoHideParent(org.opencms.gwt.client.ui.I_CmsAutoHider)
*/
public void setAutoHideParent(I_CmsAutoHider autoHideParent) {
m_autoHideParent = autoHideParent;
}
/**
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setEnabled(boolean)
*/
public void setEnabled(boolean enabled) {
if (!enabled) {
m_tmpValue = getValue();
m_box.setFormValueAsString(Messages.get().key(Messages.GUI_INPUT_NOT_USED_0));
} else {
setValue(m_tmpValue);
}
m_box.setEnabled(enabled);
}
/**
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setErrorMessage(java.lang.String)
*/
public void setErrorMessage(String errorMessage) {
m_box.setErrorMessage(errorMessage);
}
/**
* Expects the value as String in form of a long.<p>
*
* @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setFormValueAsString(java.lang.String)
*/
public void setFormValueAsString(String value) {
if (value != null) {
try {
long time = Long.parseLong(value);
setValue(new Date(time));
} catch (NumberFormatException e) {
// if the String value is none long number make the field empty
setValue(null);
}
} else {
// if the value is <code>null</code> make the field empty
setValue(null);
}
}
/**
* Sets the initial date shown, when the date picker is opened and no date was set before.<p>
*
* @param initialDate the initial date
*/
public void setInitialDate(Date initialDate) {
m_initialDate = initialDate;
}
/**
* @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object)
*/
public void setValue(Date value) {
setValue(value, false);
}
/**
* @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, boolean)
*/
public void setValue(Date value, boolean fireEvents) {
m_tmpValue = value;
if (fireEvents) {
fireChange(getValue(), value);
m_oldValue = value;
}
m_box.setFormValueAsString(CmsDateConverter.toString(value));
}
/**
* Updates the updates the close behavior and sets the value of the date box to the value from the picker.<p>
*/
protected void executeTimeAction() {
if (isValidTime()) {
updateFromPicker();
}
updateCloseBehavior();
}
/**
* Fires the value change event if needed.<p>
*
* @param oldValue the old value
* @param newValue the new value
*/
protected void fireChange(Date oldValue, Date newValue) {
ValueChangeEvent.<Date> fireIfNotEqual(this, oldValue, CalendarUtil.copyDate(newValue));
}
/**
* If the am or pm radio button is clicked update the date box from the date time picker.<p>
*/
protected void onAmPmClick() {
updateFromPicker();
}
/**
* The date box on blur action.<p>
*
* If the date box loses the focus the date time picker should be updated from the date box value.<p>
*/
protected void onDateBoxBlur() {
if (!m_popup.isShowing()) {
updateFromTextBox(false);
}
updateCloseBehavior();
}
/**
* The date box on click action.<p>
*
* If the date box is clicked the time date picker should be shown.<p>
*/
protected void onDateBoxClick() {
if (!m_popup.isShowing()) {
showPopup();
}
}
/**
* The date box on key down action.<p>
* <ul>
* <li>If enter or tab is pressed in the date box the date time
* picker should be updated with the value from the date box.</li>
* <li>If the escape key is pressed the picker should be hided.</li>
* <li>If the up key is pressed the value should be taken from the date box.</li>
* <li>If the down key is pressed the picker should be hided.</li>
* </ul>
*
* @param event the key down event
*/
protected void onDateBoxKeyPress(KeyPressEvent event) {
switch (event.getNativeEvent().getKeyCode()) {
case KeyCodes.KEY_ENTER:
case KeyCodes.KEY_TAB:
case KeyCodes.KEY_ESCAPE:
case KeyCodes.KEY_UP:
updateFromTextBox(false);
hidePopup();
break;
case KeyCodes.KEY_DOWN:
showPopup();
break;
default:
hidePopup();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
public void execute() {
updateCloseBehavior();
if (isValideDateBox()) {
setErrorMessage(null);
}
}
});
break;
}
}
/**
* Adds the preview handler.<p>
*/
protected void onFocusTimeBox() {
m_previewRegistration = Event.addNativePreviewHandler(m_previewHandler);
}
/**
* If the value of the picker changes, the value of the date time picker should be updated.<p>
*/
protected void onPickerValueChanged() {
setErrorMessage(null);
updateFromPicker();
}
/**
* If the time field loses the focus the entered time should be checked.<p>
*/
protected void onTimeBlur() {
if (m_previewRegistration != null) {
m_previewRegistration.removeHandler();
}
checkTime();
}
/**
* If the user presses enter in the time field the value of the
* picker should be updated and the the popup should be closed.<p>
*
* In any other case the popup should be prevented to being closed.<p>
*
* @param event the key pressed event
*/
protected void onTimeKeyPressed(KeyPressEvent event) {
switch (event.getCharCode()) {
case KeyCodes.KEY_ENTER:
updateFromPicker();
hidePopup();
break;
default:
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
public void execute() {
executeTimeAction();
}
});
break;
}
}
/**
* Blurs the time box if the user clicks outside of it.<p>
*
* @param event the native preview event
*/
protected void previewNativeEvent(NativePreviewEvent event) {
Event nativeEvent = Event.as(event.getNativeEvent());
if ((nativeEvent.getTypeInt() == Event.ONCLICK)) {
EventTarget target = nativeEvent.getEventTarget();
if (!Element.is(target)) {
return;
}
Element element = Element.as(target);
if (!m_time.getElement().isOrHasChild(element)) {
m_time.blur();
}
}
}
/**
* Updates the auto hide partner from the parent widget.<p>
*
* If there is any invalid user input the parent widget should not be closed automatically.<p>
*/
protected void updateCloseBehavior() {
if (isEnabled()) {
if (!m_isValidTime && isValidTime()) {
m_isValidTime = true;
m_popup.setAutoHideEnabled(true);
} else if (m_isValidTime && !isValidTime()) {
m_isValidTime = false;
m_popup.setAutoHideEnabled(false);
}
if (!m_isValidDateBox && isValideDateBox()) {
m_isValidDateBox = true;
if (m_autoHideParent != null) {
m_autoHideParent.removeAutoHidePartner(RootPanel.getBodyElement().getParentElement());
}
} else if (m_isValidDateBox && !isValideDateBox()) {
m_isValidDateBox = false;
if (m_autoHideParent != null) {
m_autoHideParent.addAutoHidePartner(RootPanel.getBodyElement().getParentElement());
}
}
}
}
/**
* Validates the time and prints out an error message if the time format is incorrect.<p>
*/
private void checkTime() {
if (!isValidTime()) {
m_time.setErrorMessageWidth((m_popup.getOffsetWidth() - 32) + Unit.PX.toString());
m_time.setErrorMessage(Messages.get().key(Messages.ERR_DATEBOX_INVALID_TIME_FORMAT_0));
} else if (isValidTime()) {
m_time.setErrorMessage(null);
}
updateCloseBehavior();
}
/**
* Returns the time text field value as string.<p>
*
* @return the time text field value as string
*/
private String getTimeText() {
String timeAsString = m_time.getText().trim();
if (CmsDateConverter.is12HourPresentation()) {
if (!(timeAsString.contains(CmsDateConverter.AM) || timeAsString.contains(CmsDateConverter.PM))) {
if (m_am.isChecked()) {
timeAsString = timeAsString + " " + CmsDateConverter.AM;
} else {
timeAsString = timeAsString + " " + CmsDateConverter.PM;
}
}
}
return timeAsString;
}
/**
* Hides the date time popup.<p>
*/
private void hidePopup() {
if (CmsDateConverter.validateTime(getTimeText())) {
// before hiding the date picker remove the date box popup from the auto hide partners of the parent popup
if (m_autoHideParent != null) {
m_autoHideParent.removeAutoHidePartner(getElement());
}
m_popup.hide();
}
}
/**
* Checks if the String in the time input field is a valid time format.<p>
*
* @return <code>true</code> if the String in the time input field is a valid time format
*/
private boolean isValidTime() {
return CmsDateConverter.validateTime(getTimeText());
}
/**
* Sets the value of the date picker.<p>
*
* @param date the value to set
* @param fireEvents signals whether the value changed event should be fired or not
*/
private void setPickerValue(Date date, boolean fireEvents) {
if (date == null) {
date = new Date();
}
m_picker.setValue(date, fireEvents);
m_picker.setCurrentMonth(date);
m_time.setFormValueAsString(CmsDateConverter.cutSuffix(CmsDateConverter.getTime(date)).trim());
if (CmsDateConverter.isAm(date)) {
m_ampmGroup.selectButton(m_am);
} else {
m_ampmGroup.selectButton(m_pm);
}
}
/**
* Shows the date picker popup.<p>
*/
private void showPopup() {
updateFromTextBox(true);
m_box.setPreventShowError(true);
m_popup.showRelativeTo(m_box);
}
/**
* Sets the value of the date box.<p>
*
* @param date the new date
*/
private void updateFromPicker() {
checkTime();
Date date = m_picker.getValue();
String timeAsString = getTimeText();
date = CmsDateConverter.getDateWithTime(date, timeAsString);
setValue(date);
setErrorMessage(null);
fireChange(m_oldValue, date);
m_oldValue = date;
}
/**
* Updates the picker if the user manually modified the date of the text box.<p>
*
* @param initial <code>true</code> if the datebox is being initially opened
*/
private void updateFromTextBox(boolean initial) {
Date date = getValue();
if (initial && (date == null)) {
date = m_initialDate;
}
setPickerValue(date, false);
m_time.setErrorMessage(null);
fireChange(m_oldValue, getValue());
m_oldValue = date;
}
}