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

Комментарии (0)