diff options
author | Denis <denis@vaadin.com> | 2017-01-10 12:22:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-10 12:22:34 +0200 |
commit | 3ef30789d6ac773eed7346dcaa670426fa6f662c (patch) | |
tree | 2e5aee20b96e3a66088aa6cde13796256ec2bcf1 /client | |
parent | b4af93bebf1b7e51d33330c42e3c89d5e3e4fd45 (diff) | |
download | vaadin-framework-3ef30789d6ac773eed7346dcaa670426fa6f662c.tar.gz vaadin-framework-3ef30789d6ac773eed7346dcaa670426fa6f662c.zip |
Refactor AbstractDateField. (#8146)
First round for #8132.
Diffstat (limited to 'client')
16 files changed, 1707 insertions, 1160 deletions
diff --git a/client/src/main/java/com/vaadin/client/DateTimeService.java b/client/src/main/java/com/vaadin/client/DateTimeService.java index bfb8533f39..ec7c18e94d 100644 --- a/client/src/main/java/com/vaadin/client/DateTimeService.java +++ b/client/src/main/java/com/vaadin/client/DateTimeService.java @@ -22,7 +22,7 @@ import java.util.logging.Logger; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.i18n.shared.DateTimeFormat; -import com.vaadin.shared.ui.datefield.Resolution; +import com.vaadin.shared.ui.datefield.DateResolution; /** * This class provides date/time parsing services to all components on the @@ -203,7 +203,7 @@ public class DateTimeService { } public static boolean isInRange(Date date, Date rangeStart, Date rangeEnd, - Resolution resolution) { + DateResolution resolution) { Date s; Date e; if (rangeStart.after(rangeEnd)) { @@ -217,19 +217,19 @@ public class DateTimeService { long end = e.getYear() * 10000000000l; long target = date.getYear() * 10000000000l; - if (resolution == Resolution.YEAR) { + if (resolution == DateResolution.YEAR) { return (start <= target && end >= target); } start += s.getMonth() * 100000000l; end += e.getMonth() * 100000000l; target += date.getMonth() * 100000000l; - if (resolution == Resolution.MONTH) { + if (resolution == DateResolution.MONTH) { return (start <= target && end >= target); } start += s.getDate() * 1000000l; end += e.getDate() * 1000000l; target += date.getDate() * 1000000l; - if (resolution == Resolution.DAY) { + if (resolution == DateResolution.DAY) { return (start <= target && end >= target); } start += s.getHours() * 10000l; diff --git a/client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java b/client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java index 858b21d308..d7b6d52f98 100644 --- a/client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/AbstractComponentConnector.java @@ -706,7 +706,7 @@ public abstract class AbstractComponentConnector extends AbstractConnector * updated in another widget in addition to the one returned by the * <code>Connector</code>'s {@link #getWidget()}, or if the prefix should be * different. For example see - * {@link com.vaadin.client.ui.datefield.DateFieldConnector#setWidgetStyleNameWithPrefix(String, String, boolean)} + * {@link com.vaadin.client.ui.datefield.TextualDateConnector#setWidgetStyleNameWithPrefix(String, String, boolean)} * </p> * * @param styleName diff --git a/client/src/main/java/com/vaadin/client/ui/VCalendarPanel.java b/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java index 2a03b91d2b..601348fea4 100644 --- a/client/src/main/java/com/vaadin/client/ui/VCalendarPanel.java +++ b/client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java @@ -18,6 +18,10 @@ package com.vaadin.client.ui; import java.util.Date; import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import com.google.gwt.aria.client.Roles; import com.google.gwt.aria.client.SelectedValue; @@ -51,13 +55,22 @@ import com.vaadin.client.BrowserInfo; import com.vaadin.client.DateTimeService; import com.vaadin.client.VConsole; import com.vaadin.client.WidgetUtil; -import com.vaadin.shared.ui.datefield.Resolution; import com.vaadin.shared.util.SharedUtil; +/** + * Abstract calendar panel to show and select a date using a resolution. The + * class is parameterized by the date resolution enumeration type. + * + * @author Vaadin Ltd + * + * @param <R> + * the resolution type which this field is based on (day, month, ...) + */ @SuppressWarnings("deprecation") -public class VCalendarPanel extends FocusableFlexTable implements - KeyDownHandler, KeyPressHandler, MouseOutHandler, MouseDownHandler, - MouseUpHandler, BlurHandler, FocusHandler, SubPartAware { +public abstract class VAbstractCalendarPanel<R extends Enum<R>> + extends FocusableFlexTable implements KeyDownHandler, KeyPressHandler, + MouseOutHandler, MouseDownHandler, MouseUpHandler, BlurHandler, + FocusHandler, SubPartAware { public interface SubmitListener { @@ -96,9 +109,9 @@ public class VCalendarPanel extends FocusableFlexTable implements */ private class VEventButton extends Button { public VEventButton() { - addMouseDownHandler(VCalendarPanel.this); - addMouseOutHandler(VCalendarPanel.this); - addMouseUpHandler(VCalendarPanel.this); + addMouseDownHandler(VAbstractCalendarPanel.this); + addMouseOutHandler(VAbstractCalendarPanel.this); + addMouseUpHandler(VAbstractCalendarPanel.this); } } @@ -131,7 +144,8 @@ public class VCalendarPanel extends FocusableFlexTable implements } Date newDate = ((Day) event.getSource()).getDate(); - if (!isDateInsideRange(newDate, Resolution.DAY)) { + if (!isDateInsideRange(newDate, + getResolution(VAbstractCalendarPanel.this::isDay))) { return; } if (newDate.getMonth() != displayedMonth.getMonth() @@ -158,7 +172,7 @@ public class VCalendarPanel extends FocusableFlexTable implements private FlexTable days = new FlexTable(); - private Resolution resolution = Resolution.YEAR; + private R resolution; private Timer mouseTimer; @@ -184,11 +198,11 @@ public class VCalendarPanel extends FocusableFlexTable implements private boolean hasFocus = false; - private VDateField parent; + private VDateField<R> parent; private boolean initialRenderDone = false; - public VCalendarPanel() { + public VAbstractCalendarPanel() { getElement().setId(DOM.createUniqueId()); setStyleName(VDateField.CLASSNAME + "-calendarpanel"); Roles.getGridRole().set(getElement()); @@ -207,7 +221,7 @@ public class VCalendarPanel extends FocusableFlexTable implements addBlurHandler(this); } - public void setParentField(VDateField parent) { + public void setParentField(VDateField<R> parent) { this.parent = parent; } @@ -221,7 +235,7 @@ public class VCalendarPanel extends FocusableFlexTable implements */ private void focusDay(Date date) { // Only used when calender body is present - if (isDay(getResolution())) { + if (acceptDayFocus()) { if (focusedDay != null) { focusedDay.removeStyleDependentName(CN_FOCUSED); } @@ -233,7 +247,8 @@ public class VCalendarPanel extends FocusableFlexTable implements int cellCount = days.getCellCount(i); for (int j = 0; j < cellCount; j++) { Widget widget = days.getWidget(i, j); - if (widget != null && widget instanceof Day) { + if (widget != null + && widget instanceof VAbstractCalendarPanel.Day) { Day curday = (Day) widget; if (curday.getDate().equals(date)) { curday.addStyleDependentName(CN_FOCUSED); @@ -247,8 +262,80 @@ public class VCalendarPanel extends FocusableFlexTable implements } } - private boolean isDay(Resolution resolution) { - return Resolution.DAY.equals(resolution); + /** + * Returns {@code true} if current resolution assumes handling focus event + * for day UI component. + * + * @return {@code true} if day focus events should be handled, {@code false} + * otherwise + */ + protected abstract boolean acceptDayFocus(); + + /** + * Returns {@code true} if the provided {@code resolution} represents a day. + * + * @param resolution + * the given resolution + * @return {@code true} if the {@code resolution} represents a day + */ + protected abstract boolean isDay(R resolution); + + /** + * Returns {@code true} if the provided {@code resolution} represents a + * month. + * + * @param resolution + * the given resolution + * @return {@code true} if the {@code resolution} represents a month + */ + protected abstract boolean isMonth(R resolution); + + /** + * Returns {@code true} if the provided {@code resolution} represents an + * year. + * + * @param resolution + * the given resolution + * @return {@code true} if the {@code resolution} represents a year + */ + protected boolean isYear(R resolution) { + return parent.isYear(resolution); + } + + /** + * Returns {@code true} if the {@code resolution} representation is strictly + * below month (day, hour, etc..). + * + * @param resolution + * the given resolution + * @return whether the {@code resolution} is below the month resolution + */ + protected abstract boolean isBelowMonth(R resolution); + + /** + * Returns all available resolutions for the widget. + * + * @return all available resolutions + */ + protected Stream<R> getResolutions() { + return parent.getResolutions(); + } + + /** + * Finds the resolution by the {@code filter}. + * + * @param filter + * predicate to filter resolutions + * @return the resolution accepted by the {@code filter} + */ + protected R getResolution(Predicate<R> filter) { + List<R> resolutions = getResolutions().filter(filter) + .collect(Collectors.toList()); + assert resolutions + .size() == 1 : "The result of filtering by the predicate " + + "contains unexpected number of resolution items :" + + resolutions.size(); + return resolutions.get(0); } /** @@ -271,7 +358,8 @@ public class VCalendarPanel extends FocusableFlexTable implements int cellCount = days.getCellCount(i); for (int j = 0; j < cellCount; j++) { Widget widget = days.getWidget(i, j); - if (widget != null && widget instanceof Day) { + if (widget != null + && widget instanceof VAbstractCalendarPanel.Day) { Day curday = (Day) widget; if (curday.getDate().equals(date)) { curday.addStyleDependentName(CN_SELECTED); @@ -289,7 +377,8 @@ public class VCalendarPanel extends FocusableFlexTable implements * Updates year, month, day from focusedDate to value */ private void selectFocused() { - if (focusedDate != null && isDateInsideRange(focusedDate, resolution)) { + if (focusedDate != null + && isDateInsideRange(focusedDate, getResolution())) { if (value == null) { // No previously selected value (set to null on server side). // Create a new date using current date and time @@ -324,11 +413,11 @@ public class VCalendarPanel extends FocusableFlexTable implements return false; } - public Resolution getResolution() { + public R getResolution() { return resolution; } - public void setResolution(Resolution resolution) { + public void setResolution(R resolution) { this.resolution = resolution; } @@ -460,14 +549,15 @@ public class VCalendarPanel extends FocusableFlexTable implements Date prevMonthDate = (Date) focusedDate.clone(); removeOneMonth(prevMonthDate); - if (!isDateInsideRange(prevMonthDate, Resolution.MONTH)) { + R month = getResolution(VAbstractCalendarPanel.this::isMonth); + if (!isDateInsideRange(prevMonthDate, month)) { prevMonth.addStyleName(CN_OUTSIDE_RANGE); } else { prevMonth.removeStyleName(CN_OUTSIDE_RANGE); } Date nextMonthDate = (Date) focusedDate.clone(); addOneMonth(nextMonthDate); - if (!isDateInsideRange(nextMonthDate, Resolution.MONTH)) { + if (!isDateInsideRange(nextMonthDate, month)) { nextMonth.addStyleName(CN_OUTSIDE_RANGE); } else { nextMonth.removeStyleName(CN_OUTSIDE_RANGE); @@ -476,7 +566,8 @@ public class VCalendarPanel extends FocusableFlexTable implements Date prevYearDate = (Date) focusedDate.clone(); prevYearDate.setYear(prevYearDate.getYear() - 1); - if (!isDateInsideRange(prevYearDate, Resolution.YEAR)) { + R year = getResolution(VAbstractCalendarPanel.this::isYear); + if (!isDateInsideRange(prevYearDate, year)) { prevYear.addStyleName(CN_OUTSIDE_RANGE); } else { prevYear.removeStyleName(CN_OUTSIDE_RANGE); @@ -484,7 +575,7 @@ public class VCalendarPanel extends FocusableFlexTable implements Date nextYearDate = (Date) focusedDate.clone(); nextYearDate.setYear(nextYearDate.getYear() + 1); - if (!isDateInsideRange(nextYearDate, Resolution.YEAR)) { + if (!isDateInsideRange(nextYearDate, year)) { nextYear.addStyleName(CN_OUTSIDE_RANGE); } else { nextYear.removeStyleName(CN_OUTSIDE_RANGE); @@ -521,7 +612,7 @@ public class VCalendarPanel extends FocusableFlexTable implements * @param date * @return */ - private boolean isDateInsideRange(Date date, Resolution minResolution) { + private boolean isDateInsideRange(Date date, R minResolution) { assert (date != null); return isAcceptedByRangeEnd(date, minResolution) @@ -539,8 +630,7 @@ public class VCalendarPanel extends FocusableFlexTable implements * @param minResolution * @return */ - private boolean isAcceptedByRangeStart(Date date, - Resolution minResolution) { + private boolean isAcceptedByRangeStart(Date date, R minResolution) { assert (date != null); // rangeStart == null means that we accept all values below rangeEnd @@ -551,10 +641,10 @@ public class VCalendarPanel extends FocusableFlexTable implements Date valueDuplicate = (Date) date.clone(); Date rangeStartDuplicate = (Date) rangeStart.clone(); - if (minResolution == Resolution.YEAR) { + if (isYear(minResolution)) { return valueDuplicate.getYear() >= rangeStartDuplicate.getYear(); } - if (minResolution == Resolution.MONTH) { + if (isMonth(minResolution)) { valueDuplicate = clearDateBelowMonth(valueDuplicate); rangeStartDuplicate = clearDateBelowMonth(rangeStartDuplicate); } else { @@ -576,7 +666,7 @@ public class VCalendarPanel extends FocusableFlexTable implements * @param minResolution * @return */ - private boolean isAcceptedByRangeEnd(Date date, Resolution minResolution) { + private boolean isAcceptedByRangeEnd(Date date, R minResolution) { assert (date != null); // rangeEnd == null means that we accept all values above rangeStart @@ -587,10 +677,10 @@ public class VCalendarPanel extends FocusableFlexTable implements Date valueDuplicate = (Date) date.clone(); Date rangeEndDuplicate = (Date) rangeEnd.clone(); - if (minResolution == Resolution.YEAR) { + if (isYear(minResolution)) { return valueDuplicate.getYear() <= rangeEndDuplicate.getYear(); } - if (minResolution == Resolution.MONTH) { + if (isMonth(minResolution)) { valueDuplicate = clearDateBelowMonth(valueDuplicate); rangeEndDuplicate = clearDateBelowMonth(rangeEndDuplicate); } else { @@ -667,7 +757,7 @@ public class VCalendarPanel extends FocusableFlexTable implements if (day > 6) { day = 0; } - if (isDay(getResolution())) { + if (isBelowMonth(getResolution())) { days.setHTML(headerRow, firstWeekdayColumn + i, "<strong>" + getDateTimeService().getShortDay(day) + "</strong>"); } else { @@ -705,7 +795,7 @@ public class VCalendarPanel extends FocusableFlexTable implements day.setStyleName( parent.getStylePrimaryName() + "-calendarpanel-day"); - if (!isDateInsideRange(dayDate, Resolution.DAY)) { + if (!isDateInsideRange(dayDate, getResolution(this::isDay))) { day.addStyleDependentName(CN_OUTSIDE_RANGE); } @@ -771,7 +861,24 @@ public class VCalendarPanel extends FocusableFlexTable implements * selected. */ public void renderCalendar(boolean updateDate) { + doRenderCalendar(updateDate); + + initialRenderDone = true; + } + /** + * Performs the rendering required by the {@link #renderCalendar(boolean)}. + * Subclasses may override this method to provide a custom implementation + * avoiding {@link #renderCalendar(boolean)} override. The latter method + * contains a common logic which should not be overriden. + * + * @param updateDate + * The value false prevents setting the selected date of the + * calendar based on focusedDate. That can be used when only the + * resolution of the calendar is changed and no date has been + * selected. + */ + protected void doRenderCalendar(boolean updateDate) { super.setStylePrimaryName( parent.getStylePrimaryName() + "-calendarpanel"); @@ -788,15 +895,13 @@ public class VCalendarPanel extends FocusableFlexTable implements focusChangeListener.focusChanged(new Date(focusedDate.getTime())); } - final boolean needsMonth = !getResolution().equals(Resolution.YEAR); + final boolean needsMonth = !isYear(getResolution()); boolean needsBody = isDay(getResolution()); buildCalendarHeader(needsMonth); clearCalendarBody(!needsBody); if (needsBody) { buildCalendarBody(); } - - initialRenderDone = true; } /** @@ -809,7 +914,7 @@ public class VCalendarPanel extends FocusableFlexTable implements Date focusCopy = ((Date) focusedDate.clone()); focusCopy.setDate(focusedDate.getDate() + days); - if (!isDateInsideRange(focusCopy, resolution)) { + if (!isDateInsideRange(focusCopy, getResolution())) { // If not inside allowed range, then do not move anything return; } @@ -850,14 +955,16 @@ public class VCalendarPanel extends FocusableFlexTable implements Date requestedNextMonthDate = (Date) focusedDate.clone(); addOneMonth(requestedNextMonthDate); - if (!isDateInsideRange(requestedNextMonthDate, Resolution.MONTH)) { + if (!isDateInsideRange(requestedNextMonthDate, + getResolution(this::isMonth))) { return; } // Now also checking whether the day is inside the range or not. If not // inside, // correct it - if (!isDateInsideRange(requestedNextMonthDate, Resolution.DAY)) { + if (!isDateInsideRange(requestedNextMonthDate, + getResolution(this::isDay))) { requestedNextMonthDate = adjustDateToFitInsideRange( requestedNextMonthDate); } @@ -910,11 +1017,13 @@ public class VCalendarPanel extends FocusableFlexTable implements Date requestedPreviousMonthDate = (Date) focusedDate.clone(); removeOneMonth(requestedPreviousMonthDate); - if (!isDateInsideRange(requestedPreviousMonthDate, Resolution.MONTH)) { + if (!isDateInsideRange(requestedPreviousMonthDate, + getResolution(this::isMonth))) { return; } - if (!isDateInsideRange(requestedPreviousMonthDate, Resolution.DAY)) { + if (!isDateInsideRange(requestedPreviousMonthDate, + getResolution(this::isDay))) { requestedPreviousMonthDate = adjustDateToFitInsideRange( requestedPreviousMonthDate); } @@ -935,12 +1044,12 @@ public class VCalendarPanel extends FocusableFlexTable implements Date previousYearDate = (Date) focusedDate.clone(); previousYearDate.setYear(previousYearDate.getYear() - years); // Do not focus if not inside range - if (!isDateInsideRange(previousYearDate, Resolution.YEAR)) { + if (!isDateInsideRange(previousYearDate, getResolution(this::isYear))) { return; } // If we remove one year, but have to roll back a bit, fit it // into the calendar. Also the months have to be changed - if (!isDateInsideRange(previousYearDate, Resolution.DAY)) { + if (!isDateInsideRange(previousYearDate, getResolution(this::isDay))) { previousYearDate = adjustDateToFitInsideRange(previousYearDate); focusedDate.setYear(previousYearDate.getYear()); @@ -977,12 +1086,12 @@ public class VCalendarPanel extends FocusableFlexTable implements Date nextYearDate = (Date) focusedDate.clone(); nextYearDate.setYear(nextYearDate.getYear() + years); // Do not focus if not inside range - if (!isDateInsideRange(nextYearDate, Resolution.YEAR)) { + if (!isDateInsideRange(nextYearDate, getResolution(this::isYear))) { return; } // If we add one year, but have to roll back a bit, fit it // into the calendar. Also the months have to be changed - if (!isDateInsideRange(nextYearDate, Resolution.DAY)) { + if (!isDateInsideRange(nextYearDate, getResolution(this::isDay))) { nextYearDate = adjustDateToFitInsideRange(nextYearDate); focusedDate.setYear(nextYearDate.getYear()); @@ -1339,15 +1448,15 @@ public class VCalendarPanel extends FocusableFlexTable implements return false; } - else if (resolution == Resolution.YEAR) { + else if (isYear(getResolution())) { return handleNavigationYearMode(keycode, ctrl, shift); } - else if (resolution == Resolution.MONTH) { + else if (isMonth(getResolution())) { return handleNavigationMonthMode(keycode, ctrl, shift); } - else if (resolution == Resolution.DAY) { + else if (isDay(getResolution())) { return handleNavigationDayMode(keycode, ctrl, shift); } @@ -1461,8 +1570,8 @@ public class VCalendarPanel extends FocusableFlexTable implements // Timer is first used for a 500ms delay after mousedown. After that has // elapsed, another timer is triggered to go off every 150ms. Both // timers are cancelled on mouseup or mouseout. - if (event.getNativeButton() == NativeEvent.BUTTON_LEFT - && event.getSource() instanceof VEventButton) { + if (event.getNativeButton() == NativeEvent.BUTTON_LEFT && event + .getSource() instanceof VAbstractCalendarPanel.VEventButton) { final VEventButton sender = (VEventButton) event.getSource(); processClickEvent(sender); mouseTimer = new Timer() { @@ -1517,7 +1626,27 @@ public class VCalendarPanel extends FocusableFlexTable implements * The date to set */ public void setDate(Date currentDate) { + doSetDate(currentDate, false, () -> { + }); + } + /** + * The actual implementation of the logic which sets the data of the Panel. + * The method {@link #setDate(Date)} just delegate a call to this method + * providing additional config parameters. + * + * @param currentDate + * currentDate The date to set + * @param needRerender + * if {@code true} then calendar will be rerendered regardless of + * internal logic, otherwise the decision will be made on the + * internal state inside the method + * @param focusAction + * an additional action which will be executed in case + * rerendering is not required + */ + protected void doSetDate(Date currentDate, boolean needRerender, + Runnable focusAction) { // Check that we are not re-rendering an already active date if (currentDate == value && currentDate != null) { return; @@ -1525,7 +1654,7 @@ public class VCalendarPanel extends FocusableFlexTable implements boolean currentDateWasAdjusted = false; // Check that selected date is inside the allowed range if (currentDate != null - && !isDateInsideRange(currentDate, resolution)) { + && !isDateInsideRange(currentDate, getResolution())) { currentDate = adjustDateToFitInsideRange(currentDate); currentDateWasAdjusted = true; } @@ -1566,13 +1695,14 @@ public class VCalendarPanel extends FocusableFlexTable implements } // Re-render calendar if the displayed month is changed. - if (oldDisplayedMonth == null || value == null + if (needRerender || oldDisplayedMonth == null || value == null || oldDisplayedMonth.getYear() != value.getYear() || oldDisplayedMonth.getMonth() != value.getMonth()) { renderCalendar(); } else { focusDay(focusedDate); selectFocused(); + focusAction.run(); } if (!hasFocus) { @@ -1666,7 +1796,7 @@ public class VCalendarPanel extends FocusableFlexTable implements */ @Override public void onBlur(final BlurEvent event) { - if (event.getSource() instanceof VCalendarPanel) { + if (event.getSource() instanceof VAbstractCalendarPanel) { hasFocus = false; focusDay(null); } @@ -1681,7 +1811,7 @@ public class VCalendarPanel extends FocusableFlexTable implements */ @Override public void onFocus(FocusEvent event) { - if (event.getSource() instanceof VCalendarPanel) { + if (event.getSource() instanceof VAbstractCalendarPanel) { hasFocus = true; // Focuses the current day if the calendar shows the days @@ -1749,7 +1879,7 @@ public class VCalendarPanel extends FocusableFlexTable implements * @param subElement * @return true if {@code w} is a parent of subElement, false otherwise. */ - private boolean contains(Widget w, Element subElement) { + protected boolean contains(Widget w, Element subElement) { if (w == null || w.getElement() == null) { return false; } @@ -1782,7 +1912,7 @@ public class VCalendarPanel extends FocusableFlexTable implements Iterator<Widget> iter = days.iterator(); while (iter.hasNext()) { Widget w = iter.next(); - if (w instanceof Day) { + if (w instanceof VAbstractCalendarPanel.Day) { Day day = (Day) w; if (day.getDate().equals(date)) { return day.getElement(); @@ -1846,8 +1976,8 @@ public class VCalendarPanel extends FocusableFlexTable implements } private void setLabel() { - if (parent instanceof VPopupCalendar) { - ((VPopupCalendar) parent).setFocusedDate(this); + if (parent instanceof VAbstractPopupCalendar) { + ((VAbstractPopupCalendar) parent).setFocusedDate(this); } } } diff --git a/client/src/main/java/com/vaadin/client/ui/VAbstractDateFieldCalendar.java b/client/src/main/java/com/vaadin/client/ui/VAbstractDateFieldCalendar.java new file mode 100644 index 0000000000..4e6aa26020 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VAbstractDateFieldCalendar.java @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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. + */ + +package com.vaadin.client.ui; + +import com.google.gwt.event.dom.client.DomEvent; +import com.vaadin.client.ui.VAbstractCalendarPanel.FocusOutListener; +import com.vaadin.client.ui.VAbstractCalendarPanel.SubmitListener; + +/** + * A client side implementation for inline date field. + */ +public abstract class VAbstractDateFieldCalendar<R extends Enum<R>> + extends VDateField<R> { + + /** For internal use only. May be removed or replaced in the future. */ + public final VAbstractCalendarPanel<R> calendarPanel; + + public VAbstractDateFieldCalendar(VAbstractCalendarPanel<R> panel, + R resolution) { + super(resolution); + calendarPanel = panel; + calendarPanel.setParentField(this); + add(calendarPanel); + calendarPanel.setSubmitListener(new SubmitListener() { + @Override + public void onSubmit() { + updateValueFromPanel(); + } + + @Override + public void onCancel() { + // TODO Auto-generated method stub + + } + }); + calendarPanel.setFocusOutListener(new FocusOutListener() { + @Override + public boolean onFocusOut(DomEvent<?> event) { + updateValueFromPanel(); + return false; + } + }); + } + + @SuppressWarnings("deprecation") + public abstract void updateValueFromPanel(); + + public void setTabIndex(int tabIndex) { + calendarPanel.getElement().setTabIndex(tabIndex); + } + + public int getTabIndex() { + return calendarPanel.getElement().getTabIndex(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java b/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java new file mode 100644 index 0000000000..74530835f1 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java @@ -0,0 +1,730 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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. + */ + +package com.vaadin.client.ui; + +import java.util.Date; +import java.util.Locale; + +import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.LiveValue; +import com.google.gwt.aria.client.Roles; +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.DomEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.MouseOutEvent; +import com.google.gwt.event.dom.client.MouseOutHandler; +import com.google.gwt.event.dom.client.MouseOverEvent; +import com.google.gwt.event.dom.client.MouseOverHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComputedStyle; +import com.vaadin.client.VConsole; +import com.vaadin.client.ui.VAbstractCalendarPanel.FocusOutListener; +import com.vaadin.client.ui.VAbstractCalendarPanel.SubmitListener; +import com.vaadin.client.ui.aria.AriaHelper; +import com.vaadin.shared.ui.datefield.TextualDateFieldState; + +/** + * Represents a date selection component with a text field and a popup date/time + * selector. + * + * <b>Note:</b> To change the keyboard assignments used in the popup dialog you + * should extend <code>com.vaadin.client.ui.VAbstractCalendarPanel</code> and + * then pass set it by calling the + * <code>setCalendarPanel(VAbstractCalendarPanel panel)</code> method. + * + */ +public abstract class VAbstractPopupCalendar<R extends Enum<R>> + extends VAbstractTextualDate<R> + implements Field, ClickHandler, CloseHandler<PopupPanel>, SubPartAware { + + /** For internal use only. May be removed or replaced in the future. */ + public final Button calendarToggle = new Button(); + + /** For internal use only. May be removed or replaced in the future. */ + public VAbstractCalendarPanel<R> calendar; + + /** For internal use only. May be removed or replaced in the future. */ + public final VOverlay popup; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean parsable = true; + + private boolean open = false; + + /* + * #14857: If calendarToggle button is clicked when calendar popup is + * already open we should prevent calling openCalendarPanel() in onClick, + * since we don't want to reopen it again right after it closes. + */ + private boolean preventOpenPopupCalendar = false; + private boolean cursorOverCalendarToggleButton = false; + private boolean toggleButtonClosesWithGuarantee = false; + + private boolean textFieldEnabled = true; + + private String captionId; + + private Label selectedDate; + + private Element descriptionForAssisitveDevicesElement; + + private final String CALENDAR_TOGGLE_ID = "popupButton"; + + public VAbstractPopupCalendar(VAbstractCalendarPanel<R> calendarPanel, + R resolution) { + super(resolution); + + calendarToggle.setText(""); + calendarToggle.addClickHandler(this); + + calendarToggle.addDomHandler(new MouseOverHandler() { + @Override + public void onMouseOver(MouseOverEvent event) { + cursorOverCalendarToggleButton = true; + } + }, MouseOverEvent.getType()); + + calendarToggle.addDomHandler(new MouseOutHandler() { + @Override + public void onMouseOut(MouseOutEvent event) { + cursorOverCalendarToggleButton = false; + } + }, MouseOutEvent.getType()); + + // -2 instead of -1 to avoid FocusWidget.onAttach to reset it + calendarToggle.getElement().setTabIndex(-2); + + Roles.getButtonRole().set(calendarToggle.getElement()); + Roles.getButtonRole().setAriaHiddenState(calendarToggle.getElement(), + true); + + add(calendarToggle); + + // Description of the usage of the widget for assisitve device users + descriptionForAssisitveDevicesElement = DOM.createDiv(); + descriptionForAssisitveDevicesElement.setInnerText( + TextualDateFieldState.DESCRIPTION_FOR_ASSISTIVE_DEVICES); + AriaHelper.ensureHasId(descriptionForAssisitveDevicesElement); + Roles.getTextboxRole().setAriaDescribedbyProperty(text.getElement(), + Id.of(descriptionForAssisitveDevicesElement)); + AriaHelper.setVisibleForAssistiveDevicesOnly( + descriptionForAssisitveDevicesElement, true); + + calendar = calendarPanel; + calendar.setParentField(this); + calendar.setFocusOutListener(new FocusOutListener() { + @Override + public boolean onFocusOut(DomEvent<?> event) { + event.preventDefault(); + closeCalendarPanel(); + return true; + } + }); + + // FIXME: Problem is, that the element with the provided id does not + // exist yet in html. This is the same problem as with the context menu. + // Apply here the same fix (#11795) + Roles.getTextboxRole().setAriaControlsProperty(text.getElement(), + Id.of(calendar.getElement())); + Roles.getButtonRole().setAriaControlsProperty( + calendarToggle.getElement(), Id.of(calendar.getElement())); + + calendar.setSubmitListener(new SubmitListener() { + @Override + public void onSubmit() { + // Update internal value and send valuechange event if immediate + updateValue(calendar.getDate()); + + // Update text field (a must when not immediate). + buildDate(true); + + closeCalendarPanel(); + } + + @Override + public void onCancel() { + closeCalendarPanel(); + } + }); + + popup = new VOverlay(true, false); + popup.setOwner(this); + + FlowPanel wrapper = new FlowPanel(); + selectedDate = new Label(); + selectedDate.setStyleName(getStylePrimaryName() + "-selecteddate"); + AriaHelper.setVisibleForAssistiveDevicesOnly(selectedDate.getElement(), + true); + + Roles.getTextboxRole().setAriaLiveProperty(selectedDate.getElement(), + LiveValue.ASSERTIVE); + Roles.getTextboxRole().setAriaAtomicProperty(selectedDate.getElement(), + true); + wrapper.add(selectedDate); + wrapper.add(calendar); + + popup.setWidget(wrapper); + popup.addCloseHandler(this); + + DOM.setElementProperty(calendar.getElement(), "id", + "PID_VAADIN_POPUPCAL"); + + sinkEvents(Event.ONKEYDOWN); + + updateStyleNames(); + } + + @Override + protected void onAttach() { + super.onAttach(); + DOM.appendChild(RootPanel.get().getElement(), + descriptionForAssisitveDevicesElement); + } + + @Override + protected void onDetach() { + super.onDetach(); + descriptionForAssisitveDevicesElement.removeFromParent(); + closeCalendarPanel(); + } + + @SuppressWarnings("deprecation") + public void updateValue(Date newDate) { + Date currentDate = getCurrentDate(); + if (currentDate == null || newDate.getTime() != currentDate.getTime()) { + setCurrentDate((Date) newDate.clone()); + getClient().updateVariable(getId(), + getResolutionVariable( + calendar.getResolution(calendar::isYear)), + newDate.getYear() + 1900, false); + if (!calendar.isYear(getCurrentResolution())) { + getClient().updateVariable(getId(), + getResolutionVariable( + calendar.getResolution(calendar::isMonth)) + .toLowerCase(Locale.ENGLISH), + newDate.getMonth() + 1, false); + if (!calendar.isMonth(getCurrentResolution())) { + getClient().updateVariable(getId(), + getResolutionVariable( + calendar.getResolution(calendar::isDay)), + newDate.getDate(), false); + } + } + } + } + + /** + * Checks whether the text field is enabled. + * + * @see VAbstractPopupCalendar#setTextFieldEnabled(boolean) + * @return The current state of the text field. + */ + public boolean isTextFieldEnabled() { + return textFieldEnabled; + } + + /** + * Sets the state of the text field of this component. By default the text + * field is enabled. Disabling it causes only the button for date selection + * to be active, thus preventing the user from entering invalid dates. See + * {@link http://dev.vaadin.com/ticket/6790}. + * + * @param state + */ + public void setTextFieldEnabled(boolean textFieldEnabled) { + this.textFieldEnabled = textFieldEnabled; + updateTextFieldEnabled(); + } + + protected void updateTextFieldEnabled() { + boolean reallyEnabled = isEnabled() && isTextFieldEnabled(); + // IE has a non input disabled themeing that can not be overridden so we + // must fake the functionality using readonly and unselectable + if (BrowserInfo.get().isIE()) { + if (!reallyEnabled) { + text.getElement().setAttribute("unselectable", "on"); + text.getElement().setAttribute("readonly", ""); + text.setTabIndex(-2); + } else if (reallyEnabled + && text.getElement().hasAttribute("unselectable")) { + text.getElement().removeAttribute("unselectable"); + text.getElement().removeAttribute("readonly"); + text.setTabIndex(0); + } + } else { + text.setEnabled(reallyEnabled); + } + + if (reallyEnabled) { + calendarToggle.setTabIndex(-1); + Roles.getButtonRole() + .setAriaHiddenState(calendarToggle.getElement(), true); + } else { + calendarToggle.setTabIndex(0); + Roles.getButtonRole() + .setAriaHiddenState(calendarToggle.getElement(), false); + } + + handleAriaAttributes(); + } + + /** + * Set correct tab index for disabled text field in IE as the value set in + * setTextFieldEnabled(...) gets overridden in + * TextualDateConnection.updateFromUIDL(...) + * + * @since 7.3.1 + */ + public void setTextFieldTabIndex() { + if (BrowserInfo.get().isIE() && !textFieldEnabled) { + // index needs to be -2 because FocusWidget updates -1 to 0 onAttach + text.setTabIndex(-2); + } + } + + @Override + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { + if (captionElement == null) { + captionId = null; + } else { + captionId = captionElement.getId(); + } + + if (isTextFieldEnabled()) { + super.bindAriaCaption(captionElement); + } else { + AriaHelper.bindCaption(calendarToggle, captionElement); + } + + handleAriaAttributes(); + } + + private void handleAriaAttributes() { + Widget removeFromWidget; + Widget setForWidget; + + if (isTextFieldEnabled()) { + setForWidget = text; + removeFromWidget = calendarToggle; + } else { + setForWidget = calendarToggle; + removeFromWidget = text; + } + + Roles.getFormRole() + .removeAriaLabelledbyProperty(removeFromWidget.getElement()); + if (captionId == null) { + Roles.getFormRole() + .removeAriaLabelledbyProperty(setForWidget.getElement()); + } else { + Roles.getFormRole().setAriaLabelledbyProperty( + setForWidget.getElement(), Id.of(captionId)); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String) + */ + @Override + public void setStyleName(String style) { + super.setStyleName(style); + updateStyleNames(); + } + + @Override + public void setStylePrimaryName(String style) { + removeStyleName(getStylePrimaryName() + "-popupcalendar"); + super.setStylePrimaryName(style); + updateStyleNames(); + } + + @Override + protected void updateStyleNames() { + super.updateStyleNames(); + if (getStylePrimaryName() != null && calendarToggle != null) { + addStyleName(getStylePrimaryName() + "-popupcalendar"); + calendarToggle.setStyleName(getStylePrimaryName() + "-button"); + popup.setStyleName(getStylePrimaryName() + "-popup"); + calendar.setStyleName(getStylePrimaryName() + "-calendarpanel"); + } + } + + /** + * Opens the calendar panel popup + */ + public void openCalendarPanel() { + + if (!open && !readonly && isEnabled()) { + open = true; + + if (getCurrentDate() != null) { + calendar.setDate((Date) getCurrentDate().clone()); + } else { + calendar.setDate(new Date()); + } + + // clear previous values + popup.setWidth(""); + popup.setHeight(""); + popup.setPopupPositionAndShow(new PopupPositionCallback()); + } else { + VConsole.error("Cannot reopen popup, it is already open!"); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event + * .dom.client.ClickEvent) + */ + @Override + public void onClick(ClickEvent event) { + if (event.getSource() == calendarToggle && isEnabled()) { + if (!preventOpenPopupCalendar) { + openCalendarPanel(); + } + preventOpenPopupCalendar = false; + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt + * .event.logical.shared.CloseEvent) + */ + @Override + public void onClose(CloseEvent<PopupPanel> event) { + if (event.getSource() == popup) { + buildDate(); + if (!BrowserInfo.get().isTouchDevice() && textFieldEnabled) { + /* + * Move focus to textbox, unless on touch device (avoids opening + * virtual keyboard) or if textField is disabled. + */ + focus(); + } + + open = false; + + if (cursorOverCalendarToggleButton + && !toggleButtonClosesWithGuarantee) { + preventOpenPopupCalendar = true; + } + + toggleButtonClosesWithGuarantee = false; + } + } + + /** + * Sets focus to Calendar panel. + * + * @param focus + */ + public void setFocus(boolean focus) { + calendar.setFocus(focus); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + updateTextFieldEnabled(); + calendarToggle.setEnabled(enabled); + Roles.getButtonRole().setAriaDisabledState(calendarToggle.getElement(), + !enabled); + } + + /** + * Sets the content of a special field for assistive devices, so that they + * can recognize the change and inform the user (reading out in case of + * screen reader) + * + * @param selectedDate + * Date that is currently selected + */ + public void setFocusedDate(Date selectedDate) { + this.selectedDate.setText(DateTimeFormat.getFormat("dd, MMMM, yyyy") + .format(selectedDate)); + } + + /** + * For internal use only. May be removed or replaced in the future. + * + * @see com.vaadin.client.ui.VAbstractTextualDate#buildDate() + */ + @Override + public void buildDate() { + // Save previous value + String previousValue = getText(); + super.buildDate(); + + // Restore previous value if the input could not be parsed + if (!parsable) { + setText(previousValue); + } + updateTextFieldEnabled(); + } + + /** + * Update the text field contents from the date. See {@link #buildDate()}. + * + * @param forceValid + * true to force the text field to be updated, false to only + * update if the parsable flag is true. + */ + protected void buildDate(boolean forceValid) { + if (forceValid) { + parsable = true; + } + buildDate(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google + * .gwt.user.client.Event) + */ + @Override + public void onBrowserEvent(com.google.gwt.user.client.Event event) { + super.onBrowserEvent(event); + if (DOM.eventGetType(event) == Event.ONKEYDOWN + && event.getKeyCode() == getOpenCalenderPanelKey()) { + openCalendarPanel(); + event.preventDefault(); + } + } + + /** + * Get the key code that opens the calendar panel. By default it is the down + * key but you can override this to be whatever you like + * + * @return + */ + protected int getOpenCalenderPanelKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Closes the open popup panel + */ + public void closeCalendarPanel() { + if (open) { + toggleButtonClosesWithGuarantee = true; + popup.hide(true); + } + } + + @Override + public com.google.gwt.user.client.Element getSubPartElement( + String subPart) { + if (subPart.equals(CALENDAR_TOGGLE_ID)) { + return calendarToggle.getElement(); + } + + return super.getSubPartElement(subPart); + } + + @Override + public String getSubPartName( + com.google.gwt.user.client.Element subElement) { + if (calendarToggle.getElement().isOrHasChild(subElement)) { + return CALENDAR_TOGGLE_ID; + } + + return super.getSubPartName(subElement); + } + + /** + * Set a description that explains the usage of the Widget for users of + * assistive devices. + * + * @param descriptionForAssistiveDevices + * String with the description + */ + public void setDescriptionForAssistiveDevices( + String descriptionForAssistiveDevices) { + descriptionForAssisitveDevicesElement + .setInnerText(descriptionForAssistiveDevices); + } + + /** + * Get the description that explains the usage of the Widget for users of + * assistive devices. + * + * @return String with the description + */ + public String getDescriptionForAssistiveDevices() { + return descriptionForAssisitveDevicesElement.getInnerText(); + } + + /** + * Sets the start range for this component. The start range is inclusive, + * and it depends on the current resolution, what is considered inside the + * range. + * + * @param startDate + * - the allowed range's start date + */ + public void setRangeStart(Date rangeStart) { + calendar.setRangeStart(rangeStart); + } + + /** + * Sets the end range for this component. The end range is inclusive, and it + * depends on the current resolution, what is considered inside the range. + * + * @param endDate + * - the allowed range's end date + */ + public void setRangeEnd(Date rangeEnd) { + calendar.setRangeEnd(rangeEnd); + } + + private class PopupPositionCallback implements PositionCallback { + + @Override + public void setPosition(int offsetWidth, int offsetHeight) { + final int width = offsetWidth; + final int height = offsetHeight; + final int browserWindowWidth = Window.getClientWidth() + + Window.getScrollLeft(); + final int windowHeight = Window.getClientHeight() + + Window.getScrollTop(); + int left = calendarToggle.getAbsoluteLeft(); + + // Add a little extra space to the right to avoid + // problems with IE7 scrollbars and to make it look + // nicer. + int extraSpace = 30; + + boolean overflow = left + width + extraSpace > browserWindowWidth; + if (overflow) { + // Part of the popup is outside the browser window + // (to the right) + left = browserWindowWidth - width - extraSpace; + } + + int top = calendarToggle.getAbsoluteTop(); + int extraHeight = 2; + boolean verticallyRepositioned = false; + ComputedStyle style = new ComputedStyle(popup.getElement()); + int[] margins = style.getMargin(); + int desiredPopupBottom = top + height + + calendarToggle.getOffsetHeight() + margins[0] + + margins[2]; + + if (desiredPopupBottom > windowHeight) { + int updatedLeft = left; + left = getLeftPosition(left, width, style, overflow); + + // if position has not been changed then it means there is no + // space to make popup fully visible + if (updatedLeft == left) { + // let's try to show popup on the top of the field + int updatedTop = top - extraHeight - height - margins[0] + - margins[2]; + verticallyRepositioned = updatedTop >= 0; + if (verticallyRepositioned) { + top = updatedTop; + } + } + // Part of the popup is outside the browser window + // (below) + if (!verticallyRepositioned) { + verticallyRepositioned = true; + top = windowHeight - height - extraSpace + extraHeight; + } + } + if (verticallyRepositioned) { + popup.setPopupPosition(left, top); + } else { + popup.setPopupPosition(left, + top + calendarToggle.getOffsetHeight() + extraHeight); + } + doSetFocus(); + } + + private int getLeftPosition(int left, int width, ComputedStyle style, + boolean overflow) { + if (positionRightSide()) { + // Show to the right of the popup button unless we + // are in the lower right corner of the screen + if (overflow) { + return left; + } else { + return left + calendarToggle.getOffsetWidth(); + } + } else { + int[] margins = style.getMargin(); + int desiredLeftPosition = calendarToggle.getAbsoluteLeft() + - width - margins[1] - margins[3]; + if (desiredLeftPosition >= 0) { + return desiredLeftPosition; + } else { + return left; + } + } + } + + private boolean positionRightSide() { + int buttonRightSide = calendarToggle.getAbsoluteLeft() + + calendarToggle.getOffsetWidth(); + int textRightSide = text.getAbsoluteLeft() + text.getOffsetWidth(); + return buttonRightSide >= textRightSide; + } + + private void doSetFocus() { + /* + * We have to wait a while before focusing since the popup needs to + * be opened before we can focus + */ + Timer focusTimer = new Timer() { + @Override + public void run() { + setFocus(true); + } + }; + + focusTimer.schedule(100); + } + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VTextualDate.java b/client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java index b7c89c6397..d99e1c0a6c 100644 --- a/client/src/main/java/com/vaadin/client/ui/VTextualDate.java +++ b/client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java @@ -19,12 +19,9 @@ package com.vaadin.client.ui; import java.util.Date; import com.google.gwt.aria.client.Roles; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.DomEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; @@ -39,10 +36,20 @@ import com.vaadin.client.ui.aria.HandlesAriaCaption; import com.vaadin.client.ui.aria.HandlesAriaInvalid; import com.vaadin.client.ui.aria.HandlesAriaRequired; import com.vaadin.shared.EventId; -import com.vaadin.shared.ui.datefield.Resolution; -public class VTextualDate extends VDateField implements Field, ChangeHandler, - Focusable, SubPartAware, HandlesAriaCaption, HandlesAriaInvalid, +/** + * Abstract textual date field base implementation. Provides a text box as an + * editor for a date. The class is parameterized by the date resolution + * enumeration type. + * + * @author Vaadin Ltd + * + * @param <R> + * the resolution type which this field is based on (day, month, ...) + */ +public abstract class VAbstractTextualDate<R extends Enum<R>> + extends VDateField<R> implements Field, ChangeHandler, Focusable, + SubPartAware, HandlesAriaCaption, HandlesAriaInvalid, HandlesAriaRequired, KeyDownHandler { private static final String PARSE_ERROR_CLASSNAME = "-parseerror"; @@ -51,51 +58,30 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler, public final TextBox text; /** For internal use only. May be removed or replaced in the future. */ - public String formatStr; + public boolean lenient; + + private final String TEXTFIELD_ID = "field"; /** For internal use only. May be removed or replaced in the future. */ - public boolean lenient; + public String formatStr; - public VTextualDate() { - super(); + public VAbstractTextualDate(R resoluton) { + super(resoluton); text = new TextBox(); text.addChangeHandler(this); - text.addFocusHandler(new FocusHandler() { - @Override - public void onFocus(FocusEvent event) { - text.addStyleName(VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS); - if (getClient() != null && getClient() - .hasEventListeners(VTextualDate.this, EventId.FOCUS)) { - getClient().updateVariable(getId(), EventId.FOCUS, "", - true); - } - - // Needed for tooltip event handling - VTextualDate.this.fireEvent(event); - } - }); - text.addBlurHandler(new BlurHandler() { - @Override - public void onBlur(BlurEvent event) { - text.removeStyleName(VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS); - String value = getText(); - if (getClient() != null && getClient() - .hasEventListeners(VTextualDate.this, EventId.BLUR)) { - getClient().updateVariable(getId(), EventId.BLUR, "", true); - } - - // Needed for tooltip event handling - VTextualDate.this.fireEvent(event); - } - }); + text.addFocusHandler( + event -> fireBlurFocusEvent(event, true, EventId.FOCUS)); + text.addBlurHandler( + event -> fireBlurFocusEvent(event, false, EventId.BLUR)); if (BrowserInfo.get().isIE()) { addDomHandler(this, KeyDownEvent.getType()); } add(text); } + /** + * Updates style names for the widget (and its children). + */ protected void updateStyleNames() { if (text != null) { text.setStyleName(VTextField.CLASSNAME); @@ -103,9 +89,14 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler, } } + /** + * Gets the date format string for the current locale. + * + * @return the format string + */ protected String getFormatString() { if (formatStr == null) { - if (currentResolution == Resolution.YEAR) { + if (isYear(getCurrentResolution())) { formatStr = "yyyy"; // force full year } else { @@ -224,34 +215,39 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler, getClient().updateVariable(getId(), "dateString", text.getText(), false); + updateDateVariables(); + } + + /** + * Updates variables to send a response to the server. + * <p> + * The method can be overridden by subclasses to provide a custom logic for + * date variables to avoid overriding the {@link #onChange(ChangeEvent)} + * method. + */ + protected void updateDateVariables() { // Update variables // (only the smallest defining resolution needs to be // immediate) Date currentDate = getDate(); - getClient().updateVariable(getId(), "year", + getClient().updateVariable(getId(), + getResolutionVariable(getResolutions().filter(this::isYear) + .findFirst().get()), currentDate != null ? currentDate.getYear() + 1900 : -1, - currentResolution == Resolution.YEAR); - if (currentResolution.compareTo(Resolution.MONTH) <= 0) { - getClient().updateVariable(getId(), "month", - currentDate != null ? currentDate.getMonth() + 1 : -1, - currentResolution == Resolution.MONTH); - } - if (currentResolution.compareTo(Resolution.DAY) <= 0) { - getClient().updateVariable(getId(), "day", - currentDate != null ? currentDate.getDate() : -1, - currentResolution == Resolution.DAY); - } + isYear(getCurrentResolution())); } - private String cleanFormat(String format) { - // Remove unnecessary d & M if resolution is too low - if (currentResolution.compareTo(Resolution.DAY) > 0) { - format = format.replaceAll("d", ""); - } - if (currentResolution.compareTo(Resolution.MONTH) > 0) { - format = format.replaceAll("M", ""); - } - + /** + * Clean date format string to make it suitable for + * {@link #getFormatString()}. + * + * @see #getFormatString() + * + * @param format + * date format string + * @return cleaned up string + */ + protected String cleanFormat(String format) { // Remove unsupported patterns // TODO support for 'G', era designator (used at least in Japan) format = format.replaceAll("[GzZwWkK]", ""); @@ -311,8 +307,6 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler, this.text.setText(text); } - private final String TEXTFIELD_ID = "field"; - @Override public com.google.gwt.user.client.Element getSubPartElement( String subPart) { @@ -342,4 +336,22 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler, onChange(null); } } + + private void fireBlurFocusEvent(DomEvent<?> event, + boolean addFocusStyleName, String eventId) { + String styleName = VTextField.CLASSNAME + "-" + + VTextField.CLASSNAME_FOCUS; + if (addFocusStyleName) { + text.addStyleName(styleName); + } else { + text.removeStyleName(styleName); + } + if (getClient() != null && getClient() + .hasEventListeners(VAbstractTextualDate.this, eventId)) { + getClient().updateVariable(getId(), eventId, "", true); + } + + // Needed for tooltip event handling + fireEvent(event); + } } diff --git a/client/src/main/java/com/vaadin/client/ui/VDateCalendarPanel.java b/client/src/main/java/com/vaadin/client/ui/VDateCalendarPanel.java new file mode 100644 index 0000000000..9685b05f10 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VDateCalendarPanel.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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. + */ +package com.vaadin.client.ui; + +import com.vaadin.shared.ui.datefield.DateResolution; + +/** + * @author Vaadin Ltd + * + */ +public class VDateCalendarPanel extends VAbstractCalendarPanel<DateResolution> { + + @Override + protected boolean acceptDayFocus() { + return isDay(getResolution()); + } + + @Override + protected boolean isDay(DateResolution resolution) { + return DateResolution.DAY.equals(resolution); + } + + @Override + protected boolean isMonth(DateResolution resolution) { + return DateResolution.MONTH.equals(resolution); + } + + @Override + protected boolean isBelowMonth(DateResolution resolution) { + return isDay(resolution); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/VDateField.java b/client/src/main/java/com/vaadin/client/ui/VDateField.java index 2f8c72081e..e7a49ef5be 100644 --- a/client/src/main/java/com/vaadin/client/ui/VDateField.java +++ b/client/src/main/java/com/vaadin/client/ui/VDateField.java @@ -17,14 +17,25 @@ package com.vaadin.client.ui; import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Stream; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasEnabled; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.DateTimeService; -import com.vaadin.shared.ui.datefield.Resolution; -public class VDateField extends FlowPanel implements Field, HasEnabled { +/** + * A very base widget class for a date field. + * + * @author Vaadin Ltd + * + * @param <R> + * the resolution type which this field is based on (day, month, ...) + */ +public abstract class VDateField<R extends Enum<R>> extends FlowPanel + implements Field, HasEnabled { public static final String CLASSNAME = "v-datefield"; @@ -34,18 +45,7 @@ public class VDateField extends FlowPanel implements Field, HasEnabled { /** For internal use only. May be removed or replaced in the future. */ public ApplicationConnection client; - /** For internal use only. May be removed or replaced in the future. */ - public static String resolutionToString(Resolution res) { - if (res == Resolution.DAY) { - return "day"; - } - if (res == Resolution.MONTH) { - return "month"; - } - return "year"; - } - - protected Resolution currentResolution = Resolution.YEAR; + private R currentResolution; protected String currentLocale; @@ -64,33 +64,17 @@ public class VDateField extends FlowPanel implements Field, HasEnabled { protected boolean showISOWeekNumbers = false; - public VDateField() { + public VDateField(R resolution) { setStyleName(CLASSNAME); dts = new DateTimeService(); + currentResolution = resolution; } - /** - * For internal use only. May be removed or replaced in the future. - */ - public static Date getTime(int year, int month, int day) { - Date date = new Date(2000 - 1900, 0, 1); - if (year >= 0) { - date.setYear(year - 1900); - } - if (month >= 0) { - date.setMonth(month - 1); - } - if (day >= 0) { - date.setDate(day); - } - return date; - } - - public Resolution getCurrentResolution() { + public R getCurrentResolution() { return currentResolution; } - public void setCurrentResolution(Resolution currentResolution) { + public void setCurrentResolution(R currentResolution) { this.currentResolution = currentResolution; } @@ -110,6 +94,20 @@ public class VDateField extends FlowPanel implements Field, HasEnabled { this.date = date; } + /** + * Set the current date using a map with date values. + * <p> + * The map contains integer representation of values per resolution. The + * method should construct a date based on the map and set it via + * {@link #setCurrentDate(Date)} + * + * @param dateValues + * a map with date values to convert into a date + */ + public void setCurrentDate(Map<R, Integer> dateValues) { + setCurrentDate(getDate(dateValues)); + } + public boolean isReadonly() { return readonly; } @@ -182,4 +180,71 @@ public class VDateField extends FlowPanel implements Field, HasEnabled { protected void setDate(Date date) { this.date = date; } + + /** + * Returns a resolution variable name for the given {@code resolution}. + * + * @param resolution + * the given resolution + * @return the resolution variable name + */ + public String getResolutionVariable(R resolution) { + return resolution.name().toLowerCase(Locale.ENGLISH); + } + + /** + * Returns all available resolutions for the field in the ascending order + * (which is the same as order of enumeration ordinals). + * <p> + * The method uses {@link #doGetResolutions()} to make sure that the order + * is the correct one. + * + * @see #doGetResolutions() + * + * @return stream of all available resolutions in the ascending order. + */ + public Stream<R> getResolutions() { + return Stream.of(doGetResolutions()).sorted(); + } + + /** + * Returns a current resolution as a string. + * <p> + * The method is used to generate a style name for the current resolution. + * + * @return the current resolution as a string + */ + public abstract String resolutionAsString(); + + /** + * Checks whether the given {@code resolution} represents an year. + * + * @param resolution + * the given resolution + * @return {@code true} if the {@code resolution} represents an year + */ + public abstract boolean isYear(R resolution); + + /** + * Returns a date based on the provided date values map. + * + * @see #setCurrentDate(Map) + * + * @param dateVaules + * a map with date values to convert into a date + * @return the date based on the dateValues map + */ + protected abstract Date getDate(Map<R, Integer> dateVaules); + + /** + * Returns all available resolutions as an array. + * <p> + * No any order is required (in contrary to {@link #getResolutions()}. + * + * @see #getResolutions() + * + * @return all available resolutions + */ + protected abstract R[] doGetResolutions(); + } diff --git a/client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java b/client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java index 18d896e487..8c3c3a805f 100644 --- a/client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java +++ b/client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java @@ -13,48 +13,25 @@ * License for the specific language governing permissions and limitations under * the License. */ - package com.vaadin.client.ui; import java.util.Date; +import java.util.Map; -import com.google.gwt.event.dom.client.DomEvent; -import com.vaadin.client.ui.VCalendarPanel.FocusOutListener; -import com.vaadin.client.ui.VCalendarPanel.SubmitListener; -import com.vaadin.shared.ui.datefield.Resolution; +import com.google.gwt.core.shared.GWT; +import com.vaadin.shared.ui.datefield.DateResolution; /** - * A client side implementation for InlineDateField + * A client side implementation for InlineDateField. + * + * @author Vaadin Ltd + * */ -public class VDateFieldCalendar extends VDateField { - - /** For internal use only. May be removed or replaced in the future. */ - public final VCalendarPanel calendarPanel; +public class VDateFieldCalendar + extends VAbstractDateFieldCalendar<DateResolution> { public VDateFieldCalendar() { - super(); - calendarPanel = new VCalendarPanel(); - calendarPanel.setParentField(this); - add(calendarPanel); - calendarPanel.setSubmitListener(new SubmitListener() { - @Override - public void onSubmit() { - updateValueFromPanel(); - } - - @Override - public void onCancel() { - // TODO Auto-generated method stub - - } - }); - calendarPanel.setFocusOutListener(new FocusOutListener() { - @Override - public boolean onFocusOut(DomEvent<?> event) { - updateValueFromPanel(); - return false; - } - }); + super(GWT.create(VDateCalendarPanel.class), DateResolution.YEAR); } /** @@ -62,10 +39,9 @@ public class VDateFieldCalendar extends VDateField { * <p> * For internal use only. May be removed or replaced in the future. */ - + @Override @SuppressWarnings("deprecation") public void updateValueFromPanel() { - // If field is invisible at the beginning, client can still be null when // this function is called. if (getClient() == null) { @@ -76,25 +52,50 @@ public class VDateFieldCalendar extends VDateField { Date currentDate = getCurrentDate(); if (currentDate == null || date2.getTime() != currentDate.getTime()) { setCurrentDate((Date) date2.clone()); - getClient().updateVariable(getId(), "year", date2.getYear() + 1900, - false); - if (getCurrentResolution().compareTo(Resolution.YEAR) < 0) { - getClient().updateVariable(getId(), "month", + getClient().updateVariable(getId(), + getResolutionVariable(DateResolution.YEAR), + // Java Date uses the year aligned to 1900 (no to zero). + // So we should add 1900 to get a correct year aligned to 0. + date2.getYear() + 1900, false); + if (getCurrentResolution().compareTo(DateResolution.YEAR) < 0) { + getClient().updateVariable(getId(), + getResolutionVariable(DateResolution.MONTH), date2.getMonth() + 1, false); - if (getCurrentResolution().compareTo(Resolution.MONTH) < 0) { - getClient().updateVariable(getId(), "day", date2.getDate(), - false); + if (getCurrentResolution() + .compareTo(DateResolution.MONTH) < 0) { + getClient().updateVariable(getId(), + getResolutionVariable(DateResolution.DAY), + date2.getDate(), false); } } getClient().sendPendingVariableChanges(); } } - public void setTabIndex(int tabIndex) { - calendarPanel.getElement().setTabIndex(tabIndex); + @Override + public void setCurrentResolution(DateResolution resolution) { + super.setCurrentResolution( + resolution == null ? DateResolution.YEAR : resolution); + } + + @Override + public String resolutionAsString() { + return getResolutionVariable(getCurrentResolution()); + } + + @Override + public boolean isYear(DateResolution resolution) { + return DateResolution.YEAR.equals(resolution); + } + + @Override + protected DateResolution[] doGetResolutions() { + return DateResolution.values(); } - public int getTabIndex() { - return calendarPanel.getElement().getTabIndex(); + @Override + protected Date getDate(Map<DateResolution, Integer> dateVaules) { + return VPopupCalendar.makeDate(dateVaules); } + } diff --git a/client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java b/client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java index eece76b39a..20425faa8b 100644 --- a/client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java +++ b/client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java @@ -13,709 +13,91 @@ * License for the specific language governing permissions and limitations under * the License. */ - package com.vaadin.client.ui; import java.util.Date; +import java.util.Map; -import com.google.gwt.aria.client.Id; -import com.google.gwt.aria.client.LiveValue; -import com.google.gwt.aria.client.Roles; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.DomEvent; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.MouseOutEvent; -import com.google.gwt.event.dom.client.MouseOutHandler; -import com.google.gwt.event.dom.client.MouseOverEvent; -import com.google.gwt.event.dom.client.MouseOverHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ComputedStyle; -import com.vaadin.client.VConsole; -import com.vaadin.client.ui.VCalendarPanel.FocusOutListener; -import com.vaadin.client.ui.VCalendarPanel.SubmitListener; -import com.vaadin.client.ui.aria.AriaHelper; -import com.vaadin.shared.ui.datefield.DateFieldState; -import com.vaadin.shared.ui.datefield.Resolution; +import com.vaadin.shared.ui.datefield.DateResolution; /** * Represents a date selection component with a text field and a popup date * selector. - * - * <b>Note:</b> To change the keyboard assignments used in the popup dialog you - * should extend <code>com.vaadin.client.ui.VCalendarPanel</code> and then pass - * set it by calling the <code>setCalendarPanel(VCalendarPanel panel)</code> - * method. + * + * @author Vaadin Ltd * */ -public class VPopupCalendar extends VTextualDate - implements Field, ClickHandler, CloseHandler<PopupPanel>, SubPartAware { - - /** For internal use only. May be removed or replaced in the future. */ - public final Button calendarToggle = new Button(); - - /** For internal use only. May be removed or replaced in the future. */ - public VCalendarPanel calendar; - - /** For internal use only. May be removed or replaced in the future. */ - public final VOverlay popup; - - /** For internal use only. May be removed or replaced in the future. */ - public boolean parsable = true; - - private boolean open = false; - - /* - * #14857: If calendarToggle button is clicked when calendar popup is - * already open we should prevent calling openCalendarPanel() in onClick, - * since we don't want to reopen it again right after it closes. - */ - private boolean preventOpenPopupCalendar = false; - private boolean cursorOverCalendarToggleButton = false; - private boolean toggleButtonClosesWithGuarantee = false; - - private boolean textFieldEnabled = true; - - private String captionId; - - private Label selectedDate; - - private Element descriptionForAssisitveDevicesElement; +public class VPopupCalendar extends VAbstractPopupCalendar<DateResolution> { public VPopupCalendar() { - super(); - - calendarToggle.setText(""); - calendarToggle.addClickHandler(this); - - calendarToggle.addDomHandler(new MouseOverHandler() { - @Override - public void onMouseOver(MouseOverEvent event) { - cursorOverCalendarToggleButton = true; - } - }, MouseOverEvent.getType()); - - calendarToggle.addDomHandler(new MouseOutHandler() { - @Override - public void onMouseOut(MouseOutEvent event) { - cursorOverCalendarToggleButton = false; - } - }, MouseOutEvent.getType()); - - // -2 instead of -1 to avoid FocusWidget.onAttach to reset it - calendarToggle.getElement().setTabIndex(-2); - - Roles.getButtonRole().set(calendarToggle.getElement()); - Roles.getButtonRole().setAriaHiddenState(calendarToggle.getElement(), - true); - - add(calendarToggle); - - // Description of the usage of the widget for assisitve device users - descriptionForAssisitveDevicesElement = DOM.createDiv(); - descriptionForAssisitveDevicesElement - .setInnerText(DateFieldState.DESCRIPTION_FOR_ASSISTIVE_DEVICES); - AriaHelper.ensureHasId(descriptionForAssisitveDevicesElement); - Roles.getTextboxRole().setAriaDescribedbyProperty(text.getElement(), - Id.of(descriptionForAssisitveDevicesElement)); - AriaHelper.setVisibleForAssistiveDevicesOnly( - descriptionForAssisitveDevicesElement, true); - - calendar = GWT.create(VCalendarPanel.class); - calendar.setParentField(this); - calendar.setFocusOutListener(new FocusOutListener() { - @Override - public boolean onFocusOut(DomEvent<?> event) { - event.preventDefault(); - closeCalendarPanel(); - return true; - } - }); - - // FIXME: Problem is, that the element with the provided id does not - // exist yet in html. This is the same problem as with the context menu. - // Apply here the same fix (#11795) - Roles.getTextboxRole().setAriaControlsProperty(text.getElement(), - Id.of(calendar.getElement())); - Roles.getButtonRole().setAriaControlsProperty( - calendarToggle.getElement(), Id.of(calendar.getElement())); - - calendar.setSubmitListener(new SubmitListener() { - @Override - public void onSubmit() { - // Update internal value and send valuechange event if immediate - updateValue(calendar.getDate()); - - // Update text field (a must when not immediate). - buildDate(true); - - closeCalendarPanel(); - } - - @Override - public void onCancel() { - closeCalendarPanel(); - } - }); - - popup = new VOverlay(true, false); - popup.setOwner(this); - - FlowPanel wrapper = new FlowPanel(); - selectedDate = new Label(); - selectedDate.setStyleName(getStylePrimaryName() + "-selecteddate"); - AriaHelper.setVisibleForAssistiveDevicesOnly(selectedDate.getElement(), - true); - - Roles.getTextboxRole().setAriaLiveProperty(selectedDate.getElement(), - LiveValue.ASSERTIVE); - Roles.getTextboxRole().setAriaAtomicProperty(selectedDate.getElement(), - true); - wrapper.add(selectedDate); - wrapper.add(calendar); - - popup.setWidget(wrapper); - popup.addCloseHandler(this); - - DOM.setElementProperty(calendar.getElement(), "id", - "PID_VAADIN_POPUPCAL"); - - sinkEvents(Event.ONKEYDOWN); - - updateStyleNames(); - } - - @Override - protected void onAttach() { - super.onAttach(); - DOM.appendChild(RootPanel.get().getElement(), - descriptionForAssisitveDevicesElement); - } - - @Override - protected void onDetach() { - super.onDetach(); - descriptionForAssisitveDevicesElement.removeFromParent(); - closeCalendarPanel(); - } - - @SuppressWarnings("deprecation") - public void updateValue(Date newDate) { - Date currentDate = getCurrentDate(); - if (currentDate == null || newDate.getTime() != currentDate.getTime()) { - setCurrentDate((Date) newDate.clone()); - getClient().updateVariable(getId(), "year", - newDate.getYear() + 1900, false); - if (getCurrentResolution().compareTo(Resolution.YEAR) < 0) { - getClient().updateVariable(getId(), "month", - newDate.getMonth() + 1, false); - if (getCurrentResolution().compareTo(Resolution.MONTH) < 0) { - getClient().updateVariable(getId(), "day", - newDate.getDate(), false); - } - } - } - } - - /** - * Checks whether the text field is enabled. - * - * @see VPopupCalendar#setTextFieldEnabled(boolean) - * @return The current state of the text field. - */ - public boolean isTextFieldEnabled() { - return textFieldEnabled; - } - - /** - * Sets the state of the text field of this component. By default the text - * field is enabled. Disabling it causes only the button for date selection - * to be active, thus preventing the user from entering invalid dates. See - * {@link http://dev.vaadin.com/ticket/6790}. - * - * @param state - */ - public void setTextFieldEnabled(boolean textFieldEnabled) { - this.textFieldEnabled = textFieldEnabled; - updateTextFieldEnabled(); - } - - protected void updateTextFieldEnabled() { - boolean reallyEnabled = isEnabled() && isTextFieldEnabled(); - // IE has a non input disabled themeing that can not be overridden so we - // must fake the functionality using readonly and unselectable - if (BrowserInfo.get().isIE()) { - if (!reallyEnabled) { - text.getElement().setAttribute("unselectable", "on"); - text.getElement().setAttribute("readonly", ""); - text.setTabIndex(-2); - } else if (reallyEnabled - && text.getElement().hasAttribute("unselectable")) { - text.getElement().removeAttribute("unselectable"); - text.getElement().removeAttribute("readonly"); - text.setTabIndex(0); - } - } else { - text.setEnabled(reallyEnabled); - } - - if (reallyEnabled) { - calendarToggle.setTabIndex(-1); - Roles.getButtonRole() - .setAriaHiddenState(calendarToggle.getElement(), true); - } else { - calendarToggle.setTabIndex(0); - Roles.getButtonRole() - .setAriaHiddenState(calendarToggle.getElement(), false); - } - - handleAriaAttributes(); - } - - /** - * Set correct tab index for disabled text field in IE as the value set in - * setTextFieldEnabled(...) gets overridden in - * TextualDateConnection.updateFromUIDL(...) - * - * @since 7.3.1 - */ - public void setTextFieldTabIndex() { - if (BrowserInfo.get().isIE() && !textFieldEnabled) { - // index needs to be -2 because FocusWidget updates -1 to 0 onAttach - text.setTabIndex(-2); - } - } - - @Override - public void bindAriaCaption( - com.google.gwt.user.client.Element captionElement) { - if (captionElement == null) { - captionId = null; - } else { - captionId = captionElement.getId(); - } - - if (isTextFieldEnabled()) { - super.bindAriaCaption(captionElement); - } else { - AriaHelper.bindCaption(calendarToggle, captionElement); - } - - handleAriaAttributes(); - } - - private void handleAriaAttributes() { - Widget removeFromWidget; - Widget setForWidget; - - if (isTextFieldEnabled()) { - setForWidget = text; - removeFromWidget = calendarToggle; - } else { - setForWidget = calendarToggle; - removeFromWidget = text; - } - - Roles.getFormRole() - .removeAriaLabelledbyProperty(removeFromWidget.getElement()); - if (captionId == null) { - Roles.getFormRole() - .removeAriaLabelledbyProperty(setForWidget.getElement()); - } else { - Roles.getFormRole().setAriaLabelledbyProperty( - setForWidget.getElement(), Id.of(captionId)); - } + super(GWT.create(VDateCalendarPanel.class), DateResolution.YEAR); } - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String) - */ @Override - public void setStyleName(String style) { - super.setStyleName(style); - updateStyleNames(); + protected DateResolution[] doGetResolutions() { + return DateResolution.values(); } @Override - public void setStylePrimaryName(String style) { - removeStyleName(getStylePrimaryName() + "-popupcalendar"); - super.setStylePrimaryName(style); - updateStyleNames(); + public String resolutionAsString() { + return getResolutionVariable(getCurrentResolution()); } @Override - protected void updateStyleNames() { - super.updateStyleNames(); - if (getStylePrimaryName() != null && calendarToggle != null) { - addStyleName(getStylePrimaryName() + "-popupcalendar"); - calendarToggle.setStyleName(getStylePrimaryName() + "-button"); - popup.setStyleName(getStylePrimaryName() + "-popup"); - calendar.setStyleName(getStylePrimaryName() + "-calendarpanel"); - } - } - - /** - * Opens the calendar panel popup - */ - public void openCalendarPanel() { - - if (!open && !readonly && isEnabled()) { - open = true; - - if (getCurrentDate() != null) { - calendar.setDate((Date) getCurrentDate().clone()); - } else { - calendar.setDate(new Date()); - } - - // clear previous values - popup.setWidth(""); - popup.setHeight(""); - popup.setPopupPositionAndShow(new PopupPositionCallback()); - } else { - VConsole.error("Cannot reopen popup, it is already open!"); - } + public void setCurrentResolution(DateResolution resolution) { + super.setCurrentResolution( + resolution == null ? DateResolution.YEAR : resolution); } - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event - * .dom.client.ClickEvent) - */ - @Override - public void onClick(ClickEvent event) { - if (event.getSource() == calendarToggle && isEnabled()) { - if (!preventOpenPopupCalendar) { - openCalendarPanel(); - } - preventOpenPopupCalendar = false; + public static Date makeDate(Map<DateResolution, Integer> dateVaules) { + if (dateVaules.get(DateResolution.YEAR) == -1) { + return null; } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt - * .event.logical.shared.CloseEvent) - */ - @Override - public void onClose(CloseEvent<PopupPanel> event) { - if (event.getSource() == popup) { - buildDate(); - if (!BrowserInfo.get().isTouchDevice() && textFieldEnabled) { - /* - * Move focus to textbox, unless on touch device (avoids opening - * virtual keyboard) or if textField is disabled. - */ - focus(); - } - - open = false; - - if (cursorOverCalendarToggleButton - && !toggleButtonClosesWithGuarantee) { - preventOpenPopupCalendar = true; - } - - toggleButtonClosesWithGuarantee = false; + Date date = new Date(2000 - 1900, 0, 1); + int year = dateVaules.get(DateResolution.YEAR); + if (year >= 0) { + date.setYear(year - 1900); } - } - - /** - * Sets focus to Calendar panel. - * - * @param focus - */ - public void setFocus(boolean focus) { - calendar.setFocus(focus); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - updateTextFieldEnabled(); - calendarToggle.setEnabled(enabled); - Roles.getButtonRole().setAriaDisabledState(calendarToggle.getElement(), - !enabled); - } - - /** - * Sets the content of a special field for assistive devices, so that they - * can recognize the change and inform the user (reading out in case of - * screen reader) - * - * @param selectedDate - * Date that is currently selected - */ - public void setFocusedDate(Date selectedDate) { - this.selectedDate.setText(DateTimeFormat.getFormat("dd, MMMM, yyyy") - .format(selectedDate)); - } - - /** - * For internal use only. May be removed or replaced in the future. - * - * @see com.vaadin.client.ui.VTextualDate#buildDate() - */ - @Override - public void buildDate() { - // Save previous value - String previousValue = getText(); - super.buildDate(); - - // Restore previous value if the input could not be parsed - if (!parsable) { - setText(previousValue); + int month = dateVaules.get(DateResolution.MONTH); + if (month >= 0) { + date.setMonth(month - 1); } - updateTextFieldEnabled(); - } - - /** - * Update the text field contents from the date. See {@link #buildDate()}. - * - * @param forceValid - * true to force the text field to be updated, false to only - * update if the parsable flag is true. - */ - protected void buildDate(boolean forceValid) { - if (forceValid) { - parsable = true; + int day = dateVaules.get(DateResolution.DAY); + if (day >= 0) { + date.setDate(day); } - buildDate(); + return date; } - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google - * .gwt.user.client.Event) - */ @Override - public void onBrowserEvent(com.google.gwt.user.client.Event event) { - super.onBrowserEvent(event); - if (DOM.eventGetType(event) == Event.ONKEYDOWN - && event.getKeyCode() == getOpenCalenderPanelKey()) { - openCalendarPanel(); - event.preventDefault(); - } - } - - /** - * Get the key code that opens the calendar panel. By default it is the down - * key but you can override this to be whatever you like - * - * @return - */ - protected int getOpenCalenderPanelKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Closes the open popup panel - */ - public void closeCalendarPanel() { - if (open) { - toggleButtonClosesWithGuarantee = true; - popup.hide(true); - } + public boolean isYear(DateResolution resolution) { + return DateResolution.YEAR.equals(resolution); } - private final String CALENDAR_TOGGLE_ID = "popupButton"; - @Override - public com.google.gwt.user.client.Element getSubPartElement( - String subPart) { - if (subPart.equals(CALENDAR_TOGGLE_ID)) { - return calendarToggle.getElement(); - } - - return super.getSubPartElement(subPart); + protected Date getDate(Map<DateResolution, Integer> dateValues) { + return makeDate(dateValues); } @Override - public String getSubPartName( - com.google.gwt.user.client.Element subElement) { - if (calendarToggle.getElement().isOrHasChild(subElement)) { - return CALENDAR_TOGGLE_ID; - } - - return super.getSubPartName(subElement); - } - - /** - * Set a description that explains the usage of the Widget for users of - * assistive devices. - * - * @param descriptionForAssistiveDevices - * String with the description - */ - public void setDescriptionForAssistiveDevices( - String descriptionForAssistiveDevices) { - descriptionForAssisitveDevicesElement - .setInnerText(descriptionForAssistiveDevices); - } - - /** - * Get the description that explains the usage of the Widget for users of - * assistive devices. - * - * @return String with the description - */ - public String getDescriptionForAssistiveDevices() { - return descriptionForAssisitveDevicesElement.getInnerText(); - } - - /** - * Sets the start range for this component. The start range is inclusive, - * and it depends on the current resolution, what is considered inside the - * range. - * - * @param startDate - * - the allowed range's start date - */ - public void setRangeStart(Date rangeStart) { - calendar.setRangeStart(rangeStart); - } - - /** - * Sets the end range for this component. The end range is inclusive, and it - * depends on the current resolution, what is considered inside the range. - * - * @param endDate - * - the allowed range's end date - */ - public void setRangeEnd(Date rangeEnd) { - calendar.setRangeEnd(rangeEnd); - } - - private class PopupPositionCallback implements PositionCallback { - - @Override - public void setPosition(int offsetWidth, int offsetHeight) { - final int width = offsetWidth; - final int height = offsetHeight; - final int browserWindowWidth = Window.getClientWidth() - + Window.getScrollLeft(); - final int windowHeight = Window.getClientHeight() - + Window.getScrollTop(); - int left = calendarToggle.getAbsoluteLeft(); - - // Add a little extra space to the right to avoid - // problems with IE7 scrollbars and to make it look - // nicer. - int extraSpace = 30; - - boolean overflow = left + width + extraSpace > browserWindowWidth; - if (overflow) { - // Part of the popup is outside the browser window - // (to the right) - left = browserWindowWidth - width - extraSpace; - } - - int top = calendarToggle.getAbsoluteTop(); - int extraHeight = 2; - boolean verticallyRepositioned = false; - ComputedStyle style = new ComputedStyle(popup.getElement()); - int[] margins = style.getMargin(); - int desiredPopupBottom = top + height - + calendarToggle.getOffsetHeight() + margins[0] - + margins[2]; - - if (desiredPopupBottom > windowHeight) { - int updatedLeft = left; - left = getLeftPosition(left, width, style, overflow); - - // if position has not been changed then it means there is no - // space to make popup fully visible - if (updatedLeft == left) { - // let's try to show popup on the top of the field - int updatedTop = top - extraHeight - height - margins[0] - - margins[2]; - verticallyRepositioned = updatedTop >= 0; - if (verticallyRepositioned) { - top = updatedTop; - } - } - // Part of the popup is outside the browser window - // (below) - if (!verticallyRepositioned) { - verticallyRepositioned = true; - top = windowHeight - height - extraSpace + extraHeight; - } - } - if (verticallyRepositioned) { - popup.setPopupPosition(left, top); - } else { - popup.setPopupPosition(left, - top + calendarToggle.getOffsetHeight() + extraHeight); - } - doSetFocus(); - } - - private int getLeftPosition(int left, int width, ComputedStyle style, - boolean overflow) { - if (positionRightSide()) { - // Show to the right of the popup button unless we - // are in the lower right corner of the screen - if (overflow) { - return left; - } else { - return left + calendarToggle.getOffsetWidth(); - } - } else { - int[] margins = style.getMargin(); - int desiredLeftPosition = calendarToggle.getAbsoluteLeft() - - width - margins[1] - margins[3]; - if (desiredLeftPosition >= 0) { - return desiredLeftPosition; - } else { - return left; - } - } - } - - private boolean positionRightSide() { - int buttonRightSide = calendarToggle.getAbsoluteLeft() - + calendarToggle.getOffsetWidth(); - int textRightSide = text.getAbsoluteLeft() + text.getOffsetWidth(); - return buttonRightSide >= textRightSide; - } - - private void doSetFocus() { - /* - * We have to wait a while before focusing since the popup needs to - * be opened before we can focus - */ - Timer focusTimer = new Timer() { - @Override - public void run() { - setFocus(true); - } - }; - - focusTimer.schedule(100); + protected void updateDateVariables() { + super.updateDateVariables(); + // Update variables + // (only the smallest defining resolution needs to be + // immediate) + Date currentDate = getDate(); + if (getCurrentResolution().compareTo(DateResolution.MONTH) <= 0) { + getClient().updateVariable(getId(), + getResolutionVariable(DateResolution.MONTH), + currentDate != null ? currentDate.getMonth() + 1 : -1, + getCurrentResolution() == DateResolution.MONTH); + } + if (getCurrentResolution().compareTo(DateResolution.DAY) <= 0) { + getClient().updateVariable(getId(), + getResolutionVariable(DateResolution.DAY), + currentDate != null ? currentDate.getDate() : -1, + getCurrentResolution() == DateResolution.DAY); } } diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java index 5e0fe7cc2e..c851c13679 100644 --- a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java @@ -15,6 +15,12 @@ */ package com.vaadin.client.ui.datefield; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import com.vaadin.client.ApplicationConnection; import com.vaadin.client.LocaleNotLoadedException; import com.vaadin.client.Paintable; @@ -22,12 +28,10 @@ import com.vaadin.client.UIDL; import com.vaadin.client.VConsole; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.VDateField; -import com.vaadin.client.ui.VTextualDate; import com.vaadin.shared.ui.datefield.DateFieldConstants; -import com.vaadin.shared.ui.datefield.Resolution; -public class AbstractDateFieldConnector extends AbstractFieldConnector - implements Paintable { +public abstract class AbstractDateFieldConnector<R extends Enum<R>> + extends AbstractFieldConnector implements Paintable { @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { @@ -62,46 +66,42 @@ public class AbstractDateFieldConnector extends AbstractFieldConnector uidl.getBooleanAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS) && getWidget().dts.getFirstDayOfWeek() == 1); - Resolution newResolution; - if (uidl.hasVariable("day")) { - newResolution = Resolution.DAY; - } else if (uidl.hasVariable("month")) { - newResolution = Resolution.MONTH; - } else { - newResolution = Resolution.YEAR; - } - // Remove old stylename that indicates current resolution - setWidgetStyleName( - getWidget().getStylePrimaryName() + "-" + VDateField - .resolutionToString(getWidget().getCurrentResolution()), - false); + setWidgetStyleName(getWidget().getStylePrimaryName() + "-" + + getWidget().resolutionAsString(), false); - getWidget().setCurrentResolution(newResolution); + updateResolution(uidl); // Add stylename that indicates current resolution - setWidgetStyleName( - getWidget().getStylePrimaryName() + "-" + VDateField - .resolutionToString(getWidget().getCurrentResolution()), - true); - - final Resolution resolution = getWidget().getCurrentResolution(); - final int year = uidl.getIntVariable("year"); - final int month = resolution.compareTo(Resolution.MONTH) <= 0 - ? uidl.getIntVariable("month") : -1; - final int day = resolution.compareTo(Resolution.DAY) <= 0 - ? uidl.getIntVariable("day") : -1; - - // Construct new date for this datefield (only if not null) - if (year > -1) { - getWidget().setCurrentDate(VTextualDate.getTime(year, month, day)); - } else { - getWidget().setCurrentDate(null); - } + setWidgetStyleName(getWidget().getStylePrimaryName() + "-" + + getWidget().resolutionAsString(), true); + + getWidget().setCurrentDate(getTimeValues(uidl)); } + private void updateResolution(UIDL uidl) { + Optional<R> newResolution = getWidget().getResolutions().filter( + res -> uidl.hasVariable(getWidget().getResolutionVariable(res))) + .findFirst(); + + getWidget().setCurrentResolution(newResolution.orElse(null)); + } + + protected Map<R, Integer> getTimeValues(UIDL uidl) { + Stream<R> resolutions = getWidget().getResolutions(); + R resolution = getWidget().getCurrentResolution(); + return resolutions + .collect(Collectors.toMap(Function.identity(), + res -> (resolution.compareTo(res) <= 0) + ? uidl.getIntVariable( + getWidget().getResolutionVariable(res)) + : -1)); + } + + @SuppressWarnings("unchecked") @Override - public VDateField getWidget() { - return (VDateField) super.getWidget(); + public VDateField<R> getWidget() { + return (VDateField<R>) super.getWidget(); } + } diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java new file mode 100644 index 0000000000..5252c40d77 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java @@ -0,0 +1,127 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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. + */ +package com.vaadin.client.ui.datefield; + +import java.util.Date; + +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.UIDL; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.VAbstractCalendarPanel.FocusChangeListener; +import com.vaadin.client.ui.VAbstractDateFieldCalendar; +import com.vaadin.shared.ui.datefield.InlineDateFieldState; + +/** + * Base class for inline data field connector. + * + * @author Vaadin Ltd + * + * @param <R> + * the resolution type which the field is based on (day, month, ...) + */ +public abstract class AbstractInlineDateFieldConnector<R extends Enum<R>> + extends AbstractDateFieldConnector<R> { + + @Override + @SuppressWarnings("deprecation") + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().calendarPanel + .setShowISOWeekNumbers(getWidget().isShowISOWeekNumbers()); + getWidget().calendarPanel + .setDateTimeService(getWidget().getDateTimeService()); + getWidget().calendarPanel + .setResolution(getWidget().getCurrentResolution()); + Date currentDate = getWidget().getCurrentDate(); + if (currentDate != null) { + getWidget().calendarPanel.setDate(new Date(currentDate.getTime())); + } else { + getWidget().calendarPanel.setDate(null); + } + + updateListeners(); + + // Update possible changes + getWidget().calendarPanel.renderCalendar(); + } + + /** + * Updates listeners registered (or register them) for the widget based on + * the current resolution. + * <p> + * Subclasses may override this method to keep the common logic inside the + * {@link #updateFromUIDL(UIDL, ApplicationConnection)} method as is and + * customizing only listeners logic. + */ + protected void updateListeners() { + if (isResolutionMonthOrHigher()) { + getWidget().calendarPanel + .setFocusChangeListener(new FocusChangeListener() { + @Override + public void focusChanged(Date date) { + Date date2 = new Date(); + if (getWidget().calendarPanel.getDate() != null) { + date2.setTime(getWidget().calendarPanel + .getDate().getTime()); + } + /* + * Update the value of calendarPanel + */ + date2.setYear(date.getYear()); + date2.setMonth(date.getMonth()); + getWidget().calendarPanel.setDate(date2); + /* + * Then update the value from panel to server + */ + getWidget().updateValueFromPanel(); + } + }); + } else { + getWidget().calendarPanel.setFocusChangeListener(null); + } + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + getWidget().setTabIndex(getState().tabIndex); + getWidget().calendarPanel.setRangeStart(getState().rangeStart); + getWidget().calendarPanel.setRangeEnd(getState().rangeEnd); + } + + @Override + public VAbstractDateFieldCalendar<R> getWidget() { + return (VAbstractDateFieldCalendar<R>) super.getWidget(); + } + + @Override + public InlineDateFieldState getState() { + return (InlineDateFieldState) super.getState(); + } + + /** + * Returns {@code true} is the current resolution of the widget is month or + * less specific (e.g. month, year, quarter, etc). + * + * @return {@code true} if the current resolution is above month + */ + protected abstract boolean isResolutionMonthOrHigher(); + +} diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java new file mode 100644 index 0000000000..b9448862e7 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2016 Vaadin Ltd. + * + * 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. + */ + +package com.vaadin.client.ui.datefield; + +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.UIDL; +import com.vaadin.client.ui.VAbstractTextualDate; +import com.vaadin.shared.ui.datefield.AbstractTextualDateFieldState; + +public abstract class AbstractTextualDateConnector<R extends Enum<R>> + extends AbstractDateFieldConnector<R> { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + R origRes = getWidget().getCurrentResolution(); + String oldLocale = getWidget().getCurrentLocale(); + super.updateFromUIDL(uidl, client); + if (origRes != getWidget().getCurrentResolution() + || oldLocale != getWidget().getCurrentLocale()) { + // force recreating format string + getWidget().formatStr = null; + } + if (uidl.hasAttribute("format")) { + getWidget().formatStr = uidl.getStringAttribute("format"); + } + + getWidget().lenient = !uidl.getBooleanAttribute("strict"); + + getWidget().buildDate(); + // not a FocusWidget -> needs own tabindex handling + getWidget().text.setTabIndex(getState().tabIndex); + + if (getWidget().isReadonly()) { + getWidget().text.addStyleDependentName("readonly"); + } else { + getWidget().text.removeStyleDependentName("readonly"); + } + + } + + @Override + public VAbstractTextualDate<R> getWidget() { + return (VAbstractTextualDate<R>) super.getWidget(); + } + + @Override + public AbstractTextualDateFieldState getState() { + return (AbstractTextualDateFieldState) super.getState(); + } + +} diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/DateFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/DateFieldConnector.java index 7161283176..35a9420ada 100644 --- a/client/src/main/java/com/vaadin/client/ui/datefield/DateFieldConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/datefield/DateFieldConnector.java @@ -13,124 +13,25 @@ * License for the specific language governing permissions and limitations under * the License. */ - package com.vaadin.client.ui.datefield; -import java.util.Date; - -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.user.client.ui.PopupPanel; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.UIDL; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.VCalendarPanel.FocusChangeListener; import com.vaadin.client.ui.VPopupCalendar; import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.datefield.DateFieldState; -import com.vaadin.shared.ui.datefield.Resolution; -import com.vaadin.ui.AbstractDateField; - -@Connect(AbstractDateField.class) -public class DateFieldConnector extends TextualDateConnector { - - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.AbstractConnector#init() - */ - @Override - protected void init() { - getWidget().popup.addCloseHandler(new CloseHandler<PopupPanel>() { +import com.vaadin.shared.ui.datefield.DateResolution; +import com.vaadin.shared.ui.datefield.LocalDateFieldState; +import com.vaadin.ui.AbstractLocalDateField; - @Override - public void onClose(CloseEvent<PopupPanel> event) { - /* - * FIXME This is a hack so we do not have to rewrite half of the - * datefield so values are not sent while selecting a date - * (#6252). - * - * The datefield will now only set the date UIDL variables while - * the user is selecting year/month/date/time and not send them - * directly. Only when the user closes the popup (by clicking on - * a day/enter/clicking outside of popup) then the new value is - * communicated to the server. - */ - getConnection().getServerRpcQueue().flush(); - } - }); - } +/** + * @author Vaadin Ltd + * + */ +@Connect(AbstractLocalDateField.class) +public class DateFieldConnector extends TextualDateConnector<DateResolution> { - /* - * (non-Javadoc) - * - * @see com.vaadin.client.ui.VTextualDate#updateFromUIDL(com.vaadin - * .client.UIDL, com.vaadin.client.ApplicationConnection) - */ @Override - @SuppressWarnings("deprecation") - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - String oldLocale = getWidget().getCurrentLocale(); - - getWidget().parsable = uidl.getBooleanAttribute("parsable"); - - super.updateFromUIDL(uidl, client); - - getWidget().calendar - .setDateTimeService(getWidget().getDateTimeService()); - getWidget().calendar - .setShowISOWeekNumbers(getWidget().isShowISOWeekNumbers()); - if (getWidget().calendar.getResolution() != getWidget() - .getCurrentResolution()) { - boolean hasSelectedDate = false; - getWidget().calendar - .setResolution(getWidget().getCurrentResolution()); - if (getWidget().calendar.getDate() != null - && getWidget().getCurrentDate() != null) { - hasSelectedDate = true; - getWidget().calendar - .setDate((Date) getWidget().getCurrentDate().clone()); - } - // force re-render when changing resolution only - getWidget().calendar.renderCalendar(hasSelectedDate); - } - - // Force re-render of calendar if locale has changed (#12153) - if (!getWidget().getCurrentLocale().equals(oldLocale)) { - getWidget().calendar.renderCalendar(); - } - - if (getWidget().getCurrentResolution() - .compareTo(Resolution.MONTH) >= 0) { - getWidget().calendar - .setFocusChangeListener(new FocusChangeListener() { - @Override - public void focusChanged(Date date) { - - getWidget().updateValue(date); - getWidget().buildDate(); - Date date2 = getWidget().calendar.getDate(); - date2.setYear(date.getYear()); - date2.setMonth(date.getMonth()); - } - }); - } else { - getWidget().calendar.setFocusChangeListener(null); - } - - if (getWidget().isReadonly()) { - getWidget().calendarToggle.addStyleName( - VPopupCalendar.CLASSNAME + "-button-readonly"); - } else { - getWidget().calendarToggle.removeStyleName( - VPopupCalendar.CLASSNAME + "-button-readonly"); - } - - getWidget().setDescriptionForAssistiveDevices( - getState().descriptionForAssistiveDevices); - - getWidget().setTextFieldTabIndex(); + protected boolean isResolutionAboveMonth() { + return getWidget().getCurrentResolution() + .compareTo(DateResolution.MONTH) >= 0; } @Override @@ -139,50 +40,7 @@ public class DateFieldConnector extends TextualDateConnector { } @Override - public DateFieldState getState() { - return (DateFieldState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - getWidget().setTextFieldEnabled(getState().textFieldEnabled); - getWidget().setRangeStart(nullSafeDateClone(getState().rangeStart)); - getWidget().setRangeEnd(nullSafeDateClone(getState().rangeEnd)); - } - - private Date nullSafeDateClone(Date date) { - if (date == null) { - return null; - } else { - return (Date) date.clone(); - } - } - - @Override - protected void setWidgetStyleName(String styleName, boolean add) { - super.setWidgetStyleName(styleName, add); - - // update the style change to popup calendar widget - getWidget().popup.setStyleName(styleName, add); - } - - @Override - protected void setWidgetStyleNameWithPrefix(String prefix, String styleName, - boolean add) { - super.setWidgetStyleNameWithPrefix(prefix, styleName, add); - - // update the style change to popup calendar widget with the correct - // prefix - if (!styleName.startsWith("-")) { - getWidget().popup.setStyleName( - getWidget().getStylePrimaryName() + "-popup-" + styleName, - add); - } else { - getWidget().popup.setStyleName( - getWidget().getStylePrimaryName() + "-popup" + styleName, - add); - } + public LocalDateFieldState getState() { + return (LocalDateFieldState) super.getState(); } - } diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java index add25ae99b..7339191ee9 100644 --- a/client/src/main/java/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java @@ -15,88 +15,27 @@ */ package com.vaadin.client.ui.datefield; -import java.util.Date; - -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.UIDL; -import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.ui.VCalendarPanel.FocusChangeListener; import com.vaadin.client.ui.VDateFieldCalendar; import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.datefield.InlineDateFieldState; -import com.vaadin.shared.ui.datefield.Resolution; +import com.vaadin.shared.ui.datefield.DateResolution; import com.vaadin.ui.InlineDateField; +/** + * @author Vaadin Ltd + * + */ @Connect(InlineDateField.class) -public class InlineDateFieldConnector extends AbstractDateFieldConnector { - - @Override - @SuppressWarnings("deprecation") - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - super.updateFromUIDL(uidl, client); - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().calendarPanel - .setShowISOWeekNumbers(getWidget().isShowISOWeekNumbers()); - getWidget().calendarPanel - .setDateTimeService(getWidget().getDateTimeService()); - getWidget().calendarPanel - .setResolution(getWidget().getCurrentResolution()); - Date currentDate = getWidget().getCurrentDate(); - if (currentDate != null) { - getWidget().calendarPanel.setDate(new Date(currentDate.getTime())); - } else { - getWidget().calendarPanel.setDate(null); - } - - if (getWidget().getCurrentResolution() - .compareTo(Resolution.MONTH) >= 0) { - getWidget().calendarPanel - .setFocusChangeListener(new FocusChangeListener() { - @Override - public void focusChanged(Date date) { - Date date2 = new Date(); - if (getWidget().calendarPanel.getDate() != null) { - date2.setTime(getWidget().calendarPanel - .getDate().getTime()); - } - /* - * Update the value of calendarPanel - */ - date2.setYear(date.getYear()); - date2.setMonth(date.getMonth()); - getWidget().calendarPanel.setDate(date2); - /* - * Then update the value from panel to server - */ - getWidget().updateValueFromPanel(); - } - }); - } else { - getWidget().calendarPanel.setFocusChangeListener(null); - } - - // Update possible changes - getWidget().calendarPanel.renderCalendar(); - } +public class InlineDateFieldConnector + extends AbstractInlineDateFieldConnector<DateResolution> { @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - getWidget().setTabIndex(getState().tabIndex); - getWidget().calendarPanel.setRangeStart(getState().rangeStart); - getWidget().calendarPanel.setRangeEnd(getState().rangeEnd); + protected boolean isResolutionMonthOrHigher() { + return getWidget().getCurrentResolution() + .compareTo(DateResolution.MONTH) >= 0; } @Override public VDateFieldCalendar getWidget() { return (VDateFieldCalendar) super.getWidget(); } - - @Override - public InlineDateFieldState getState() { - return (InlineDateFieldState) super.getState(); - } } diff --git a/client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java b/client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java index bf77d3b25f..2179c7a9ca 100644 --- a/client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java @@ -16,49 +16,172 @@ package com.vaadin.client.ui.datefield; +import java.util.Date; + +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.user.client.ui.PopupPanel; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.UIDL; -import com.vaadin.client.ui.VTextualDate; -import com.vaadin.shared.ui.datefield.Resolution; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.VAbstractCalendarPanel.FocusChangeListener; +import com.vaadin.client.ui.VAbstractPopupCalendar; import com.vaadin.shared.ui.datefield.TextualDateFieldState; -public class TextualDateConnector extends AbstractDateFieldConnector { +public abstract class TextualDateConnector<R extends Enum<R>> + extends AbstractTextualDateConnector<R> { + + @Override + protected void init() { + getWidget().popup.addCloseHandler(new CloseHandler<PopupPanel>() { + + @Override + public void onClose(CloseEvent<PopupPanel> event) { + /* + * FIXME This is a hack so we do not have to rewrite half of the + * datefield so values are not sent while selecting a date + * (#6252). + * + * The datefield will now only set the date UIDL variables while + * the user is selecting year/month/date/time and not send them + * directly. Only when the user closes the popup (by clicking on + * a day/enter/clicking outside of popup) then the new value is + * communicated to the server. + */ + getConnection().getServerRpcQueue().flush(); + } + }); + } @Override + @SuppressWarnings("deprecation") public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - Resolution origRes = getWidget().getCurrentResolution(); + String oldLocale = getWidget().getCurrentLocale(); + + getWidget().parsable = uidl.getBooleanAttribute("parsable"); + super.updateFromUIDL(uidl, client); - if (origRes != getWidget().getCurrentResolution() - || oldLocale != getWidget().getCurrentLocale()) { - // force recreating format string - getWidget().formatStr = null; - } - if (uidl.hasAttribute("format")) { - getWidget().formatStr = uidl.getStringAttribute("format"); + + getWidget().calendar + .setDateTimeService(getWidget().getDateTimeService()); + getWidget().calendar + .setShowISOWeekNumbers(getWidget().isShowISOWeekNumbers()); + if (getWidget().calendar.getResolution() != getWidget() + .getCurrentResolution()) { + boolean hasSelectedDate = false; + getWidget().calendar + .setResolution(getWidget().getCurrentResolution()); + if (getWidget().calendar.getDate() != null + && getWidget().getCurrentDate() != null) { + hasSelectedDate = true; + getWidget().calendar + .setDate((Date) getWidget().getCurrentDate().clone()); + } + // force re-render when changing resolution only + getWidget().calendar.renderCalendar(hasSelectedDate); } - getWidget().lenient = !uidl.getBooleanAttribute("strict"); + // Force re-render of calendar if locale has changed (#12153) + if (!getWidget().getCurrentLocale().equals(oldLocale)) { + getWidget().calendar.renderCalendar(); + } - getWidget().buildDate(); - // not a FocusWidget -> needs own tabindex handling - getWidget().text.setTabIndex(getState().tabIndex); + updateListeners(); if (getWidget().isReadonly()) { - getWidget().text.addStyleDependentName("readonly"); + getWidget().calendarToggle.addStyleName( + VAbstractPopupCalendar.CLASSNAME + "-button-readonly"); } else { - getWidget().text.removeStyleDependentName("readonly"); + getWidget().calendarToggle.removeStyleName( + VAbstractPopupCalendar.CLASSNAME + "-button-readonly"); } + getWidget().setDescriptionForAssistiveDevices( + getState().descriptionForAssistiveDevices); + + getWidget().setTextFieldTabIndex(); } + /** + * Updates listeners registered (or register them) for the widget based on + * the current resolution. + * <p> + * Subclasses may override this method to keep the common logic inside the + * {@link #updateFromUIDL(UIDL, ApplicationConnection)} method as is and + * customizing only listeners logic. + */ + protected void updateListeners() { + if (isResolutionAboveMonth()) { + getWidget().calendar + .setFocusChangeListener(new FocusChangeListener() { + @Override + public void focusChanged(Date date) { + + getWidget().updateValue(date); + getWidget().buildDate(); + Date date2 = getWidget().calendar.getDate(); + date2.setYear(date.getYear()); + date2.setMonth(date.getMonth()); + } + }); + } else { + getWidget().calendar.setFocusChangeListener(null); + } + } + + protected abstract boolean isResolutionAboveMonth(); + @Override - public VTextualDate getWidget() { - return (VTextualDate) super.getWidget(); + public VAbstractPopupCalendar<R> getWidget() { + return (VAbstractPopupCalendar<R>) super.getWidget(); } @Override public TextualDateFieldState getState() { return (TextualDateFieldState) super.getState(); } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + getWidget().setTextFieldEnabled(getState().textFieldEnabled); + getWidget().setRangeStart(nullSafeDateClone(getState().rangeStart)); + getWidget().setRangeEnd(nullSafeDateClone(getState().rangeEnd)); + } + + private Date nullSafeDateClone(Date date) { + if (date == null) { + return null; + } else { + return (Date) date.clone(); + } + } + + @Override + protected void setWidgetStyleName(String styleName, boolean add) { + super.setWidgetStyleName(styleName, add); + + // update the style change to popup calendar widget + getWidget().popup.setStyleName(styleName, add); + } + + @Override + protected void setWidgetStyleNameWithPrefix(String prefix, String styleName, + boolean add) { + super.setWidgetStyleNameWithPrefix(prefix, styleName, add); + + // update the style change to popup calendar widget with the correct + // prefix + if (!styleName.startsWith("-")) { + getWidget().popup.setStyleName( + getWidget().getStylePrimaryName() + "-popup-" + styleName, + add); + } else { + getWidget().popup.setStyleName( + getWidget().getStylePrimaryName() + "-popup" + styleName, + add); + } + } + } |