Интеграция Primefaces в приложение на Spring Boot. Часть 4 — Вывод списка данных в виде таблицы

от автора

Во второй части мы научились динамически переключать контент, который выводится в главной части страницы компонентом ui:include по клику на пункте меню. Теперь попробуем заполнить динамические инклуды полезной информацией. В моем случае полезной информацией является таблица, которая будет включать в себя данные, получаемые из списка, причем по каждому из полей таблицы мы реализуем фильтрацию по текстовому содержанию поля.

Пожалуй, трудно найти в библиотеке PrimeFaces компонент, у которого было бы больше разнообразных вариантов реализации, чем у компонента Data Table! На момент, когда пишутся эти строки, я насчитал 29 вариантов с разнообразными полезными плюшками и красивостями, причем каждый вариант часто представлен в 2–3 подвариантах, не исключено, что их со временем будет еще больше. Самый базовый вариант, где выводятся только строки таблицы без каких‑либо дополнений:

DataTable Basic. DataTable displays data in tabular format

Мы возьмем компонент Data Table Filter с простым дефолтным фильтром, который представлен дополнительными полями для фильтрации, расположенными над каждым полем/колонкой таблицы:

DataTable Filter. Filtering updates the data based on the constraints

Как обычно, реализация представлена отдельной xhtml-страницей с размещенным на ней компонентом и файлом управляемого бина компонента. Посмотрим на страницу:

<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"       xmlns:h="http://xmlns.jcp.org/jsf/html"       xmlns:f="http://xmlns.jcp.org/jsf/core"       xmlns:p="http://primefaces.org/ui">  <f:view contentType="text/html;charset=UTF-8" encoding="UTF-8"> <h:head>     <h:outputStylesheet library="webjars" name="primeflex/3.2.0/primeflex.min.css" />     <h:outputStylesheet library="css" name="styles.css"/>     <meta charset="utf-8" />     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />     <title>Заголовок страницы</title> </h:head>     <h:body>         <div class="card">             <h:form>                 <div class="col-12 md:col-2">                     <p:button href="/employee/edit/0" value="Создать"/>                 </div>                 <p:dataTable var="employee" value="#{employeeFilterView.employees}" widgetVar="employeesTable"                              emptyMessage="No employees found with given criteria"                              filteredValue="#{employeeFilterView.filteredEmployees}"                              filterBy="#{employeeFilterView.filterBy}"                 >                      <f:facet name="header">                         <span>Список сотрудников</span>                     </f:facet>                      <p:column headerText="ФИО" sortBy="#{employee.name}"                               filterMatchMode="contains"                               filterBy="#{employee.name}">                         <h:outputText value="#{employee.name}" />                     </p:column>                      <p:column headerText="Специализация" sortBy="#{employee.specialties}"                               filterMatchMode="contains"                               filterBy="#{employee.specialties}">                         <h:outputText value="#{employee.specialties}" />                     </p:column>                      <p:column headerText="Отдел" sortBy="#{employee.employeeDepartment}"                               filterMatchMode="contains"                               filterBy="#{employee.employeeDepartment}">                         <h:outputText value="#{employee.employeeDepartment}" />                     </p:column>                      <p:column headerText="Компетенции" sortBy="#{employee.skills}"                               filterMatchMode="contains"                               filterBy="#{employee.skills}">                         <h:outputText value="#{employee.skills}" />                     </p:column>                      <p:column headerText="Уволен" sortBy="#{employee.archived}"                               filterMatchMode="contains"                               filterBy="#{employee.archived}">                         <h:outputText value="#{employee.archived}" />                     </p:column>                      <p:column headerText="Карточка ресурса">                         <h:outputLink value="#{employee.linkToLK}">                             <h:outputText value="Открыть карточку" />                         </h:outputLink>                     </p:column>                 </p:dataTable>             </h:form>         </div>     </h:body> </f:view>  </html> 

Как мы видим, это обычная xhtml‑страница со всеми ее обычными блоками. Если вы подумали, что страница инклудится в главную страницу, и потому заголовочные теги, может быть, не нужны, то вы подумали так же точно, как и я вначале. Но это ошибка, так работать не будет, проверял. Позже, в продолжениях статьи, мы столкнемся с ситуациями, которые покажут нам, что вложенная страница вида в PrimeFaces имеет еще и другие ограничения, например, далеко не все виды компонентов будут на ней работать корректно. Но в таком упрощенном виде компонент таблицы с данными здесь работать будет корректно.

Главное, что нужно знать о настройке компонента p:dataTable на странице следующее:

  • value="#{employeeFilterView.employees}" — ссылка на поле бина, которое содержит список, из которого выбираются строки таблицы;

  • emptyMessage="No employees found with given criteria" — сообщение, которое будет выводиться в случае, если список компонентов не заполнен;

  • filteredValue="#{employeeFilterView.filteredEmployees}" — ссылка на поле бина, которое содержит уже отфильтрованный список, когда фильтр заполнен и применен.

