fr.putnami.pwt.core.widget.client.InputDatePicker.java Source code

Java tutorial

Introduction

Here is the source code for fr.putnami.pwt.core.widget.client.InputDatePicker.java

Source

/**
 * This file is part of pwt.
 *
 * pwt 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 3 of the
 * License, or (at your option) any later version.
 *
 * pwt 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.
 *
 * You should have received a copy of the GNU Lesser General Public License along with pwt. If not,
 * see <http://www.gnu.org/licenses/>.
 */
package fr.putnami.pwt.core.widget.client;

import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.LIElement;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.dom.client.UListElement;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.KeyCodes;
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.LocaleInfo;
import com.google.gwt.i18n.shared.DateTimeFormatInfo;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.CalendarUtil;

import java.util.Collection;
import java.util.Date;
import java.util.List;

import fr.putnami.pwt.core.editor.client.EditorInput;
import fr.putnami.pwt.core.editor.client.EditorLeaf;
import fr.putnami.pwt.core.editor.client.Error;
import fr.putnami.pwt.core.editor.client.helper.TakesValueEditorWrapper;
import fr.putnami.pwt.core.editor.client.util.ValidationUtils;
import fr.putnami.pwt.core.editor.client.validator.Validator;
import fr.putnami.pwt.core.event.client.EventBus;
import fr.putnami.pwt.core.model.client.base.HasDrawable;
import fr.putnami.pwt.core.model.client.base.HasHtmlId;
import fr.putnami.pwt.core.theme.client.CssStyle;
import fr.putnami.pwt.core.widget.client.base.SimpleStyle;
import fr.putnami.pwt.core.widget.client.constant.WidgetParams;
import fr.putnami.pwt.core.widget.client.event.AskFocusEvent;
import fr.putnami.pwt.core.widget.client.util.KeyEventUtils;
import fr.putnami.pwt.core.widget.client.util.StyleUtils;

