I recently brought the OmniFaces library into my project to make use of its Ajax utility, but since having done that, my PrimeFaces editable datatable is now ignoring validation errors.
I currently have a p:datatable with a custom validator and filter like so:
<p:dataTable var="ticket" value="#{myBean.tickets}"
id="ticketTable" widgetVar="ticketTable" editable="true"
rowKey="#{ticket.idTicket}"
filteredValue="#{myBean.filteredTickets}"
paginator="true" rows="20"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="#{myBean.rowsPerPageTemplate}">
<p:ajax event="rowEdit" listener="#{myBean.onEdit}"
/>
<p:ajax event="rowEditCancel"
listener="#{myBean.onCancel}" />
<p:column headerText="Title" sortBy="#{ticket.title}"
filterBy="#{ticket.title}">
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{ticket.title}" />
</f:facet>
<f:facet name="input">
<p:inputText value="#{ticket.title}" />
</f:facet>
</p:cellEditor>
</p:column>
<p:column sortBy="#{ticket.start}">
<f:facet name="header">
<h:panelGrid columns="1">
<h:outputText value="Start" />
<h:panelGrid columns="3">
<h:outputLabel value="From:" for="filterTripDateFrom" />
<p:calendar id="filterTripDateFrom"
value="#{myBean.filterStart}" navigator="true"
effect="fadeIn" pattern="MM/dd/yy" size="8">
<p:ajax event="dateSelect"
listener="#{myBean.filterDates()}"
update="ticketTable" />
</p:calendar>
<p:commandButton value="Clear"
action="#{myBean.clearStart()}"
update="filterTripDateFrom, ticketTable" />
</h:panelGrid>
</h:panelGrid>
</f:facet>
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{ticket.start}">
<f:convertDateTime pattern="EE, MMM dd, yyyy: HH:mm z" />
</h:outputText>
</f:facet>
<f:facet name="input">
<p:calendar value="#{ticket.start}" pattern="MM/dd/yy HH:mm"
stepMinute="15">
<f:validator validatorId="dateValidator" />
<f:attribute name="endDate"
value="#{editEndDate}" />
</p:calendar>
</f:facet>
</p:cellEditor>
</p:column>
<p:column sortBy="#{ticket.end}">
<f:facet name="header">
<h:panelGrid columns="1">
<h:outputText value="End" />
<h:panelGrid columns="3">
<h:outputLabel value="To:" for="filterTripDateTo" />
<p:calendar id="filterTripDateTo"
value="#{myBean.filterEnd}" navigator="true"
effect="fadeIn" pattern="MM/dd/yyyy" size="8">
<p:ajax event="dateSelect"
listener="#{myBean.filterDates()}"
update="ticketTable" />
</p:calendar>
<p:commandButton value="Clear"
action="#{myBean.clearEnd()}"
update="filterTripDateTo, ticketTable" />
</h:panelGrid>
</h:panelGrid>
</f:facet>
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{ticket.end}">
<f:convertDateTime pattern="EE, MMM dd, yyyy: HH:mm z" />
</h:outputText>
</f:facet>
<f:facet name="input">
<p:calendar value="#{ticket.end}" pattern="MM/dd/yyyy HH:mm"
stepMinute="15" binding="#{editEndDate}" />
</f:facet>
</p:cellEditor>
</p:column>
<p:column headerText="Options" style="width:50px">
<p:rowEditor />
</p:column>
</p:dataTable>
The behavior before adding OmniFaces was that if my custom date validator (provided below) threw a ValidatorException
, the row in the table that was being edited would stay open and the page would display the FacesMessage
from the exception.
After adding the OmniFaces library, the FacesMessage
still displays, but the row in the table is closed as if there was no exception thrown. I have tried using OmniFaces 1.2 & 1.3 SNAPSHOT, and both have the same behavior.
Is there anyway to get back the original functionality, or will I have to remove OmniFaces from my project?
Thank you for your help
addl info: Tomcat 7.0; MyFaces 2.1; PrimeFaces 3.4.1; OmniFaces 1.3 SNAPSHOT 20121027
My custom Date Validator:
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException
{
// get the submitted value for the start date
DateValidator.logger.debug("starting validation");
Date startDate = (Date) value;
// get the bound component that contains the end date
DateValidator.logger.debug("getting UI component");
UIInput endDateComponent = (UIInput) component.getAttributes().get(
"endDate");
// get the value of the bound component
DateValidator.logger.debug("getting second date");
String endDateString = (String) endDateComponent.getSubmittedValue();
// and parse it into a date
DateValidator.logger.debug("converting date");
Date endDate = JodaUtils
.stringToUtil(endDateString, "MM/dd/yyyy HH:mm");
// if either of the submitted values were empty, let the required tag
// take care of it
if (startDate == null || endDate == null)
{
DateValidator.logger.debug("a date was null; start: " + startDate
+ "; end: "
+ endDate);
return;
}
// otherwise if the start time is the same as, or before the end time
else if (startDate.getTime() >= endDate.getTime())
{
DateValidator.logger
.debug("end date was the same as or before start date; start: "
+ startDate + "; end: " + endDate);
// set the bound component as invalid
endDateComponent.setValid(false);
// update the container containing the components to show that the
// fields were invalid
Ajax.update(endDateComponent.getParent().getClientId());
// and send a notification to the front end
throw new ValidatorException(new FacesMessage(
FacesMessage.SEVERITY_ERROR,
"The end time must be after the start time.",
"The end time must be after the start time."));
}
else
{
DateValidator.logger.debug("all clear; start: " + startDate
+ "; end: "
+ endDate);
}
Ajax.update(":form:ticketTable");
}
I was able to reproduce your problem. This is basically caused by the combination of MyFaces and OmniPartialResponseWriter
. MyFaces' standard PartialResponseWriter
doesn't delegate all methods to the getWrapped()
method, but instead delegates to the local wrapped
variable directly. In Mojarra, all methods are delegated to getWrapped()
and this could thus be overridden in the component library specific PartialResponseWriter
implementation.
Your code works fine in Mojarra, but in MyFaces the PartialResponseWriter#getWrapped()
method isn't called (the OmniFaces one would return the PrimeFaces one), instead the local wrapped
instance is been referenced (which is in case of OmniPartialResponseWriter
just MyFaces own one instead of the PrimeFaces one) which caused the PrimePartialResponseWriter
of PrimeFaces being completely skipped. It could thus not add the following extension to the XML response which contains information for PrimeFaces ajax engine that JSF validation has failed.
<extension ln="primefaces" type="args">{"validationFailed":true}</extension>
This is a rather unfortunate problem. This issue is been fixed in OmniFaces 1.3. The solution was to generate a bunch of PartialResponseWriter
delegate methods anyway (which defeats the whole wrapper design pattern) even when they don't need to be implemented.
I'm not sure if I have to blame MyFaces for not delegating to getWrapped()
or not. The PartialResponseWriter
documentation isn't explicit enough about it, but it'd make sense to me that such an approach is obvious in wrapper design pattern. It would save you from writing/generating a bunch of delegate methods in every single implementation.
Unrelated to the concrete problem, given that you're using OmniFaces already, you could also use its <o:validateOrder>
instead of the custom validator.
<f:facet name="input">
<p:calendar id="start" value="#{ticket.start}" pattern="MM/dd/yy HH:mm" stepMinute="15" />
</f:facet>
...
<f:facet name="input">
<p:calendar id="end" value="#{ticket.end}" pattern="MM/dd/yyyy HH:mm" stepMinute="15" binding="#{editEndDate}" />
<o:validateOrder components="start end" message="The end time must be after the start time." />
</f:facet>
That's basically all you need.
As to your Ajax#update()
approach in case of validation failure, I'm unsure why exactly you need this, PrimeFaces already updates the entire row in case of validation failure. In case of validation success, you might want to call it in the method behind #{myBean.onEdit}
instead.