Во второй части мы научились динамически переключать контент, который выводится в главной части страницы компонентом 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 сами, если для их задач это важно, как например, вот здесь:
Если это допустимо вашим приложением, то можно попробовать использовать 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/
Добавить комментарий