public class InputDatePicker extends FocusWidget
        implements EditorLeaf, EditorInput<Date>, HasHtmlId, HasDrawable, HasValue<Date> {

    private static final CssStyle STYLE_DATEPICKER = new SimpleStyle("datepicker");
    private static final CssStyle STYLE_POPUP = new SimpleStyle("datepicker-popup");
    private static final CssStyle STYLE_HEADER = new SimpleStyle("datepicker-header");
    private static final CssStyle STYLE_MONTH_PICKER_BUTTON = new SimpleStyle("month-picker-button");
    private static final CssStyle STYLE_MONTH_PICKER = new SimpleStyle("month-picker");
    private static final CssStyle STYLE_MONTH_PAGER = new SimpleStyle("month-pager");
    private static final CssStyle STYLE_MONTH_PREVIOUS = new SimpleStyle("month-previous");
    private static final CssStyle STYLE_MONTH_NEXT = new SimpleStyle("month-next");
    private static final CssStyle STYLE_YEAR_BUTTON = new SimpleStyle("year-button");
    private static final CssStyle STYLE_CALENDAR_PICKER = new SimpleStyle("calendar-picker");
    private static final CssStyle STYLE_MUTED = new SimpleStyle("muted");
    private static final CssStyle STYLE_SELECTED = new SimpleStyle("selected");
    private static final CssStyle STYLE_TODAY = new SimpleStyle("today");
    private static final CssStyle STYLE_SHOW = new SimpleStyle("in");

    public enum Mode {
        CALENDAR, MONTH;
    }

    private static final WidgetParams WIDGET_PARAMS = WidgetParams.Util.get();

    private static final CssStyle STYLE_FADE = new SimpleStyle("fade");

    private static final String ATTRIBUTE_DATA_DATE = "data-date";
    private static final String ATTRIBUTE_DATA_CURSOR = "data-cursor";
    private static final String ATTRIBUTE_DATA_YEAR = "data-year";

    private static final int DAYS_IN_WEEK = 7;

    private static final int YEAR_OFFSET = 1900;

    private static final DateTimeFormatInfo DATE_TIME_FORMAT_INFO = LocaleInfo.getCurrentLocale()
            .getDateTimeFormatInfo();
    private static final String[] DAYS = InputDatePicker.DATE_TIME_FORMAT_INFO.weekdaysShortStandalone();

    private static final DateTimeFormat MONTH_YEAR_FORMAT = DateTimeFormat
            .getFormat(InputDatePicker.WIDGET_PARAMS.inputDatePickerMonthYearFormat());
    private static final DateTimeFormat MONTH_ABBR_FORMAT = DateTimeFormat
            .getFormat(InputDatePicker.WIDGET_PARAMS.inputDatePickerMonthFormat());

    private static final DateTimeFormat ATTRIBUTE_DATE_FORMAT = DateTimeFormat.getFormat("yyyy-MM-dd");

    private final DivElement datepickerHeader = Document.get().createDivElement();

    private final DivElement monthPickerButton = Document.get().createDivElement();

    private final UListElement monthPagerUl = Document.get().createULElement();
    private final LIElement pagePreviusMonthLi = Document.get().createLIElement();
    private final LIElement pageTodayLi = Document.get().createLIElement();
    private final LIElement pageNextMonthLi = Document.get().createLIElement();

    private final TableElement calendarTable = Document.get().createTableElement();
    private final TableSectionElement calendatBody = Document.get().createTBodyElement();

    private final DivElement monthPicker = Document.get().createDivElement();
    private final DivElement monthPickerInner = Document.get().createDivElement();
    private final UListElement monthPickerUlMonthElement = Document.get().createULElement();

    private HandlerRegistration popupBlurHandler;

    private Mode mode = Mode.CALENDAR;

    private Date today;
    private Date cursor;
    private Date value;

    private String path;

    private List<Error> errors = Lists.newArrayList();
    private Collection<Validator<Date>> validators;
    private String htmlId;
    private com.google.web.bindery.event.shared.HandlerRegistration askFocusRegistration;

    public InputDatePicker() {
        super(Document.get().createDivElement());

        StyleUtils.addStyle(this, InputDatePicker.STYLE_DATEPICKER);

        this.getElement().appendChild(this.datepickerHeader);
        StyleUtils.addStyle(this.datepickerHeader, InputDatePicker.STYLE_HEADER);

        /* month selecter */
        this.datepickerHeader.appendChild(this.monthPickerButton);
        StyleUtils.addStyle(this.monthPickerButton, InputDatePicker.STYLE_MONTH_PICKER_BUTTON);

        /* pagination */
        this.datepickerHeader.appendChild(this.monthPagerUl);
        StyleUtils.addStyle(this.monthPagerUl, InputDatePicker.STYLE_MONTH_PAGER);
        this.createLi(this.pagePreviusMonthLi, InputDatePicker.STYLE_MONTH_PREVIOUS, "&#9668;");
        this.createLi(this.pageTodayLi, InputDatePicker.STYLE_TODAY, "&#9673;");
        this.createLi(this.pageNextMonthLi, InputDatePicker.STYLE_MONTH_NEXT, "&#9658;");

        this.monthPagerUl.appendChild(this.pagePreviusMonthLi);
        this.monthPagerUl.appendChild(this.pageTodayLi);
        this.monthPagerUl.appendChild(this.pageNextMonthLi);

        /* Calendar Picker */
        this.getElement().appendChild(this.monthPicker);
        this.monthPicker.appendChild(this.monthPickerInner);
        StyleUtils.addStyle(this.monthPicker, InputDatePicker.STYLE_MONTH_PICKER);

        /* Calendar Picker */
        this.getElement().appendChild(this.calendarTable);
        StyleUtils.addStyle(this.calendarTable, InputDatePicker.STYLE_CALENDAR_PICKER);

        /* DayPicker Header */
        TableSectionElement head = Document.get().createTHeadElement();
        TableRowElement headRow = Document.get().createTRElement();
        this.calendarTable.appendChild(head);
        head.appendChild(headRow);
        for (int i = 0; i < 7; i++) {
            TableCellElement th = Document.get().createTHElement();
            headRow.appendChild(th);
            int dayToDisplay = (i + InputDatePicker.DATE_TIME_FORMAT_INFO.firstDayOfTheWeek())
                    % InputDatePicker.DAYS.length;
            th.setInnerText(InputDatePicker.DAYS[dayToDisplay]);
        }
        /* DayPicker Body */
        this.calendarTable.appendChild(this.calendatBody);

        this.today = InputDatePicker.ATTRIBUTE_DATE_FORMAT
                .parse(InputDatePicker.ATTRIBUTE_DATE_FORMAT.format(new Date()));
        this.setValue(this.today);

        Event.sinkEvents(this.getElement(), Event.ONKEYDOWN);
        Event.sinkEvents(this.monthPickerButton, Event.ONCLICK);
        Event.sinkEvents(this.pagePreviusMonthLi, Event.ONCLICK);
        Event.sinkEvents(this.pageTodayLi, Event.ONCLICK);
        Event.sinkEvents(this.pageNextMonthLi, Event.ONCLICK);

        this.redraw();
    }

    public InputDatePicker(InputDatePicker source) {
        this();
        this.today = source.today;
        this.cursor = source.cursor;
        this.path = source.path;
        this.validators = source.validators;
    }

    @Override
    public IsWidget cloneWidget() {
        return new InputDatePicker(this);
    }

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

    @Override
    public Date getValue() {
        return this.value == null ? null : new Date(this.value.getTime());
    }

    @Override
    public void setValue(Date value) {
        this.setValue(value, false);
    }

    @Override
    public void setValue(Date value, boolean fireEvents) {
        if (fireEvents) {
            ValueChangeEvent.fireIfNotEqual(this, this.value, value);
            this.setFocus(true);
        }
        if (value == null) {
            this.value = null;
            this.cursor = new Date(this.today.getTime());
        } else {
            this.value = new Date(value.getTime());
            this.cursor = new Date(value.getTime());
        }
        this.redraw();
    }

    @Override
    public void onBrowserEvent(Event event) {
        int type = event.getTypeInt();
        Element target = event.getCurrentTarget();
        if (type == Event.ONCLICK) {
            String dataDate = target.getAttribute(InputDatePicker.ATTRIBUTE_DATA_DATE);
            String dataCursor = target.getAttribute(InputDatePicker.ATTRIBUTE_DATA_CURSOR);
            String dataYear = target.getAttribute(InputDatePicker.ATTRIBUTE_DATA_YEAR);
            if (dataDate != null && dataDate.length() > 0) {
                this.mode = Mode.CALENDAR;
                this.setValue(InputDatePicker.ATTRIBUTE_DATE_FORMAT.parse(dataDate), true);
            } else if (dataCursor != null && dataCursor.length() > 0) {
                this.mode = Mode.CALENDAR;
                this.cursor = InputDatePicker.ATTRIBUTE_DATE_FORMAT.parse(dataCursor);
                this.redraw();
            } else if (dataYear != null && dataYear.length() > 0) {
                this.openMonthOfYear(Integer.valueOf(dataYear));
            } else if (target == this.monthPickerButton) {
                if (this.mode != Mode.MONTH) {
                    this.mode = Mode.MONTH;
                } else {
                    this.mode = Mode.CALENDAR;
                }
                this.redraw();
            }
            event.stopPropagation();
            event.preventDefault();
        } else if (type == Event.ONKEYDOWN) {
            this.handleKeyPress(event.getKeyCode());
            event.stopPropagation();
            event.preventDefault();
        } else {
            super.onBrowserEvent(event);
        }
    }

    @Override
    public void redraw() {
        if (this.mode == Mode.CALENDAR) {
            this.redrawCalendarPicker();
        } else if (this.mode == Mode.MONTH) {
            this.redrawMonthPicker();
        }
    }

    private void redrawMonthPicker() {
        this.monthPicker.getStyle().setWidth(this.calendarTable.getClientWidth(), Unit.PX);
        this.calendarTable.getStyle().setDisplay(Display.NONE);
        this.monthPicker.getStyle().clearDisplay();

        int currentYear = this.cursor.getYear() + InputDatePicker.YEAR_OFFSET;
        if (this.monthPickerInner.getChildCount() == 0) {
            for (int year = currentYear - 100; year < currentYear + 100; year++) {
                DivElement yearDiv = Document.get().createDivElement();
                yearDiv.setInnerText(String.valueOf(year));
                StyleUtils.addStyle(yearDiv, InputDatePicker.STYLE_YEAR_BUTTON);
                Event.sinkEvents(yearDiv, Event.ONCLICK);
                this.monthPickerInner.appendChild(yearDiv);
                yearDiv.setAttribute(InputDatePicker.ATTRIBUTE_DATA_YEAR, String.valueOf(year));
            }
        }
        this.openMonthOfYear(this.cursor.getYear() + InputDatePicker.YEAR_OFFSET);
    }

    private void openMonthOfYear(int year) {
        String yearString = String.valueOf(year);
        this.monthPickerUlMonthElement.removeFromParent();
        for (int i = 0; i < this.monthPickerInner.getChildCount(); i++) {
            Element child = (Element) this.monthPickerInner.getChild(i);
            if (yearString.equals(child.getAttribute(InputDatePicker.ATTRIBUTE_DATA_YEAR))) {
                this.monthPickerInner.insertAfter(this.monthPickerUlMonthElement, child);
                Date monthButtonDate = new Date(this.cursor.getTime());
                monthButtonDate.setYear(year - InputDatePicker.YEAR_OFFSET);
                if (this.monthPickerUlMonthElement.getChildCount() == 0) {
                    for (int month = 0; month < 12; month++) {
                        LIElement monthElement = Document.get().createLIElement();
                        this.monthPickerUlMonthElement.appendChild(monthElement);
                        Event.sinkEvents(monthElement, Event.ONCLICK);
                        monthButtonDate.setMonth(month);
                        monthElement.setInnerText(InputDatePicker.MONTH_ABBR_FORMAT.format(monthButtonDate));
                    }
                }
                for (int month = 0; month < 12; month++) {
                    LIElement monthElement = (LIElement) this.monthPickerUlMonthElement.getChild(month);
                    monthButtonDate.setMonth(month);
                    monthElement.setAttribute(InputDatePicker.ATTRIBUTE_DATA_CURSOR,
                            InputDatePicker.ATTRIBUTE_DATE_FORMAT.format(monthButtonDate));
                }
                this.monthPicker.setScrollTop(child.getOffsetTop());
                break;
            }
        }
    }

    private void redrawCalendarPicker() {
        this.monthPicker.getStyle().setDisplay(Display.NONE);
        this.calendarTable.getStyle().clearDisplay();

        this.calendatBody.removeAllChildren();

        int firstDayOfWeek = InputDatePicker.DATE_TIME_FORMAT_INFO.firstDayOfTheWeek();
        int lastDayOfWeek = (firstDayOfWeek + InputDatePicker.DAYS_IN_WEEK) % InputDatePicker.DAYS_IN_WEEK;

        /* Display month */
        this.monthPickerButton.setInnerHTML(
                InputDatePicker.MONTH_YEAR_FORMAT.format(this.cursor) + "<span class=\"caret\"></span>");

        Date lastMonth = new Date(this.cursor.getTime());
        CalendarUtil.addMonthsToDate(lastMonth, -1);
        this.pagePreviusMonthLi.setAttribute(InputDatePicker.ATTRIBUTE_DATA_CURSOR,
                InputDatePicker.ATTRIBUTE_DATE_FORMAT.format(lastMonth));
        this.pageTodayLi.setAttribute(InputDatePicker.ATTRIBUTE_DATA_CURSOR,
                InputDatePicker.ATTRIBUTE_DATE_FORMAT.format(this.today));
        Date nextMonth = new Date(this.cursor.getTime());
        CalendarUtil.addMonthsToDate(nextMonth, 1);
        this.pageNextMonthLi.setAttribute(InputDatePicker.ATTRIBUTE_DATA_CURSOR,
                InputDatePicker.ATTRIBUTE_DATE_FORMAT.format(nextMonth));

        /* Draw daypicker */
        Date dateToDrow = new Date(this.cursor.getTime());
        int selectedMonth = dateToDrow.getMonth();
        int firstMonthToDisplay = (selectedMonth + 11) % 12;
        int lastMonthToDisplay = (selectedMonth + 13) % 12;
        do {
            CalendarUtil.addDaysToDate(dateToDrow, -1);
        } while (firstMonthToDisplay != dateToDrow.getMonth() || dateToDrow.getDay() != firstDayOfWeek);

        // drow calendarTable
        TableRowElement headRow = null;
        while (dateToDrow.getMonth() != lastMonthToDisplay || dateToDrow.getDay() != lastDayOfWeek
                || dateToDrow.getDate() == 1 && dateToDrow.getDay() == firstDayOfWeek) {
            if (headRow == null || dateToDrow.getDay() == firstDayOfWeek) {
                headRow = Document.get().createTRElement();
                this.calendatBody.appendChild(headRow);
            }
            TableCellElement td = Document.get().createTDElement();
            headRow.appendChild(td);
            DivElement div = Document.get().createDivElement();
            td.appendChild(div);
            div.setInnerText(String.valueOf(dateToDrow.getDate()));
            div.setAttribute(InputDatePicker.ATTRIBUTE_DATA_DATE,
                    InputDatePicker.ATTRIBUTE_DATE_FORMAT.format(dateToDrow));
            if (dateToDrow.getMonth() != selectedMonth) {
                StyleUtils.addStyle(td, InputDatePicker.STYLE_MUTED);
            }
            if (dateToDrow.equals(this.cursor)) {
                StyleUtils.addStyle(td, InputDatePicker.STYLE_SELECTED);
            }
            if (this.today.equals(dateToDrow)) {
                StyleUtils.addStyle(td, InputDatePicker.STYLE_TODAY);
            }
            Event.sinkEvents(div, Event.ONCLICK);

            CalendarUtil.addDaysToDate(dateToDrow, 1);
        }
    }

    private boolean handleKeyPress(int keyCode) {
        if (KeyEventUtils.isModifierKeyDown(Event.getCurrentEvent())) {
            return false;
        }
        boolean handleKey = false;
        switch (keyCode) {
        case KeyCodes.KEY_LEFT:
            CalendarUtil.addDaysToDate(this.cursor, -1);
            handleKey = true;
            break;
        case KeyCodes.KEY_RIGHT:
            CalendarUtil.addDaysToDate(this.cursor, 1);
            handleKey = true;
            break;
        case KeyCodes.KEY_UP:
            CalendarUtil.addDaysToDate(this.cursor, -7);
            handleKey = true;
            break;
        case KeyCodes.KEY_DOWN:
            CalendarUtil.addDaysToDate(this.cursor, 7);
            handleKey = true;
            break;
        case KeyCodes.KEY_PAGEUP:
            CalendarUtil.addMonthsToDate(this.cursor, -1);
            handleKey = true;
            break;
        case KeyCodes.KEY_PAGEDOWN:
            CalendarUtil.addMonthsToDate(this.cursor, 1);
            handleKey = true;
            break;
        case KeyCodes.KEY_ENTER:
            this.setValue(this.cursor, true);
            handleKey = true;
            break;
        case KeyCodes.KEY_ESCAPE:
            this.setValue(this.value);
            this.setFocus(false);
            handleKey = true;
            break;
        default:
            break;
        }
        if (handleKey) {
            this.redraw();
        }

        return handleKey;
    }

    private void createLi(LIElement liElement, CssStyle style, String text) {
        StyleUtils.addStyle(liElement, style);
        liElement.setInnerHTML(text);
    }

    public void popup(Widget container, Widget relativeTo) {
        this.setVisible(true);
        StyleUtils.addStyle(this, InputDatePicker.STYLE_POPUP);
        RootPanel.get().add(this);

        Element positioningElement = this.getElement();
        Element relativeElement = relativeTo.getElement();

        int targetHeight = relativeElement.getOffsetHeight();
        int targetTop = relativeElement.getAbsoluteTop();

        int positioningWidth = positioningElement.getOffsetWidth();
        int targetRight = relativeElement.getAbsoluteRight();

        Style elementStyle = positioningElement.getStyle();
        elementStyle.setPosition(Position.ABSOLUTE);
        elementStyle.setLeft(targetRight - positioningWidth, Unit.PX);
        elementStyle.setTop(targetTop + targetHeight, Unit.PX);

        StyleUtils.addStyle(this, InputDatePicker.STYLE_FADE);
        StyleUtils.addStyle(this, InputDatePicker.STYLE_SHOW);

        this.setFocus(true);

        if (this.popupBlurHandler == null) {
            this.popupBlurHandler = this.addBlurHandler(new BlurHandler() {

                @Override
                public void onBlur(BlurEvent event) {
                    InputDatePicker.this.hide();
                }
            });
        }
    }

    public void hide() {
        StyleUtils.removeStyle(this.getElement(), InputDatePicker.STYLE_SHOW);
        Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {

            @Override
            public boolean execute() {
                InputDatePicker.this.setVisible(false);
                RootPanel.get().remove(InputDatePicker.this);
                return false;
            }
        }, 200);
    }

    public void togglePopup(Widget container, Widget relativeTo) {
        if (!this.isVisible() || !this.isAttached()) {
            this.popup(container, relativeTo);
        } else {
            this.hide();
        }
    }

    @Override
    public String getPath() {
        return this.path;
    }

    @Override
    public Date flush() {
        this.errors.clear();
        this.errors.addAll(ValidationUtils.validate(this.validators, this, this.value));
        return this.getValue();
    }

    @Override
    public void edit(Date value) {
        this.errors.clear();
        this.setValue(value);
    }

    @Override
    public LeafValueEditor<Date> asEditor() {
        return new TakesValueEditorWrapper<Date>(this);
    }

    @Override
    public boolean hasErrors() {
        return !this.errors.isEmpty();
    }

    @Override
    public Iterable<Error> getErrors() {
        return Iterables.unmodifiableIterable(this.errors);
    }

    @Override
    public void addValidator(Validator<Date> validator) {
        if (this.validators == null) {
            this.validators = Lists.newArrayList();
        }
        this.validators.add(validator);
    }

    @Override
    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public void setHtmlId(String htmlId) {
        this.htmlId = htmlId;
        if (this.askFocusRegistration != null) {
            this.askFocusRegistration.removeHandler();
        }
        if (htmlId != null) {
            AskFocusEvent.Handler handler = new AskFocusEvent.Handler() {

                @Override
                public void onAskFocus(AskFocusEvent event) {
                    if (Objects.equal(event.getHtmlId(), InputDatePicker.this.htmlId)) {
                        InputDatePicker.this.setFocus(true);
                    }
                }
            };
            this.askFocusRegistration = EventBus.get().addHandler(AskFocusEvent.TYPE, handler);
        }
    }

    @Override
    public String getHtmlId() {
        return this.htmlId;
    }

}