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

от автора

В предыдущей части мы разобрали, как заполнить инклуды контентом из дополнительных страниц, разместив на них компонент Primefaces Data Table Filter. Теперь в моем приложении по клику на пункте в левом меню в главной части страницы динамически подгружаются три разных списка, с разными источниками данных и разным набором колонок в таблицах. Но все они используют одну и ту же схему — компонент Data Table.

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

Поскольку в моем приложении предполагается сравнительно простая ролевая модель безопасности, в которой просмотр всей карточки сущности и редактирование сущности разделяются по группам пользователей, я решил не усложнять разработку разделением настроек доступа и скрытием/отображением отдельных полей и компонентов на одной и той же странице, а просто сделать две отдельные страницы для двух отдельных карточек — карточки просмотра и карточки редактирования. Однако, так как данные, в основном, одни и те же, то управляемый бин с кодом компонентов я сделал один общий. Обратите внимание, что я написал «управляемый бин с кодом компонентов», а не «бин компонента», как было ранее в предыдущих частях статьи, потому что на самом деле в бине вполне можно размещать и настраивать код не одного, а нескольких компонентов, и даже из кода одного бина ссылаться на код других управляемых бинов. Впрочем, для тех, кто работал с фреймворком Spring, это привычная ситуация. Разница лишь в том, здесь я применяю инжектирования бина одного вида в бине другого вида, и таким образом, строго говоря, немного нарушаю классическую архитектуру MVC, превращая ее во что-то вроде M(VVV…..VVVV)C (цепочка вызываемых бинов вложенных видов может оказаться сколько угодно длинной, но обычно мне хватало двух, то есть я делал MVVC).

