Tree component ​
The tree component supports selection, drag-and-drop, editing, and lazy loading. This section shows how to create a tree and use its APIs and events.
This article references tree APIs and events. See API Reference:
Create a tree component ​
Use the <tree> tag in a template as shown below. The tree supports three attributes:
draggable: Enable drag-and-drop (default off). When enabled, nodes can be dropped on other nodes and firedragend.editableeditable: Enable in-place editing (default off). Double-click to edit; Enter confirms and firesconfirm-edit.lazy-mode: Lazy child rendering (default"none"). With"passive", children are created only when visible, which helps with large trees.
html
<tree
draggable="true"
editable="true"
lazy-mode="passive"
></tree>Display tree data ​
The previous example created an empty tree. Display data in two ways:
- Bind data with
@setter.data="expression"in the template. - Update data with the TreeComponent
setDataAPI.
Tip: Do not mix both approaches to avoid sync ordering issues.
Examples using the directive and setData. Click Run:
Data via
@setter.data:html<html> <div id="pdf-ui"></div> <template id="layout-template"> <webpdf class="custom-webpdf-container"> <my-tree class="my-tree-container"></my-tree> <viewer style="flex: 1"></viewer> </webpdf> </template> </html> <style> html{ overflow:hidden; } body { height: 100vh; } #pdf-ui { position: relative; top: 50px; } .custom-webpdf-container { display: flex; flex-direction: row; } .my-tree-container { width: 200px; border-right: 1px solid #ddd; overflow-y: scroll; } </style> <script> class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({ template: `<div @var.my_tree="$component"> <tree @setter.data="my_tree.treeData" ></tree> <xbutton @on.click="my_tree.handleClick()">Show More</xbutton> </div>` }) { static getName() { return 'my-tree' } treeData = [{ title: 'First', id: '1' }] handleClick() { this.treeData = [{ title: 'First', id: '1' }, { title: 'Second', id: '2' }, { title: 'Third', id: '3', isLeaf: false, children: [{ title: 'Fourth', id: '4' }] }]; this.digest(); } } UIExtension.modular.root().registerComponent(MyTreeComponent); const CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); const libPath = window.top.location.origin + '/lib'; const pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: '#pdf-ui', appearance: CustomAppearance, addons: [] }); </script>Data via
setDataAPIhtml<html> <div id="pdf-ui"></div> <template id="layout-template"> <webpdf class="custom-webpdf-container"> <my-tree class="my-tree-container"></my-tree> <viewer style="flex: 1"></viewer> </webpdf> </template> </html> <style> html{ overflow:hidden; } body { height: 100vh; } #pdf-ui { position: relative; top: 50px; } .custom-webpdf-container { display: flex; flex-direction: row; } .my-tree-container { width: 200px; border-right: 1px solid #ddd; overflow-y: scroll; } </style> <script> class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({ template: `<div @var.my_tree="$component"> <tree></tree> <xbutton @on.click="my_tree.handleClick()">Show Tree Data</xbutton> </div>` }) { static getName() { return 'my-tree' } handleClick() { const tree = this.childAt(0); tree.setData([{ title: 'First', id: '1' }, { title: 'Second', id: '2' }, { title: 'Third', id: '3', isLeaf: false, children: [{ title: 'Fourth', id: '4' }] }]) } } UIExtension.modular.root().registerComponent(MyTreeComponent); const CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); const libPath = window.top.location.origin + '/lib'; const pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: '#pdf-ui', appearance: CustomAppearance, addons: [] }); </script>
Editable tree ​
Set editable="true" on <tree> to edit node labels by double-clicking.
Example:
html
<tree editable="true"></tree>With editing enabled, double-click fires edit; update node data in the handler.
Listen for confirm-edit to get the final text.
Example:
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
editable="true"
@setter.data="my_tree.treeData"
@on.confirm-edit="my_tree.onConfirmEdit($args[0])"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'First',
id: '1'
}, {
title: 'Second',
id: '2'
}, {
title: 'Third',
id: '3',
isLeaf: false,
children: [{
title: 'Fourth',
id: '4'
}]
}]
onConfirmEdit(event) {
console.log(event.oldTitle, event.newTitle, event.nodeId);
}
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Set editable: false on a node in treeData to disable editing for that node.
Example:
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
editable="true"
@setter.data="my_tree.treeData"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'First',
id: '1'
}, {
title: 'Not editable',
id: '2',
editable: false
}, {
title: 'Third',
id: '3',
isLeaf: false,
children: [{
title: 'Fourth',
id: '4'
}]
}]
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Tip: If the tree
editableattribute isfalse, per-nodeeditablehas no effect.
Draggable tree ​
Set draggable on <tree>:
html
<tree draggable="true"></tree>Drag nodes onto other nodes; dragend provides dragged node data, target ID, and the new hierarchy—typically used to persist changes.
Example:
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
draggable="true"
@setter.data="my_tree.treeData"
@on.dragend="my_tree.handleDragendEvent($args[0])"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'First',
id: '1'
}, {
title: 'Second',
id: '2'
}, {
title: 'Third',
id: '3',
isLeaf: false,
children: [{
title: 'Fourth',
id: '4'
}]
}]
handleDragendEvent(event) {
console.log('On dragend event:', event)
}
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Set draggable: false on a node in treeData to prevent dragging that node.
Example:
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
draggable="true"
@setter.data="my_tree.treeData"
@on.dragend="my_tree.handleDragendEvent($args[0])"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'First',
id: '1'
}, {
title: 'Not draggable',
id: '2',
draggable: false
}, {
title: 'Third',
id: '3',
isLeaf: false,
children: [{
title: 'Fourth',
id: '4'
}]
}]
handleDragendEvent(event) {
console.log('On dragend event:', event)
}
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Tip: If tree draggable is false, per-node draggable has no effect.
Selectable tree nodes ​
Set selectable on <tree>:
html
<tree selectable="true"></tree>Selected nodes get the fv__ui-tree-node-selected CSS class; style it in your theme. The select event fires.
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
.fv__ui-tree-node-selected > .fv__ui-tree-node-wrapper {
color: red;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
selectable="true"
@setter.data="my_tree.treeData"
@on.select="my_tree.handleSelectEvent($args[0])"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'First',
id: '1'
}, {
title: 'Second',
id: '2',
}, {
title: 'Third',
id: '3',
isLeaf: false,
children: [{
title: 'Fourth',
id: '4'
}]
}]
handleSelectEvent(nodeId) {
console.log('On select event:', nodeId)
}
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Set selectable: false on a node in treeData.
Example:
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
.fv__ui-tree-node-selected > .fv__ui-tree-node-wrapper {
color: red;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
selectable="true"
@setter.data="my_tree.treeData"
@on.select="my_tree.handleSelectEvent($args[0])"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'First',
id: '1'
}, {
title: 'Not selectable',
id: '2',
selectable: false
}, {
title: 'Third',
id: '3',
isLeaf: false,
children: [{
title: 'Fourth',
id: '4'
}]
}]
handleSelectEvent(nodeId) {
console.log('On select event:', nodeId)
}
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Tip: If tree selectable is false, per-node selectable has no effect.
Tree node state ​
Node state fields:
disabled: Defaultfalse. Disabled nodes cannot be edited, selected, or dragged.activated: Whether children are expanded (no effect on leaf nodes).selected: Selection state (no effect if the node is not selectable).editting: Edit mode (no effect if the node is not editable).
Example:
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
.fv__ui-tree-node-selected > .fv__ui-tree-node-wrapper {
color: red;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
selectable="true"
draggable="true"
editable="true"
@setter.data="my_tree.treeData"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'Selected',
id: '1',
selected: true
}, {
title: 'Editting',
id: '2',
editting: true
}, {
title: 'Expanded',
id: '3',
isLeaf: false,
activated: true,
children: [{
title: 'Sub node',
id: '4'
}]
}, {
title: 'Disabled',
id: '5',
disabled: true,
}]
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Lazy mode ​
Lazy mode defers rendering, not data. Large trees should not insert all DOM nodes at once. Use lazy-mode="passive" to render nodes as needed.
Example with lazy mode:
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
.fv__ui-tree-node-selected > .fv__ui-tree-node-wrapper {
color: red;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
selectable="true"
draggable="true"
editable="true"
lazy-mode="passive"
@setter.data="my_tree.treeData"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'More than 10000+ Children',
id: '1',
isLeaf: false,
children: Array(10000).fill(0).map((_, i) => {
return {
title: '' + (Date.now() + i).toString(26),
id: Date.now() + i
};
})
}];
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Example without lazy mode:
html
<html>
<div id="pdf-ui"></div>
<template id="layout-template">
<webpdf class="custom-webpdf-container">
<my-tree class="my-tree-container"></my-tree>
<viewer style="flex: 1"></viewer>
</webpdf>
</template>
</html>
<style>
html {
overflow: hidden;
}
body {
height: 100vh;
}
#pdf-ui {
position: relative;
top: 50px;
}
.custom-webpdf-container {
display: flex;
flex-direction: row;
}
.my-tree-container {
width: 200px;
border-right: 1px solid #ddd;
overflow-y: scroll;
}
.fv__ui-tree-node-selected > .fv__ui-tree-node-wrapper {
color: red;
}
</style>
<script>
class MyTreeComponent extends UIExtension.SeniorComponentFactory.createSuperClass({
template: `<div @var.my_tree="$component">
<tree
selectable="true"
draggable="true"
editable="true"
lazy-mode="none"
@setter.data="my_tree.treeData"
></tree>
</div>`
}) {
static getName() {
return 'my-tree'
}
treeData = [{
title: 'More than 10000+ Children',
id: '1',
isLeaf: false,
children: Array(10000).fill(0).map((_, i) => {
return {
title: '' + (Date.now() + i).toString(26),
id: Date.now() + i
};
})
}];
}
UIExtension.modular.root().registerComponent(MyTreeComponent);
const CustomAppearance = UIExtension.appearances.Appearance.extend({
getLayoutTemplate: function () {
return document.getElementById('layout-template').innerHTML;
},
disableAll: function () {
}
});
const libPath = window.top.location.origin + '/lib';
const pdfui = new UIExtension.PDFUI({
viewerOptions: {
libPath: libPath,
jr: {
licenseSN: licenseSN,
licenseKey: licenseKey
}
},
renderTo: '#pdf-ui',
appearance: CustomAppearance,
addons: []
});
</script>Expanding a subtree with 10,000+ nodes is noticeably slower without lazy mode.