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

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

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

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

xhtml代码

  

Start

End

validation码

  public void doCrossFieldValidation(ComponentSystemEvent cse) { UIData eventsDaysStable = (UIData) cse.getComponent().findComponent("eventDaysTable"); if (null != eventsDaysStable && eventsDaysStable.isRendered()) { Iterator startDateCalendarIterator = eventsDaysStable.findComponent("eventDayStartColumn").getChildren().iterator(); Iterator 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); } } } 

我究竟做错了什么

以非标准JSF方式在数据表的上下文之外执行validation。 行数据仅您(或JSF)迭代数据表时可用。 每个列中只有一个组件,它具有多个不同的状态,具体取决于当前的数据表迭代轮次。 当您没有迭代数据表时,这些状态不可用。 那么你只会得到null作为值。

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

例如,

 dataTable.visitTree(VisitContext.createVisitContext(), new VisitCallback() { @Override public VisitResult visit(VisitContext context, UIComponent component) { // Check if component is instance of  and collect its value by its ID. return VisitResult.ACCEPT; } }); 

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


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

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

例如

          

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

由于两个组件都在同一行,每次调用此validation器时, startDateComponent将“自动”在getValue()getValue()正确的值。 请注意,此validation器也可在数据表外部重用,而您的初始方法则不可。

或者,您可以使用OmniFaces 作为完整的解决方案。 它的展示示例甚至展示了组件的特定用例。