Начнем с карточки просмотра сущности. Вот пример одной из них, 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:metadata>     <f:viewParam name="id" value="#{employeeCardView.id}"/>     <f:viewAction action="#{employeeCardView.onload}"/>     <f:viewParam name="id" value="#{employeeFileDownloadView.id}"/>     <f:viewAction action="#{employeeFileDownloadView.onload}"/>     <f:viewParam name="id" value="#{employeeSkillsSelectionView.id}"/>     <f:viewAction action="#{employeeSkillsSelectionView.onload()}"/>     <f:viewParam name="id" value="#{employeeCardView.employeeRatingView.id}"/>     <f:viewAction action="#{employeeCardView.employeeRatingView.onload}"/> </f:metadata>  <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>                 <p:dataTable emptyMessage="">                     <f:facet name="header">                         <span>Карточка сотрудника</span>                     </f:facet>                 </p:dataTable>             </h:form>             <h:form id="form">                 <div class="grid ui-fluid">                     <div class="col-12 md:col-2">                         <p:button href="/employee/edit/#{employeeCardView.id}" value="Редактировать"/>                     </div>                     <div class="col-12 md:col-2">                         <p:button href="/" value="На главную"/>                     </div>                 </div>                 <p:dialog modal="true" widgetVar="statusDialog" header="Status" draggable="false" closable="false"                           resizable="false">                     <i class="pi pi-spinner pi-spin" style="font-size:3rem"></i>                 </p:dialog>                 <h2>Общая информация</h2>                 <p:divider/>                 <div class="grid ui-fluid">                     <div class="col-12 md:col-3">                         <h3>Имя</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.firstName}"/>                         <h3>Фамилия</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.surname}"/>                         <h3>Отчество</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.secondName}"/>                     </div>                      <div class="col-12 md:col-3">                         <h3>Подразделение</h3>                         <h:outputText styleClass="fieldData"                                       value="#{employeeCardView.employeeDepartment.finDepartment.name}"/>                         <h3>Возраст</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.age}"/>                         <h3>Стаж работы</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.workExperience} лет"/>                     </div>                      <div class="col-12 md:col-3">                         <h3>Ставка себестоимости</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.costPriceRate} в час"/>                         <h3>Уволен/работает</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.archived}"/>                     </div>                      <div class="col-12 md:col-3">                         <h3>Профиль роли сотрудника</h3>                         <div class="col-12 md:col-12">                             <h4>Основная роль</h4>                             <h:outputText  styleClass="fieldData mb-4" value="#{employeeCardView.employeeRatingView.mainRoleName}"/>                             <p:rating id="rate_1" value="#{employeeCardView.employeeRatingView.mainGradeId}" readonly="false" stars="6"/>                         </div>                         <div class="col-12 md:col-12">                             <h4>Дополнительные роли</h4>                             <p:dataList value="#{employeeCardView.employeeRatingView.extraRoleEntryList}"                                         var="entry" styleClass="noBorders" itemType="none">                                 <p>                                     #{entry.key.name}                                 </p>                                 <p:rating value="#{entry.value.id}" readonly="false" stars="6"/>                             </p:dataList>                         </div>                         <div class="col-12 md:col-8">                             <p:button value="Редактировать" icon="pi pi-pencil"                                       href="/employee/roles/edit/#{employeeCardView.id}"/>                         </div>                     </div>                      <p:divider/>                      <div class="col-12 md:col-3">                         <h3>Локация</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.location.city}"/>                         , <h:outputText styleClass="fieldData" value="#{employeeCardView.location.personalAddress}"/>                         <h3>Контактные данные</h3>                         <div><h:outputText styleClass="fieldData" value="Email: #{employeeCardView.contactObj.email}"/>                         </div>                         <div><h:outputText styleClass="fieldData" value="Phone: #{employeeCardView.contactObj.phone}"/>                         </div>                         <div><h:outputText styleClass="fieldData"                                            value="Mobile: #{employeeCardView.contactObj.mobile}"/>                         </div>                     </div>                      <div class="col-12 md:col-3">                         <h3>Образование</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.education.organization}"/>,                         <h:outputText styleClass="fieldData" value="#{employeeCardView.education.educationGrade.name}"/>                         <h3>Грейд</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.grade.name}"/>                         <h3>Уровень владения английским языком</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.englishLevel.name}"/>                     </div>                      <div class="col-12 md:col-3">                         <h3>Резюме на русском языке</h3>                         <h:outputText value="будет сгенерировано автоматически"/>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.russianResume}"/>                         <h3>Резюме на английском языке</h3>                         <h:outputText value="будет сгенерировано автоматически"/>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.englishResume}"/>                     </div>                 </div>                  <p:divider/>                  <h2>Дополнительная информация</h2>                 <p:divider/>                 <div class="grid ui-fluid">                     <div class="col-12 md:col-4">                         <h3>Квалификационный профиль сотрудника</h3>                         <p:dataList value="#{employeeCardView.skillNamesSet}"                                     var="level" styleClass="noBorders" itemType="none">                             <p>                                 #{level}                             </p>                         </p:dataList>                         <div class="col-12 md:col-4">                             <p:commandButton icon="pi pi-window-maximize"                                              oncomplete="PF('detailDialog').show()"                                              value="Подробности"/>                         </div>                     </div>                     <div class="col-12 md:col-4">                         <h3>Дополнительная информация от сотрудника</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.selfInfo}"/>                     </div>                      <div class="col-12 md:col-4">                         <h3>Дополнительная информация от руководителя</h3>                         <h:outputText styleClass="fieldData" value="#{employeeCardView.chefInfo}"/>                     </div>                 </div>                  <p:divider/>                  <div class="grid ui-fluid">                     <div class="col-12 md:col-12">                         <h3>Проектный опыт</h3>                         <h4>будет выгружаться из 1С</h4>                         <p:dataTable lazy="false" var="item" value="#{employeeCardView.satelProjectExperienceList}"                                      widgetVar="itemsTable"                                      emptyMessage="Проектный опыт у сотрудника отсутствует">                              <p:column headerText="№ сделки">                                 <h:outputText value="#{item.contractNumber}"/>                             </p:column>                             <p:column headerText="Название проекта">                                 <h:outputText value="#{item.name}"/>                             </p:column>                             <p:column headerText="Проектная роль">                                 <h:outputText value="#{item.projectRole} часов"/>                             </p:column>                             <p:column headerText="Проектные работы">                                 <h:outputText value="#{item.projectWorks}"/>                             </p:column>                          </p:dataTable>                     </div>                 </div>                  <div class="grid ui-fluid">                     <div class="col-12 md:col-6">                         <h3>Файлы</h3>                         <p:dataList id="employee-files-list" lazy="false" var="entry" widgetVar="itemsTable"                                     itemStyleClass="fileItem"                                     emptyMessage="Файлы сотрудника не загружены" itemType="none"                                     value="#{employeeCardView.employeeFileDownloadView.streamedContentEntrySet}">                             <span class="fileButton">                                 <p:commandButton value="#{entry.value.name}" ajax="false"                                                  onclick="PrimeFaces.monitorDownload(start, stop);"                                                  icon="pi pi-arrow-down" styleClass="mr-2">                                     <p:fileDownload value="#{entry.value.streamedContent}"/>                                 </p:commandButton>                             </span>                         </p:dataList>                     </div>                 </div>                 <div class="grid ui-fluid">                     <div class="col-12 md:col-2">                         <p:button href="/employee/edit/#{employeeCardView.id}" value="Редактировать"/>                     </div>                     <div class="col-12 md:col-2">                         <p:button href="/" value="На главную"/>                     </div>                 </div>             </h:form>             <h:form id="dialogs">                 <p:dialog header="Компетенции сотрудника" showEffect="fade" modal="true"                           widgetVar="detailDialog"                           responsive="true">                     <p:outputPanel id="detail-skill-content" class="ui-fluid">                         <p:dataList value="#{employeeCardView.skillNamesSet}"                                     var="level" styleClass="noBorders" itemType="none">                             <p>                                 #{level}                             </p>                             <div>                                 <p:rating value="3" readonly="false" stars="6"/>                             </div>                             <p><h:outputText>Здесь будут подробности оценки навыка</h:outputText></p>                             <p:divider/>                         </p:dataList>                     </p:outputPanel>                     <f:facet name="footer">                         <p:button value="Редактировать" icon="pi pi-pencil"                                          href="/employee/skills/edit/#{employeeCardView.id}"/>                         <p:commandButton value="Закрыть" icon="pi pi-times" onclick="PF('detailDialog').hide()"                                          class="ui-button-secondary" type="button"/>                     </f:facet>                 </p:dialog>             </h:form>         </div>     </h:body> </f:view>  </html> 

