Datatable: Filtering broken for including dynamic column multiple times
See original GitHub issue1) Environment
- PrimeFaces version: 8.0
- Does it work on the newest released PrimeFaces version? Version? No
- Does it work on the newest sources in GitHub? (Build by source -> https://github.com/primefaces/primefaces/wiki/Building-From-Source): Tried it but couldnt get my project to work as the whole page rendering was messed up
- Application server + version: Tomcat 9.0.31
- Affected browsers: Firefox, Chrome
2) Expected behavior
When filtering a column for a LocalDateTime as start date then all rows should stay in place which are after that date
3) Actual behavior
The filtering for the specific column does not work as filterValue in filterFunction is always null. It is actually null because that filterValue is actually the value from the next columns filter
4) Steps to reproduce
Write a dynamic column which you include 2 or more times. Then filter the first one and it should use the last columns value in filter for filtering
5) Sample XHTML
Included composition in table:
<p:dataTable id="clientDatatable"
value="#{clientBean.entityList}"
tableStyle="table-layout: auto"
widgetVar="clientDatatableWidgetVar"
var="client">
<f:facet name="header">
<h:outputText value="#{msg['title.clients']}"/>
</f:facet>
<p:column headerText="#{msg['generic.name']}"
filterBy="#{client.name}"
filterMatchMode="contains"
sortBy="#{client.name}">
<h:outputText value="#{client.name}"/>
</p:column>
<ui:include src="../shared/timeColumnRangeFilter.xhtml">
<ui:param name="columnId" value="clientFirstSeenColumn"/>
<ui:param name="time" value="#{client.firstSeen}"/>
<ui:param name="headerMessageKey" value="#{msg['generic.firstSeen']}"/>
<ui:param name="datatableWidgetVar" value="clientDatatableWidgetVar"/>
<ui:param name="isColumnVisible" value="#{true}"/>
<ui:param name="isColumnExportable" value="#{true}"/>
<ui:param name="filterType" value="dateOnly"/>
</ui:include>
<ui:include src="../shared/timeColumnRangeFilter.xhtml">
<ui:param name="columnId" value="clientLastSeenColumn"/>
<ui:param name="time" value="#{client.lastSeen}"/>
<ui:param name="headerMessageKey" value="#{msg['generic.lastSeen']}"/>
<ui:param name="datatableWidgetVar" value="clientDatatableWidgetVar"/>
<ui:param name="isColumnVisible" value="#{true}"/>
<ui:param name="isColumnExportable" value="#{true}"/>
<ui:param name="filterType" value="dateOnly"/>
</ui:include>
<ui:include src="/pages/shared/clientStateColumn.xhtml">
<ui:param name="columnId" value="clientStateColumn"/>
<ui:param name="clientStatus" value="#{client.clientStatus}"/>
<ui:param name="headerMessageKey" value="ClientStatus"/>
<ui:param name="headerText" value="#{msg['ClientStatus']}"/>
<ui:param name="datatableWidgetVar" value="clientDatatableWidgetVar"/>
<ui:param name="isColumnVisible" value="#{true}"/>
<ui:param name="isColumnExportable" value="#{true}"/>
</ui:include>
</p:dataTable>
Actual column
<!-- filterType date only (Default) -->
<c:if test="#{empty filterType or filterType eq 'dateOnly'}">
<ui:param name="filterFunction" value="filterByDateRange"/>
<ui:param name="pattern" value="dd.MM.yyyy"/>
</c:if>
<!-- filterType DateTime -->
<c:if test="#{filterType eq 'dateTime'}">
<ui:param name="filterFunction" value="filterByDateTimeRange"/>
<ui:param name="pattern" value="dd.MM.yyyy HH:mm:ss"/>
</c:if>
<!-- filterType Time Only -->
<c:if test="#{filterType eq 'timeOnly'}">
<ui:param name="filterFunction" value="filterByTimeRange"/>
<ui:param name="pattern" value="HH:mm:ss"/>
</c:if>
<p:column id="#{columnId}"
filterBy="#{time}"
sortBy="#{time}"
exportFunction="#{columnExporter.panelGroupToOutputText}"
filterFunction="#{filterController[filterFunction]}"
filterMatchMode="contains" style="text-align: center; min-width: 170px"
exportHeaderValue="#{headerMessageKey}"
visible="#{isColumnVisible != null ? isColumnVisible : true}"
exportable="#{isColumnExportable != null ? isColumnExportable : true}"
toggleable="#{isColumnToggleable != null ? isColumnToggleable : true}">
<f:facet name="header">
<h:outputText value="#{headerMessageKey}"/>
<p:watermark
value="#{filterType eq 'timeOnly' ? msg['placeholder.chooseTime'] : msg['placeholder.chooseDate'] }"
for="#{columnId}"/>
<p:outputPanel style="text-align: center;">
<h:outputText value="#{msg['generic.from']}"/>
<p:calendar id="#{columnId}_from"
inputStyle="width: 105px"
widgetVar="#{columnId}_fromWidgetVar"
label="#{msg['generic.from']}"
navigator="true"
pattern="#{pattern}"
timeOnly="#{filterType eq 'timeOnly'}"
readonly="true"
showButtonPanel="true">
<p:ajax event="dateSelect"
global="false"
onstart="$(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_filter'), view).clientId}'))[0].value = $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_from'), view).clientId}_input'))[0].value + '-' + $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_to'), view).clientId}_input'))[0].value"
oncomplete="PF('#{datatableWidgetVar}').filter()"/>
</p:calendar>
<p:commandButton icon="fas fa-trash"
ignoreAutoUpdate="true"
global="false"
title="#{msg['generic.reset']}"
style="height: 25px; width: 25px; margin-left: 2px; top: 1px"
onstart="PF('#{columnId}_fromWidgetVar').setDate(null); $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_filter'), view).clientId}'))[0].value = $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_from'), view).clientId}_input'))[0].value + '-' + $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_to'), view).clientId}_input'))[0].value;"
oncomplete="PF('#{datatableWidgetVar}').filter()"/>
</p:outputPanel>
<p:outputPanel style="text-align: center;">
<h:outputText value="#{msg['generic.till']} "/>
<p:calendar id="#{columnId}_to"
inputStyle="width: 105px"
widgetVar="#{columnId}_toWidgetVar"
label="#{msg['generic.till']}"
navigator="true"
readonly="true"
timeOnly="#{filterType eq 'timeOnly'}"
showButtonPanel="true"
pattern="#{pattern}">
<p:ajax event="dateSelect"
global="false"
onstart="$(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_filter'), view).clientId}'))[0].value = $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_from'), view).clientId}_input'))[0].value + '-' + $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_to'), view).clientId}_input'))[0].value"
oncomplete="PF('#{datatableWidgetVar}').filter()"/>
</p:calendar>
<p:commandButton icon="fas fa-trash"
title="#{msg['generic.reset']}"
ignoreAutoUpdate="true"
global="false"
style="height: 25px; width: 25px; margin-left: 2px; top: 1px"
onstart="PF('#{columnId}_toWidgetVar').setDate(null); $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_filter'), view).clientId}'))[0].value = $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_from'), view).clientId}_input'))[0].value + '-' + $(PrimeFaces.escapeClientId('#{p:resolveFirstComponentWithId(columnId.concat('_to'), view).clientId}_input'))[0].value"
oncomplete="PF('#{datatableWidgetVar}').filter()"/>
</p:outputPanel>
</f:facet>
<f:facet name="filter">
<h:inputHidden id="#{columnId}_filter"/>
</f:facet>
<h:panelGroup id="timeRangeColumnPanelGroup#{columnId}"
styleClass="updatableColumnComponent">
<h:outputText value="#{time}">
<f:converter binding="#{localDateTimeConverter}"/>
<f:attribute name="pattern" value="dd.MM.yyyy HH:mm:ss"/>
</h:outputText>
</h:panelGroup>
</p:column>
6) Sample bean
7) Actual problem:
FilterFeature in Datatable changed from Primefaces 7.0 to 8.0.
In 7.0, the filter method was:
public void filter(FacesContext context, DataTable table, List<FilterMeta> filterMetadata, String globalFilterValue)
In 8.0 it is:
public void filter(FacesContext context, DataTable table, Map<String, FilterMeta> filterBy)
So the Map has the filterValue as Key which leads to the problem that 2 dynamic columns will result in the last one being added to the map and therefore filtering the wrong column.
Issue Analytics
- State:
- Created 4 years ago
- Comments:5 (3 by maintainers)
I have a similar problem. I am not using <p:column> directly, but always use a JSF custom tag (fullColumn.xhtml) which (among other things) includes filtering automatically by wrapping
As a result, the filterBy map has only one entry with key “property”, although there are many columns, which broke filtering after migrating to 8.0 But reading the PF code, I saw that FilterFeature.getFilterField() tries to use the column field attribute before falling back to resolving the filterByVE. Therefore, this solved the problem for me:
Closing this issue as the OP stated “But reading the PF code, I saw that FilterFeature.getFilterField() tries to use the column field attribute before falling back to resolving the filterByVE. Therefore, this solved the problem for me:” so using
Column#field
is the correct solution and closing ticket.