PrimeFaces p:tree的p:ajax更新失效:展开节点无法折叠兄弟节点
Let's work through this issue together. The core problem here is that even though your backend correctly marks sibling nodes as collapsed, the frontend doesn't reflect this change immediately—only after you manually click the update button. This happens because of how PrimeFaces handles dynamic tree nodes and AJAX state sync.
Fix 1: Use PrimeFaces Tree Widget API Directly (Frontend Approach)
Instead of simulating clicks on togglers, leverage the built-in collapseNode method of the PrimeFaces Tree widget. This is far more reliable since it aligns with how PrimeFaces natively manages tree state.
Here's the updated frontend code:
<h:form> <p:tree id="tree" widgetVar="treeW" value="#{problemController.root}" var="node" dynamic="true" orientation="horizontal"> <p:treeNode> <h:outputText value="#{node}" /> </p:treeNode> <p:ajax event="expand" listener="#{problemController.onNodeExpanded}" oncomplete="closeSiblings(PF('treeW'), '#{event.rowKey}')"/> </p:tree> <h:commandButton value="Update" immediate="true"> <f:ajax render="tree" /> </h:commandButton> <h:outputScript> //<![CDATA[ function closeSiblings(treeWidget, expandedRowKey) { // Get the parent row key of the expanded node const parentRowKey = expandedRowKey.substring(0, expandedRowKey.lastIndexOf(':')); // Iterate through all nodes in the tree model treeWidget.cfg.model.forEach(node => { // Collapse siblings that are currently expanded if (node.parent === parentRowKey && node.rowKey !== expandedRowKey && node.expanded) { treeWidget.collapseNode(node.rowKey); } }); } //]]> </h:outputScript> </h:form>
Why this works:
- We use
#{event.rowKey}directly in theoncompletehandler to get the expanded node's unique identifier, avoiding messy URL parsing. - The Tree widget's
cfg.modelgives us direct access to all nodes, including their parent relationship and expanded state. - Calling
treeWidget.collapseNode(rowKey)triggers PrimeFaces' official collapse logic, which updates both the internal model and the UI correctly—even for dynamically loaded nodes.
Fix 2: Ensure Backend State Sync (Backend + AJAX Approach)
If you prefer handling most logic in the backend, make sure you're not just updating node states but also forcing the tree to refresh properly. Your original update="tree" might not work as expected because PrimeFaces doesn't fully re-render dynamic nodes unless you explicitly optimize the AJAX call.
Modify your p:ajax tag to include process="@this" and ensure the tree is updated correctly:
<p:ajax event="expand" listener="#{problemController.onNodeExpanded}" process="@this" update="tree" />
Also, update your backend method to recursively collapse child nodes (this helps with state sync for dynamic trees):
public void onNodeExpanded(NodeExpandEvent event) { TreeNode expandedNode = event.getTreeNode(); System.out.printf("Expanded %s\n", expandedNode); TreeNode parent = expandedNode.getParent(); if (parent != null) { for (TreeNode sibling : parent.getChildren()) { if (sibling.isExpanded() && sibling != expandedNode) { // Collapse the sibling and all its child nodes collapseNodeRecursively(sibling); System.out.printf("Collapsed %s\n", sibling); } } } } private void collapseNodeRecursively(TreeNode node) { node.setExpanded(false); for (TreeNode child : node.getChildren()) { collapseNodeRecursively(child); } }
Why this works:
process="@this"ensures only the expanded node is processed, avoiding unnecessary overhead and potential state conflicts.- Recursively collapsing child nodes ensures the entire subtree is marked as collapsed, which helps the frontend render the correct state when the tree is updated via AJAX.
Key Notes for PrimeFaces 6.1
- Dynamic trees rely on lazy loading, so frontend state can easily get out of sync if you don't use the widget's official methods.
- Your environment (Java 1.8, Mojarra 2.3, Weld 3.0) shouldn't interfere with this logic, but double-check that your view-scoped bean is properly maintained (no unexpected serialization issues).
内容的提问来源于stack exchange,提问作者Verticon

