null

Правильная фильтрация таблиц в Primefaces

Доброго времени суток.

При методике разработки ПО, известной как copying and pasting from StackOverflow, зачастую приходится сталкиваться со странными ошибками, которые решаются по той же методике, что и привела к их появлению - поиску ответа в интернете (хотя зачастую внимательного изучения примера из официальной документации бывает достаточно). Именно поэтому я пишу эту статью - я столкнулся с некоторой проблемой при фильтрации таблиц (dataTable) в чудесной библиотеке компонентов Primefaces (в её реализации для JSF). 

Итак, начнём с простейшего примера - без какой-либо фильтрации, только таблица.
 

@Data
@ManagedBean
@ViewScoped
public class TableTestBean implements Serializable {

    private List<TableRow> rows;

    @PostConstruct
    public void init() {
        rows = fillData();
    }

    private List<TableRow> fillData() {
        // fill some data
    }


    @Data
    @AllArgsConstructor
    public static class TableRow implements Serializable {
        private long id;
        private String stringData;
        private String anotherStringData;
    }
}
<?xml version="1.0"?>

<f:view
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:p="http://primefaces.org/ui"
        xmlns:ui="http://java.sun.com/jsf/facelets"
>
    <h:head>
    </h:head>
    <h:body>
        <h:form>
            <p:dataTable value="#{tableTestBean.rows}" var="row">
                <p:column headerText="Id">
                    <h:outputText value="#{row.id}"/>
                </p:column>
                <p:column headerText="First data">
                    <h:outputText value="#{row.stringData}"/>
                </p:column>
                <p:column headerText="Second data">
                    <h:outputText value="#{row.anotherStringData}"/>
                </p:column>
            </p:dataTable>
        </h:form>
    </h:body>
</f:view>

Вполне предсказуемо, этот пример работает. Попробуем расширить его - добавим фильтрацию по колонкам.

<p:dataTable value="#{tableTestBean.rows}" var="row">
    <p:column headerText="Id">
        <h:outputText value="#{row.id}"/>
    </p:column>
    <p:column headerText="First data" filterBy="#{row.stringData}" filterMatchMode="contains">
        <h:outputText value="#{row.stringData}"/>
    </p:column>
    <p:column headerText="Second data" filterBy="#{row.anotherStringData}" filterMatchMode="contains">
        <h:outputText value="#{row.anotherStringData}"/>
    </p:column>
</p:dataTable>

Вроде бы всё так, как и должно быть - таблица успешно фильтруется.

До применения фильтра:

После применения фильтра:

 

Фильтр по другой колонке:

Но данный пример начинает себя вести неадекватно, стоит лишь добавить в него какие-либо инпуты.

<p:dataTable value="#{tableTestBean.rows}" var="row">
    <p:column headerText="Id">
        <h:outputText value="#{row.id}"/>
    </p:column>
    <p:column headerText="First data" filterBy="#{row.stringData}" filterMatchMode="contains">
        <h:outputText value="#{row.stringData}"/>
    </p:column>
    <p:column headerText="Second data" filterBy="#{row.anotherStringData}" filterMatchMode="contains">
        <p:inputText value="#{row.anotherStringData}" style="display: inline-block; width: 200px">
            <p:ajax process="@this" update="@this out"/>
        </p:inputText>
        <h:outputText id="out" value="#{row.anotherStringData.toUpperCase()}"/>
    </p:column>
</p:dataTable>

В третью колонку таблицы был добавлен инпут для редактирования соответствующего значения. И редактирование работает.

До редактирования:

После:


Но стоит лишь начать фильтровать, как происходит странная магия - первая строка начинает заполняться другими значениями.

До фильтрации:

После фильтрации:

После сброса фильтра (третья ячейка в первой строке изменилась):

Решение проблемы оказалось очень простым - нужно было лишь внимательно почитать документацию, из которой можно узнать, что при фильтрации таблицы необходимо указывать атрибут filteredValue, который будет хранить список строк, соответствующих фильтру. Таким образом, вносим изменения в бин: добавляем ещё один список для  сохранения отфильтрованных значений, при инициализации заполняем его значениями из исходного списка.

private List<TableRow> filteredRows;

@PostConstruct
public void init() {
    rows = fillData();
    filteredRows = new ArrayList<>(rows);
}

и указываем новый атрибут у dataTable

<p:dataTable value="#{tableTestBean.rows}" filteredValue="#{tableTestBean.filteredRows}" var="row">

Проверяем работу.

До фильтра:

После фильтра:

После сброса фильтра (первая строка не изменилась):

Как видно, теперь при сбросе фильтра всё работает - значения в строках остаются теми же, что и до фильтрации.
Мораль проста: внимательно читайте документацию, чтобы не наталкиваться на такие досадные проблемы.
Засим откланиваюсь, прощайте.