Vue3 Tree is used to display hierarchical data. It supports Vue 3 with PrimeVue 3 and Vue 2 with PrimeVue 2.
Setup
Refer to PrimeVue setup documentation for download and installation steps for your environment such as Vue CLI, Vite or browser.
Import
import Tree from 'primevue/tree';
Getting Started
Tree component requires an array of TreeNode objects as its value.
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);
}
}
import axios from 'axios';
export default class NodeService {
getTreeNodes() {
return axios.get('demo/data/treenodes.json').then(res => res.data.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",
"children": [{
"key": "2-0",
"icon": "pi pi-fw pi-star",
"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",
"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" }]
}]
}
]
}
Programmatic Control
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;
});
}
}
Selection
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
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
}];
}
}
}
Templating
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'}
]
}
]
}
}
}
Filtering
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);
}
}
}
}
}
Theming
Tree supports various themes featuring Material, Bootstrap, Fluent as well as your own custom themes via the Designer tool.
Resources
Visit the PrimeVue Tree showcase for demos and documentation.