Tree

Table of Contents

Overview

Since 3.2

The ace:tree component is used for the display of hierarchical data. The data can be supplied to the tree as a List of a javax.swing.tree.TreeNode implementer or as an instance of a NodeDataModel. For lazy loading cases an abstract implementation of NodeDataModel, LazyNodeDataModel should be implemented and supplied.

See the ICEfaces Showcase Live Demo of this component, complete with source code.

Getting Started

<ace:tree id="tree"
          expansion="true"
          selection="true"
          reordering="true"
          value="#{treeBean.treeRoots}"
          var="node"
          type="#{node.type}">
        <ace:node type="country">
            Country: <h:outputText value="#{node.name}" />
        </ace:node>
        <ace:node type="province">
            Province: <h:outputText value="#{node.name}" />
        </ace:node>
        // Default node type
        <ace:node>
            City: <h:outputText value="#{node.name}" />
        </ace:node>
</ace:tree>
    private List<LocationNodeImpl> treeRoots = TreeDataFactory.getTreeRoots();

    ...

    public List<LocationNodeImpl> getTreeRoots() {
        return treeRoots;
    }
public class TreeDataFactory {
    public static LocationNodeImpl getSingleRoot() {
        LocationNodeImpl[] bcCities = {
                new LocationNodeImpl("Vancouver",   "city",     1),
                new LocationNodeImpl("Kelowna",     "city",     1),
                new LocationNodeImpl("Kamloops",    "city",     1)
        };

        LocationNodeImpl[] abCities = {
                new LocationNodeImpl("Edmonton",    "city",     1),
                new LocationNodeImpl("Calgary",     "city",     1),
                new LocationNodeImpl("Red Deer",    "city",     1)
        };

        LocationNodeImpl[] onCities = {
                new LocationNodeImpl("Toronto",     "city",     1),
                new LocationNodeImpl("Waterloo",    "city",     1),
                new LocationNodeImpl("Ottawa",      "city",     1)
        };

        LocationNodeImpl[] mbCities = {
                new LocationNodeImpl("Winnipeg",    "city",     1),
                new LocationNodeImpl("Brandon",     "city",     1),
                new LocationNodeImpl("Churchill",   "city",     1)
        };

        LocationNodeImpl[] skCities = {
                new LocationNodeImpl("Regina",      "city",     1),
                new LocationNodeImpl("Saskatoon",   "city",     1),
                new LocationNodeImpl("Moose Jaw",   "city",     1)
        };

        LocationNodeImpl[] qbCities = {
                new LocationNodeImpl("Quebec City", "city",     1),
                new LocationNodeImpl("Montreal",    "city",     1),
                new LocationNodeImpl("Gatineau",    "city",     1)
        };

        LocationNodeImpl[] nbCities = {
                new LocationNodeImpl("Saint John",  "city",     1),
                new LocationNodeImpl("Moncton",     "city",     1),
                new LocationNodeImpl("Fredericton", "city",     1)
        };

        LocationNodeImpl[] nfCities = {
                new LocationNodeImpl("St. John's",      "city",     1),
                new LocationNodeImpl("Conception Bay",  "city",     1),
                new LocationNodeImpl("Mount Pearl",     "city",     1)
        };

        LocationNodeImpl[] nsCities = {
                new LocationNodeImpl("Halifax",     "city",     1),
                new LocationNodeImpl("Cape Breton", "city",     1),
                new LocationNodeImpl("Truro",       "city",     1)
        };

        LocationNodeImpl[] provinces = {
                new LocationNodeImpl("British Columbia",    "province",    1, bcCities),
                new LocationNodeImpl("Alberta",             "province",    1, abCities),
                new LocationNodeImpl("Saskatchewan",        "province",    1, skCities),
                new LocationNodeImpl("Manitoba",            "province",    1, mbCities),
                new LocationNodeImpl("Ontario",             "province",    1, onCities),
                new LocationNodeImpl("Quebec",              "province",    1, qbCities),
                new LocationNodeImpl("New Brunswick",       "province",    1, nbCities),
                new LocationNodeImpl("Newfoundland",        "province",    1, nfCities),
                new LocationNodeImpl("Nova Scotia",         "province",    1, nsCities),
        };

        return new LocationNodeImpl("Canada", "country", 1, provinces);
    }

    public static List<LocationNodeImpl> getTreeRoots() {
        return new ArrayList<LocationNodeImpl>() {{
            add(getSingleRoot());
        }};
    }
}
import org.apache.commons.collections.IteratorUtils;

import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import java.io.Serializable;
import java.util.*;

public class LocationNodeImpl implements MutableTreeNode, Serializable {
    LocationNodeImpl parent;
    List<LocationNodeImpl> children;
    String name;
    String type;
    Integer population;

    public LocationNodeImpl(String name, String type, Integer population, LocationNodeImpl[] children) {
        this.name = name;
        this.type = type;
        this.population = population;

        this.children = new ArrayList<LocationNodeImpl>(Arrays.asList(children));

        for (LocationNodeImpl t : children) {
            t.setupParent(this);
        }
    }

