A requirement on software i developing is concerned rich:tree selection.
My customer wants that the node expansion must be performed not only on “plus/minus icon” but also on simple node selection. Than on node selection must be expanded ONLY the path to that node and all others must be collapsed.
I realized that using changeExpandListener and nodeSelectListener events. The first one is triggered on clicking on “plus/minus icon” of the tree. The other one on node selection.
The idea is to tracking, on backing-bean, the current and the old TreeRowKeys selected and, on changeExpandListener or nodeSelectListner events, collapse old TreeRowKey and expand current one.
Let’s see xhtml implementation:
<rich:tree switchType="ajax" binding="${gestioneArchivioMenuBean.tree}" id="gestioneArchivioTree" ajaxSingle="true">
<!-- ROOT NODE -->
<rich:treeNodesAdaptor id="adaptorRoot" nodes="${backingBean.treeModelList}" var="root">
<rich:treeNode changeExpandListener="${backingBean.processExpansion}"
nodeSelectListener="${backingBean.selectNode}"
data="${root.desc}">
<f:facet name="icon">
<h:graphicImage value="/img/sitemap.png"/>
</f:facet>
</rich:treeNode>
<!-- FIRST LEVEL NODES -->
<rich:treeNodesAdaptor id="adaptorFirstLevel" nodes="${root.subDir}" var="firstLevel">
<rich:treeNode changeExpandListener="${backingBean.processExpansion}"
nodeSelectListener="${backingBean.selectNode}"
data="${firstLevel.desc}">
<f:facet name="icon">
<h:graphicImage value="${firstLevel.imageUrl}"/>
</f:facet>
</rich:treeNode>
<!-- SECOND LEVEL NODES -->
<rich:treeNodesAdaptor id="adaptorSecondLevel"
nodes="${firstLevel.subDir}"
var="secondLevel">
<rich:treeNode changeExpandListener="${backingBean.processExpansion}"
nodeSelectListener="${backingBean.selectNode}"
data="${secondLevel.desc}">
<f:facet name="icon">
<h:graphicImage value="${secondLevel.imageUrl}"/>
</f:facet>
</rich:treeNode>
</rich:treeNodesAdaptor>
</rich:treeNodesAdaptor>
</rich:treeNodesAdaptor>
</rich:tree>
As you can see i set on each rich:treeNode element, the two events described before, bound to two actionListeners on backingBean. So every time i expand/collapse or select a node the two methods are triggered.
Here is the interesting portion of my backingBean:
public class treeManager {
import org.richfaces.component.UITree;
import org.richfaces.component.html.HtmlTreeNode;
import org.richfaces.event.NodeExpandedEvent;
import org.richfaces.event.NodeSelectedEvent;
import org.richfaces.model.TreeRowKey;
import javax.faces.component.UIComponent;
//this attribute stores old selection tree node
private TreeRowKey oldSelectionTree;
//The method is called on node select event
public void selectNode(NodeSelectedEvent ev) throws IOException {
//The event source is an HtmlTreeNode object, from which i take UITree ref
UITree tree = ((HtmlTreeNode) ev.getSource()).getUITree();
//tracking of oldSelection and currentSelection
// oldSelectionTree is a class attribute
TreeRowKey oldSel = oldSelectionTree;
//new selection is get from tree object
TreeRowKey newSel = (TreeRowKey) tree.getRowKey();
/*i check the exists new and old selection and that the currentSelection's depth is
less than new one or equals.*/
if (newSel != null && oldSel != null && newSel.depth() <= oldSel.depth()) {
//in this case iterating on oldSel, i collapse nodes until i arrive
//to same depth of newSel
while (null != oldSel && oldSel.depth() > 1
&& oldSel.depth() >= newSel.depth()) {
tree.queueNodeCollapse(oldSel);
oldSel = oldSel.getParentKey();
}
//if newSel's depth is greater than oldSel's one i have to do nothing because
//surely i'm expanding a child of oldSel and for this reason it haven't to be collapsed
//i expand the new node (if this event is triggered from processExpansion method
//i'm just re-doing the expanding), if no i wants that on node selection, it must be
//expanded
tree.queueNodeExpand(newSel);
//i set oldSelectionTree as newSel
this.oldSelectionTree = newSel;
}
//This method is triggered on "expand/collapse" icon on the tree.
public void processExpansion(NodeExpandedEvent evt) throws IOException {
Object source = evt.getSource();
if (source instanceof HtmlTreeNode) {
HtmlTreeNode node = (HtmlTreeNode) source;
//i take tree ref
UITree tree = ((HtmlTreeNode) source).getUITree();
//and current selection (the node relative to "expand/collapse" icon clicked
TreeRowKey newSel = (TreeRowKey) tree.getRowKey();
//i create a NodeSelectedEvent setting HtmlTreeNode as source and oldSelectionTree
//as oldSelected attribut of the event. It's not very important because
//i get old selected node from oldSelectedTree class attribute in selectNode event
NodeSelectedEvent ev = new NodeSelectedEvent((UIComponent) node,
oldSelectionTree);
//and i call selectNode event method, in this way all is managed from that method,
//to make two behaviours synchronized
selectNode(ev);
oldSelectionTree = newSel;
}
}
}
As you can see in selectNode method i track old and new node selections and, if newSel’s depth is less or equals to oldSel’s one, i simply collapse iteratively oldSel until it’s depth is equals to newSel’s one. At the end of methods i call a queueNodeExpand so, also on selecting node ,it will expand itself.
processExpansion method manage on Expand/Collapse event. To have the two events synchronized, in this method, i only build a NodeSelectEvent to call selectNode and let it to do everything.-