From: Matti Tahvonen Date: Wed, 4 Nov 2009 12:45:38 +0000 (+0000) Subject: fixes #3175, now non-lenient by default, can be changed via setLenient() X-Git-Tag: 6.7.0.beta1~2335 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=79ef77855d98132bb0c9c9c802f4047e4ad825f8;p=vaadin-framework.git fixes #3175, now non-lenient by default, can be changed via setLenient() svn changeset:9624/svn branch:6.2 --- diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java b/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java index af04890881..ea91c5eac1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java @@ -1,330 +1,346 @@ -/* -@ITMillApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; - -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.i18n.client.DateTimeFormat; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.TextBox; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ClientExceptionHandler; -import com.vaadin.terminal.gwt.client.ContainerResizedListener; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.LocaleNotLoadedException; -import com.vaadin.terminal.gwt.client.LocaleService; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; - -public class VTextualDate extends VDateField implements Paintable, Field, - ChangeHandler, ContainerResizedListener, Focusable { - - private static final String PARSE_ERROR_CLASSNAME = CLASSNAME - + "-parseerror"; - - private final TextBox text; - - private String formatStr; - - private String width; - - private boolean needLayout; - - protected int fieldExtraWidth = -1; - - public VTextualDate() { - super(); - text = new TextBox(); - // use normal textfield styles as a basis - text.setStyleName(VTextField.CLASSNAME); - // add datefield spesific style name also - text.addStyleName(CLASSNAME + "-textfield"); - text.addChangeHandler(this); - text.addFocusHandler(new FocusHandler() { - public void onFocus(FocusEvent event) { - text.addStyleName(VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS); - } - }); - text.addBlurHandler(new BlurHandler() { - public void onBlur(BlurEvent event) { - text.removeStyleName(VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS); - } - }); - add(text); - } - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - int origRes = currentResolution; - super.updateFromUIDL(uidl, client); - if (origRes != currentResolution) { - // force recreating format string - formatStr = null; - } - if (uidl.hasAttribute("format")) { - formatStr = uidl.getStringAttribute("format"); - } - - buildDate(); - // not a FocusWidget -> needs own tabindex handling - if (uidl.hasAttribute("tabindex")) { - text.setTabIndex(uidl.getIntAttribute("tabindex")); - } - - if (readonly) { - text.addStyleDependentName("readonly"); - } else { - text.removeStyleDependentName("readonly"); - } - } - - protected String getFormatString() { - if (formatStr == null) { - if (currentResolution == RESOLUTION_YEAR) { - formatStr = "yyyy"; // force full year - } else { - - try { - String frmString = LocaleService - .getDateFormat(currentLocale); - frmString = cleanFormat(frmString); - String delim = LocaleService - .getClockDelimiter(currentLocale); - - if (currentResolution >= RESOLUTION_HOUR) { - if (dts.isTwelveHourClock()) { - frmString += " hh"; - } else { - frmString += " HH"; - } - if (currentResolution >= RESOLUTION_MIN) { - frmString += ":mm"; - if (currentResolution >= RESOLUTION_SEC) { - frmString += ":ss"; - if (currentResolution >= RESOLUTION_MSEC) { - frmString += ".SSS"; - } - } - } - if (dts.isTwelveHourClock()) { - frmString += " aaa"; - } - - } - - formatStr = frmString; - } catch (LocaleNotLoadedException e) { - ClientExceptionHandler.displayError(e); - } - } - } - return formatStr; - } - - /** - * - */ - protected void buildDate() { - removeStyleName(PARSE_ERROR_CLASSNAME); - // Create the initial text for the textfield - String dateText; - if (date != null) { - dateText = DateTimeFormat.getFormat(getFormatString()).format(date); - } else { - dateText = ""; - } - - text.setText(dateText); - text.setEnabled(enabled); - text.setReadOnly(readonly); - - if (readonly) { - text.addStyleName("v-readonly"); - } else { - text.removeStyleName("v-readonly"); - } - - } - - public void onChange(ChangeEvent event) { - if (!text.getText().equals("")) { - try { - DateTimeFormat format = DateTimeFormat - .getFormat(getFormatString()); - date = format.parse(text.getText()); - long stamp = date.getTime(); - if (stamp == 0) { - // If date parsing fails in firefox the stamp will be 0 - date = null; - addStyleName(PARSE_ERROR_CLASSNAME); - } else { - // remove possibly added invalid value indication - removeStyleName(PARSE_ERROR_CLASSNAME); - } - } catch (final Exception e) { - ClientExceptionHandler.displayError(e.getMessage()); - - addStyleName(PARSE_ERROR_CLASSNAME); - // this is a hack that may eventually be removed - client.updateVariable(id, "lastInvalidDateString", text - .getText(), false); - date = null; - } - } else { - date = null; - // remove possibly added invalid value indication - removeStyleName(PARSE_ERROR_CLASSNAME); - } - // always send the date string - client.updateVariable(id, "dateString", text.getText(), false); - - if (date != null) { - showingDate = new Date(date.getTime()); - } - - // Update variables - // (only the smallest defining resolution needs to be - // immediate) - client.updateVariable(id, "year", date != null ? date.getYear() + 1900 - : -1, currentResolution == VDateField.RESOLUTION_YEAR - && immediate); - if (currentResolution >= VDateField.RESOLUTION_MONTH) { - client.updateVariable(id, "month", - date != null ? date.getMonth() + 1 : -1, - currentResolution == VDateField.RESOLUTION_MONTH - && immediate); - } - if (currentResolution >= VDateField.RESOLUTION_DAY) { - client - .updateVariable(id, "day", date != null ? date.getDate() - : -1, - currentResolution == VDateField.RESOLUTION_DAY - && immediate); - } - if (currentResolution >= VDateField.RESOLUTION_HOUR) { - client.updateVariable(id, "hour", date != null ? date.getHours() - : -1, currentResolution == VDateField.RESOLUTION_HOUR - && immediate); - } - if (currentResolution >= VDateField.RESOLUTION_MIN) { - client.updateVariable(id, "min", date != null ? date.getMinutes() - : -1, currentResolution == VDateField.RESOLUTION_MIN - && immediate); - } - if (currentResolution >= VDateField.RESOLUTION_SEC) { - client.updateVariable(id, "sec", date != null ? date.getSeconds() - : -1, currentResolution == VDateField.RESOLUTION_SEC - && immediate); - } - if (currentResolution == VDateField.RESOLUTION_MSEC) { - client.updateVariable(id, "msec", date != null ? getMilliseconds() - : -1, immediate); - } - - } - - private String cleanFormat(String format) { - // Remove unnecessary d & M if resolution is too low - if (currentResolution < VDateField.RESOLUTION_DAY) { - format = format.replaceAll("d", ""); - } - if (currentResolution < VDateField.RESOLUTION_MONTH) { - format = format.replaceAll("M", ""); - } - - // Remove unsupported patterns - // TODO support for 'G', era designator (used at least in Japan) - format = format.replaceAll("[GzZwWkK]", ""); - - // Remove extra delimiters ('/' and '.') - while (format.startsWith("/") || format.startsWith(".") - || format.startsWith("-")) { - format = format.substring(1); - } - while (format.endsWith("/") || format.endsWith(".") - || format.endsWith("-")) { - format = format.substring(0, format.length() - 1); - } - - // Remove duplicate delimiters - format = format.replaceAll("//", "/"); - format = format.replaceAll("\\.\\.", "."); - format = format.replaceAll("--", "-"); - - return format.trim(); - } - - @Override - public void setWidth(String newWidth) { - if (!"".equals(newWidth) && (width == null || !newWidth.equals(width))) { - if (BrowserInfo.get().isIE6()) { - // in IE6 cols ~ min-width - DOM.setElementProperty(text.getElement(), "size", "1"); - } - needLayout = true; - width = newWidth; - super.setWidth(width); - iLayout(); - if (newWidth.indexOf("%") < 0) { - needLayout = false; - } - } else { - if ("".equals(newWidth) && width != null && !"".equals(width)) { - if (BrowserInfo.get().isIE6()) { - // revert IE6 hack - DOM.setElementProperty(text.getElement(), "size", ""); - } - super.setWidth(""); - needLayout = true; - iLayout(); - needLayout = false; - width = null; - } - } - } - - /** - * Returns pixels in x-axis reserved for other than textfield content. - * - * @return extra width in pixels - */ - protected int getFieldExtraWidth() { - if (fieldExtraWidth < 0) { - text.setWidth("0"); - fieldExtraWidth = text.getOffsetWidth(); - if (BrowserInfo.get().isFF3()) { - // Firefox somehow always leaves the INPUT element 2px wide - fieldExtraWidth -= 2; - } - } - return fieldExtraWidth; - } - - public void updateWidth() { - needLayout = true; - fieldExtraWidth = -1; - iLayout(); - } - - public void iLayout() { - if (needLayout) { - text.setWidth((getOffsetWidth() - getFieldExtraWidth()) + "px"); - } - } - - public void focus() { - text.setFocus(true); - } -} +/* +@ITMillApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import java.util.Date; + +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.i18n.client.DateTimeFormat; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.TextBox; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ClientExceptionHandler; +import com.vaadin.terminal.gwt.client.ContainerResizedListener; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.LocaleNotLoadedException; +import com.vaadin.terminal.gwt.client.LocaleService; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; + +public class VTextualDate extends VDateField implements Paintable, Field, + ChangeHandler, ContainerResizedListener, Focusable { + + private static final String PARSE_ERROR_CLASSNAME = CLASSNAME + + "-parseerror"; + + private final TextBox text; + + private String formatStr; + + private String width; + + private boolean needLayout; + + protected int fieldExtraWidth = -1; + + private boolean lenient; + + public VTextualDate() { + + super(); + text = new TextBox(); + // use normal textfield styles as a basis + text.setStyleName(VTextField.CLASSNAME); + // add datefield spesific style name also + text.addStyleName(CLASSNAME + "-textfield"); + text.addChangeHandler(this); + text.addFocusHandler(new FocusHandler() { + public void onFocus(FocusEvent event) { + text.addStyleName(VTextField.CLASSNAME + "-" + + VTextField.CLASSNAME_FOCUS); + } + }); + text.addBlurHandler(new BlurHandler() { + public void onBlur(BlurEvent event) { + text.removeStyleName(VTextField.CLASSNAME + "-" + + VTextField.CLASSNAME_FOCUS); + } + }); + add(text); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + int origRes = currentResolution; + super.updateFromUIDL(uidl, client); + if (origRes != currentResolution) { + // force recreating format string + formatStr = null; + } + if (uidl.hasAttribute("format")) { + formatStr = uidl.getStringAttribute("format"); + } + + lenient = !uidl.getBooleanAttribute("strict"); + + buildDate(); + // not a FocusWidget -> needs own tabindex handling + if (uidl.hasAttribute("tabindex")) { + text.setTabIndex(uidl.getIntAttribute("tabindex")); + } + + if (readonly) { + text.addStyleDependentName("readonly"); + } else { + text.removeStyleDependentName("readonly"); + } + } + + protected String getFormatString() { + if (formatStr == null) { + if (currentResolution == RESOLUTION_YEAR) { + formatStr = "yyyy"; // force full year + } else { + + try { + String frmString = LocaleService + .getDateFormat(currentLocale); + frmString = cleanFormat(frmString); + String delim = LocaleService + .getClockDelimiter(currentLocale); + + if (currentResolution >= RESOLUTION_HOUR) { + if (dts.isTwelveHourClock()) { + frmString += " hh"; + } else { + frmString += " HH"; + } + if (currentResolution >= RESOLUTION_MIN) { + frmString += ":mm"; + if (currentResolution >= RESOLUTION_SEC) { + frmString += ":ss"; + if (currentResolution >= RESOLUTION_MSEC) { + frmString += ".SSS"; + } + } + } + if (dts.isTwelveHourClock()) { + frmString += " aaa"; + } + + } + + formatStr = frmString; + } catch (LocaleNotLoadedException e) { + ClientExceptionHandler.displayError(e); + } + } + } + return formatStr; + } + + /** + * + */ + protected void buildDate() { + removeStyleName(PARSE_ERROR_CLASSNAME); + // Create the initial text for the textfield + String dateText; + if (date != null) { + dateText = DateTimeFormat.getFormat(getFormatString()).format(date); + } else { + dateText = ""; + } + + text.setText(dateText); + text.setEnabled(enabled); + text.setReadOnly(readonly); + + if (readonly) { + text.addStyleName("v-readonly"); + } else { + text.removeStyleName("v-readonly"); + } + + } + + public void onChange(ChangeEvent event) { + if (!text.getText().equals("")) { + try { + DateTimeFormat format = DateTimeFormat + .getFormat(getFormatString()); + if (lenient) { + date = format.parse(text.getText()); + if (date != null) { + // if date value was leniently parsed, normalize text + // presentation + text.setValue(DateTimeFormat.getFormat( + getFormatString()).format(date), false); + } + } else { + date = format.parseStrict(text.getText()); + } + + long stamp = date.getTime(); + if (stamp == 0) { + // If date parsing fails in firefox the stamp will be 0 + date = null; + addStyleName(PARSE_ERROR_CLASSNAME); + } else { + // remove possibly added invalid value indication + removeStyleName(PARSE_ERROR_CLASSNAME); + } + } catch (final Exception e) { + ClientExceptionHandler.displayError(e.getMessage()); + + addStyleName(PARSE_ERROR_CLASSNAME); + // this is a hack that may eventually be removed + client.updateVariable(id, "lastInvalidDateString", text + .getText(), false); + date = null; + } + } else { + date = null; + // remove possibly added invalid value indication + removeStyleName(PARSE_ERROR_CLASSNAME); + } + // always send the date string + client.updateVariable(id, "dateString", text.getText(), false); + + if (date != null) { + showingDate = new Date(date.getTime()); + } + + // Update variables + // (only the smallest defining resolution needs to be + // immediate) + client.updateVariable(id, "year", date != null ? date.getYear() + 1900 + : -1, currentResolution == VDateField.RESOLUTION_YEAR + && immediate); + if (currentResolution >= VDateField.RESOLUTION_MONTH) { + client.updateVariable(id, "month", + date != null ? date.getMonth() + 1 : -1, + currentResolution == VDateField.RESOLUTION_MONTH + && immediate); + } + if (currentResolution >= VDateField.RESOLUTION_DAY) { + client + .updateVariable(id, "day", date != null ? date.getDate() + : -1, + currentResolution == VDateField.RESOLUTION_DAY + && immediate); + } + if (currentResolution >= VDateField.RESOLUTION_HOUR) { + client.updateVariable(id, "hour", date != null ? date.getHours() + : -1, currentResolution == VDateField.RESOLUTION_HOUR + && immediate); + } + if (currentResolution >= VDateField.RESOLUTION_MIN) { + client.updateVariable(id, "min", date != null ? date.getMinutes() + : -1, currentResolution == VDateField.RESOLUTION_MIN + && immediate); + } + if (currentResolution >= VDateField.RESOLUTION_SEC) { + client.updateVariable(id, "sec", date != null ? date.getSeconds() + : -1, currentResolution == VDateField.RESOLUTION_SEC + && immediate); + } + if (currentResolution == VDateField.RESOLUTION_MSEC) { + client.updateVariable(id, "msec", date != null ? getMilliseconds() + : -1, immediate); + } + + } + + private String cleanFormat(String format) { + // Remove unnecessary d & M if resolution is too low + if (currentResolution < VDateField.RESOLUTION_DAY) { + format = format.replaceAll("d", ""); + } + if (currentResolution < VDateField.RESOLUTION_MONTH) { + format = format.replaceAll("M", ""); + } + + // Remove unsupported patterns + // TODO support for 'G', era designator (used at least in Japan) + format = format.replaceAll("[GzZwWkK]", ""); + + // Remove extra delimiters ('/' and '.') + while (format.startsWith("/") || format.startsWith(".") + || format.startsWith("-")) { + format = format.substring(1); + } + while (format.endsWith("/") || format.endsWith(".") + || format.endsWith("-")) { + format = format.substring(0, format.length() - 1); + } + + // Remove duplicate delimiters + format = format.replaceAll("//", "/"); + format = format.replaceAll("\\.\\.", "."); + format = format.replaceAll("--", "-"); + + return format.trim(); + } + + @Override + public void setWidth(String newWidth) { + if (!"".equals(newWidth) && (width == null || !newWidth.equals(width))) { + if (BrowserInfo.get().isIE6()) { + // in IE6 cols ~ min-width + DOM.setElementProperty(text.getElement(), "size", "1"); + } + needLayout = true; + width = newWidth; + super.setWidth(width); + iLayout(); + if (newWidth.indexOf("%") < 0) { + needLayout = false; + } + } else { + if ("".equals(newWidth) && width != null && !"".equals(width)) { + if (BrowserInfo.get().isIE6()) { + // revert IE6 hack + DOM.setElementProperty(text.getElement(), "size", ""); + } + super.setWidth(""); + needLayout = true; + iLayout(); + needLayout = false; + width = null; + } + } + } + + /** + * Returns pixels in x-axis reserved for other than textfield content. + * + * @return extra width in pixels + */ + protected int getFieldExtraWidth() { + if (fieldExtraWidth < 0) { + text.setWidth("0"); + fieldExtraWidth = text.getOffsetWidth(); + if (BrowserInfo.get().isFF3()) { + // Firefox somehow always leaves the INPUT element 2px wide + fieldExtraWidth -= 2; + } + } + return fieldExtraWidth; + } + + public void updateWidth() { + needLayout = true; + fieldExtraWidth = -1; + iLayout(); + } + + public void iLayout() { + if (needLayout) { + text.setWidth((getOffsetWidth() - getFieldExtraWidth()) + "px"); + } + } + + public void focus() { + text.setFocus(true); + } +} diff --git a/src/com/vaadin/ui/DateField.java b/src/com/vaadin/ui/DateField.java index 789ae06b48..425040a016 100644 --- a/src/com/vaadin/ui/DateField.java +++ b/src/com/vaadin/ui/DateField.java @@ -116,6 +116,8 @@ public class DateField extends AbstractField { */ private String dateString; + private boolean lenient = false; + /* Constructors */ /** @@ -202,6 +204,10 @@ public class DateField extends AbstractField { target.addAttribute("format", dateFormat); } + if (!isLenient()) { + target.addAttribute("strict", true); + } + target.addAttribute("type", type); // Gets the calendar @@ -511,4 +517,32 @@ public class DateField extends AbstractField { return dateFormat; } + /** + * Specifies whether or not date/time interpretation in component is to be + * lenient. + * + * @see Calendar#setLenient(boolean) + * @see #isLenient() + * + * @param lenient + * true if the lenient mode is to be turned on; false if it is to + * be turned off. + */ + public void setLenient(boolean lenient) { + this.lenient = lenient; + requestRepaint(); + } + + /** + * Specifies whether or not date/time interpretation is to be lenient. + * + * @see #setLenient(boolean) + * + * @return true if the interpretation mode of this calendar is lenient; + * false otherwise. + */ + public boolean isLenient() { + return lenient; + } + } diff --git a/tests/src/com/vaadin/tests/components/datefield/LenientMode.java b/tests/src/com/vaadin/tests/components/datefield/LenientMode.java new file mode 100644 index 0000000000..96eaa2117f --- /dev/null +++ b/tests/src/com/vaadin/tests/components/datefield/LenientMode.java @@ -0,0 +1,80 @@ +package com.vaadin.tests.components.datefield; + +import java.util.Date; +import java.util.Locale; + +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.tests.components.TestBase; +import com.vaadin.ui.Button; +import com.vaadin.ui.DateField; + +public class LenientMode extends TestBase implements ValueChangeListener { + + private static final long serialVersionUID = -9064553409580072387L; + + @Override + protected String getDescription() { + return "In lenien mode DateField should accept date input from user like '32/12/09'. In normal mode, an exception should be thrown. "; + } + + @Override + protected Integer getTicketNumber() { + return 3175; + } + + @Override + protected void setup() { + + Date d = new Date(2009 - 1900, 12 - 1, 31, 23, 59, 59); + + DateField df = new DateField("Lenient "); + df.setLocale(new Locale("fi")); + df.setResolution(DateField.RESOLUTION_DAY); + df.setLenient(true); + df.setImmediate(true); + df.setValue(d); + + DateField df2 = new DateField("Normal "); + df2.setLocale(new Locale("fi")); + df2.setResolution(DateField.RESOLUTION_DAY); + // df2.setLenient(false); + df2.setValue(null); + df2.setImmediate(true); + df2.setValue(d); + + addComponent(df); + addComponent(df2); + + df.addListener(this); + df2.addListener(this); + + df = new DateField("Lenient with time"); + df.setLocale(new Locale("fi")); + df.setLenient(true); + df.setImmediate(true); + df.setValue(d); + + df2 = new DateField("Normal with time"); + df2.setLocale(new Locale("fi")); + // df2.setLenient(false); + df2.setValue(null); + df2.setImmediate(true); + df2.setValue(d); + + addComponent(df); + addComponent(df2); + + df.addListener(this); + df2.addListener(this); + + addComponent(new Button("Visit server")); + + } + + public void valueChange(ValueChangeEvent event) { + getMainWindow().showNotification( + "New value" + event.getProperty().getValue()); + + } +}