    public LocationNodeImpl(String name, String type, Integer population, Collection<LocationNodeImpl> children) {
        this.name = name;
        this.type = type;
        this.population = population;

        this.children = new ArrayList<LocationNodeImpl>(children);

        for (LocationNodeImpl t : children) {
            t.setupParent(this);
        }
    }

    public LocationNodeImpl(String name, String type, int population) {
        this.name = name;
        this.type = type;
        this.population = population;
    }

    public TreeNode getChildAt(int i) {
        if (children == null) return null;
        return children.get(i);
    }

    private boolean childrenSet() {
        return children != null && children.size() > 0;
    }

    public int getChildCount() {
        if (childrenSet())
            return children.size();
        else
            return 0;
    }

    public TreeNode getParent() {
        return parent;
    }

    public int getIndex(TreeNode treeNode) {
        return children.indexOf(treeNode);
    }

    public boolean getAllowsChildren() {
        return children != null;
    }

    public boolean isLeaf() {
        return getChildCount() == 0;
    }

    // Only to be used at inital construction as this
    // is not a mutable tree.
    public void setupParent(LocationNodeImpl parent) {
        this.parent = parent;
    }

    public Enumeration children() {
        if (children == null)
            return IteratorUtils.asEnumeration(IteratorUtils.emptyIterator());
        return IteratorUtils.asEnumeration(children.iterator());
    }

    public Integer getPopulation() {
        return population;
    }

