Browse Source

Convert AbstractDateField not to be a LegacyComponent (#10148)

tags/8.2.0.alpha3
Ahmed Ashour 6 years ago
parent
commit
dd5597d901
23 changed files with 691 additions and 602 deletions
  1. 1
    0
      all/src/main/templates/release-notes.html
  2. 1
    2
      client/src/main/java/com/vaadin/client/ApplicationConnection.java
  3. 14
    15
      client/src/main/java/com/vaadin/client/DateTimeService.java
  4. 1
    1
      client/src/main/java/com/vaadin/client/LocaleService.java
  5. 8
    4
      client/src/main/java/com/vaadin/client/ui/CalendarEntry.java
  6. 0
    3
      client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java
  7. 24
    18
      client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java
  8. 23
    15
      client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java
  9. 54
    3
      client/src/main/java/com/vaadin/client/ui/VDateField.java
  10. 10
    13
      client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java
  11. 16
    12
      client/src/main/java/com/vaadin/client/ui/VDateTimeCalendarPanel.java
  12. 24
    29
      client/src/main/java/com/vaadin/client/ui/VDateTimeFieldCalendar.java
  13. 15
    17
      client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java
  14. 50
    59
      client/src/main/java/com/vaadin/client/ui/VPopupTimeCalendar.java
  15. 77
    70
      client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java
  16. 18
    27
      client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java
  17. 33
    30
      client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java
  18. 63
    72
      client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java
  19. 140
    174
      server/src/main/java/com/vaadin/ui/AbstractDateField.java
  20. 12
    12
      server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java
  21. 55
    0
      shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldServerRpc.java
  22. 52
    0
      shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java
  23. 0
    26
      shared/src/main/java/com/vaadin/shared/ui/datefield/DateFieldConstants.java

+ 1
- 0
all/src/main/templates/release-notes.html View File

@@ -123,6 +123,7 @@
<li><tt>Notification</tt> method <tt>show</tt> returns <tt>Notification</tt>, instead of <tt>void</tt>.</li>
<li><tt>SharedState</tt> field <tt>registeredEventListeners</tt> is a <tt>Map</tt> instead of <tt>Set</tt>.</li>
<li>The client side <tt>SelectionModel</tt> interface has a new method <tt>isMultiSelectionAllowed</tt>.</li>
<li><tt>AbstractDateField</tt> is not a <tt>LegacyComponent</tt> anymore.</li>

<h2>For incompatible or behavior-altering changes in 8.1, please see <a href="https://vaadin.com/download/release/8.1/8.1.0/release-notes.html#incompatible">8.1 release notes</a></h2>

+ 1
- 2
client/src/main/java/com/vaadin/client/ApplicationConnection.java View File

@@ -1358,8 +1358,7 @@ public class ApplicationConnection implements HasHandlers {
return false;
}

return hasEventListeners(getConnectorMap().getConnector(widget),
eventIdentifier);
return hasEventListeners(connector, eventIdentifier);
}

