jsf 2 - filtrar - Primefaces datatable data range filter with filterFunction
filtrar tabla primefaces (4)
Encontré el mismo problema pero en mi caso era un botón en la faceta del filtro para mostrar el panel de superposición con el deslizador de rango dentro.
Para resolverlo, use faceta de encabezado en su lugar:
<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>
Además, no es necesario el atributo de actualización en los componentes p: ajax.
Utilizo el método filterFunction de datatable en primefaces 5.0. Quiero filtrar cumpleaños por rango de fecha en el encabezado de la columna.
En la consola del navegador recibo este 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>
Tabla de datos :
<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>
Frijol:
@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
Y tengo una clase de persona POJO simple que contiene solo ''nombre'' y ''cumpleaños''.
¿Cómo puedo filtrar el cumpleaños según las fechas que ingresé en el encabezado de la columna? Veo que obtiene el componente UIPanel en lugar de valores de fechas. (Supongo que espera un componente con un valor, y cuando encuentra dos componentes, devuelve el componente contenedor en sí, ¿verdad?)
Gracias
Si no quiere el truco con el campo de entrada oculto más el beneficio de reutilizar dicho filtro, considere escribir un componente compuesto cuyo tipo de componente amplíe javax.faces.component.UIInput. Primefaces espera que el filtro sea un subtipo de javax.faces.component.ValueHolder.
Así es como se vería el componente compuesto.
<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>
Observe componentType="dateRange"
en la etiqueta cc:interface
. Esto hace referencia a la clase de componente raíz de este componente compuesto. Que es tan simple como
@FacesComponent("dateRange")
public class DateRangeComponent extends UIInput implements NamingContainer {
@Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
}
El valor que toma el componente compuesto es un POJO simple.
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;
}
}
Después de todo, el uso es muy 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>
También observe la función de filtro personalizado. Whis es tan simple como
public boolean filterByDate(Object value, Object filter, Locale locale) {
Date colDate = (Date) value;
return this.range.getFrom().before(colDate) && this.range.getTo().after(colDate);
}
Todo esto se verá así.
Solución avanzada:
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));
}
}
Esta es solo la implementación de Java 8 de la respuesta de @Stephan, utilizando la clase java.time en lugar de Date.
La sección JSF es casi idéntica, excepto el patrón de fecha.
<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>
Frijol:
@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));
}