如何在Vaadin中正确验证DateField?

时间:2013-11-18 08:03:25

标签: java validation vaadin vaadin7 datefield

我在验证DateFields时遇到了一些麻烦。 在我的应用程序中,我有一个具有DateField属性的表,用户应该可以通过按下编辑按钮来编辑它们。我还有一个提交字段的OK按钮和一个丢弃它们的取消按钮。

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

  • 首先,日期只能更改为当天直至9999-12-31。
  • 其次,我最好希望验证是动态的(如你所说)
  • 原始日期(进入编辑模式时已经在表格中的日期)可以是任何日期,您应该能够按原样提交这些日期,即使它们已经过去了。
  • 如果您将日期更改为无效日期(您仍然可以“手动”执行此操作,即直接在字段中,而不是在日期选择器中)或在DateField中输入无效字符,则应显示错误图标在您输入有效日期之前,不要让您提交更改。
  • 如果您将日期更改为无效日期(显示错误图标),然后更改为有效日期,则错误图标将消失。

我设法实施的当前行为执行以下操作:

  • 允许“原始日期” - 确定
  • 允许更改为有效日期 - 确定
  • 当更改为无效日期(可以“手动”,不使用日期选择器)并按字段中的Enter键时,该字段会立即重置为原始日期,但仍显示错误图标 - 不行
  • 输入无效字符(可以“手动”完成,不使用日期选择器)并在字段中按Enter键时,会在提交时抛出NPE,也不会显示错误图标 - 不行
  • 当更改为无效日期并按字段中的Enter键并返回有效日期并按字段中的Enter键时,错误图标仍然存在 - 不正常
  • 当更改为无效日期并按OK(即commit())时,该字段首先重置为原始日期,并且提交更改(即根本不对字段进行任何更改) - 不行

现在,我尝试实现一个包装器,以便我可以监听值的更改,但是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...
}

1 个答案:

答案 0 :(得分:0)

有点复杂,但我希望它有效(在Vaadin 7中) 我使用了一些Apache Commons和Joda-Time辅助方法 也许需要一些定制。

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
        }

    }

}

进入你的styles.css:

.truncated-date-field > input.v-datefield-textfield {
    display: none;
}