LayoutManager getLayoutManager() {

+ 14
- 15
client/src/main/java/com/vaadin/client/DateTimeService.java View File

@@ -35,7 +35,7 @@ import com.vaadin.shared.ui.datefield.DateResolution;
@SuppressWarnings("deprecation")
public class DateTimeService {

private String currentLocale;
private String locale;

private static int[] maxDaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30,
31, 30, 31 };
@@ -44,7 +44,7 @@ public class DateTimeService {
* Creates a new date time service with the application default locale.
*/
public DateTimeService() {
currentLocale = LocaleService.getDefaultLocale();
locale = LocaleService.getDefaultLocale();
}

/**
@@ -59,20 +59,19 @@ public class DateTimeService {
}

public void setLocale(String locale) throws LocaleNotLoadedException {
if (LocaleService.getAvailableLocales().contains(locale)) {
currentLocale = locale;
} else {
if (!LocaleService.getAvailableLocales().contains(locale)) {
throw new LocaleNotLoadedException(locale);
}
this.locale = locale;
}

public String getLocale() {
return currentLocale;
return locale;
}

public String getMonth(int month) {
try {
return LocaleService.getMonthNames(currentLocale)[month];
return LocaleService.getMonthNames(locale)[month];
} catch (final LocaleNotLoadedException e) {
getLogger().log(Level.SEVERE, "Error in getMonth", e);
return null;
@@ -81,7 +80,7 @@ public class DateTimeService {

public String getShortMonth(int month) {
try {
return LocaleService.getShortMonthNames(currentLocale)[month];
return LocaleService.getShortMonthNames(locale)[month];
} catch (final LocaleNotLoadedException e) {
getLogger().log(Level.SEVERE, "Error in getShortMonth", e);
return null;
@@ -90,7 +89,7 @@ public class DateTimeService {

public String getDay(int day) {
try {
return LocaleService.getDayNames(currentLocale)[day];
return LocaleService.getDayNames(locale)[day];
} catch (final LocaleNotLoadedException e) {
getLogger().log(Level.SEVERE, "Error in getDay", e);
return null;
@@ -99,7 +98,7 @@ public class DateTimeService {

public String getShortDay(int day) {
try {
return LocaleService.getShortDayNames(currentLocale)[day];
return LocaleService.getShortDayNames(locale)[day];
} catch (final LocaleNotLoadedException e) {
getLogger().log(Level.SEVERE, "Error in getShortDay", e);
return null;
@@ -108,7 +107,7 @@ public class DateTimeService {

public int getFirstDayOfWeek() {
try {
return LocaleService.getFirstDayOfWeek(currentLocale);
return LocaleService.getFirstDayOfWeek(locale);
} catch (final LocaleNotLoadedException e) {
getLogger().log(Level.SEVERE, "Error in getFirstDayOfWeek", e);
return 0;
@@ -117,7 +116,7 @@ public class DateTimeService {

public boolean isTwelveHourClock() {
try {
return LocaleService.isTwelveHourClock(currentLocale);
return LocaleService.isTwelveHourClock(locale);
} catch (final LocaleNotLoadedException e) {
getLogger().log(Level.SEVERE, "Error in isTwelveHourClock", e);
return false;
@@ -126,7 +125,7 @@ public class DateTimeService {

public String getClockDelimeter() {
try {
return LocaleService.getClockDelimiter(currentLocale);
return LocaleService.getClockDelimiter(locale);
} catch (final LocaleNotLoadedException e) {
getLogger().log(Level.SEVERE, "Error in getClockDelimiter", e);
return ":";
@@ -137,7 +136,7 @@ public class DateTimeService {

public String[] getAmPmStrings() {
try {
return LocaleService.getAmPmStrings(currentLocale);
return LocaleService.getAmPmStrings(locale);
} catch (final LocaleNotLoadedException e) {
// TODO can this practically even happen? Should die instead?
getLogger().log(Level.SEVERE,
@@ -151,7 +150,7 @@ public class DateTimeService {
date.getMonth(), 1);
int firstDay;
try {
firstDay = LocaleService.getFirstDayOfWeek(currentLocale);
firstDay = LocaleService.getFirstDayOfWeek(locale);
} catch (final LocaleNotLoadedException e) {
getLogger().log(Level.SEVERE, "Locale not loaded, using fallback 0",
e);

+ 1
- 1
client/src/main/java/com/vaadin/client/LocaleService.java View File

@@ -42,7 +42,7 @@ public class LocaleService {
if (cache.containsKey(key)) {
cache.remove(key);
}
getLogger().fine("Received locale data for " + localeData.name);
getLogger().fine("Received locale data for " + key);
cache.put(key, localeData);
if (cache.size() == 1) {
setDefaultLocale(key);

+ 8
- 4
client/src/main/java/com/vaadin/client/ui/CalendarEntry.java View File

@@ -113,10 +113,10 @@ public class CalendarEntry {
+ "." + start.getDate() + " ";
}
int i = start.getHours();
s += (i < 10 ? "0" : "") + i;
s += asTwoDigits(i);
s += ":";
i = start.getMinutes();
s += (i < 10 ? "0" : "") + i;
s += asTwoDigits(i);
if (!start.equals(end)) {
s += " - ";
if (!DateTimeService.isSameDay(start, end)) {
@@ -124,10 +124,10 @@ public class CalendarEntry {
+ "." + end.getDate() + " ";
}
i = end.getHours();
s += (i < 10 ? "0" : "") + i;
s += asTwoDigits(i);
s += ":";
i = end.getMinutes();
s += (i < 10 ? "0" : "") + i;
s += asTwoDigits(i);
}
s += " ";
}
@@ -137,4 +137,8 @@ public class CalendarEntry {
return s;
}

private static String asTwoDigits(int i) {
return (i < 10 ? "0" : "") + i;
}

}

+ 0
- 3
client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java View File

@@ -1153,9 +1153,6 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
*
* @param sender
* The component that was clicked
* @param updateVariable
* Should the value field be updated
*
*/
private void processClickEvent(Widget sender) {
if (!isEnabled() || isReadonly()) {

+ 24
- 18
client/src/main/java/com/vaadin/client/ui/VAbstractPopupCalendar.java View File

@@ -79,16 +79,16 @@ public abstract class VAbstractPopupCalendar<PANEL extends VAbstractCalendarPane
/** For internal use only. May be removed or replaced in the future. */
public boolean parsable = true;

private boolean open = false;
private boolean open;

/*
* #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 preventOpenPopupCalendar;
private boolean cursorOverCalendarToggleButton;
private boolean toggleButtonClosesWithGuarantee;

private boolean textFieldEnabled = true;

@@ -217,25 +217,31 @@ public abstract class VAbstractPopupCalendar<PANEL extends VAbstractCalendarPane
closeCalendarPanel();
}

/**
* Changes the current date, and updates the
* {@link VDateField#bufferedResolutions}, possibly
* {@link VDateField#sendBufferedValues()} to the server if needed
*
* @param newDate
* the new {@code Date} to update
*/
@SuppressWarnings("deprecation")
public void updateValue(Date newDate) {
Date currentDate = getCurrentDate();
R resolution = getCurrentResolution();
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)),
newDate.getMonth() + 1, false);
if (!calendar.isMonth(getCurrentResolution())) {
getClient().updateVariable(getId(),
getResolutionVariable(
calendar.getResolution(calendar::isDay)),
newDate.getDate(), false);
bufferedResolutions.put(
calendar.getResolution(calendar::isYear).name(),
newDate.getYear() + 1900);
if (!calendar.isYear(resolution)) {
bufferedResolutions.put(
calendar.getResolution(calendar::isMonth).name(),
newDate.getMonth() + 1);
if (!calendar.isMonth(resolution)) {
bufferedResolutions.put(
calendar.getResolution(calendar::isDay).name(),
newDate.getDate());
}
}
}

+ 23
- 15
client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java View File

@@ -66,7 +66,7 @@ public abstract class VAbstractTextualDate<R extends Enum<R>>
/** For internal use only. May be removed or replaced in the future. */
public boolean lenient;

private final String TEXTFIELD_ID = "field";
private static final String TEXTFIELD_ID = "field";

/** For internal use only. May be removed or replaced in the future. */
private String formatStr;
@@ -104,7 +104,7 @@ public abstract class VAbstractTextualDate<R extends Enum<R>>
*
* @return the format string
*/
protected String getFormatString() {
public String getFormatString() {
if (formatStr == null) {
setFormatString(createFormatString());
}
@@ -138,8 +138,8 @@ public abstract class VAbstractTextualDate<R extends Enum<R>>
* Sets the date format string to use for the text field.
*
* @param formatString
* the format string to use, or null to force re-creating the
* format string from the locale the next time it is needed
* the format string to use, or {@code null} to force re-creating
* the format string from the locale the next time it is needed
* @since 8.1
*/
public void setFormatString(String formatString) {
@@ -244,8 +244,7 @@ public abstract class VAbstractTextualDate<R extends Enum<R>>

addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
// this is a hack that may eventually be removed
getClient().updateVariable(getId(), "lastInvalidDateString",
text.getText(), false);
bufferedInvalidDateString = true;
setDate(null);
}
} else {
@@ -253,10 +252,9 @@ public abstract class VAbstractTextualDate<R extends Enum<R>>
// remove possibly added invalid value indication
removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
}
// always send the date string
getClient().updateVariable(getId(), "dateString", text.getText(),
false);

// always send the date string
bufferedDateString = text.getText();
updateDateVariables();
}

@@ -266,17 +264,20 @@ public abstract class VAbstractTextualDate<R extends Enum<R>>
* The method can be overridden by subclasses to provide a custom logic for
* date variables to avoid overriding the {@link #onChange(ChangeEvent)}
* method.
*
* @since
*/
protected void updateDateVariables() {
// Update variables
// (only the smallest defining resolution needs to be
// immediate)
Date currentDate = getDate();
getClient().updateVariable(getId(),
getResolutionVariable(getResolutions().filter(this::isYear)
.findFirst().get()),
currentDate != null ? currentDate.getYear() + 1900 : -1,
isYear(getCurrentResolution()));
bufferedResolutions.put(
getResolutions().filter(this::isYear).findFirst().get().name(),
currentDate != null ? currentDate.getYear() + 1900 : null);
if (isYear(getCurrentResolution())) {
sendBufferedValues();
}
}

/**
@@ -390,7 +391,14 @@ public abstract class VAbstractTextualDate<R extends Enum<R>>
}
if (getClient() != null && getClient()
.hasEventListeners(VAbstractTextualDate.this, eventId)) {
getClient().updateVariable(getId(), eventId, "", true);
// may excessively send events if if focus went to another
// sub-component
if (EventId.FOCUS.equals(eventId)) {
rpc.focus();
} else {
rpc.blur();
}
sendBufferedValues();
}

// Needed for tooltip event handling

+ 54
- 3
client/src/main/java/com/vaadin/client/ui/VDateField.java View File

@@ -17,6 +17,7 @@
package com.vaadin.client.ui;

import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
@@ -25,6 +26,7 @@ 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.AbstractDateFieldServerRpc;

/**
* A very base widget class for a date field.
@@ -53,22 +55,57 @@ public abstract class VDateField<R extends Enum<R>> extends FlowPanel

protected boolean enabled;

/**
* The RPC send calls to the server.
*
* @since
*/
public AbstractDateFieldServerRpc rpc;

/**
* A temporary holder of the time units (resolutions), which would be sent
* to the server through {@link #sendBufferedValues()}.
*
* The key is the resolution name e.g. "HOUR", "MINUTE".
*
* The value can be {@code null}.
*
* @since
*/
protected Map<String, Integer> bufferedResolutions = new HashMap<>();

/**
* A temporary holder of the date string, which would be sent to the server
* through {@link #sendBufferedValues()}.
*
* @since
*/
protected String bufferedDateString;

/**
* A temporary holder of whether the date string was invalid or not, which
* would be sent to the server through {@link #sendBufferedValues()}.
*
* @since
*/
protected boolean bufferedInvalidDateString;

/**
* The date that is displayed the date field before a value is selected. If
* null, display the current date.
*/
private Date defaultDate = null;
private Date defaultDate;

/**
* The date that is selected in the date field. Null if an invalid date is
* specified.
*/
private Date date = null;
private Date date;

/** For internal use only. May be removed or replaced in the future. */
public DateTimeService dts;

protected boolean showISOWeekNumbers = false;
protected boolean showISOWeekNumbers;

public VDateField(R resolution) {
setStyleName(CLASSNAME);
@@ -231,6 +268,20 @@ public abstract class VDateField<R extends Enum<R>> extends FlowPanel
return resolution.name().toLowerCase(Locale.ENGLISH);
}

/**
* Sends the {@link #bufferedDateString}, {@link #bufferedInvalidDateString}
* and {@link #bufferedResolutions} to the server, and clears their values.
*
* @since
*/
public void sendBufferedValues() {
rpc.update(bufferedDateString, bufferedInvalidDateString,
new HashMap<>(bufferedResolutions));
bufferedDateString = null;
bufferedInvalidDateString = false;
bufferedResolutions.clear();
}

/**
* Returns all available resolutions for the field in the ascending order
* (which is the same as order of enumeration ordinals).

+ 10
- 13
client/src/main/java/com/vaadin/client/ui/VDateFieldCalendar.java View File

@@ -50,25 +50,22 @@ public class VDateFieldCalendar

Date date2 = calendarPanel.getDate();
Date currentDate = getCurrentDate();
DateResolution resolution = getCurrentResolution();
if (currentDate == null || date2.getTime() != currentDate.getTime()) {
setCurrentDate((Date) date2.clone());
getClient().updateVariable(getId(),
getResolutionVariable(DateResolution.YEAR),
bufferedResolutions.put(DateResolution.YEAR.name(),
// 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(DateResolution.MONTH) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateResolution.DAY),
date2.getDate(), false);
date2.getYear() + 1900);
if (resolution.compareTo(DateResolution.YEAR) < 0) {
bufferedResolutions.put(DateResolution.MONTH.name(),
date2.getMonth() + 1);
if (resolution.compareTo(DateResolution.MONTH) < 0) {
bufferedResolutions.put(DateResolution.DAY.name(),
date2.getDate());
}
}
getClient().sendPendingVariableChanges();
sendBufferedValues();
}
}


+ 16
- 12
client/src/main/java/com/vaadin/client/ui/VDateTimeCalendarPanel.java View File

@@ -91,11 +91,11 @@ public class VDateTimeCalendarPanel
if (getDateTimeService().isTwelveHourClock()) {
hours.addItem("12");
for (int i = 1; i < 12; i++) {
hours.addItem((i < 10) ? "0" + i : "" + i);
hours.addItem(asTwoDigits(i));
}
} else {
for (int i = 0; i < 24; i++) {
hours.addItem((i < 10) ? "0" + i : "" + i);
hours.addItem(asTwoDigits(i));
}
}

@@ -111,14 +111,14 @@ public class VDateTimeCalendarPanel
if (getResolution().compareTo(DateTimeResolution.MINUTE) <= 0) {
mins = createListBox();
for (int i = 0; i < 60; i++) {
mins.addItem((i < 10) ? "0" + i : "" + i);
mins.addItem(asTwoDigits(i));
}
mins.addChangeHandler(this);
}
if (getResolution().compareTo(DateTimeResolution.SECOND) <= 0) {
sec = createListBox();
for (int i = 0; i < 60; i++) {
sec.addItem((i < 10) ? "0" + i : "" + i);
sec.addItem(asTwoDigits(i));
}
sec.addChangeHandler(this);
}
@@ -132,7 +132,7 @@ public class VDateTimeCalendarPanel
if (getDateTimeService().isTwelveHourClock()) {
h -= h < 12 ? 0 : 12;
}
add(new VLabel(h < 10 ? "0" + h : "" + h));
add(new VLabel(asTwoDigits(h)));
} else {
add(hours);
}
@@ -141,7 +141,7 @@ public class VDateTimeCalendarPanel
add(new VLabel(delimiter));
if (isReadonly()) {
final int m = mins.getSelectedIndex();
add(new VLabel(m < 10 ? "0" + m : "" + m));
add(new VLabel(asTwoDigits(m)));
} else {
add(mins);
}
@@ -150,7 +150,7 @@ public class VDateTimeCalendarPanel
add(new VLabel(delimiter));
if (isReadonly()) {
final int s = sec.getSelectedIndex();
add(new VLabel(s < 10 ? "0" + s : "" + s));
add(new VLabel(asTwoDigits(s)));
} else {
add(sec);
}
@@ -193,7 +193,6 @@ public class VDateTimeCalendarPanel
}
}
});

}

private ListBox getLastDropDown() {
@@ -243,14 +242,16 @@ public class VDateTimeCalendarPanel
if (ampm != null) {
ampm.setEnabled(isEnabled());
}

}

private DateTimeService getDateTimeService() {
if (VDateTimeCalendarPanel.this.getDateTimeService() == null) {
setDateTimeService(new DateTimeService());
DateTimeService dts = VDateTimeCalendarPanel.this
.getDateTimeService();
if (dts == null) {
dts = new DateTimeService();
setDateTimeService(dts);
}
return VDateTimeCalendarPanel.this.getDateTimeService();
return dts;
}

/*
@@ -312,7 +313,10 @@ public class VDateTimeCalendarPanel
event.stopPropagation();
}
}
}

private static String asTwoDigits(int i) {
return (i < 10 ? "0" : "") + i;
}

/**

+ 24
- 29
client/src/main/java/com/vaadin/client/ui/VDateTimeFieldCalendar.java View File

@@ -46,46 +46,41 @@ public class VDateTimeFieldCalendar extends

Date date2 = calendarPanel.getDate();
Date currentDate = getCurrentDate();
DateTimeResolution resolution = getCurrentResolution();
if (currentDate == null || date2.getTime() != currentDate.getTime()) {
setCurrentDate((Date) date2.clone());
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.YEAR),
date2.getYear() + 1900, false);
if (getCurrentResolution().compareTo(DateTimeResolution.YEAR) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.MONTH),
date2.getMonth() + 1, false);
if (getCurrentResolution()
.compareTo(DateTimeResolution.MONTH) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.DAY),
date2.getDate(), false);
if (getCurrentResolution()
.compareTo(DateTimeResolution.DAY) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.HOUR),
date2.getHours(), false);
if (getCurrentResolution()
.compareTo(DateTimeResolution.HOUR) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(
DateTimeResolution.MINUTE),
date2.getMinutes(), false);
if (getCurrentResolution()
addBufferedResolution(DateTimeResolution.YEAR,
date2.getYear() + 1900);
if (resolution.compareTo(DateTimeResolution.YEAR) < 0) {
addBufferedResolution(DateTimeResolution.MONTH,
date2.getMonth() + 1);
if (resolution.compareTo(DateTimeResolution.MONTH) < 0) {
addBufferedResolution(DateTimeResolution.DAY,
date2.getDate());
if (resolution.compareTo(DateTimeResolution.DAY) < 0) {
addBufferedResolution(DateTimeResolution.HOUR,
date2.getHours());
if (resolution.compareTo(DateTimeResolution.HOUR) < 0) {
addBufferedResolution(DateTimeResolution.MINUTE,
date2.getMinutes());
if (resolution
.compareTo(DateTimeResolution.MINUTE) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(
DateTimeResolution.SECOND),
date2.getSeconds(), false);
addBufferedResolution(DateTimeResolution.SECOND,
date2.getSeconds());
}
}
}
}
}
getClient().sendPendingVariableChanges();
sendBufferedValues();
}
}

private void addBufferedResolution(DateTimeResolution resolution,
Integer value) {
bufferedResolutions.put(resolution.name(), value);
}

@Override
public String resolutionAsString() {
if (getCurrentResolution().compareTo(DateTimeResolution.DAY) >= 0) {

+ 15
- 17
client/src/main/java/com/vaadin/client/ui/VPopupCalendar.java View File

@@ -52,20 +52,20 @@ public class VPopupCalendar
}

public static Date makeDate(Map<DateResolution, Integer> dateValues) {
if (dateValues.get(DateResolution.YEAR) == -1) {
if (dateValues.get(DateResolution.YEAR) == null) {
return null;
}
Date date = new Date(2000 - 1900, 0, 1);
int year = dateValues.get(DateResolution.YEAR);
if (year >= 0) {
Integer year = dateValues.get(DateResolution.YEAR);
if (year != null) {
date.setYear(year - 1900);
}
int month = dateValues.get(DateResolution.MONTH);
if (month >= 0) {
Integer month = dateValues.get(DateResolution.MONTH);
if (month != null) {
date.setMonth(month - 1);
}
int day = dateValues.get(DateResolution.DAY);
if (day >= 0) {
Integer day = dateValues.get(DateResolution.DAY);
if (day != null) {
date.setDate(day);
}
return date;
@@ -84,22 +84,20 @@ public class VPopupCalendar
@Override
protected void updateDateVariables() {
super.updateDateVariables();
DateResolution resolution = getCurrentResolution();
// 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 (resolution.compareTo(DateResolution.MONTH) <= 0) {
bufferedResolutions.put(DateResolution.MONTH.name(),
currentDate != null ? currentDate.getMonth() + 1 : null);
}
if (getCurrentResolution().compareTo(DateResolution.DAY) <= 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateResolution.DAY),
currentDate != null ? currentDate.getDate() : -1,
getCurrentResolution() == DateResolution.DAY);
if (resolution.compareTo(DateResolution.DAY) <= 0) {
bufferedResolutions.put(DateResolution.DAY.name(),
currentDate != null ? currentDate.getDate() : null);
}
sendBufferedValues();
}

@Override

+ 50
- 59
client/src/main/java/com/vaadin/client/ui/VPopupTimeCalendar.java View File

@@ -61,32 +61,32 @@ public class VPopupTimeCalendar extends
}

public static Date makeDate(Map<DateTimeResolution, Integer> dateValues) {
if (dateValues.get(DateTimeResolution.YEAR) == -1) {
if (dateValues.get(DateTimeResolution.YEAR) == null) {
return null;
}
Date date = new Date(2000 - 1900, 0, 1);
int year = dateValues.get(DateTimeResolution.YEAR);
if (year >= 0) {
Integer year = dateValues.get(DateTimeResolution.YEAR);
if (year != null) {
date.setYear(year - 1900);
}
int month = dateValues.get(DateTimeResolution.MONTH);
if (month >= 0) {
Integer month = dateValues.get(DateTimeResolution.MONTH);
if (month != null) {
date.setMonth(month - 1);
}
int day = dateValues.get(DateTimeResolution.DAY);
if (day >= 0) {
Integer day = dateValues.get(DateTimeResolution.DAY);
if (day != null) {
date.setDate(day);
}
int hour = dateValues.get(DateTimeResolution.HOUR);
if (hour >= 0) {
Integer hour = dateValues.get(DateTimeResolution.HOUR);
if (hour != null) {
date.setHours(hour);
}
int minute = dateValues.get(DateTimeResolution.MINUTE);
if (minute >= 0) {
Integer minute = dateValues.get(DateTimeResolution.MINUTE);
if (minute != null) {
date.setMinutes(minute);
}
int second = dateValues.get(DateTimeResolution.SECOND);
if (second >= 0) {
Integer second = dateValues.get(DateTimeResolution.SECOND);
if (second != null) {
date.setSeconds(second);
}
return date;
@@ -105,40 +105,36 @@ public class VPopupTimeCalendar extends
@Override
protected void updateDateVariables() {
super.updateDateVariables();
// Update variables
DateTimeResolution resolution = getCurrentResolution();
// (only the smallest defining resolution needs to be
// immediate)
Date currentDate = getDate();
if (getCurrentResolution().compareTo(DateTimeResolution.MONTH) <= 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.MONTH),
currentDate != null ? currentDate.getMonth() + 1 : -1,
getCurrentResolution() == DateTimeResolution.MONTH);
}
if (getCurrentResolution().compareTo(DateTimeResolution.DAY) <= 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.DAY),
currentDate != null ? currentDate.getDate() : -1,
getCurrentResolution() == DateTimeResolution.DAY);
}
if (getCurrentResolution().compareTo(DateTimeResolution.HOUR) <= 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.HOUR),
currentDate != null ? currentDate.getHours() : -1,
getCurrentResolution() == DateTimeResolution.HOUR);
}
if (getCurrentResolution().compareTo(DateTimeResolution.MINUTE) <= 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.MINUTE),
currentDate != null ? currentDate.getMinutes() : -1,
getCurrentResolution() == DateTimeResolution.MINUTE);
}
if (getCurrentResolution().compareTo(DateTimeResolution.SECOND) <= 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.SECOND),
currentDate != null ? currentDate.getSeconds() : -1,
getCurrentResolution() == DateTimeResolution.SECOND);
if (resolution.compareTo(DateTimeResolution.MONTH) <= 0) {
addBufferedResolution(DateTimeResolution.MONTH,
currentDate != null ? currentDate.getMonth() + 1 : null);
}
if (resolution.compareTo(DateTimeResolution.DAY) <= 0) {
addBufferedResolution(DateTimeResolution.DAY,
currentDate != null ? currentDate.getDate() : null);
}
if (resolution.compareTo(DateTimeResolution.HOUR) <= 0) {
addBufferedResolution(DateTimeResolution.HOUR,
currentDate != null ? currentDate.getHours() : null);
}
if (resolution.compareTo(DateTimeResolution.MINUTE) <= 0) {
addBufferedResolution(DateTimeResolution.MINUTE,
currentDate != null ? currentDate.getMinutes() : null);
}
if (resolution.compareTo(DateTimeResolution.SECOND) <= 0) {
addBufferedResolution(DateTimeResolution.SECOND,
currentDate != null ? currentDate.getSeconds() : null);
}
sendBufferedValues();
}

private void addBufferedResolution(DateTimeResolution resolutionToAdd,
Integer value) {
bufferedResolutions.put(resolutionToAdd.name(), value);
}

@Override
@@ -146,22 +142,18 @@ public class VPopupTimeCalendar extends
public void updateValue(Date newDate) {
Date currentDate = getCurrentDate();
super.updateValue(newDate);
DateTimeResolution resolution = getCurrentResolution();
if (currentDate == null || newDate.getTime() != currentDate.getTime()) {
if (getCurrentResolution().compareTo(DateTimeResolution.DAY) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.HOUR),
newDate.getHours(), false);
if (getCurrentResolution()
.compareTo(DateTimeResolution.HOUR) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(DateTimeResolution.MINUTE),
newDate.getMinutes(), false);
if (getCurrentResolution()
.compareTo(DateTimeResolution.MINUTE) < 0) {
getClient().updateVariable(getId(),
getResolutionVariable(
DateTimeResolution.SECOND),
newDate.getSeconds(), false);
if (resolution.compareTo(DateTimeResolution.DAY) < 0) {
bufferedResolutions.put(DateTimeResolution.HOUR.name(),
newDate.getHours());
if (resolution.compareTo(DateTimeResolution.HOUR) < 0) {
bufferedResolutions.put(DateTimeResolution.MINUTE.name(),
newDate.getMinutes());
if (resolution.compareTo(DateTimeResolution.MINUTE) < 0) {
bufferedResolutions.put(
DateTimeResolution.SECOND.name(),
newDate.getSeconds());
}
}
}
@@ -197,7 +189,6 @@ public class VPopupTimeCalendar extends
if (dts.isTwelveHourClock()) {
frmString += " aaa";
}

}

return frmString;

+ 77
- 70
client/src/main/java/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java View File

@@ -21,101 +21,61 @@ 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;
import com.vaadin.client.UIDL;
import com.vaadin.client.VConsole;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.VDateField;
import com.vaadin.shared.ui.datefield.DateFieldConstants;
import com.vaadin.shared.ui.datefield.AbstractDateFieldServerRpc;
import com.vaadin.shared.ui.datefield.AbstractDateFieldState;

public abstract class AbstractDateFieldConnector<R extends Enum<R>>
extends AbstractFieldConnector implements Paintable {
extends AbstractFieldConnector {

@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
if (!isRealUpdate(uidl)) {
return;
}

// Save details
getWidget().client = client;
getWidget().paintableId = uidl.getId();

getWidget().setReadonly(isReadOnly());
getWidget().setEnabled(isEnabled());

if (uidl.hasAttribute("locale")) {
final String locale = uidl.getStringAttribute("locale");
try {
getWidget().dts.setLocale(locale);
getWidget().setCurrentLocale(locale);
} catch (final LocaleNotLoadedException e) {
getWidget().setCurrentLocale(getWidget().dts.getLocale());
VConsole.error("Tried to use an unloaded locale \"" + locale
+ "\". Using default locale ("
+ getWidget().getCurrentLocale() + ").");
VConsole.error(e);
}
}

// We show week numbers only if the week starts with Monday, as ISO 8601
// specifies
getWidget().setShowISOWeekNumbers(
uidl.getBooleanAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS)
&& getWidget().dts.getFirstDayOfWeek() == 1);

// Remove old stylename that indicates current resolution
setWidgetStyleName(getWidget().getStylePrimaryName() + "-"
+ getWidget().resolutionAsString(), false);

updateResolution(uidl);

// Add stylename that indicates current resolution
setWidgetStyleName(getWidget().getStylePrimaryName() + "-"
+ getWidget().resolutionAsString(), true);

getWidget().setCurrentDate(getTimeValues(uidl));
getWidget().setDefaultDate(getDefaultValues(uidl));
protected void init() {
super.init();
getWidget().rpc = getRpcProxy(AbstractDateFieldServerRpc.class);
}

private void updateResolution(UIDL uidl) {
Optional<R> newResolution = getWidget().getResolutions().filter(
res -> uidl.hasVariable(getWidget().getResolutionVariable(res)))
private void updateResolution() {
VDateField<R> widget = getWidget();
Map<String, Integer> stateResolutions = getState().resolutions;
Optional<R> newResolution = widget.getResolutions()
.filter(res -> stateResolutions.containsKey(res.name()))
.findFirst();

getWidget().setCurrentResolution(newResolution.orElse(null));
widget.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));
private Map<R, Integer> getTimeValues() {
VDateField<R> widget = getWidget();
Map<String, Integer> stateResolutions = getState().resolutions;
Stream<R> resolutions = widget.getResolutions();
R resolution = widget.getCurrentResolution();
return resolutions.collect(Collectors.toMap(Function.identity(),
res -> res == null ? null
: (resolution.compareTo(res) <= 0)
? stateResolutions.get(res.name())
: null));
}

/**
* Returns the default date (when no date is selected) components as a map
* from Resolution to the corresponding value.
*
* @param uidl
* UIDL with corresponding variables
* @return default date component map
* @since 8.1.2
* @since
*/
protected Map<R, Integer> getDefaultValues(UIDL uidl) {
protected Map<R, Integer> getDefaultValues() {
Map<String, Integer> stateResolutions = getState().resolutions;
Stream<R> resolutions = getWidget().getResolutions();
R resolution = getWidget().getCurrentResolution();
return resolutions.collect(Collectors.toMap(Function.identity(),
res -> (resolution.compareTo(res) <= 0)
? uidl.getIntVariable("default-"
+ getWidget().getResolutionVariable(res))
: -1));
res -> res == null ? null
: (resolution.compareTo(res) <= 0)
? stateResolutions.get("default-" + res.name())
: null));
}

@SuppressWarnings("unchecked")
@@ -124,4 +84,51 @@ public abstract class AbstractDateFieldConnector<R extends Enum<R>>
return (VDateField<R>) super.getWidget();
}

@Override
public AbstractDateFieldState getState() {
return (AbstractDateFieldState) super.getState();
}

@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
VDateField<R> widget = getWidget();

// Save details
widget.client = getConnection();
widget.paintableId = getConnectorId();

widget.setReadonly(isReadOnly());
widget.setEnabled(isEnabled());

final String locale = getState().locale;
try {
widget.dts.setLocale(locale);
widget.setCurrentLocale(locale);
} catch (final LocaleNotLoadedException e) {
widget.setCurrentLocale(widget.dts.getLocale());
VConsole.error("Tried to use an unloaded locale \"" + locale
+ "\". Using default locale (" + widget.getCurrentLocale()
+ ").");
VConsole.error(e);
}

// We show week numbers only if the week starts with Monday, as ISO 8601
// specifies
widget.setShowISOWeekNumbers(getState().showISOWeekNumbers
&& widget.dts.getFirstDayOfWeek() == 1);

// Remove old stylename that indicates current resolution
setWidgetStyleName(widget.getStylePrimaryName() + "-"
+ widget.resolutionAsString(), false);

updateResolution();

// Add stylename that indicates current resolution
setWidgetStyleName(widget.getStylePrimaryName() + "-"
+ widget.resolutionAsString(), true);

widget.setCurrentDate(getTimeValues());
widget.setDefaultDate(getDefaultValues());
}
}

