# 树组件
树组件是一个功能强大的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+的子树,会发现,没有使用延迟模式的情况下,在开始时浏览器会出现明显的卡顿。
← @on