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

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

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

В предыдущей части мы разобрали, как заполнить инклуды контентом из дополнительных страниц, разместив на них компонент 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/


Интересные статьи

Интересные статьи

Задачи отчетности (Reporting Tasks) В первой статье мы рассмотрели вопросы мониторинга потоков данных и состояния системы средствами GUI NiFi. Теперь рассмотрим, как передать необходимые м...
Друг может растеряться, друг может не понимать программу. Друг не программист, не разработчик, он не работал долгие часы над программой и поэтому не знает все нюансы работы программы (Ино...
Приветствую во второй публикации цикла статей, посвященному Cisco ISE. В первой статье  были освещены преимущества и отличия Network Access Control (NAC) решений от стандартных ...
В предыдущей статье мы начали освещать тему эффективности применения методологии TDD для микроконтроллеров (далее – МК) на примере разработки прошивки для STM32. Мы выполнили следующее: Опре...
КДВП © Diana, made by Siuzanna Часть 2. Практическая Вы твёрдо решили построить свою домашнюю солнечную станцию? Взвесили все риски, нашли финансирование, прикинули взором, где она будет ст...