com.petro.span.client.CustomDateBox.java Source code

Java tutorial

Introduction

Here is the source code for com.petro.span.client.CustomDateBox.java

Source

package com.petro.span.client;

/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

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.editor.client.IsEditor;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.editor.client.adapters.TakesValueEditor;
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.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
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.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasEnabled;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.datepicker.client.DatePicker;

import java.util.Date;

/**
 * A text box that shows a {@link DatePicker} when the user focuses on it.
 *
 * <h3>CSS Style Rules</h3>
 *
 * <dl>
 * <dt>.gwt-DateBox</dt>
 * <dd>default style name</dd>
 * <dt>.dateBoxPopup</dt>
 * <dd>Applied to the popup around the DatePicker</dd>
 * <dt>.dateBoxFormatError</dt>
 * <dd>Default style for when the date box has bad input. Applied by
 * {@link CustomDateBox.DefaultFormat} when the text does not represent a date that
 * can be parsed</dd>
 * </dl>
 *
 * <p>
 * <h3>Example</h3>
 * {@example com.google.gwt.examples.DateBoxExample}
 * </p>
 */
public class CustomDateBox extends Composite
        implements HasEnabled, HasValue<Date>, IsEditor<LeafValueEditor<Date>> {
    /**
     * Default {@link CustomDateBox.Format} class. The date is first parsed using the
     * {@link DateTimeFormat} supplied by the user, or
     * {@link PredefinedFormat#DATE_TIME_MEDIUM} by default.
     * <p>
     * If that fails, we then try to parse again using the default browser date
     * parsing.
     * </p>
     * If that fails, the <code>dateBoxFormatError</code> css style is applied to
     * the {@link CustomDateBox}. The style will be removed when either a successful
     * {@link #parse(CustomDateBox,String, boolean)} is called or
     * {@link #format(CustomDateBox,Date)} is called.
     * <p>
     * Use a different {@link CustomDateBox.Format} instance to change that behavior.
     * </p>
     */
    public static class DefaultFormat implements Format {

        private final DateTimeFormat dateTimeFormat;

        /**
         * Creates a new default format instance.
         */
        public DefaultFormat() {
            dateTimeFormat = DateTimeFormat.getFormat(PredefinedFormat.DATE_TIME_MEDIUM);
        }

        /**
         * Creates a new default format instance.
         *
         * @param dateTimeFormat the {@link DateTimeFormat} to use with this
         *          {@link Format}.
         */
        public DefaultFormat(DateTimeFormat dateTimeFormat) {
            this.dateTimeFormat = dateTimeFormat;
        }

        public String format(CustomDateBox box, Date date) {
            if (date == null) {
                return "";
            } else {
                return dateTimeFormat.format(date);
            }
        }

        /**
         * Gets the date time format.
         *
         * @return the date time format
         */
        public DateTimeFormat getDateTimeFormat() {
            return dateTimeFormat;
        }

        @SuppressWarnings("deprecation")
        public Date parse(CustomDateBox dateBox, String dateText, boolean reportError) {
            Date date = null;
            try {
                if (dateText.length() > 0) {
                    date = dateTimeFormat.parse(dateText);
                }
            } catch (IllegalArgumentException exception) {
                try {
                    date = new Date(dateText);
                    // We have parsed the text at higher resolution so do a round trip to get rid of extra:
                    date = dateTimeFormat.parse(dateTimeFormat.format(date));
                } catch (IllegalArgumentException e) {
                    if (reportError) {
                        dateBox.addStyleName(DATE_BOX_FORMAT_ERROR);
                    }
                    return null;
                }
            }
            return date;
        }

        public void reset(CustomDateBox dateBox, boolean abandon) {
            dateBox.removeStyleName(DATE_BOX_FORMAT_ERROR);
        }
    }

    /**
     * Implemented by a delegate to handle the parsing and formating of date
     * values. The default {@link Format} uses a new {@link DefaultFormat}
     * instance.
     */
    public interface Format {

        /**
         * Formats the provided date. Note, a null date is a possible input.
         *
         * @param dateBox the date box you are formatting
         * @param date the date to format
         * @return the formatted date as a string
         */
        String format(CustomDateBox dateBox, Date date);

        /**
         * Parses the provided string as a date.
         *
         * @param dateBox the date box
         * @param text the string representing a date
         * @param reportError should the formatter indicate a parse error to the
         *          user?
         * @return the date created, or null if there was a parse error
         */
        Date parse(CustomDateBox dateBox, String text, boolean reportError);

        /**
         * If the format did any modifications to the date box's styling, reset them
         * now.
         *
         * @param abandon true when the current format is being replaced by another
         * @param dateBox the date box
         */
        void reset(CustomDateBox dateBox, boolean abandon);
    }

    private class DateBoxHandler implements ValueChangeHandler<Date>, FocusHandler, BlurHandler, ClickHandler,
            KeyDownHandler, CloseHandler<PopupPanel> {

        public void onBlur(BlurEvent event) {
            if (isDatePickerShowing() == false) {
                updateDateFromTextBox();
            }
        }

        public void onClick(ClickEvent event) {
            showDatePicker();
        }

        public void onClose(CloseEvent<PopupPanel> event) {
            // If we are not closing because we have picked a new value, make sure the
            // current value is updated.
            if (allowDPShow) {
                updateDateFromTextBox();
            }
        }

        public void onFocus(FocusEvent event) {
            if (allowDPShow && isDatePickerShowing() == false) {
                showDatePicker();
            }
        }

        public void onKeyDown(KeyDownEvent event) {
            switch (event.getNativeKeyCode()) {
            case KeyCodes.KEY_ENTER:
            case KeyCodes.KEY_TAB:
                updateDateFromTextBox();
                // Deliberate fall through
            case KeyCodes.KEY_ESCAPE:
            case KeyCodes.KEY_UP:
                hideDatePicker();
                break;
            case KeyCodes.KEY_DOWN:
                showDatePicker();
                break;
            }
        }

        public void onValueChange(ValueChangeEvent<Date> event) {
            setValue(parseDate(false), normalize(event.getValue()), true, true);
            hideDatePicker();
            preventDatePickerPopup();
            box.setFocus(true);
        }

        // Round trips on render & parse to convert the date to our interpretation based on the format
        // See issue https://code.google.com/p/google-web-toolkit/issues/detail?id=4785
        private Date normalize(Date date) {
            CustomDateBox dateBox = CustomDateBox.this;
            return getFormat().parse(dateBox, getFormat().format(dateBox, date), false);
        }
    }

    /**
     * Default style name added when the date box has a format error.
     */
    private static final String DATE_BOX_FORMAT_ERROR = "dateBoxFormatError";

    /**
     * Default style name.
     */
    public static final String DEFAULT_STYLENAME = "gwt-DateBox";
    private static final DefaultFormat DEFAULT_FORMAT = GWT.create(DefaultFormat.class);
    private final PopupPanel popup;
    private final TextBox box = new TextBox();
    private final DatePicker picker;
    private LeafValueEditor<Date> editor;
    private Format format;
    private boolean allowDPShow = true;
    private boolean fireNullValues = false;

    /**
     * Create a date box with a new {@link DatePicker}.
     */
    public CustomDateBox() {
        this(new DatePicker(), null, DEFAULT_FORMAT);
    }

    /**
     * Create a new date box.
     *
     * @param date the default date.
     * @param picker the picker to drop down from the date box
     * @param format to use to parse and format dates
     */
    public CustomDateBox(DatePicker picker, Date date, Format format) {
        this.picker = picker;
        this.popup = new PopupPanel(true);
        assert format != null : "You may not construct a date box with a null format";
        this.format = format;

        popup.addAutoHidePartner(box.getElement());
        popup.setWidget(picker);
        popup.setStyleName("dateBoxPopup");

        initWidget(box);
        setStyleName(DEFAULT_STYLENAME);

        DateBoxHandler handler = new DateBoxHandler();
        picker.addValueChangeHandler(handler);
        box.addFocusHandler(handler);
        box.addBlurHandler(handler);
        box.addClickHandler(handler);
        box.addKeyDownHandler(handler);
        box.setDirectionEstimator(false);
        popup.addCloseHandler(handler);
        setValue(date);
    }

    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Date> handler) {
        return addHandler(handler, ValueChangeEvent.getType());
    }

    /**
     * Returns a {@link TakesValueEditor} backed by the DateBox.
     */
    public LeafValueEditor<Date> asEditor() {
        if (editor == null) {
            editor = TakesValueEditor.of(this);
        }
        return editor;
    }

    /**
     * Gets the current cursor position in the date box.
     *
     * @return the cursor position
     *
     */
    public int getCursorPos() {
        return box.getCursorPos();
    }

    /**
     * Gets the date picker.
     *
     * @return the date picker
     */
    public DatePicker getDatePicker() {
        return picker;
    }

    /**
     * Returns true iff the date box will fire {@code ValueChangeEvents} with a
     * date value of {@code null} for invalid or empty string values.
     */
    public boolean getFireNullValues() {
        return fireNullValues;
    }

    /**
     * Gets the format instance used to control formatting and parsing of this
     * {@link CustomDateBox}.
     *
     * @return the format
     */
    public Format getFormat() {
        return this.format;
    }

    /**
     * Gets the date box's position in the tab index.
     *
     * @return the date box's tab index
     */
    public int getTabIndex() {
        return box.getTabIndex();
    }

    /**
     * Get text box.
     *
     * @return the text box used to enter the formatted date
     */
    public TextBox getTextBox() {
        return box;
    }

    /**
     * Get the date displayed, or null if the text box is empty, or cannot be
     * interpreted.
     *
     * @return the current date value
     */
    public Date getValue() {
        return parseDate(true);
    }

    /**
     * Hide the date picker.
     */
    public void hideDatePicker() {
        popup.hide();
    }

    /**
     * Returns true if date picker is currently showing, false if not.
     */
    public boolean isDatePickerShowing() {
        return popup.isShowing();
    }

    /**
     * Returns true if the date box is enabled, false if not.
     */
    public boolean isEnabled() {
        return box.isEnabled();
    }

    /**
     * Sets the date box's 'access key'. This key is used (in conjunction with a
     * browser-specific modifier key) to automatically focus the widget.
     *
     * @param key the date box's access key
     */
    public void setAccessKey(char key) {
        box.setAccessKey(key);
    }

    /**
     * Sets whether the date box is enabled.
     *
     * @param enabled is the box enabled
     */
    public void setEnabled(boolean enabled) {
        box.setEnabled(enabled);
    }

    /**
     * Sets whether or not the date box will fire {@code ValueChangeEvents} with a
     * date value of {@code null} for invalid or empty string values.
     */
    public void setFireNullValues(boolean fireNullValues) {
        this.fireNullValues = fireNullValues;
    }

    /**
     * Explicitly focus/unfocus this widget. Only one widget can have focus at a
     * time, and the widget that does will receive all keyboard events.
     *
     * @param focused whether this widget should take focus or release it
     */
    public void setFocus(boolean focused) {
        box.setFocus(focused);
    }

    /**
     * Sets the format used to control formatting and parsing of dates in this
     * {@link CustomDateBox}. If this {@link CustomDateBox} is not empty, the contents of date
     * box will be replaced with current contents in the new format.
     *
     * @param format the new date format
     */
    public void setFormat(Format format) {
        assert format != null : "A Date box may not have a null format";
        if (this.format != format) {
            Date date = getValue();

            // This call lets the formatter do whatever other clean up is required to
            // switch formatters.
            //
            this.format.reset(this, true);

            // Now update the format and show the current date using the new format.
            this.format = format;
            setValue(date);
        }
    }

    /**
     * Sets the date box's position in the tab index. If more than one widget has
     * the same tab index, each such widget will receive focus in an arbitrary
     * order. Setting the tab index to <code>-1</code> will cause this widget to
     * be removed from the tab order.
     *
     * @param index the date box's tab index
     */
    public void setTabIndex(int index) {
        box.setTabIndex(index);
    }

    /**
     * Set the date.
     */
    public void setValue(Date date) {
        setValue(date, false);
    }

    public void setValue(Date date, boolean fireEvents) {
        setValue(picker.getValue(), date, fireEvents, true);
    }

    /**
     * Parses the current date box's value and shows that date.
     */
    public void showDatePicker() {
        Date current = parseDate(false);
        if (current == null) {
            current = new Date();
        }
        picker.setCurrentMonth(current);
        popup.showRelativeTo(this);
    }

    private Date parseDate(boolean reportError) {
        if (reportError) {
            getFormat().reset(this, false);
        }
        String text = box.getText().trim();
        return getFormat().parse(this, text, reportError);
    }

    private void preventDatePickerPopup() {
        allowDPShow = false;
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            public void execute() {
                allowDPShow = true;
            }
        });
    }

    private void setValue(Date oldDate, Date date, boolean fireEvents, boolean updateText) {
        if (date != null) {
            picker.setCurrentMonth(date);
        }
        picker.setValue(date, false);

        if (updateText) {
            format.reset(this, false);
            box.setText(getFormat().format(this, date));
        }

        if (fireEvents) {
            DateChangeEvent.fireIfNotEqualDates(this, oldDate, date);
        }
    }

    private void updateDateFromTextBox() {
        Date parsedDate = parseDate(true);
        if (fireNullValues || (parsedDate != null)) {
            setValue(picker.getValue(), parsedDate, true, false);
        } else
            setValue(picker.getValue(), null, true, false);
    }
}