使用p:calendar在jsf h:datatable中进行交叉字段验证

时间:2012-06-21 21:28:34

标签: java jsf jsf-2 primefaces

我注意到这个问题已经被问到,但是没有正确回答。

我有一个数据表有两列开始日期结束日期。两者都包含primefaces p:日历控件。我需要确保对于每一行,column1中的日期不在column2中的日期之后。我想把它绑定到JSF验证框架,但我遇到了麻烦。

我已经尝试将数据表标记为rowStatePreserved =“true”,这允许我获取值,但是仍然存在错误,因为当它失败时,第一行中的所有值都会覆盖所有其他值。我做错了什么,或者我应该采用完全不同的策略?

xhtml代码

    <h:form>
 <f:event type="postValidate" listener="#{bean.doCrossFieldValidation}"/>
       <p:dataTable id="eventDaysTable" value="#{course.courseSchedules}" var="_eventDay" styleClass="compactDataTable"
                                 >
                        <p:column id="eventDayStartColumn">
                            <f:facet name="header">
                                Start
                            </f:facet>
                            <p:calendar id="startDate" required="true"  value="#{_eventDay.startTime}" pattern="MM/dd/yyyy hh:mm a"/>
                        </p:column>
                        <p:column id="eventDayEndColumn">
                            <f:facet name="header">
                                End
                            </f:facet>
                            <p:calendar id="endDate" required="true"  value="#{_eventDay.endTime}" pattern="MM/dd/yyyy hh:mm a"/>
                        </p:column>                                        
                    </p:dataTable>
        </h:form>

validationCode

 public void doCrossFieldValidation(ComponentSystemEvent cse) {


        UIData eventsDaysStable = (UIData) cse.getComponent().findComponent("eventDaysTable");

        if (null != eventsDaysStable && eventsDaysStable.isRendered()) {

            Iterator<UIComponent> startDateCalendarIterator = eventsDaysStable.findComponent("eventDayStartColumn").getChildren().iterator();
            Iterator<UIComponent> endDateCalendarIterator = eventsDaysStable.findComponent("eventDayEndColumn").getChildren().iterator();

            while (startDateCalendarIterator.hasNext() && endDateCalendarIterator.hasNext()) {
                org.primefaces.component.calendar.Calendar startDateComponent = (org.primefaces.component.calendar.Calendar) startDateCalendarIterator.next();
                org.primefaces.component.calendar.Calendar endDateComponent = (org.primefaces.component.calendar.Calendar) endDateCalendarIterator.next();

                Date startDate = (Date) startDateComponent.getValue();
                Date endDate = (Date) endDateComponent.getValue();


                if (null != startDate && null != endDate && startDate.after(endDate)) {
                    eventScheduleChronologyOk = false;
                    startDateComponent.setValid(false);
                    endDateComponent.setValid(false);
                }

            }

            if (!eventScheduleChronologyOk) {
                showErrorMessage(ProductManagementMessage.PRODUCT_SCHEDULE_OUT_OF_ORDER);
            }

        }

    }

1 个答案:

答案 0 :(得分:16)

  

我做错了什么

以非标准JSF方式在数据表的上下文之外执行验证。行数据仅在时可用,您(或JSF)在数据表上进行迭代。根据当前的数据表迭代轮次,每个列中只有一个<p:calendar>组件具有多个不同的状态。当您没有迭代数据表时,这些状态不可用。您只能获得null作为值。

从技术上讲,到目前为止,使用不同的验证方法,您应该在UIData组件上调用visitTree()方法并在VisitCallback实现中执行该作业。这将迭代数据表。

例如,

dataTable.visitTree(VisitContext.createVisitContext(), new VisitCallback() {
    @Override
    public VisitResult visit(VisitContext context, UIComponent component) {
        // Check if component is instance of <p:calendar> and collect its value by its ID.

        return VisitResult.ACCEPT;
    }
});

这只是笨拙的。这为您提供了每一行,您需要自己维护和检查行索引并收集值。请注意,调用UIInput#setValid()也应该在VisitCallback实现中完成。


  

或者我应该使用完全不同的策略吗?

是的,使用标准JSF方式的正常Validator。您可以将一个组件作为另一个组件的属性传递。

E.g。

<p:column>
    <p:calendar binding="#{startDateComponent}" id="startDate" required="true"  value="#{item.start}" pattern="MM/dd/yyyy hh:mm a"/>
</p:column>
<p:column >
    <p:calendar id="endDate" required="true"  value="#{item.end}" pattern="MM/dd/yyyy hh:mm a">
        <f:validator validatorId="dateRangeValidator" />
        <f:attribute name="startDateComponent" value="#{startDateComponent}" />
    </p:calendar>
</p:column>                                        

@FacesValidator("dateRangeValidator")
public class DateRangeValidator implements Validator {

    @Override
    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        if (value == null) {
            return; // Let required="true" handle.
        }

        UIInput startDateComponent = (UIInput) component.getAttributes().get("startDateComponent");

        if (!startDateComponent.isValid()) {
            return; // Already invalidated. Don't care about it then.
        }

        Date startDate = (Date) startDateComponent.getValue();

        if (startDate == null) {
            return; // Let required="true" handle.
        }

        Date endDate = (Date) value;

        if (startDate.after(endDate)) {
            startDateComponent.setValid(false);
            throw new ValidatorException(new FacesMessage(
                FacesMessage.SEVERITY_ERROR, "Start date may not be after end date.", null));
        }
    }

}

由于两个组件都在同一行,每次调用此验证程序时,startDateComponent将“自动”在getValue()上返回正确的值。请注意,此验证器也可以在数据表之外重复使用,而您的初始方法不是。

或者,您可以使用OmniFaces <o:validateOrder>作为完整解决方案。它的showcase示例甚至展示了<p:calendar><p:dataTable>组件的特定用例。