    public void setPopulation(Integer population) {
        this.population = population;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void insert(MutableTreeNode mutableTreeNode, int i) {
        mutableTreeNode.setParent(this);
        children.add(i, (LocationNodeImpl)mutableTreeNode);
    }

    public void remove(int i) {
        children.remove(i);
    }

    public void remove(MutableTreeNode mutableTreeNode) {
        children.remove(mutableTreeNode);
    }

    public void setUserObject(Object o) {
        // Not required for any ace:tree functionality
        throw new UnsupportedOperationException();
    }

    public void removeFromParent() {
        if (parent != null)
            parent.remove(this);
    }

    public void setParent(MutableTreeNode mutableTreeNode) {
        parent = (LocationNodeImpl) mutableTreeNode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        LocationNodeImpl that = (LocationNodeImpl) o;

        if (!name.equals(that.name)) return false;
        if (!population.equals(that.population)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + population.hashCode();
        return result;
    }
}

For examples (and source code) of using Lazy Loading and Selection, please see the Live Demo.

Attributes

TagLib Documentation
This section covers attributes involved in the typical use-cases for this component. For reference, the complete taglib documentation for this component is available here.

expansion: Enable the expansion of nodes via clicks to the expansion icons.

selection: Enable the selection & deselection of nodes via clicks to the node body.

reordering: Enable the reordering of nodes by click-dragging the nodes.

Note that reordering is not supported when using a lazily-loaded tree data model.

value: The node data of the tree, a List of TreeNode objects or a NodeDataModel.

stateCreationCallback: Enables use of a NodeStateCreationCallback, which lazily initializes the component NodeStates based on a given node data object.

var: Specifies the request-scope attribute which will expose the current node data object.

stateMap: Define a NodeStateMap ValueExpression to access the store of ace:tree node data object state information. The state map provides an API for looking up the state of a particular node object, as well as reverse look-ups to get node objects with a particular state.

stateVar: Specifies the request-scope attribute which will expose the NodeState object the current node data object.

type: Defines a ValueExpression for a String that will evaluate per-node data object to determine which ace:node template will be rendered by matching that String against the type property of the ace:node components.

Client Behaviour Events

Name Description
select Fired when a node is clicked & selected.
deselect Fired when a node is clicked & deselected.
expand Fired when a node expansion switch is clicked to reveal subnodes.
contract Fired when a node expansion switch is clicked to hide subnodes.
reorder Fired when an item is dropped into a new position.

Client and Server Side Modes

The ace:tree component can work in both modes out of the box. The intuitive user interface in the client side manipulates the state of the tree as one may expect (i.e. expanding/contracting nodes). All the changes in the client side are reflected in the state of the tree in the server side (i.e. in the state map object org.icefaces.ace.model.tree.NodeStateMap). The converse is also true, all changes in the state of the tree made programmatically in the server side are reflected in the client side as well.

When setting the expansionMode and selectionMode attributes to client, every time the user contracts, expands, or selects a node, a request to the server WON'T be made. All the stat information will stay on the client until the containing form is submitted in another way.

Javascript API

ICEfaces 3.x

The client side component object is exposed through the global variable name specified in the widgetVar attribute.

ICEfaces 4+

The "widgetVar" attribute on the ACE components has been removed in ICEfaces 4 and in its place a new "ice.ace.instance()" client JavaScript object lookup API has been introduced. The reason for this change is to enable lazy-initialization of the ACE component JavaScript objects to improve runtime performance and reduce browser memory use.

var widget = ice.ace.instance('frm:componentId);
The ice.ace.instance function requires the full client id of the component to be specified, such as "j_idt77:componentId" instead of just "componentId". To reduce the complexity of working with complete IDs with this function it may be preferable in some cases to use prependId="false" in the enclosing form (e.g. <h:form prependId="false">).
This component doesn't have a client-side API made specifically to be used by application developers. However, the component's internal methods and variables can be accessed in this way, including the underlying jQuery object and objects from underlying Javascript libraries (if applicable), and can be used for whatever purpose a developer might have in mind.

CSS Classes

The following markup represents the basic HTML structure of the component and the CSS classes it uses.

<!-- Root container -->
<span class="if-tree if-node-sub ui-widget-content ui-corner-all ui-sortable">
	<table class="if-node-cnt">
		<tbody>
			<!-- 1st level node -->
			<tr class="if-node-tr">
				<td class="if-node-sw">
					<div class="if-node-ln">
						<img />
						<span class="ui-icon ui-icon-minus"></span>
					</div>
				</td>
				<td class="if-node">
					<div class="if-node-wrp">
						Item label
					</div>
				</td>
			</tr>
			<tr class="if-node-tr">
				<td class="if-node-td">
					<div class="if-node-ln">
						<img />
					</div>
				</td>
				<td class="if-node-sub ui-sortable">
					<table class="if-node-cnt">
						<tbody>
							<!-- 2nd level node -->
							<tr class="if-node-tr">
								<td class="if-node-sw">
									<div class="if-node-ln">
										<img />
										<span class="ui-icon ui-icon-minus"></span>
									</div>
								</td>
								<td class="if-node">
									<div class="if-node-wrp">
										Item label
									</div>
								</td>
							</tr>
							<tr class="if-node-tr">
								<td class="if-node-td">
									<div class="if-node-ln">
										<img />
									</div>
								</td>
								<td class="if-node-sub ui-sortable">
									<table class="if-node-cnt">
										<tbody>
											<!-- 3rd level node -->
											<tr class="if-node-tr">
												<td class="if-node-sw">
													<div class="if-node-ln">
														<img />
													</div>
												</td>
												<td class="if-node">
													<div class="if-node-wrp">
														Item label
													</div>
												</td>
											</tr>
											<tr class="if-node-tr">
												<td class="if-node-td">
													<div class="if-node-ln">
														<img />
													</div>
												</td>
												<td class="if-node-sub"></td>
											</tr>
										</tbody>
									</table>
								</td>
							</tr>
						</tbody>
					</table>
					<table class="if-node-cnt">
						<tbody>
							<!-- 1st level node -->
							<tr class="if-node-tr">
								<td class="if-node-sw">
									<div class="if-node-ln">
										<img />
										<span class="ui-icon ui-icon-plus"></span>
									</div>
								</td>
								<td class="if-node">
									<div class="if-node-wrp">
										Item label
									</div>
								</td>
							</tr>
							<tr class="if-node-tr">
								<td class="if-node-td">
									<div class="if-node-ln">
										<img />
									</div>
								</td>
								<td class="if-node-sub ui-sortable"></td>
							</tr>
						</tbody>
					</table>
				</td>
			</tr>
		</tbody>
	</table>
</span>

Expand/Contract Icons

In order to change the plus and minus icons used for expansion and contraction, respectively, follow these steps.

First, you'll have to define a CSS rule specific enough to override the tree icon styling. For example, this one works on the showcase application: ".if-node-tr .ui-icon.ui-icon-plus". Then, you'll have to override the actual default styling. This would look like this:

	.if-node-tr .ui-icon.ui-icon-plus {
		/* override default tree icon styling */
		background-image: none;
		background-position: 0 0;
		text-indent: 0;
		overflow: visible;

		/* custom styling */
	}

That's all you need to use a different background image. If you want to use FontAwesome, then you'll have to copy all the contents of the "fa" class (from font-awesome.css) inside the class above, and then define an additional rule to specify the actual FontAwesome character that you want to display (in the same way as all FontAwesome characters are defined in CSS). All of this would look as follows.

	.if-node-tr .ui-icon.ui-icon-plus {
		/* override default tree icon styling */
		background-image: none;
		background-position: 0 0;
		text-indent: 0;
		overflow: visible;

		/* contents of "fa" class */
		display: inline-block;
		font: normal normal normal 14px/1 FontAwesome;
		font-size: inherit;
		text-rendering: auto;
		-webkit-font-smoothing: antialiased;
		-moz-osx-font-smoothing: grayscale;
	}
	.if-node-tr .ui-icon.ui-icon-plus:before {
		content: "\f055";
	}

The last rule specifies the plus sign inside a circle. You'll have to look for the right FontAwesome character code that you want to use.

To do all this for the minus sign, siimply replace "plus" for "minus" in the CSS rule definitions above.

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.

© Copyright 2021 ICEsoft Technologies Canada Corp.