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
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.
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.
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.
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.
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.
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>
.