+ 18
- 27
client/src/main/java/com/vaadin/client/ui/datefield/AbstractInlineDateFieldConnector.java View File

@@ -39,33 +39,6 @@ import com.vaadin.shared.ui.datefield.InlineDateFieldState;
public abstract class AbstractInlineDateFieldConnector<PANEL extends VAbstractCalendarPanel<R>, 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.
@@ -108,6 +81,24 @@ public abstract class AbstractInlineDateFieldConnector<PANEL extends VAbstractCa
getWidget().setTabIndex(getState().tabIndex);
getWidget().calendarPanel.setRangeStart(getState().rangeStart);
getWidget().calendarPanel.setRangeEnd(getState().rangeEnd);

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();
}

@Override

+ 33
- 30
client/src/main/java/com/vaadin/client/ui/datefield/AbstractTextualDateConnector.java View File

@@ -18,9 +18,8 @@ package com.vaadin.client.ui.datefield;

import com.google.gwt.i18n.client.TimeZone;
import com.google.gwt.i18n.client.TimeZoneInfo;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.UIDL;
import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.VAbstractTextualDate;
import com.vaadin.shared.ui.datefield.AbstractTextualDateFieldState;

@@ -36,34 +35,6 @@ 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().setFormatString(null);
}
if (uidl.hasAttribute("format")) {
getWidget().setFormatString(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();
@@ -88,4 +59,36 @@ public abstract class AbstractTextualDateConnector<R extends Enum<R>>
getWidget().setTimeZone(timeZone);
}

@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
VAbstractTextualDate<R> widget = getWidget();
AbstractTextualDateFieldState state = getState();

R origRes = widget.getCurrentResolution();
String oldLocale = widget.getCurrentLocale();

super.onStateChanged(stateChangeEvent);

if (origRes != widget.getCurrentResolution()
|| oldLocale != widget.getCurrentLocale()) {
// force recreating format string
widget.setFormatString(null);
}
if (state.format != widget.getFormatString()) {
widget.setFormatString(state.format);
}

widget.lenient = state.lenient;

// may be excessively called on every state change
widget.buildDate();
// not a FocusWidget -> needs own tabindex handling
widget.text.setTabIndex(state.tabIndex);

if (widget.isReadonly()) {
widget.text.addStyleDependentName("readonly");
} else {
widget.text.removeStyleDependentName("readonly");
}
}
}

