I noticed this question was asked, but it has not been answered correctly.
我注意到有人问了这个问题,但没有得到正确的回答。
I have a datatable that has two columns start date and end date. Both contain primefaces p:calendar controls in them. I need to ensure that for each row that the date in column1 is not after the date in column2. I would like to tie this into the JSF validation framework, but I'm having trouble.
我有一个datatable,它有两个列开始日期和结束日期。两者都包含了primefaces:calendar控件。我需要确保对于每一行,column1中的日期都不在column2中的日期之后。我想把它绑定到JSF验证框架中,但是我遇到了麻烦。
i've tried marking the datatable rowStatePreserved="true" , this allows me to get the values, but something is still wrong as when it fails, all the values in the first row overwrite all the other values. What am I doing wrong, or should I be using a completely different strategy?
我尝试过标记datatable rowstateretain ="true",这允许我获取值,但当它失败时,仍然有问题,第一行中的所有值将覆盖所有其他值。我做错了什么,或者我应该采用完全不同的策略吗?
xhtml code
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
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 个解决方案
#1
16
What am I doing wrong
我做错了什么
Performing validation outside the context of the datatable in a non-standard JSF way. The row data is only available while you're (or JSF is) iterating over the datatable. There's physically only one <p:calendar>
component in every column which has multiple different states depending on the current datatable iteration round. Those states are not available when you're not iterating over the datatable. You'll only get null
as value then.
以非标准的JSF方式在datatable的上下文之外执行验证。行数据只在您(或JSF)迭代数据表时可用。在物理上,每个列中只有一个
Technically, with your different validation approach so far, you should be invoking visitTree()
method on the UIData
component and performing the job in the VisitCallback
implementation. This will iterate over the datatable.
从技术上讲,到目前为止,使用不同的验证方法,您应该在UIData组件上调用visitTree()方法,并在VisitCallback实现中执行任务。这将迭代datatable。
For example,
例如,
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;
}
});
This is only clumsy. This gives you every single row, you'd need to maintain and check the row index youself and collect the values. Note that calling UIInput#setValid()
should also be done inside the VisitCallback
implementation.
这只是笨拙。这将为您提供每一行,您需要维护和检查行索引,并收集这些值。注意,调用UIInput#setValid()也应该在VisitCallback实现中完成。
or should I be using a completely different strategy?
或者我应该采用完全不同的策略?
Yes, use a normal Validator
the standard JSF way. You can pass the one component as an attribute of the other component.
是的,使用标准JSF方法的普通验证器。您可以将一个组件作为另一个组件的属性传递。
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>
with
与
@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));
}
}
}
As both components are in the same row, the startDateComponent
will "automagically" give the right value back on getValue()
everytime this validator is invoked. Note that this validator is also reuseable outside the datatable while your initial approach isn't.
由于两个组件都位于同一行,每次调用这个验证器时,startDateComponent都会“自动地”返回getValue()上的正确值。注意,这个验证器也可以在datatable之外重用,而您的初始方法则不行。
Alternatively, you can use OmniFaces <o:validateOrder>
as a complete solution. Its showcase example even shows off this specific use case of <p:calendar>
components inside a <p:dataTable>
.
或者,您可以使用OmniFaces
#1
16
What am I doing wrong
我做错了什么
Performing validation outside the context of the datatable in a non-standard JSF way. The row data is only available while you're (or JSF is) iterating over the datatable. There's physically only one <p:calendar>
component in every column which has multiple different states depending on the current datatable iteration round. Those states are not available when you're not iterating over the datatable. You'll only get null
as value then.
以非标准的JSF方式在datatable的上下文之外执行验证。行数据只在您(或JSF)迭代数据表时可用。在物理上,每个列中只有一个
Technically, with your different validation approach so far, you should be invoking visitTree()
method on the UIData
component and performing the job in the VisitCallback
implementation. This will iterate over the datatable.
从技术上讲,到目前为止,使用不同的验证方法,您应该在UIData组件上调用visitTree()方法,并在VisitCallback实现中执行任务。这将迭代datatable。
For example,
例如,
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;
}
});
This is only clumsy. This gives you every single row, you'd need to maintain and check the row index youself and collect the values. Note that calling UIInput#setValid()
should also be done inside the VisitCallback
implementation.
这只是笨拙。这将为您提供每一行,您需要维护和检查行索引,并收集这些值。注意,调用UIInput#setValid()也应该在VisitCallback实现中完成。
or should I be using a completely different strategy?
或者我应该采用完全不同的策略?
Yes, use a normal Validator
the standard JSF way. You can pass the one component as an attribute of the other component.
是的,使用标准JSF方法的普通验证器。您可以将一个组件作为另一个组件的属性传递。
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>
with
与
@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));
}
}
}
As both components are in the same row, the startDateComponent
will "automagically" give the right value back on getValue()
everytime this validator is invoked. Note that this validator is also reuseable outside the datatable while your initial approach isn't.
由于两个组件都位于同一行,每次调用这个验证器时,startDateComponent都会“自动地”返回getValue()上的正确值。注意,这个验证器也可以在datatable之外重用,而您的初始方法则不行。
Alternatively, you can use OmniFaces <o:validateOrder>
as a complete solution. Its showcase example even shows off this specific use case of <p:calendar>
components inside a <p:dataTable>
.
或者,您可以使用OmniFaces