Далее мы указываем заголовок списка/таблицы и описываем колонки/поля таблицы. filterBy в каждом поле и выше в самом компоненте передает значения в фильтр, которые мы будем писать в специальных полях для фильтрации. Фильтр умный, поля фильтруются совместно, то есть можно фильтровать по нескольким колонкам одновременно согласно той логике, которую вы пропишете в бине компонента. Впрочем, об этом я напишу чуть подробнее ниже. Кроме фильтрации, таблица поддерживает сортировку по каждому полю, для чего служит sortBy. Очень важна настройка поля filterMatchMode="contains" — с ней фильтрация работает, как полнотекстовая, с добавлением/удалением каждого нового символа, который вы печатаете в поле фильтра, результат фильтрации моментально меняется, выполняясь как по всему слову, так и по его части или отдельным символам. Это НЕ дефолтовое значение, поэтому его нужно прописать явно. Другие возможные значения этого параметра или других полезных параметров можно найти в документации

DataTable. DataTable displays data in tabular format

Встроенные в таблицу компоненты h:outputText просто выводят значения полей в каждой строке таблицы. Если нужно обернуть это значение в ссылку, оборачиваем его еще и в компонент h:outputLink

Теперь перейдем к бину компонента:

 import jakarta.faces.view.ViewScoped; import jakarta.inject.Inject; import lombok.Getter; import lombok.Setter; import org.primefaces.model.FilterMeta; import org.primefaces.util.LangUtils; import org.satel.ressatel.service.EmployeeService; import org.springframework.stereotype.Component;  import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Locale;  @Component("employeeFilterView") @ViewScoped @Getter @Setter public class EmployeeFilterView implements Serializable {      private List<Employee> employees;     private List<Employee> filteredEmployees;     private List<FilterMeta> filterBy;      private boolean globalFilterOnly;     private final EmployeeService employeeService;      @Inject     public EmployeeFilterView(EmployeeService employeeService) {         this.employeeService = employeeService;         this.init();     }      public void init() {         employees = employeeService.getShortList();         globalFilterOnly = false;         filterBy = new ArrayList<>();     }      public boolean globalFilterFunction(Object value, Object filter, Locale locale) {         String filterText = (filter == null) ? null : filter.toString().trim().toLowerCase();         if (LangUtils.isBlank(filterText)) {             return true;         }          Employee employee = (Employee) value;         return employee.getName().toLowerCase().contains(filterText)                 || employee.getSpecialties().toLowerCase().contains(filterText)                 || employee.getEmployeeDepartment().toLowerCase().contains(filterText)                 || employee.getSkills().toLowerCase().contains(filterText)                 || employee.getArchived().toLowerCase().contains(filterText);     }      public void toggleGlobalFilter() {         setGlobalFilterOnly(!isGlobalFilterOnly());     }  }  

Обратите внимание, что я употребил аннотацию бина @ViewScoped из jakarta. Spring контекст прекрасно понял этот scope и применил аннотацию к бину. Тут нужно отметить, что абсолютно точной аналогии этого scope в Spring не существует, область действия такого бина будет ограничиваться одной открытой страницей xhtml с компонентом на ней, то есть одним видом JSF + PrimeFaces. Некоторые пишут кастомные аналоги для Spring сами, если для их задач это важно, как например, вот здесь:

JSF View scope in Spring

Если это допустимо вашим приложением, то можно попробовать использовать scope @Request, @Session или @GlobalSession, каждый решает сам. Но поскольку область видимости @ViewScoped из jakarta не привела у меня ни к каким неприятным эффектам, я оставил ее как есть.

Также я придерживаюсь инжектирования сервисов в управляемых бинах компонентов с помощью аннотации @Inject из jakarta, а в сервисах ставлю scope @ApplicationScoped тоже из jakarta, совместно с аннотацией @Service из Spring, и также никаких неудобств это пока что не вызвало.

В бине мы видим обещанную мной выше реализацию логики фильтрации в методе globalFilterFunction, не удивляйтесь, что явной связи поля filterBy с этой функцией вы не видите — все связи выполняются PrimeFaces «под капотом». filterBy просто приходит в параметр filter, а затем обрабатывается через filterText путем сравнения с данными, приходящими из БД для конкретной строки. Реализацию сервиса и репозитория приводить не буду, они у вас будут собственные, под ваши нужды.

В следующей части статьи мы начнем знакомиться с формами для отображения и редактирования данных, с типами компонентов для инпутов форм и с обработкой данных, приходящих из инпутов.

Напоминаю, что данный цикл статей подготовлен в преддверии старта курса «Java Developer. Professional«. Бесплатный урок курса по теме: «Реактивное подключение к Postgresql в приложениях на Java» доступен по этой ссылке.


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/713770/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *