Java 如何在Vaadin中正确验证日期字段?

Java 如何在Vaadin中正确验证日期字段?,java,validation,vaadin,vaadin7,datefield,Java,Validation,Vaadin,Vaadin7,Datefield,我在验证日期字段时遇到一些问题。 在我的应用程序中,我有一个带有DateField属性的表,用户应该能够通过按编辑按钮来编辑该表。我还有一个提交字段的OK按钮和一个丢弃字段的cancel按钮 以下是我想要实现的目标(当然,必须遵循一些简洁的规则): 首先,日期只能更改为9999-12-31之前的当前日期 其次,我更希望验证是动态的(当您键入时) 原始日期(进入编辑模式时已在表中的日期)可以是任何日期,您应该能够按原样提交这些日期,即使它们是在过去 如果您将日期更改为无效日期(您仍然可以“手动”

我在验证日期字段时遇到一些问题。 在我的应用程序中,我有一个带有DateField属性的表,用户应该能够通过按编辑按钮来编辑该表。我还有一个提交字段的OK按钮和一个丢弃字段的cancel按钮

以下是我想要实现的目标(当然,必须遵循一些简洁的规则):

  • 首先,日期只能更改为9999-12-31之前的当前日期
  • 其次,我更希望验证是动态的(当您键入时)
  • 原始日期(进入编辑模式时已在表中的日期)可以是任何日期,您应该能够按原样提交这些日期,即使它们是在过去
  • 如果您将日期更改为无效日期(您仍然可以“手动”执行此操作,即直接在字段中,而不是在日期选择器中)或在日期字段中输入无效字符,则应显示错误图标并显示一条消息,在输入有效日期之前不允许您提交更改
  • 如果将日期更改为无效日期(显示错误图标),然后更改为有效日期,则错误图标应消失
我设法实施的当前行为执行以下操作:

  • 允许“原始日期”-确定
  • 允许更改为有效日期-确定
  • 当更改为无效日期(可以“手动”完成,不使用日期选择器)并在字段中按enter键时,该字段将立即重置为原始日期,但错误图标仍显示-不确定
  • 输入无效字符(可以“手动”完成,不使用日期选择器)并在字段中按enter键时,提交时将抛出NPE,并且不会显示错误图标-不确定
  • 当更改为无效日期并在字段中按enter键,然后返回到有效日期并在字段中按enter键时,错误图标仍然存在-不确定
  • 更改为无效日期并按OK(即提交())时,字段首先重置为原始日期,并且更改(即对字段没有任何更改)已提交-不确定
现在,我尝试实现一个包装器,以便监听值更改,但DateField没有像TextField那样方便的方法(例如setTextChangeEventMode和setTextChangeTimeout)。我覆盖valueChange以解决一些问题,但它似乎只在更改为有效日期时调用,而不是在更改为无效日期时调用(在不使用日期选择器时,每次都必须按enter键)。。。相反,在后台调用另一个validate(),始终重置setValidationVisible()

我甚至尝试创建一个CustomDateRangeValidator,但发现它没有多大帮助

请帮我弄清楚这一点,我已经尝试了这么多的事情,现在我已经没有选择了

这是我的createField方法:

createField(){
    // some more code up here...

    if (propertyId.equals("Valid From")) {
        dField.setImmediate(true);

        dField.setRangeStart(new Date());
        dField.setRangeEnd(dateUtil.getDate(9999, 12, 31));
        dField.setDateOutOfRangeMessage("Date out of range!");

        @SuppressWarnings({ "unchecked", "rawtypes" })
        TableDataValidatingWrapper<TextField> wField = new TableDataValidatingWrapper(dField);
        return wField;
    }

    // some more code down here...
}
public class TableDataValidatingWrapper<T> extends CustomField<T> {

    private static final long serialVersionUID = 1L;
    protected Field<T> delegate;

    public TableDataValidatingWrapper(final Field<T> delegate) {
        this.delegate = delegate;

        if (delegate instanceof DateField) {
            final DateField dateField = (DateField) delegate;

            dateField.setCaption("");
            dateField.setImmediate(true);
            dateField.setInvalidAllowed(false);
            dateField.setInvalidCommitted(true);
            dateField.setValidationVisible(false);
            dateField.addValueChangeListener(new ValueChangeListener() {

                private static final long serialVersionUID = 1L;

                @Override
                public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
                    try {
                        dateField.validate();
                        dateField.setValidationVisible(false);
                    } catch (InvalidValueException ive) {
                        //handle exception
                    } catch (Exception e) {
                        //handle exception
                    }
                }
            });

        }
    }

//some other overridden methods here...
}
createField(){
//这里还有一些代码。。。
if(propertyId.equals(“生效日期”)){
dField.setImmediate(真);
dField.setRangeStart(新日期());
dField.setRangeEnd(dateUtil.getDate(9999,12,31));
dField.setDateOutOfRangeMessage(“日期超出范围!”);
@SuppressWarnings({“unchecked”,“rawtypes”})
TableDataValidatingWrapper wField=新的TableDataValidatingWrapper(dField);
返回wField;
    }
//下面还有一些代码。。。
}
。。。这是我的包装纸:

