Во второй части мы сосредоточились главным образом на создании меню на основе компонента 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".