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

от автора

Во второй части мы сосредоточились главным образом на создании меню на основе компонента Tree ContextMenu. Пришло время показать, как можно этим меню воспользоваться. В моем случае сценарий такой — необходимо по клику на одном из трех пунктов меню обновить содержание главной части страницы, в которой должно отобразиться каждый раз разное содержимое. Метод, который я покажу, достаточно универсальный, его можно применять самыми разными способами. (Вообще, основной текст xhtml страницы и бина компонента уже показаны в предыдущей части статьи, кто-то, возможно, уже и сам разобрался в этом, но все равно, разберем этот момент подробнее)

Итак, вот еще раз часть кода страницы:

<p:splitter> <p:splitterPanel :size="20" styleClass="flex align-items-center justify-content-center flex-error-left-1"> <p:tree id="docs" value="#{treeContextMenuView.root}" var="doc" selectionMode="single" selection="#{treeContextMenuView.selectedNode}" dynamic="true"> <p:treeNode expandedIcon="pi pi-folder-open" collapsedIcon="pi pi-folder"> <h:outputText value="#{doc.name}" /> </p:treeNode> <p:treeNode type="ips" icon="pi pi-folder"> <h:outputText value="#{doc.name}" /> </p:treeNode> <p:treeNode id="testid" type="contragent" icon="pi pi-file"> <h:outputText value="#{doc.name}" /> </p:treeNode> <p:ajax event="select" listener="#{treeContextMenuView.setSrc()}" /> </p:tree> </p:splitterPanel> <p:splitterPanel :size="80" styleClass="flex align-items-center justify-content-center flex-error-right-1"> <h:panelGroup id="list"> <h:panelGroup rendered="true"> <ui:include src="#{treeContextMenuView.getSrc()}" /> </h:panelGroup> </h:panelGroup> </p:splitterPanel> </p:splitter> 

Компонент p:splitter очень простой, он просто разделяет страницу на две области в заданной пропорции. В левой части, собственно, размещен наш компонент меню p:tree. В правой — размещаем компонент ui:include из библиотеки http://xmlns.jcp.org/jsf/facelets. Обратите внимание, что я дважды завернул его в компонент h:panelGroup и включил во внутренней обертке rendered="true", это сделано для того, чтобы при частичном рендеринге страницы вложенная в него страница также обновлялась. Далее, указываем источник, где размещена вложенная страница <ui:include src="#{treeContextMenuView.getSrc()}" />. getSrc() — это у меня метод бина компонента, который получает адрес вложенной страницы. В бине устанавливаем адрес по дефолту, который указывает на вложенную страницу, загружаемую при первом заходе на главную страницу. Затем нам необходимо написать метод setSrc(), который будет загружать в поле бина listSrc новый адрес при клике по пункту меню. Приведем еще раз класс бина компонента, который уже публиковался во второй части статьи, чтобы вы его не искали:

import jakarta.faces.view.ViewScoped; import jakarta.inject.Inject; import org.primefaces.PrimeFaces; import org.primefaces.model.TreeNode;  import javax.annotation.ManagedBean; import javax.faces.component.UIComponent; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import java.io.Serializable; import java.util.List;  @ManagedBean("treeContextMenuView") @ViewScoped public class PageMenuView implements Serializable {     private final TreeNode<Page> root;      private TreeNode<Page> selectedNode;     private final String DEFAULT_LIST = "employees.xhtml";     private String listSrc;      @Inject     public PageMenuView(PageService service) {         root = service.createPages();         listSrc = DEFAULT_LIST;     }       public TreeNode<Page> getRoot() {         return root;     }      public TreeNode<Page> getSelectedNode() {         return selectedNode;     }      public void setSelectedNode(TreeNode<Page> selectedNode) {         this.selectedNode = selectedNode;     }      public void setSrc() {         if (selectedNode.getData().getLink() != null) {             listSrc = selectedNode.getData().getLink();              UIViewRoot view = FacesContext.getCurrentInstance().getViewRoot();             List<UIComponent> uiComponents = view.getChildren();             UIComponent uiComponent = uiComponents.get(2).getChildren().get(0).getChildren().get(3).getChildren().get(1)                     .getChildren().get(0);             PrimeFaces.current().ajax().update(uiComponent.getClientId());         }     }      public String getSrc() {         return listSrc;     }  }  

Чтобы метод сработал, его нужно вызвать ajax запросом из компонента меню, привязав событие select к обработчику setSrc()

<p:ajax event="select" listener="#{treeContextMenuView.setSrc()}" />

Обработчик считывает выбранный узел в виде объекта TreeNode<Page>, извлекает из данных объекта новый линк, привязанный к соответствующему пункту меню и загружает линк в переменную listSrc. После чего необходимо программным способом вызвать обновление компонента <ui:include src="#{treeContextMenuView.getSrc()}" />, что мы и делаем. В моем случае я выбрал самый упрощенный путь поиска компонента в дереве DOM страницы, так как не предполагаю динамически менять ее никогда. При ре-рендеринге перезагружается другая вложенная страница с другим содержимым.

Однако, в Интернет вы найдете массу описаний, как получить компонент или просто любой другой элемент в проектах на Primefaces по id элемента или компонента. Не удивляйтесь, что в большинстве случаев они работать не будут, так как они используют другую конструкцию — FacesContext.getCurrentInstance().getViewRoot().findComponent(id) и последний метод в нем, как правило, не может найти компонент по id, просто потому, что на странице часто много динамики, много разных компонентов, инклудов и так далее, из-за которых вы фактически будете вызывать компонент из того места кода, где контекст или его часть фактически уже потеряны. И разбираться во всем этом хитросплетении чаще бывает намного дольше и сложнее, чем просто пройтись по DOM страницы и найти нужный компонент в режиме дебага. Или еще можно написать или нагуглить какой-то метод для поиска компонента по id рекурсивным поиском по DOM, например, как здесь https://stackoverflow.com/questions/14378437/find-component-by-id-in-jsf. Я не стал мучиться, и просто нашел компонент вручную.

В следующей части статьи мы посмотрим, как я делал списки для вывода из БД в компоненте DataTable Filter (список табличных данных с фильтром)

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


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


Комментарии

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

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