createField(){
    // some more code up here...

    if (propertyId.equals("Valid From")) {
        dField.setImmediate(true);

        dField.setRangeStart(new Date());
        dField.setRangeEnd(dateUtil.getDate(9999, 12, 31));
        dField.setDateOutOfRangeMessage("Date out of range!");

        @SuppressWarnings({ "unchecked", "rawtypes" })
        TableDataValidatingWrapper<TextField> wField = new TableDataValidatingWrapper(dField);
        return wField;
    }

    // some more code down here...
}
public class TableDataValidatingWrapper<T> extends CustomField<T> {

    private static final long serialVersionUID = 1L;
    protected Field<T> delegate;

    public TableDataValidatingWrapper(final Field<T> delegate) {
        this.delegate = delegate;

        if (delegate instanceof DateField) {
            final DateField dateField = (DateField) delegate;

            dateField.setCaption("");
            dateField.setImmediate(true);
            dateField.setInvalidAllowed(false);
            dateField.setInvalidCommitted(true);
            dateField.setValidationVisible(false);
            dateField.addValueChangeListener(new ValueChangeListener() {

                private static final long serialVersionUID = 1L;

                @Override
                public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
                    try {
                        dateField.validate();
                        dateField.setValidationVisible(false);
                    } catch (InvalidValueException ive) {
                        //handle exception
                    } catch (Exception e) {
                        //handle exception
                    }
                }
            });

        }
    }

//some other overridden methods here...
}
公共类TableDataValidatingWrapper扩展了CustomField{
私有静态最终长serialVersionUID=1L;
受保护领域代表;
公共TableDataValidatingWrapper(最终字段委托){
this.delegate=委托;
if(委托实例of DateField){
最终日期字段DateField=(DateField)委托;
dateField.setCaption(“”);
dateField.setImmediate(true);
dateField.setInvalidAllowed(false);
dateField.setInvalidCommitted(true);
dateField.setValidationVisible(false);
dateField.addValueChangeListener(新的ValueChangeListener(){
私有静态最终长serialVersionUID=1L;
@覆盖
public void valueChange(com.vaadin.data.Property.ValueChangeEvent事件){
试一试{
dateField.validate();
dateField.setValidationVisible(false);
}catch(InvalidValueException){
//处理异常
}catch(异常e){
//处理异常
                    }
                }
            });
        }
    }
//这里还有一些其他被重写的方法。。。
}

有点复杂,但我希望它能工作(在Vaadin 7中)。
我使用一些Apache Commons和Joda Time helper方法。
也许需要一些定制

public class MyDateField extends CustomField<Date> {

    private static final long serialVersionUID = 1L;
    private static final DateTimeFormatter DTF;

    static {
        DTF = DateTimeFormat.forPattern("yyyy-MM-dd"); // set timezone if needed
    }

    private TextField tf = new TextField();
    private DateField df = new DateField();
    private Date original;
    private Date minDay = new Date();
    private Date maxDay = new DateTime(9999, 12, 31, 23, 59).toDate();
    private boolean isInnerChange;
    private Date convertedDate;

