# 树组件
树组件是一个功能强大的 UI 组件,集成了选中、拖拽、编辑和延迟加载等功能。本节将通过示例介绍如何创建树组件以及其主要接口和事件的使用方法。
该文章将涉及到树组件的一些 API 和事件名称。有关详细信息,请参阅下面的 API Reference:
TreeComponent (opens new window) TreeNodeComponent (opens new window) TreeNodeData (opens new window)
# 开始
# 创建一个树组件
如下面的代码片段所示,你可以在模板中使用 <tree>
标签来创建一个树组件。树组件支持三个属性:
draggable
:表示是否启用拖拽功能,默认为不启用。启用拖拽功能后,树节点可以被拖拽到其他树节点上,并触发dragend
事件。editable
:表示是否启用编辑功能,默认为不启用。启用编辑功能后,可以通过双击编辑树节点上的文本,按回车键确认更新后会触发confirm-edit
事件。lazy-mode
:表示延迟加载子节点的模式,默认为"none"
,表示不启用延迟加载。当设置为"passive"
时,子节点只有在可视区域内才会被创建和渲染,这在存在大量子节点时可以提高性能。
<tree
draggable="true"
editable="true"
lazy-mode="passive"
></tree>
# 树组件数据展示
上一个示例代码仅仅创建了一个树组件,并没有显示任何内容,因为没有提供数据。在树组件中显示数据有两种方式:
- 在组件模板中使用
@setter.data="expression"
指令指定数据绑定关系,expression 表达式返回的结果将作为树组件的数据展示。 - 通过 [TreeComponent.setData] API 更新树组件的数据。
请注意,这两种方法不能混合使用,以避免由于同步顺序问题导致结果不符合预期。
以下是两个分别通过指令和 setData
展示树组件的示例,请点击 run
运行:
使用
@setter.data
指令展示数据:<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>
使用
setData
API 展示数据<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>
# 创建可编辑的树
要创建一个可编辑的树,需要在 <tree>
标签上将 editable
属性设置为 true。这将允许通过双击树节点文本来编辑节点名称。
例如:
<tree editable="true"></tree>
在启用编辑功能后,当树节点文本被双击时,将触发 edit
事件。您可以在事件监听函数中更新节点数据,使其可编辑。
当编辑完成后,您可以监听 confirm-edit
事件来获取用户编辑后的文本。
示例:
<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>
如果您想要设置指定节点为不可编辑,您可以在 treeData
中单独指定其数据参数 editable
的值为 false
。
示例:
<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>
请注意,如果树组件的 editable
参数设置为 false
,您将无法单独指定一个节点是否可编辑。
# 创建可拖拽的树
要使树组件可拖拽,您需要在 <tree>
组件上启用 draggable
属性:
<tree draggable="true"></tree>
这样,您可以将树节点拖拽到其他节点内。当拖拽结束时,会触发一个 dragend
事件。通过事件参数,您可以获取被拖拽节点的数据、目标节点的 ID 以及拖拽后节点之间的关系。通常在这个时候,应用程序需要根据这些信息更新后台数据。
示例:
<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>
如果您想要设置一个特定的节点为不可拖拽,您可以在 treeData
中单独指定其数据参数 draggable
的值为 false
。
示例:
<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>
请注意,如果树组件的 draggable
被指定为 false
,您将无法单独指定一个节点是否可拖拽。
# 可选择的树节点
要使树节点可选,您需要在 <tree>
组件上启用 selectable
属性:
<tree selectable="true"></tree>
这样,当用户点击树节点时,树节点会被添加上 fv__ui-tree-node-selected
CSS class。默认的外观不会有任何变化,但应用程序可以定制选中后的外观。同时,select
事件将会被触发。
<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>
如果您想要指定某个节点为不可选,您可以在 treeData
中单独指定其数据参数 selectable
的值为 false
。
示例:
<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>
请注意,如果将树组件的 selectable
指定为 false
,则您将无法单独指定一个节点是否可选。
# 树节点的状态
树节点有以下状态:
disabled
: 表示节点是否被禁用,默认为false
。被禁用的树节点不能被编辑、选择和拖拽。activated
: 表示子树是否被展开,默认为false
。如果节点是叶子节点,设置该值将没有效果。selected
: 表示节点是否被选中,默认为false
。如果节点是不可选的,设置该值将没有效果。editting
: 表示是否启用编辑,默认为false
。如果节点是不可编辑的,设置该值将没有效果。
示例:
<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>
# 延迟模式
延迟模式并不是指数据延迟,而是渲染延迟。考虑到可能存在大量的树节点,一次性向浏览器中插入过多的 DOM 节点可能会导致显示卡顿。为了解决这个问题,树组件提供了一种延迟模式 lazy-mode="passive"
,可以根据需要显示树节点。
使用延迟模式的示例:
<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>
不使用延迟模式的示例:
<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>
当运行上述两个示例,并点击展开一个包含超过 10000 个节点的子树时,可以观察到在没有使用延迟模式的情况下,浏览器在一开始就会出现明显的卡顿。