Rich Tree - Multiple Selection


The richfaces tree component doesn't support multiple selections out of the box. I wanted to be able to select two or more nodes and also when I clicked a node to have all its children automatically selected. The following code was used to achieve this.

1. rich:tree definition

<rich:tree id="allPPETree" ajaxChildActivationEncodeBehavior="none" switchType="ajax"
        ajaxNodeSelectionEncodeBehavior="none" ajaxKeys="#{null}"                                 ajaxSubmitSelection="true" reRender="allPPETree">
    <rich:recursiveTreeNodesAdaptor roots="#{ppeRoot}"
            nodes="#{ppeCategory.subCategories}" var="ppeCategory">

        <rich:treeNodesAdaptor nodes="#{ppeCategory.equipment}" var="ppe">
            <rich:treeNode nodeClass="#{multiSelectTreeState.isSelected(ppe) ?
                    'tree-node-selected' : ''}"
>

                <a4j:support event="onselected"
                    action="#{ppeController.processSelection(ppe)}"
                    reRender="allPPETree" />

                <h:outputText id="leafLabel" value="#{ppe.name}" />
            </rich:treeNode>
        </rich:treeNodesAdaptor>
        <rich:treeNode id="nodeNode" icon="/a4j/g/3_3_3.CR1images/iconFolder.gif"
            iconLeaf="/a4j/g/3_3_3.CR1images/iconFolder.gif"
            nodeClass="#{multiSelectTreeState.isSelected(ppeCategory) ?
                    'tree-node-selected' : ''}"
>

            <h:outputText id="nodeLabel" value="#{ppeCategory.name}" />
            <a4j:support event="onselected"
                    action="#{ppeController.processSelection(ppeCategory)}"
                    reRender="allPPETree" />

        </rich:treeNode>
    </rich:recursiveTreeNodesAdaptor>
</rich:tree>

Define a rich tree that displays your data structure. Do NOT specify a nodeSelectListener. The important bits to note are in bold above. On select we want to pass in the selected node and perform our logic to record the selection. To visually display this we need to conditionally specify a nodeClass.


2. MultiSelectTreeState class

/**
 * Copyright Software Factory - 2010
 */
package nz.co.softwarefactory.risingstars.richfaces;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import org.jboss.seam.annotations.Name;

/**
 * @author craig
 *
 */
@SuppressWarnings("serial")
@Name("multiSelectTreeState")
public class MultipleSelectionTreeState implements Serializable {

    private final Set<Object> selectedNodes = new HashSet<Object>();

    public boolean isSelected(final Object object) {
        return object == null && selectedNodes.isEmpty() ||
               selectedNodes.contains(object);

    }

    public void toggleSelection(final Object object) {
        if (!selectedNodes.remove(object)) {
            selectedNodes.add(object);
        }
    }

    public Set<?> getSelectedNodes() {
        return new HashSet<Object>(selectedNodes);
    }

    public void selectNode(final Object object) {
        selectedNodes.add(object);
    }

    public void deselectNode(final Object object) {
        selectedNodes.remove(object);
    }
}

The above class is used to record the state of each node in the tree.


3. Process selection logic

public void processSelection(final Object node) {
    multiSelectTreeState.toggleSelection(node);

    if (node instanceof PPECategory) {
        final boolean isSelected = multiSelectTreeState.isSelected(node);
        if (isSelected) {
            selectAll((PPECategory) node);
        }
        else {
            deselectAll((PPECategory) node);
        }
    }
}

private void deselectAll(final PPECategory category) {
    multiSelectTreeState.deselectNode(category);
    for (final PPE ppe : category.getEquipment()) {
        multiSelectTreeState.deselectNode(ppe);
    }
    for (final PPECategory cat : category.getSubCategories()) {
        deselectAll(cat);
    }
}

private void selectAll(final PPECategory category) {
    multiSelectTreeState.selectNode(category);
    for (final PPE ppe : category.getEquipment()) {
        multiSelectTreeState.selectNode(ppe);
    }
    for (final PPECategory cat : category.getSubCategories()) {
        selectAll(cat);
    }
}

The processSelection method doesn't take a NodeSelectedEvent is it normally would when using it as a nodeSelectListener. It takes an object which is the actual pojo object in your data structure. The first step is to toggle the selection of the current mode (select it if its not selected otherwise deselect). Then if we are selecting a node (and not a leaf) then we recursively iterate over the children of this node either selecting or deselecting.


Comments