-
Available since OmniFaces 1.0
The <o:tree>
allows the developers to have full control over the markup of a tree hierarchy by declaring the appropriate Faces components or HTML elements in the markup. The <o:tree>
does namely not render any HTML markup by itself.
The component value must point to a tree of data objects represented by a TreeModel
instance, typically established via a ValueExpression
. During iterative processing over the nodes of tree in the tree model, the object for the current node is exposed as a request attribute under the key specified by the var
attribute. The node itself is exposed as a request attribute under the key specified by the varNode
attribute.
The <o:tree>
tag supports only child tags of type <o:treeNode>
, representing parent tree nodes. There can be multiple <o:treeNode>
tags, each representing a separate parent tree node level, so that different markup could be declared for each tree node level, if necessary. The <o:treeNode>
tag in turn supports child tag <o:treeNodeItem>
which represents each child of the current parent tree node. The <o:treeNodeItem>
in turn supports child tag <o:treeInsertChildren>
which represents the insertion point of the grand children.
Here is a basic usage example where each parent tree node level is treated the same way via a single <o:treeNode>
:
<o:tree value="#{bean.treeModel}" var="item" varNode="node">
<o:treeNode>
<ul>
<o:treeNodeItem>
<li>
#{node.index} #{item.someProperty}
<o:treeInsertChildren />
</li>
</o:treeNodeItem>
</ul>
</o:treeNode>
</o:tree>
treeNode
The <o:treeNode>
represents the parent tree node. Within this component, the var
attribute of the <o:tree>
will expose the parent tree node. Each of its children is processed by <o:treeNodeItem>
on which the var
attribute of the <o:tree>
in turn exposes each child of the parent tree node.
The optional level
attribute can be used to specify for which tree node level as obtained by TreeModel.getLevel()
the <o:treeNode>
should be rendered. The root tree node has level 0. If the level
attribute is unspecified, then the <o:treeNode>
will be rendered for any tree node level which hasn't already a <o:treeNode level="x">
specified.
treeNodeItem
The <o:treeNodeItem>
represents the child item of the parent tree note as represented by <o:treeNode>
. Within this component, the var
attribute of the parent <o:tree>
component will expose the child tree node.
Within <o:treeNodeItem>
you can use <o:treeInsertChildren>
to declare the place where to recursively render the <o:treeNode>
whereby the current child item is in turn interpreted as a parent tree node (i.e. where you'd like to insert the grand-children).
treeInsertChildren
The <o:treeInsertChildren>
represents the insertion point for the grand children. This is in turn further interpreted as <o:treeNode>
.
Showcase
Note that the left menu of this showcase application is also using an <o:tree>
which is
dynamically populated in an application scoped bean based on the structure of the webapp's /showcase
folder. See also
App
source code,
Page
source code and
layout.xhtml
source code.
Also note that Page
extends ListTreeModel
, this is not necessary per se,
but it eases accessing the parent and sister pages in EL as is done in the navigation menu here above,
and the siblings as is done in the quick navigation buttons right above.
See also
showcase.xhtml
source code.
If you need a tree whereby the children are sorted based on their Comparable
implementation,
then use SortedTreeModel
instead of ListTreeModel
.
In the below editable tree example, all input values are required. Clear some of them and then submit to see proper validation message handling. If all values are valid, then the static tree will also be updated with submitted values. You can also dynamically add/remove nodes.
Static tree
0 One
- 0_0 Two
- 0_1 Three
1 Four
- 1_0 Five
Editable tree
<p>
In the below editable tree example, all input values are required. Clear some of them and then submit to see
proper validation message handling. If all values are valid, then the static tree will also be updated with
submitted values. You can also dynamically add/remove nodes.
</p>
<h3>Static tree</h3>
<h:panelGroup id="staticTree">
<o:tree value="#{treeBean.tree}" var="exampleEntity" varNode="node">
<o:treeNode level="0">
<o:treeNodeItem>
<h4>#{node.index} #{exampleEntity.value}</h4>
<o:treeInsertChildren />
</o:treeNodeItem>
</o:treeNode>
<o:treeNode>
<ul>
<o:treeNodeItem>
<li>
#{node.index} #{exampleEntity.value}
<o:treeInsertChildren />
</li>
</o:treeNodeItem>
</ul>
</o:treeNode>
</o:tree>
</h:panelGroup>
<hr/>
<h3>Editable tree</h3>
<h:form id="form">
<c:set var="saveButtonPressed" value="#{param['jakarta.faces.source'] == 'form:save'}" />
<h:commandButton id="addNode" value="Add new node" action="#{treeBean.addChild(treeBean.tree)}">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<o:tree id="tree" value="#{treeBean.tree}" var="exampleEntity" varNode="node">
<o:treeNode>
<ul>
<o:treeNodeItem>
<li>
<h:inputText id="value" value="#{exampleEntity.value}" styleClass="treeinput"
required="#{saveButtonPressed}" requiredMessage="Please enter value" />
<h:commandButton id="addChild" value="Add new child" action="#{treeBean.addChild(node)}"
rendered="#{node.level lt 10}">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<h:commandButton id="remove" value="Remove node" action="#{treeBean.remove(node)}">
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<h:message for="value" />
<o:treeInsertChildren />
</li>
</o:treeNodeItem>
</ul>
</o:treeNode>
</o:tree>
<h:commandButton id="save" value="Save" action="#{treeBean.save}">
<f:ajax execute="@form" render="@form" />
</h:commandButton> (only for this view scope ;) )
<h:outputText value="OK!" rendered="#{facesContext.postback and not facesContext.validationFailed}" />
</h:form>
<h:outputScript>
/**
* Force input elements to invoke "Save" button on enter key instead of one of those "add"/"remove" buttons.
*/
$(document).on("keypress", ".treeinput", function(event) {
if (event.keyCode == 13) {
$(this).closest("form").find("[id$=save]").click();
return false;
}
});
</h:outputScript>
<o:onloadScript>
/**
* Focus the first empty input element on every ajax response.
*/
$(".treeinput:not([value]):first").focus();
</o:onloadScript>
package org.omnifaces.showcase.components;
import java.io.Serializable;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Named;
import org.omnifaces.cdi.ViewScoped;
import org.omnifaces.model.tree.ListTreeModel;
import org.omnifaces.model.tree.TreeModel;
import org.omnifaces.showcase.model.ExampleEntity;
import org.omnifaces.util.Ajax;
@Named
@ViewScoped
public class TreeBean implements Serializable {
private static final long serialVersionUID = 1L;
private TreeModel<ExampleEntity> tree;
@PostConstruct
public void init() {
tree = new ListTreeModel<>();
tree.addChild(new ExampleEntity(1L, "One"))
.addChild(new ExampleEntity(2L, "Two")).getParent()
.addChild(new ExampleEntity(3L, "Three")).getParent()
.getParent()
.addChild(new ExampleEntity(4L, "Four"))
.addChild(new ExampleEntity(5L, "Five"));
}
public void addChild(TreeModel<ExampleEntity> node) {
node.addChild(new ExampleEntity());
}
public void remove(TreeModel<ExampleEntity> node) {
node.remove();
}
public void save() {
Ajax.update("staticTree");
}
public TreeModel<ExampleEntity> getTree() {
return tree;
}
}
package org.omnifaces.showcase.model;
import java.io.Serializable;
public class ExampleEntity implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String value;
public ExampleEntity() {
//
}
public ExampleEntity(Long id, String value) {
this.id = id;
this.value = value;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public boolean equals(Object other) {
return (other instanceof ExampleEntity) && (id != null)
? id.equals(((ExampleEntity) other).id)
: (other == this);
}
@Override
public int hashCode() {
return (id != null)
? (this.getClass().hashCode() + id.hashCode())
: super.hashCode();
}
@Override
public String toString() {
return String.format("ExampleEntity[%d, %s]", id, value);
}
}
VDL documentation
API documentation
Java source code
org.omnifaces.model.tree.AbstractTreeModel
org.omnifaces.component.tree.Tree
org.omnifaces.component.tree.TreeNode
org.omnifaces.model.tree.TreeModel
org.omnifaces.model.tree.ListTreeModel
org.omnifaces.component.tree.TreeNodeItem
org.omnifaces.component.tree.TreeFamily
org.omnifaces.component.tree.TreeInsertChildren
org.omnifaces.model.tree.SortedTreeModel