I use filterFunction method of datatable on primefaces 5.0. I want to filter birthday by date range on column header.
On browser console I receive this error:
<?xml version="1.0"
encoding="utf-8"?><partial-response><error><error-name>java.lang.ClassCastException</error-name><error-message><![CDATA[javax.faces.component.UIPanel
cannot be cast to
javax.faces.component.ValueHolder]]></error-message></error></partial-response>
Datatable :
<p:dataTable var="person" value="#{testDateRange.persons}"
id="personTable" paginator="true" styleClass="customTableStyle" editable="true"
rows="10" resizableColumns="true"
emptyMessage="No persons"
filteredValue="#{testDateRange.filteredPersons}"
widgetVar="dateRangeWidget" >
<p:column id="nameId" filterBy="name" sortBy="name" filterMatchMode="contains" headerText="Name">
<h:outputText value="#{person.name}" />
</p:column>
<p:column id="birthdayId" headerText="birthday" filterBy="birthday" filterFunction="#{testDateRange.filterByDate}">
<f:facet name="filter">
<p:calendar id="from" value="#{testDateRange.dateFrom}" styleClass="customCalendar" pattern="dd/MM/yyyy">
<p:ajax event="dateSelect" oncomplete="PF('dateRangeWidget').filter()" update="personTable"/>
</p:calendar>
<p:calendar id="to" value="#{testDateRange.dateTo}" styleClass="customCalendar" pattern="dd/MM/yyyy">
<p:ajax event="dateSelect" oncomplete="PF('dateRangeWidget').filter()" update="personTable"/>
</p:calendar>
</f:facet>
<h:outputText value="#{person.birthday}" >
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:outputText>
</p:column>
</p:dataTable>
Bean:
@Component("testDateRange")
@Scope("session")
public class TestDateRangeBean {
private List<Person> persons;
List<Person> filteredPersons;
private Date dateFrom;
private Date dateTo;
public TestDateRangeBean() {
persons = new ArrayList<>(Arrays.asList(
new Person("John", new Date(1357016400000L)),
new Person("Will",new Date(1357102800000L)),
new Person("Peter",new Date(1414900800000L)),
new Person("Cris", new Date(1438747200000L)),
new Person("Cemil", new Date(1436068800000L))
));
}
public boolean filterByDate(Object value, Object filter, Locale locale) {
// it fails before calling this method
String filterText = (filter == null) ? null : filter.toString().trim();
if(StringUtils.isEmpty(filterText)) {
return true;
}
if(value == null) {
return false;
}
DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
Date filterDate;
try {
filterDate = df.parse(filterText);
} catch (ParseException e) {
return false;
}
return filterDate.after(dateFrom) && filterDate.before(dateTo);
}
//all the getters and setters

And I have a simple POJO Person class that contains only 'name' and 'birthday'.
How can I filter birthday by the dates I enter on column header. I see that it gets the UIPanel component instead of dates values. (I assume it expects one component with a value, and when it finds two components, it returns the container component itself, am I right?)
Thanks
Advanced solution:
JSF:
<p:column headerText="#{msg.date}" sortBy="#{bean.date}" filterBy="#{bean.date}" filterFunction="#{dateRangeFilter.filterByDate}">
<f:facet name="filter">
<h:inputHidden id="filter" />
</f:facet>
<f:facet name="header">
<h:outputText value="#{msg.date}" />
<br />
<p:calendar id="from" pattern="dd.MM.yyyy">
<p:ajax event="dateSelect" onstart="$(PrimeFaces.escapeClientId('#{p:component('filter')}'))[0].value = $(PrimeFaces.escapeClientId('#{p:component('from')}_input'))[0].value + '-' + $(PrimeFaces.escapeClientId('#{p:component('to')}_input'))[0].value" oncomplete="PF('dataTable').filter()" />
</p:calendar>
<p:calendar id="to" pattern="dd.MM.yyyy">
<p:ajax event="dateSelect" onstart="$(PrimeFaces.escapeClientId('#{p:component('filter')}'))[0].value = $(PrimeFaces.escapeClientId('#{p:component('from')}_input'))[0].value + '-' + $(PrimeFaces.escapeClientId('#{p:component('to')}_input'))[0].value" oncomplete="PF('dataTable').filter()" />
</p:calendar>
</f:facet>
<h:outputText value="#{bean.date}">
<f:convertDateTime type="date" dateStyle="medium" />
</h:outputText>
</p:column>
Filter Bean:
@Named
@ApplicationScoped
public class DateRangeFilter implements Serializable {
private static final Logger LOG = Logger.getLogger(DateRangeFilter.class.getName());
public boolean filterByDate(Object value, Object filter, Locale locale) {
String filterText = (filter == null) ? null : filter.toString().trim();
if (filterText == null || filterText.isEmpty()) {
return true;
}
if (value == null) {
return false;
}
DateFormat df = SimpleDateFormat.getDateInstance(SimpleDateFormat.MEDIUM);
Date filterDate = (Date) value;
Date dateFrom;
Date dateTo;
try {
String fromPart = filterText.substring(0, filterText.indexOf("-"));
String toPart = filterText.substring(filterText.indexOf("-") + 1);
dateFrom = fromPart.isEmpty() ? null : df.parse(fromPart);
dateTo = toPart.isEmpty() ? null : df.parse(toPart);
} catch (ParseException pe) {
LOG.log(Level.SEVERE, "unable to parse date: " + filterText, pe);
return false;
}
return (dateFrom == null || filterDate.after(dateFrom)) && (dateTo == null || filterDate.before(dateTo));
}
}
If you do not want the trickery with the hidden input field plus the benefit of reusing such a filter consider writing a composite component whose component type extends javax.faces.component.UIInput. Primefaces expects the filter to be a subtype of javax.faces.component.ValueHolder.
This is how the composite component might look like.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<!-- INTERFACE -->
<cc:interface componentType="dateRange">
<cc:attribute name="fromLabel"/>
<cc:attribute name="toLabel"/>
<cc:attribute name="value" type="so.example.DateRange"/>
<cc:attribute name="onvaluechanged"/>
</cc:interface>
<!-- IMPLEMENTATION -->
<cc:implementation>
<table>
<tr>
<td style="width: 15%; border: none;">
<h:outputText value="#{cc.attrs.fromLabel}"/>
</td>
<td style="width: 35%; border: none;">
<p:calendar id="startDateCalendar" value="#{cc.attrs.value.from}" maxdate="#{cc.attrs.value.to}">
<p:ajax event="dateSelect" update="endDateCalendar" oncomplete="#{cc.attrs.onvaluechanged}"/>
</p:calendar>
</td>
<td style="width: 15%; border: none;">
<h:outputText value="#{cc.attrs.toLabel}"/>
</td>
<td style="width: 35%; border: none;">
<p:calendar id="endDateCalendar" value="#{cc.attrs.value.to}" mindate="#{cc.attrs.value.from}" maxdate="#{cc.attrs.value.now}">
<p:ajax event="dateSelect" update="startDateCalendar" oncomplete="#{cc.attrs.onvaluechanged}"/>
</p:calendar>
</td>
</tr>
</table>
</cc:implementation>
Notice componentType="dateRange"
in the cc:interface
tag. This references the root component class of this composite component. Which is as simple as.
@FacesComponent("dateRange")
public class DateRangeComponent extends UIInput implements NamingContainer {
@Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
}
The value that the composite component takes is a simple POJO.
public class DateRange implements Serializable {
private Date from;
private Date to;
private boolean ignoreTime = true;
public Date getFrom() {
return from;
}
public void setFrom(Date from) {
if (this.isIgnoreTime()) {
Calendar now = Calendar.getInstance();
now.setTime(from);
now.set(Calendar.HOUR_OF_DAY, 0);
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
this.from = now.getTime();
} else {
this.from = from;
}
}
public Date getTo() {
return to;
}
public void setTo(Date to) {
if (this.isIgnoreTime()) {
Calendar now = Calendar.getInstance();
now.setTime(to);
now.set(Calendar.HOUR_OF_DAY, 23);
now.set(Calendar.MINUTE, 59);
now.set(Calendar.SECOND, 59);
this.to = now.getTime();
} else {
this.to = to;
}
}
public Date getNow() {
return new Date();
}
public boolean isIgnoreTime() {
return ignoreTime;
}
public void setIgnoreTime(boolean ignoreTime) {
this.ignoreTime = ignoreTime;
}
}
After all that the usage is very simple.
<p:column headerText="#{labels.date}"
sortBy="#{logIn.loginDate}"
filterBy="#{logIn.loginDate}"
filterFunction="#{logInTableBean.filterByDate}"
styleClass="datetime-column">
<f:facet name="filter">
<clx:dateRange fromLabel="#{labels.from}"
toLabel="#{labels.to}"
onvaluechanged="PF('logInTable').filter();"
value="#{logInTableBean.range}"/>
</f:facet>
<h:outputText value="#{logIn.loginDate}">
<f:convertDateTime type="both" dateStyle="long"/>
</h:outputText>
</p:column>
Also notice the custom filter function. Whis is as simple as
public boolean filterByDate(Object value, Object filter, Locale locale) {
Date colDate = (Date) value;
return this.range.getFrom().before(colDate) && this.range.getTo().after(colDate);
}
The whole thing will look like this.

Encountered same problem but in my case it was button in filter facet to show overlay panel with range slider inside.
To solve it use header facet instead:
<f:facet name="filter">
<!-- to hide default filter input -->
<h:inputHidden />
</f:facet>
<f:facet name="header">
<p:outputLabel value="birthday" /><br />
<p:calendar id="from" value="#{testDateRange.dateFrom}" styleClass="customCalendar" pattern="dd/MM/yyyy">
<p:ajax event="dateSelect" oncomplete="PF('dateRangeWidget').filter()" />
</p:calendar>
<p:calendar id="to" value="#{testDateRange.dateTo}" styleClass="customCalendar" pattern="dd/MM/yyyy">
<p:ajax event="dateSelect" oncomplete="PF('dateRangeWidget').filter()" />
</p:calendar>
</f:facet>
Also there is no need in update attribute in p:ajax components.
This is just Java 8 implementation of @Stephan's answer, using java.time class instead of Date.
The JSF section is almost identical except the date pattern.
<p:column headerText="Date" filterBy="#{passbook.createdAt}" filterFunction="#{applicationController.filterByDate}">
<f:facet name="filter">
<h:inputHidden id="filter" />
</f:facet>
<f:facet name="header">
<h:outputText value="Date" />
<br />
<p:calendar id="from" pattern="dd-MMM-yyyy" size="12">
<p:ajax event="dateSelect" onstart="$(PrimeFaces.escapeClientId('#{p:component('filter')}'))[0].value = $(PrimeFaces.escapeClientId('#{p:component('from')}_input'))[0].value + '>' + $(PrimeFaces.escapeClientId('#{p:component('to')}_input'))[0].value" oncomplete="PF('passbookTable').filter()" />
</p:calendar>
<h:outputText class="fa fa-arrows-h fa-2x" style="vertical-align: top;"/>
<p:calendar id="to" pattern="dd-MMM-yyyy" size="12">
<p:ajax event="dateSelect" onstart="$(PrimeFaces.escapeClientId('#{p:component('filter')}'))[0].value = $(PrimeFaces.escapeClientId('#{p:component('from')}_input'))[0].value + '>' + $(PrimeFaces.escapeClientId('#{p:component('to')}_input'))[0].value" oncomplete="PF('passbookTable').filter()" />
</p:calendar>
</f:facet>
<h:outputText value="#{passbook.createdAt}">
<f:convertDateTime type="date" dateStyle="medium" pattern="dd-MMM-yyyy"/>
</h:outputText>
Bean:
@Named
@ApplicationScoped
public class ApplicationController implements Serializable {
private static final Logger logger = LogManager.getLogger(ApplicationController.class.getName());
public boolean filterByDate(Object value, Object filter, Locale locale) {
String filterText = (filter == null) ? null : filter.toString().trim();
if (filterText == null || filterText.isEmpty()) {
return true;
}
if (value == null) {
return false;
}
DateTimeFormatter sdf = DateTimeFormatter.ofPattern("dd-MMM-yyyy");
Instant instant = ((Date) value).toInstant(); //Zone : UTC+0
LocalDate dateValue = instant.atZone(ZoneId.of("Asia/Kolkata")).toLocalDate();
LocalDate dateFrom;
LocalDate dateTo;
try {
String fromPart = filterText.substring(0, filterText.indexOf(">"));
String toPart = filterText.substring(filterText.indexOf(">") + 1);
dateFrom = fromPart.isEmpty() ? null : LocalDate.parse(fromPart, sdf);
dateTo = toPart.isEmpty() ? null : LocalDate.parse(toPart, sdf);
} catch (Exception e) {
logger.error("unable to parse date: " + filterText, e);
return false;
}
return (dateFrom == null || dateValue.isAfter(dateFrom) || dateValue.isEqual(dateFrom))
&& (dateTo == null || dateValue.isBefore(dateTo) || dateValue.isEqual(dateTo));
}
来源:https://stackoverflow.com/questions/23791039/primefaces-datatable-date-range-filter-with-filterfunction