我注意到这个问题被问到了,但是没有正确回答。
我有一个有两列的数据表 开始日期 和 结束日期。两者都包含primefaces p:日历控件。我需要确保对于每一行,column1中的日期不在column2中的日期之后。我想把它绑定到JSF验证框架,但我遇到了麻烦。
我已经尝试将datatable标记为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>
验证码
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);
}
}
}
我究竟做错了什么
以非标准JSF方式在数据表的上下文之外执行验证。行数据仅可用 而 你是(或JSF)迭代数据表。实际上只有一个 <p:calendar>
每个列中的组件具有多个不同的状态,具体取决于当前可数据表的迭代轮次。当您没有迭代数据表时,这些状态不可用。你只会得到 null
作为价值。
从技术上讲,到目前为止,您使用不同的验证方法,您应该调用 visitTree()
关于的方法 UIData
组件和执行工作 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
实现。
或者我应该使用完全不同的策略?
是的,使用正常 Validator
标准的JSF方式。您可以将一个组件作为另一个组件的属性传递。
例如。
<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>
作为一个完整的解决方它的 橱窗 示例甚至展示了这个特定的用例 <p:calendar>
里面的组件 <p:dataTable>
。
我究竟做错了什么
以非标准JSF方式在数据表的上下文之外执行验证。行数据仅可用 而 你是(或JSF)迭代数据表。实际上只有一个 <p:calendar>
每个列中的组件具有多个不同的状态,具体取决于当前可数据表的迭代轮次。当您没有迭代数据表时,这些状态不可用。你只会得到 null
作为价值。
从技术上讲,到目前为止,您使用不同的验证方法,您应该调用 visitTree()
关于的方法 UIData
组件和执行工作 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
实现。
或者我应该使用完全不同的策略?
是的,使用正常 Validator
标准的JSF方式。您可以将一个组件作为另一个组件的属性传递。
例如。
<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>
作为一个完整的解决方它的 橱窗 示例甚至展示了这个特定的用例 <p:calendar>
里面的组件 <p:dataTable>
。