Разберем чуть подробнее некоторые поля (многие поля имеют одинаковые типы, поэтому нет смысла описывать их все)

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

<h:outputText styleClass="fieldData" value="#{employeeCardView.firstName}"/>

здесь поле будет привязано к полю firstName в бине с именем employeeCardView. Естественно, в бине это поле имеет какой-то из примитивных типов. Для вывода данных этого вполне достаточно. В случаях, если данные для вывода должны получаться из каких-то отдельных источников или генерироваться из других данных, ничто не мешает нам сделать это в самом бине через обращение к сервисам или написав непосредственно метод для генерации данных из нескольких полей или из источника, а затем записать полученный результат в новое отдельное поле бина, которое также будет привязано к полю xhtml файла.

Иногда бывает необходимо вывести некоторый список данных, которые хранятся в каком-то поле бина, имеющем тип одной из коллекций Java. В этом случае удобно использовать, например, компонент Data List, например:

    <p:dataList value="#{employeeCardView.employeeRatingView.extraRoleEntryList}"                 var="entry" styleClass="noBorders" itemType="none">         <p>             #{entry.key.name}         </p>         <p:rating value="#{entry.value.id}" readonly="false" stars="6"/>     </p:dataList> 

причем передаваемая коллекция может быть достаточно сложной. В этом примере в параметр компонента value передается ссылка на список элементов Map.Entry, извлеченных в компоненте из данных некоторой Map, привязанное поле бина выглядит следующим образом:

private List<Map.Entry<Role, Grade>> extraRoleEntryList;

параметр var компонета служит для передачи в список значения отдельной Map.Entry<Role, Grade> из списка, соответственно, мы видим, что в списке будет выводиться имя ключа, то есть поле name экземпляра класса Role.

Пожалуй, вывод данных из мапы — это самый сложный вариант вывода данных из коллекций, из простых списков по аналогии получить вывод намного легче, читатель легко с этим разберется самостоятельно. Но на всякий случай приведу ссылку на то, как это делается, в документции Primefaces:

http://www.primefaces.org:8080/showcase/ui/data/datalist/basic.xhtml?jfwid=513c8

Необязательно, чтобы поле было списком, оно может быть, например, и множеством:

<p:dataList value="#{employeeCardView.skillNamesSet}"         var="level" styleClass="noBorders" itemType="none">     <p>         #{level}     </p> </p:dataList> 
private Set<String> skillNamesSet; 

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

Традиционно в конце статьи рекомендую вам бесплатный урок от OTUS. В этот раз хочу порекомендовать урок, в рамках которого будет рассмотрена общая архитектура веб-приложений. Что происходит при клиент-серверном взаимодействии. И какое место отведено Spring MVC.


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


Комментарии

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

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