    @Override
    protected Component initContent() {
        tf.setConverter(InnerConverter.INSTANCE);
        tf.setTextChangeEventMode(TextChangeEventMode.EAGER); // or LAZY
        tf.addTextChangeListener(new TextChangeListener() {
            private static final long serialVersionUID = 1L;

            @Override
            public void textChange(TextChangeEvent event) {
                int pos = tf.getCursorPosition();
                if (isValid(event.getText())) {
                    df.setComponentError(null);
                    isInnerChange = true;
                    df.setValue(convertedDate);
                } else {
                    df.setComponentError(InnerErrorMessage.INSTANCE);
                }
                tf.setCursorPosition(pos);
            }
        });
        df.setStyleName("truncated-date-field");
        df.addValueChangeListener(new Property.ValueChangeListener() {
            private static final long serialVersionUID = 1L;

            @Override
            public void valueChange(Property.ValueChangeEvent event) {
                if (!isInnerChange) {
                    Date d = df.getValue();
                    df.setComponentError(isValid(d) ? null : InnerErrorMessage.INSTANCE);
                    tf.setValue(d == null ? "" : DTF.print(d.getTime()));
                }
                isInnerChange = false;
            }
        });
        return new HorizontalLayout(tf, df);
    }

    @Override
    public void setPropertyDataSource( @SuppressWarnings("rawtypes") Property newDS) {
        tf.setPropertyDataSource(newDS);
        if (newDS != null && getType().isAssignableFrom(newDS.getType())) {
            original = (Date) newDS.getValue();
        } else {
            original = null;
        }
        df.setValue(original);
    }

    @Override
    public void commit() throws SourceException, InvalidValueException {
        ErrorMessage em = df.getComponentError();
        if (em != null) {
            throw new InvalidValueException(em.getFormattedHtmlMessage());
        }
        tf.commit();
    }

    @Override
    public Class<? extends Date> getType() {
        return Date.class;
    }

    private boolean isValid(String s) {
        s = StringUtils.trimToNull(s);
        if (s == null) {
            convertedDate = null;
            return true;
        }
        try {
            return isValid(DTF.parseDateTime(s).toDate());
        } catch (Exception e) {
            return false;
        }
    }

    private boolean isValid(Date d) {
        if (d == null || DateUtils.truncatedEquals(original, d, Calendar.DAY_OF_MONTH)) {
            convertedDate = d;
            return true;
        }
        if (DateUtils.truncatedCompareTo(minDay, d, Calendar.DAY_OF_MONTH) <= 0
                && DateUtils.truncatedCompareTo(maxDay, d, Calendar.DAY_OF_MONTH) >= 0) {
            convertedDate = d;
            return true;
        }
        return false;
    }

    // other methods if needed

    private static class InnerErrorMessage implements ErrorMessage {

        private static final long serialVersionUID = 1L;
        private static final InnerErrorMessage INSTANCE = new InnerErrorMessage();

        @Override
        public String getFormattedHtmlMessage() {
            return "Invalid date!";
        }

        @Override
        public ErrorLevel getErrorLevel() {
            return ErrorLevel.ERROR;
        }

        private Object readResolve() {
            return INSTANCE; // preserves singleton property
        }

    }

    private static class InnerConverter implements Converter<String, Date> {

        private static final long serialVersionUID = 1L;
        private static final InnerConverter INSTANCE = new InnerConverter();

        @Override
        public Date convertToModel(String value, Class<? extends Date> targetType, Locale locale)
                throws ConversionException {
            String s = StringUtils.trimToNull(value);
            if (s == null) {
                return null;
            }
            try {
                return DTF.parseDateTime(s).toDate();
            } catch (Exception e) {
                throw new ConversionException(e);
            }
        }

        @Override
        public String convertToPresentation(Date value, Class<? extends String> targetType, Locale locale)
                throws ConversionException {
            return value == null ? "" : DTF.print(value.getTime());
        }

        @Override
        public Class<Date> getModelType() {
            return Date.class;
        }

        @Override
        public Class<String> getPresentationType() {
            return String.class;
        }

        private Object readResolve() {
            return INSTANCE; // preserves singleton property
        }

    }

}

你有什么解决方案吗?更新:Joda Time项目现在处于维护模式。它的创建者斯蒂芬·科尔伯恩(Stephen Colebourne)继续领导JSR 310,在java 8及更高版本中添加内置的java.time类。