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

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