Primefaces datatable date range filter with filterFunction

前端 未结 4 1439
栀梦
栀梦 2020-12-08 12:13

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:

<
相关标签:
4条回答
  • 2020-12-08 12:37

    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));
        }
    
    0 讨论(0)
  • 2020-12-08 12:47

    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.

    daterangefilter

    0 讨论(0)
  • 2020-12-08 12:49

    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.

    0 讨论(0)
  • 2020-12-08 13:02

    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));
        }
    }
    
    0 讨论(0)
提交回复
热议问题