import Tree from 'primevue/tree';
<script src="https://unpkg.com/primevue@^3/core/core.min.js"></script>
Tree component requires an array of TreeNode objects as its value.
Name | Type | Default | Description |
---|---|---|---|
key | any | null | Mandatory unique key of the node. |
label | string | null | Label of the node. |
data | any | null | Data represented by the node. |
type | string | null | Type of the node to match a template. |
icon | string | null | Icon of the node to display next to content. |
expandedIcon | string | null | Icon to use in expanded state. |
collapsedIcon | string | null | Icon to use in collapsed state. |
children | TreeNode[] | null | An array of treenodes as children. |
style | string | null | Inline style of the node. |
styleClass | string | null | Style class of the node. |
selectable | boolean | null | Whether the node is selectable when selection mode is enabled. |
leaf | boolean | null | Specifies if the node has children. Used in lazy loading. |
Example below loads the tree nodes from a remote datasource via a service called NodeService.
<Tree :value="nodes"></Tree>
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
}
}
export default class NodeService {
getTreeNodes() {
return fetch('demo/data/treenodes.json').then(res => res.json())
.then(d => d.root);
}
}
The json response sample would be as following.
{
"root": [
{
"key": "0",
"label": "Documents",
"data": "Documents Folder",
"icon": "pi pi-fw pi-inbox",
"children": [{
"key": "0-0",
"label": "Work",
"data": "Work Folder",
"icon": "pi pi-fw pi-cog",
"children": [{ "key": "0-0-0", "label": "Expenses.doc", "icon": "pi pi-fw pi-file", "data": "Expenses Document" }, { "key": "0-0-1", "label": "Resume.doc", "icon": "pi pi-fw pi-file", "data": "Resume Document" }]
},
{
"key": "0-1",
"label": "Home",
"data": "Home Folder",
"icon": "pi pi-fw pi-home",
"children": [{ "key": "0-1-0", "label": "Invoices.txt", "icon": "pi pi-fw pi-file", "data": "Invoices for this month" }]
}]
},
{
"key": "1",
"label": "Events",
"data": "Events Folder",
"icon": "pi pi-fw pi-calendar",
"children": [
{ "key": "1-0", "label": "Meeting", "icon": "pi pi-fw pi-calendar-plus", "data": "Meeting" },
{ "key": "1-1", "label": "Product Launch", "icon": "pi pi-fw pi-calendar-plus", "data": "Product Launch" },
{ "key": "1-2", "label": "Report Review", "icon": "pi pi-fw pi-calendar-plus", "data": "Report Review" }]
},
{
"key": "2",
"label": "Movies",
"data": "Movies Folder",
"icon": "pi pi-fw pi-star-fill",
"children": [{
"key": "2-0",
"icon": "pi pi-fw pi-star-fill",
"label": "Al Pacino",
"data": "Pacino Movies",
"children": [{ "key": "2-0-0", "label": "Scarface", "icon": "pi pi-fw pi-video", "data": "Scarface Movie" }, { "key": "2-0-1", "label": "Serpico", "icon": "pi pi-fw pi-video", "data": "Serpico Movie" }]
},
{
"key": "2-1",
"label": "Robert De Niro",
"icon": "pi pi-fw pi-star-fill",
"data": "De Niro Movies",
"children": [{ "key": "2-1-0", "label": "Goodfellas", "icon": "pi pi-fw pi-video", "data": "Goodfellas Movie" }, { "key": "2-1-1", "label": "Untouchables", "icon": "pi pi-fw pi-video", "data": "Untouchables Movie" }]
}]
}
]
}
Tree state can be controlled programmatically with the expandedKeys property that defines the keys that are expanded. This property is a Map instance whose key is the key of a node and value is a boolean. Note that expandedKeys also supports two-way binding with the v-model directive.
Example below expands and collapses all nodes with buttons.
<div>
<Button type="button" icon="pi pi-plus" label="Expand All" @click="expandAll" />
<Button type="button" icon="pi pi-minus" label="Collapse All" @click="collapseAll" />
</div>
<Tree :value="nodes" :expandedKeys="expandedKeys"></Tree>
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null,
expandedKeys: {}
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
},
methods: {
expandAll() {
for (let node of this.nodes) {
this.expandNode(node);
}
this.expandedKeys = {...this.expandedKeys};
},
collapseAll() {
this.expandedKeys = {};
},
expandNode(node) {
this.expandedKeys[node.key] = true;
if (node.children && node.children.length) {
for (let child of node.children) {
this.expandNode(child);
}
}
}
}
}
To display some nodes as expanded by default, simply add their keys to the map.
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null,
expandedKeys: {}
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => {
this.nodes = data;
this.expandedKeys[this.nodes[0].key] = true;
this.expandedKeys[this.nodes[1].key] = true;
});
}
}
Tree supports single, multiple and checkbox selection modes. Define the selectionKeys with the v-model directive and the selectionMode properties to enable the selection. By default in multiple selection mode, metaKey is necessary to add to existing selections however this can be configured with metaKeySelection property. Note that in touch enabled devices, Tree does not require metaKey. In addition selection on a particular node can be disabled if the selectable is false on the node instance.
Similarly to the expandedKeys, selectionKeys is a Map instance whose key is the key of a node and value is a boolean in "single" and "multiple" cases. On the other hand in "checkbox" mode, instead of a boolean, value should be an object that has "checked" and "partialChecked" properties to represent the checked state of a node.
<h3>Single Selection</h3>
<Tree :value="nodes" selectionMode="single" v-model:selectionKeys="selectedKey1"></Tree>
<h3>Multiple Selection with MetaKey</h3>
<Tree :value="nodes" selectionMode="multiple" v-model:selectionKeys="selectedKeys1"></Tree>
<h3>Multiple Selection without MetaKey</h3>
<Tree :value="nodes" selectionMode="multiple" v-model:selectionKeys="selectedKeys2" :metaKeySelection="false"></Tree>
<h3>Checkbox Selection</h3>
<Tree :value="nodes" selectionMode="checkbox" v-model:selectionKeys="selectedKeys3"></Tree>
<h3>Events</h3>
<Tree :value="nodes" selectionMode="single" v-model:selectionKeys="selectedKey2" :metaKeySelection="false"
@node-select="onNodeSelect" @node-unselect="onNodeUnselect"></Tree>
import NodeService from '../../service/NodeService';
export default {
data() {
return {
selectedKey1: null,
selectedKey2: null,
selectedKeys1: null,
selectedKeys2: null,
selectedKeys3: null,
nodes: null
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
},
methods: {
onNodeSelect(node) {
this.$toast.add({severity:'success', summary: 'Node Selected', detail: node.label, life: 3000});
},
onNodeUnselect(node) {
this.$toast.add({severity:'success', summary: 'Node Unselected', detail: node.label, life: 3000});
}
}
}
To display some nodes as selected by default, simply add their keys to the map.
import NodeService from '../../service/NodeService';
export default {
data() {
return {
selectedKey1: null,
selectedKey2: null,
selectedKeys1: null,
selectedKeys2: null,
selectedKeys3: null,
nodes: null
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => {
this.nodes = data;
//single preselection
this.selectedKey1[this.nodes[0].key] = true;
//multiple preselection
this.selectedKeys2[this.nodes[0].key] = true;
this.selectedKeys2[this.nodes[1].key] = true;
//checkbox preselection
this.selectedKeys2[this.nodes[1].key] = {checked: true};
});
}
}
Lazy Loading is handy to deal with huge datasets. Idea is instead of loading the whole tree, load child nodes on demand using expand expand. The important part is setting leaf to true on a node instance so that even without children, tree would render an expand icon. Example below uses an in memory collection to mimic a lazy loading scenario with timeouts.
<Tree :value="nodes" @node-expand="onNodeExpand" :loading="loading"></Tree>
import NodeService from '../../service/NodeService';
export default {
data() {
return {
loading: false,
nodes: null
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.loading = true;
setTimeout(() => {
this.nodes = this.initateNodes();
this.loading = false;
}, 2000);
},
methods: {
onNodeExpand(node) {
if (!node.children) {
this.loading = true;
setTimeout(() => {
let _node = {...node};
_node.children = [];
for (let i = 0; i < 3; i++) {
_node.children.push({
key: node.key + '-' + i,
label: 'Lazy ' + node.label + '-' + i
});
}
let _nodes = {...this.nodes}
_nodes[parseInt(node.key, 10)] = _node;
this.nodes = _nodes;
this.loading = false;
}, 500);
}
},
initateNodes() {
return [{
key: '0',
label: 'Node 0',
leaf: false
},
{
key: '1',
label: 'Node 1',
leaf: false
},
{
key: '2',
label: 'Node 2',
leaf: false
}];
}
}
}
The type property of a TreeNode is used to map a template to a node to create the node label. If it is undefined and no default template is available, label of the node is used.
<Tree :value="nodes">
<template #default="slotProps">
<b>{{slotProps.node.label}}</b>
</template>
<template #url="slotProps">
<a :href="slotProps.node.data">{{slotProps.node.label}}</a>
</template>
</Tree>
export default {
data() {
return {
nodes: [
{
key: '0',
label: 'Introduction',
children: [
{key: '0-0', label: 'What is Vue.js?', data:'https://vuejs.org/v2/guide/#What-is-Vue-js', type: 'url'},
{key: '0-1', label: 'Getting Started', data: 'https://vuejs.org/v2/guide/#Getting-Started', type: 'url'},
{key: '0-2', label: 'Declarative Rendering', data:'https://vuejs.org/v2/guide/#Declarative-Rendering', type: 'url'},
{key: '0-3', label: 'Conditionals and Loops', data: 'https://vuejs.org/v2/guide/#Conditionals-and-Loops', type: 'url'}
]
},
{
key: '1',
label: 'Components In-Depth',
children: [
{key: '1-0', label: 'Component Registration', data: 'https://vuejs.org/v2/guide/components-registration.html', type: 'url'},
{key: '1-1', llabel: 'Props', data: 'https://vuejs.org/v2/guide/components-props.html', type: 'url'},
{key: '1-2', llabel: 'Custom Events', data: 'https://vuejs.org/v2/guide/components-custom-events.html', type: 'url'},
{key: '1-3', llabel: 'Slots', data: 'https://vuejs.org/v2/guide/components-slots.html', type: 'url'}
]
}
]
}
}
}
Scrolling is used to preserve space as content of the tree is dynamic and enabled by scrollHeight property.
<Tree :value="nodes1" scrollHeight="200px"></Tree>
In cases where viewport should adjust itself according to the table parent's height instead of a fixed viewport height, set scrollHeight option as flex. In example below, table is inside a Dialog where viewport size dynamically responds to the dialog size changes such as maximizing.
<Button type="button" icon="pi pi-external-link" label="View" @click="dialogVisible = true"></Button>
<Dialog header="Flex Scroll" v-model:visible="dialogVisible" :style="{width: '50vw'}" maximizable
:contentStyle="{height: '300px'}" class="p-fluid">
<Tree :value="nodes2" scrollHeight="flex"></Tree>
<template #footer>
<Button type="button" icon="pi pi-check" @click="dialogVisible = false" class="p-button-text"></Button>
</template>
</Dialog>
Filtering is enabled by setting the filter property to true, by default label property of a node is used to compare against the value in the text field, in order to customize which field(s) should be used during search, define the filterBy property as a comma separated list.
In addition filterMode specifies the filtering strategy. In lenient mode when the query matches a node, children of the node are not searched further as all descendants of the node are included. On the other hand, in strict mode when the query matches a node, filtering continues on all descendants.
<h3>Lenient Filter</h3>
<Tree :value="nodes" :filter="true" filterMode="lenient"></Tree>
<h3>Strict Filter</h3>
<Tree :value="nodes" :filter="true" filterMode="strict"></Tree>
import NodeService from '../../service/NodeService';
export default {
data() {
return {
nodes: null,
expandedKeys: {}
}
},
nodeService: null,
created() {
this.nodeService = new NodeService();
},
mounted() {
this.nodeService.getTreeNodes().then(data => this.nodes = data);
},
methods: {
expandAll() {
for (let node of this.nodes) {
this.expandNode(node);
}
this.expandedKeys = {...this.expandedKeys};
},
collapseAll() {
this.expandedKeys = {};
},
expandNode(node) {
if (node.children << node.children.length) {
this.expandedKeys[node.key] = true;
for (let child of node.children) {
this.expandNode(child);
}
}
}
}
}
Any property such as style and class are passed to the underlying root element. Following is the additional property to configure the component.
Name | Type | Default | Description |
---|---|---|---|
value | array | null | An array of treenodes. |
expandedKeys | array | null | A map of keys to represent the expansion state in controlled mode. |
selectionMode | string | null | Defines the selection mode, valid values "single", "multiple", and "checkbox". |
selectionKeys | any | null | A map of keys to control the selection state. |
metaKeySelection | boolean | true | Defines how multiple items can be selected, when true metaKey needs to be pressed to select or unselect an item and when set to false selection of each item can be toggled individually. On touch enabled devices, metaKeySelection is turned off automatically. |
loading | boolean | false | Whether to display loading indicator. |
loadingIcon | string | pi pi-spin | Icon to display when tree is loading. |
filter | boolean | false | When specified, displays an input field to filter the items. |
filterBy | string | label | When filtering is enabled, filterBy decides which field or fields (comma separated) to search against. |
filterMode | string | lenient | Mode for filtering valid values are "lenient" and "strict". Default is lenient. |
filterPlaceholder | string | null | Placeholder text to show when filter input is empty. |
filterLocale | string | undefined | Locale to use in filtering. The default locale is the host environment's current locale. |
scrollHeight | string | null | Height of the scroll viewport in fixed units or the "flex" keyword for a dynamic size. |
aria-label | string | null | Defines a string value that labels an interactive element. |
aria-labelledby | string | null | Establishes relationships between the component and label(s) where its value should be one or more element IDs. |
Name | Parameters | Description |
---|---|---|
node-select | node: Node instance | Callback to invoke when a node is selected. |
node-unselect | node: Node instance | Callback to invoke when a node is unselected. |
node-expand | node: Node instance | Callback to invoke when a node is expanded. |
node-collapse | node: Node instance | Callback to invoke when a node is collapsed. |
Following is the list of structural style classes, for theming classes visit
Name | Element |
---|---|
p-tree | Main container element |
p-tree-horizontal | Main container element in horizontal mode |
p-tree-container | Container of nodes |
p-treenode | A treenode element |
p-treenode-content | Content of a treenode |
p-treenode-toggler | Toggle element |
p-treenode-toggler-icon | Toggle icon |
p-treenode-icon | Icon of a treenode |
p-treenode-label | Label of a treenode |
p-treenode-children | Container element for node children |
Value to describe the component can either be provided with aria-labelledby or aria-label props. The root list element has a tree role whereas each list item has a treeitem role along with aria-label, aria-selected and aria-expanded attributes. In checkbox selection, aria-checked is used instead of aria-selected. The container element of a treenode has the group role. Checkbox and toggle icons are hidden from screen readers as their parent element with treeitem role and attributes are used instead for readers and keyboard support. The aria-setsize, aria-posinset and aria-level attributes are calculated implicitly and added to each treeitem.
Key | Function |
---|---|
tab | Moves focus to the first selected node when focus enters the component, if there is none then first element receives the focus. If focus is already inside the component, moves focus to the next focusable element in the page tab sequence. |
shift + tab | Moves focus to the last selected node when focus enters the component, if there is none then first element receives the focus. If focus is already inside the component, moves focus to the previous focusable element in the page tab sequence. |
enter | Selects the focused treenode. |
space | Selects the focused treenode. |
down arrow | Moves focus to the next treenode. |
up arrow | Moves focus to the previous treenode. |
right arrow | If node is closed, opens the node otherwise moves focus to the first child node. |
left arrow | If node is open, closes the node otherwise moves focus to the parent node. |
None.