Skip to content

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:

TreeComponent
TreeNodeComponent
TreeNodeData

Create a tree component ​

Use the <tree> tag in a template as shown below. The tree supports three attributes:

  1. draggable: Enable drag-and-drop (default off). When enabled, nodes can be dropped on other nodes and fire dragend.
  2. editable
  3. editable: Enable in-place editing (default off). Double-click to edit; Enter confirms and fires confirm-edit .
  4. 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:

  1. Bind data with @setter.data="expression" in the template.
  2. Update data with the TreeComponent setData API.

Tip: Do not mix both approaches to avoid sync ordering issues.

Examples using the directive and setData. Click Run:

  1. 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>
  2. Data via setData API

    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></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 editable attribute is false, per-node editable has 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: Default false. 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.