web-dev-qa-db-ja.com

filterFunctionを使用したPrimefacesデータテーブル日付範囲フィルター

Primefaces5.0でdatatableのfilterFunctionメソッドを使用しています。列ヘッダーの日付範囲で誕生日をフィルタリングしたい。

ブラウザコンソールで、次のエラーが表示されます。

<?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>

データ表 :

 <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>

豆:

@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

enter image description here

そして、「name」と「birthday」のみを含む単純なPOJOPersonクラスがあります。

列ヘッダーに入力した日付で誕生日をフィルタリングするにはどうすればよいですか。日付値の代わりにUIPanelコンポーネントを取得していることがわかります。 (値を持つ1つのコンポーネントを期待し、2つのコンポーネントが見つかると、コンテナーコンポーネント自体を返します。正しいですか?)

ありがとう

6
zekifh

高度なソリューション:

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>

フィルタ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));
    }
}
12
Stephan

非表示の入力フィールドを使ったトリックに加えて、そのようなフィルターを再利用する利点が必要ない場合は、コンポーネントタイプがjavax.faces.component.UIInputを拡張する複合コンポーネントを作成することを検討してください。 Primefacesは、フィルターがjavax.faces.component.ValueHolderのサブタイプであることを想定しています。

これは、複合コンポーネントがどのように見えるかです。

<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>

注意componentType="dateRange" の中に cc:interface 鬼ごっこ。これは、この複合コンポーネントのルートコンポーネントクラスを参照します。これはと同じくらい簡単です。

@FacesComponent("dateRange")
public class DateRangeComponent extends UIInput implements NamingContainer {

    @Override
    public String getFamily() {
        return UINamingContainer.COMPONENT_FAMILY;
    }

}

複合コンポーネントがとる値は、単純な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;
}
}

結局のところ、使用法は非常に簡単です。

            <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>

カスタムフィルター機能にも注目してください。 Whisはシンプルです

public boolean filterByDate(Object value, Object filter, Locale locale) {
    Date colDate = (Date) value;
    return this.range.getFrom().before(colDate) && this.range.getTo().after(colDate);
}

全体がこのようになります。

daterangefilter

15

同じ問題が発生しましたが、私の場合、範囲スライダーが内部にあるオーバーレイパネルを表示するのはフィルターファセットのボタンでした。

これを解決するには、代わりにヘッダーファセットを使用します。

    <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>

また、p:ajaxコンポーネントの属性を更新する必要はありません。

3
alexSunder

これは、Dateの代わりにJava.timeクラスを使用した、@ Stephanの回答のJava 8実装です。

JSFセクションは、日付パターンを除いてほとんど同じです。

<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>

豆:

@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));
    }
1
PC.