+ 63
- 72
client/src/main/java/com/vaadin/client/ui/datefield/TextualDateConnector.java View File

@@ -48,6 +48,7 @@ public abstract class TextualDateConnector<PANEL extends VAbstractCalendarPanel<

@Override
protected void init() {
super.init();
getWidget().popup.addCloseHandler(new CloseHandler<PopupPanel>() {

@Override
@@ -55,28 +56,77 @@ public abstract class TextualDateConnector<PANEL extends VAbstractCalendarPanel<
/*
* 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).
* (#1399).
*
* The datefield will now only set the date UIDL variables while
* the user is selecting year/month/date/time and not send them
* The datefield will now only set the date 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();
getWidget().sendBufferedValues();
}
});
}

/**
* 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() {
FocusChangeListener listener;
if (isResolutionMonthOrHigher()) {
listener = new FocusChangeListener() {
@Override
public void focusChanged(Date date) {
if (isResolutionMonthOrHigher()) {
getWidget().updateValue(date);
getWidget().buildDate();
Date date2 = getWidget().calendar.getDate();
date2.setYear(date.getYear());
date2.setMonth(date.getMonth());
}
}
};
} else {
listener = null;
}
getWidget().calendar.setFocusChangeListener(listener);
}

/**
* 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();

@Override
public VAbstractPopupCalendar<PANEL, R> getWidget() {
return (VAbstractPopupCalendar<PANEL, R>) super.getWidget();
}

@Override
@SuppressWarnings("deprecation")
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
public TextualDateFieldState getState() {
return (TextualDateFieldState) super.getState();
}

@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
String oldLocale = getWidget().getCurrentLocale();

getWidget().parsable = uidl.getBooleanAttribute("parsable");
getWidget().parsable = getState().parsable;

super.updateFromUIDL(uidl, client);
super.onStateChanged(stateChangeEvent);

getWidget().setTextFieldEnabled(getState().textFieldEnabled);
getWidget().setRangeStart(nullSafeDateClone(getState().rangeStart));
getWidget().setRangeEnd(nullSafeDateClone(getState().rangeEnd));

getWidget().calendar
.setDateTimeService(getWidget().getDateTimeService());
@@ -118,66 +168,11 @@ public abstract class TextualDateConnector<PANEL extends VAbstractCalendarPanel<
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 (isResolutionMonthOrHigher()) {
getWidget().calendar
.setFocusChangeListener(new FocusChangeListener() {
@Override
public void focusChanged(Date date) {
if (isResolutionMonthOrHigher()) {
getWidget().updateValue(date);
getWidget().buildDate();
Date date2 = getWidget().calendar.getDate();
date2.setYear(date.getYear());
date2.setMonth(date.getMonth());
}
}
});
} else {
getWidget().calendar.setFocusChangeListener(null);
}
}

/**
* 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();

@Override
public VAbstractPopupCalendar<PANEL, R> getWidget() {
return (VAbstractPopupCalendar<PANEL, 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 {
if (date != null) {
return (Date) date.clone();
}
return null;
}

@Override
@@ -196,14 +191,10 @@ public abstract class TextualDateConnector<PANEL extends VAbstractCalendarPanel<
// 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);
styleName = "-" + styleName;
}
getWidget().popup.setStyleName(
getWidget().getStylePrimaryName() + "-popup" + styleName, add);
}

}

+ 140
- 174
server/src/main/java/com/vaadin/ui/AbstractDateField.java View File

@@ -48,12 +48,10 @@ import com.vaadin.event.FieldEvents.BlurNotifier;
import com.vaadin.event.FieldEvents.FocusEvent;
import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.event.FieldEvents.FocusNotifier;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.server.UserError;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.datefield.AbstractDateFieldServerRpc;
import com.vaadin.shared.ui.datefield.AbstractDateFieldState;
import com.vaadin.shared.ui.datefield.DateFieldConstants;
import com.vaadin.shared.ui.datefield.DateResolution;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
@@ -73,8 +71,86 @@ import com.vaadin.util.TimeZoneUtil;
*
*/
public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & Serializable & Comparable<? super T>, R extends Enum<R>>
extends AbstractField<T>
implements LegacyComponent, FocusNotifier, BlurNotifier {
extends AbstractField<T> implements FocusNotifier, BlurNotifier {

private AbstractDateFieldServerRpc rpc = new AbstractDateFieldServerRpc() {

@Override
public void update(String newDateString, boolean invalidDateString,
Map<String, Integer> resolutions) {
Set<String> resolutionNames = getResolutions()
.map(AbstractDateField.this::getResolutionVariable)
.collect(Collectors.toSet());
resolutionNames.retainAll(resolutions.keySet());
if (!isReadOnly()
&& (!resolutionNames.isEmpty() || newDateString != null)) {

// Old and new dates
final T oldDate = getValue();

T newDate;

boolean hasChanges = false;

if ("".equals(newDateString)) {

newDate = null;
// TODO check if the following 3 lines are necessary
hasChanges = !getState(false).parsable;
getState().parsable = true;
currentParseErrorMessage = null;
} else {
newDate = reconstructDateFromFields(resolutions, oldDate);
}

hasChanges |= !Objects.equals(dateString, newDateString)
|| !Objects.equals(oldDate, newDate);

if (hasChanges) {
dateString = newDateString;
if (newDateString == null || newDateString.isEmpty()) {
getState().parsable = true;
currentParseErrorMessage = null;
setComponentError(null);
setValue(newDate, true);
} else {
if (invalidDateString) {
Result<T> parsedDate = handleUnparsableDateString(
dateString);
parsedDate.ifOk(v -> {
getState().parsable = true;
currentParseErrorMessage = null;
setValue(v, true);
});
if (parsedDate.isError()) {
dateString = null;
getState().parsable = false;
currentParseErrorMessage = parsedDate
.getMessage().orElse("Parsing error");
setComponentError(
new UserError(getParseErrorMessage()));
setValue(null, true);
}
} else {
getState().parsable = true;
currentParseErrorMessage = null;
setValue(newDate, true);
}
}
}
}
}

@Override
public void focus() {
fireEvent(new FocusEvent(AbstractDateField.this));
}

@Override
public void blur() {
fireEvent(new BlurEvent(AbstractDateField.this));
}
};

/**
* Value of the field.
@@ -86,36 +162,19 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
*
* @since 8.1.2
*/
private T defaultValue = null;
private T defaultValue;

/**
* Specified smallest modifiable unit for the date field.
*/
private R resolution;

/**
* Overridden format string
*/
private String dateFormat;

private ZoneId zoneId;

private boolean lenient = false;

private String dateString = "";

private String currentParseErrorMessage;

/**
* Was the last entered string parsable? If this flag is false, datefields
* internal validator does not pass.
*/
private boolean uiHasValidDateString = true;

/**
* Determines if week numbers are shown in the date selector.
*/
private boolean showISOWeekNumbers = false;

private String defaultParseErrorMessage = "Date format not recognized";

private String dateOutOfRangeMessage = "Date is out of allowed range";
@@ -127,10 +186,11 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* specified {@code resolution}.
*
* @param resolution
* initial resolution for the field
* initial resolution for the field, not {@code null}
*/
public AbstractDateField(R resolution) {
this.resolution = resolution;
registerRpc(rpc);
setResolution(resolution);
}

/**
@@ -139,7 +199,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* @param caption
* the caption of the datefield.
* @param resolution
* initial resolution for the field
* initial resolution for the field, not {@code null}
*/
public AbstractDateField(String caption, R resolution) {
this(resolution);
@@ -155,7 +215,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* @param value
* the date/time value.
* @param resolution
* initial resolution for the field
* initial resolution for the field, not {@code null}
*/
public AbstractDateField(String caption, T value, R resolution) {
this(caption, resolution);
@@ -164,141 +224,21 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &

/* Component basic features */

/*
* Paints this component. Don't add a JavaDoc comment here, we use the
* default documentation from implemented interface.
*/
@Override
public void paintContent(PaintTarget target) throws PaintException {
public void beforeClientResponse(boolean initial) {
super.beforeClientResponse(initial);

// Adds the locale as attribute
final Locale l = getLocale();
if (l != null) {
target.addAttribute("locale", l.toString());
}

if (getDateFormat() != null) {
target.addAttribute("format", getDateFormat());
}

if (!isLenient()) {
target.addAttribute("strict", true);
}

target.addAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS,
isShowISOWeekNumbers());
target.addAttribute("parsable", uiHasValidDateString);
final T currentDate = getValue();

// Only paint variables for the resolution and up, e.g. Resolution DAY
// paints DAY,MONTH,YEAR
for (R res : getResolutionsHigherOrEqualTo(getResolution())) {
int value = -1;
if (currentDate != null) {
value = getDatePart(currentDate, res);
}
String variableName = getResolutionVariable(res);
target.addVariable(this, variableName, value);
if (defaultValue != null) {
int defaultValuePart = getDatePart(defaultValue, res);
target.addVariable(this, "default-" + variableName,
defaultValuePart);
} else {
target.addVariable(this, "default-" + variableName, -1);
}
}
}

/*
* Invoked when a variable of the component changes. Don't add a JavaDoc
* comment here, we use the default documentation from implemented
* interface.
*/
@Override
public void changeVariables(Object source, Map<String, Object> variables) {
Set<String> resolutionNames = getResolutions()
.map(this::getResolutionVariable).collect(Collectors.toSet());
resolutionNames.retainAll(variables.keySet());
if (!isReadOnly() && (!resolutionNames.isEmpty()
|| variables.containsKey("dateString"))) {

// Old and new dates
final T oldDate = getValue();

// this enables analyzing invalid input on the server
// this variable is null if the date was chosen with popup calendar
// or contains user-typed string
final String newDateString = (String) variables.get("dateString");

T newDate;

boolean hasChanges = false;

if ("".equals(newDateString)) {

newDate = null;
// TODO check if the following 3 lines are necessary
hasChanges = !uiHasValidDateString;
uiHasValidDateString = true;
currentParseErrorMessage = null;
} else {
newDate = reconstructDateFromFields(variables, oldDate);
}

hasChanges |= !Objects.equals(dateString, newDateString)
|| !Objects.equals(oldDate, newDate);

if (hasChanges) {
dateString = newDateString;
if (newDateString == null || newDateString.isEmpty()) {
uiHasValidDateString = true;
currentParseErrorMessage = null;
setComponentError(null);
setValue(newDate, true);
} else {
if (variables.get("lastInvalidDateString") != null) {
Result<T> parsedDate = handleUnparsableDateString(
dateString);
parsedDate.ifOk(v -> {
uiHasValidDateString = true;
currentParseErrorMessage = null;
setValue(v, true);
});
if (parsedDate.isError()) {
dateString = null;
uiHasValidDateString = false;
currentParseErrorMessage = parsedDate.getMessage()
.orElse("Parsing error");
setComponentError(
new UserError(getParseErrorMessage()));
setValue(null, true);
}
} else {
uiHasValidDateString = true;
currentParseErrorMessage = null;
setValue(newDate, true);
}
}
markAsDirty();
}
}

if (variables.containsKey(FocusEvent.EVENT_ID)) {
fireEvent(new FocusEvent(this));
}

if (variables.containsKey(BlurEvent.EVENT_ID)) {
fireEvent(new BlurEvent(this));
}
Locale locale = getLocale();
getState().locale = locale == null ? null : locale.toString();
}

/**
* Construct a date object from the individual field values received from
* the client.
*
* @since 8.1.1
* @since
*/
protected T reconstructDateFromFields(Map<String, Object> variables,
protected T reconstructDateFromFields(Map<String, Integer> variables,
T oldDate) {
Map<R, Integer> calendarFields = new HashMap<>();

@@ -307,8 +247,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
// resolutions that are painted
String variableName = getResolutionVariable(resolution);

Integer newValue = (Integer) variables.get(variableName);
if (newValue != null && newValue >= 0) {
Integer newValue = variables.get(variableName);
if (newValue != null) {
calendarFields.put(resolution, newValue);
} else {
calendarFields.put(resolution,
@@ -378,7 +318,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
*/
public void setResolution(R resolution) {
this.resolution = resolution;
markAsDirty();
updateResolutions();
}

/**
@@ -429,23 +369,23 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* override formatting. See Vaadin issue #2200.
*
* @param dateFormat
* the dateFormat to set
* the dateFormat to set, can be {@code null}
*
* @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
*/
public void setDateFormat(String dateFormat) {
this.dateFormat = dateFormat;
markAsDirty();
getState().format = dateFormat;
}

/**
* Returns a format string used to format date value on client side or null
* if default formatting from {@link Component#getLocale()} is used.
* Returns a format string used to format date value on client side or
* {@code null} if default formatting from {@link Component#getLocale()} is
* used.
*
* @return the dateFormat
*/
public String getDateFormat() {
return dateFormat;
return getState(false).format;
}

/**
@@ -484,6 +424,32 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
super.setLocale(locale);
}

private void updateResolutions() {
final T currentDate = getValue();

Map<String, Integer> resolutions = getState().resolutions;
resolutions.clear();

// Only paint variables for the resolution and up, e.g. Resolution DAY
// paints DAY,MONTH,YEAR
for (R res : getResolutionsHigherOrEqualTo(getResolution())) {
String variableName = getResolutionVariable(res);

Integer value = getValuePart(currentDate, res);
resolutions.put(variableName, value);

Integer defaultValuePart = getValuePart(defaultValue, res);
resolutions.put("default-" + variableName, defaultValuePart);
}
}

private Integer getValuePart(T date, R resolution) {
if (date == null) {
return null;
}
return getDatePart(date, resolution);
}

/**
* Returns the {@link ZoneId}, which is used when {@code z} is included
* inside the {@link #setDateFormat(String)}.
@@ -507,20 +473,19 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* be turned off.
*/
public void setLenient(boolean lenient) {
this.lenient = lenient;
markAsDirty();
getState().lenient = lenient;
}

/**
* Returns whether date/time interpretation is to be lenient.
* Returns whether date/time interpretation is lenient.
*
* @see #setLenient(boolean)
*
* @return true if the interpretation mode of this calendar is lenient;
* false otherwise.
* @return {@code true} if the interpretation mode of this calendar is
* lenient; {@code false} otherwise.
*/
public boolean isLenient() {
return lenient;
return getState(false).lenient;
}

@Override
@@ -549,6 +514,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
*/
public void setDefaultValue(T defaultValue) {
this.defaultValue = defaultValue;
updateResolutions();
}

/**
@@ -566,7 +532,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* user). No value changes should happen, but we need to do some
* internal housekeeping.
*/
if (value == null && !uiHasValidDateString) {
if (value == null && !getState(false).parsable) {
/*
* Side-effects of doSetValue clears possible previous strings and
* flags about invalid input.
@@ -585,7 +551,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* @return true if week numbers are shown, false otherwise.
*/
public boolean isShowISOWeekNumbers() {
return showISOWeekNumbers;
return getState(false).showISOWeekNumbers;
}

/**
@@ -597,8 +563,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* true if week numbers should be shown, false otherwise.
*/
public void setShowISOWeekNumbers(boolean showWeekNumbers) {
showISOWeekNumbers = showWeekNumbers;
markAsDirty();
getState().showISOWeekNumbers = showWeekNumbers;
}

/**
@@ -745,6 +710,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
} else {
setComponentError(new UserError(currentParseErrorMessage));
}
updateResolutions();
}

/**
@@ -800,7 +766,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
protected abstract Date convertToDate(T date);

private String getResolutionVariable(R resolution) {
return resolution.name().toLowerCase(Locale.ENGLISH);
return resolution.name();
}

@SuppressWarnings("unchecked")

+ 12
- 12
server/src/test/java/com/vaadin/tests/server/component/datefield/DateFieldListenersTest.java View File

@@ -1,8 +1,6 @@
package com.vaadin.tests.server.component.datefield;

import java.io.Serializable;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;

@@ -13,43 +11,45 @@ import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.BlurListener;
import com.vaadin.event.FieldEvents.FocusEvent;
import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.shared.ui.datefield.DateTimeResolution;
import com.vaadin.tests.server.component.AbstractListenerMethodsTestBase;
import com.vaadin.ui.AbstractDateField;

public class DateFieldListenersTest extends AbstractListenerMethodsTestBase {

public static class TestDateField<T extends Temporal & TemporalAdjuster & Serializable & Comparable<? super T>, R extends Enum<R>>
extends AbstractDateField<T, R> {
public static class TestDateField
extends AbstractDateField<LocalDateTime, DateTimeResolution> {

public TestDateField() {
super(null);
super(DateTimeResolution.DAY);
}

@Override
protected int getDatePart(T date, R resolution) {
protected int getDatePart(LocalDateTime date,
DateTimeResolution resolution) {
return 0;
}

@Override
protected T buildDate(Map<R, Integer> resolutionValues) {
protected LocalDateTime buildDate(
Map<DateTimeResolution, Integer> resolutionValues) {
return null;
}

@Override
protected RangeValidator<T> getRangeValidator() {
protected RangeValidator<LocalDateTime> getRangeValidator() {
return null;
}

@Override
protected T convertFromDate(Date date) {
protected LocalDateTime convertFromDate(Date date) {
return null;
}

@Override
protected Date convertToDate(T date) {
protected Date convertToDate(LocalDateTime date) {
return null;
}

}

@Test

+ 55
- 0
shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldServerRpc.java View File

@@ -0,0 +1,55 @@
/*
* 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.shared.ui.datefield;

import java.util.Map;

import com.vaadin.shared.communication.ServerRpc;

/**
* RPC interface for calls from client to server.
*
* @since
*/
public interface AbstractDateFieldServerRpc extends ServerRpc {

/**
* Updates the typed data string and resolution names and values.
*
* @param newDateString
* the value of the text field part. It enables analyzing invalid
* input on the server. {@code null} if the date was chosen with
* popup calendar or contains user-typed string
* @param invalidDateString
* Whether the last date string is invalid or not
* @param resolutions
* map of time unit (resolution) name and value, name is the
* lower-case resolution name e.g. "hour", "minute", and value
* can be {@code null}
*/
void update(String newDateString, boolean invalidDateString,
Map<String, Integer> resolutions);

/**
* Indicates to the server that the client-side has lost focus.
*/
void blur();

/**
* Indicates to the server that the client-side has acquired focus.
*/
void focus();
}

+ 52
- 0
shared/src/main/java/com/vaadin/shared/ui/datefield/AbstractDateFieldState.java View File

@@ -16,6 +16,8 @@
package com.vaadin.shared.ui.datefield;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.vaadin.shared.AbstractFieldState;
import com.vaadin.shared.annotations.NoLayout;
@@ -54,4 +56,54 @@ public class AbstractDateFieldState extends AbstractFieldState {
* @since 8.2
*/
public String timeZoneJSON;

/**
* The used Locale, can be {@code null}.
*
* @since
*/
public String locale;

/**
* Overridden date format string, can be {@code null} if default formatting
* of the components locale is used.
*
* @since
*/
public String format;

/**
* Whether the date/time interpretation is lenient.
*
* @since
*/
public boolean lenient;

/**
* The map of {@code Resolution}s which are currently used by the component.
*
* The key is the resolution name e.g. "HOUR", "MINUTE", with possibly
* prefixed by "default-".
*
* The value can be {@code null}
*
* @since
*/
public Map<String, Integer> resolutions = new HashMap<>();

/**
* Determines if week numbers are shown in the date selector.
*
* @since
*/
public boolean showISOWeekNumbers;

/**
* Was the last entered string parsable? If this flag is false, datefields
* internal validator does not pass.
*
* @since
*/
public boolean parsable = true;

}

+ 0
- 26
shared/src/main/java/com/vaadin/shared/ui/datefield/DateFieldConstants.java View File

@@ -1,26 +0,0 @@
/*
* 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.shared.ui.datefield;

import java.io.Serializable;

@Deprecated
public class DateFieldConstants implements Serializable {

@Deprecated
public static final String ATTR_WEEK_NUMBERS = "wn";

}

Loading…
Cancel
Save