写点什么

Dojo 中的 LazyTreeGrid 控件

2011 年 11 月 24 日

数据呈现是 RIA 应用中的一个重点应用,各种 JavaScript 框架也一般都提供了自己的 Grid 小部件用于呈现表格类数据。而 TreeGrid 作为一种特殊的 Grid,顾名思义,更是兼具了 Tree 多层级结构以及 Grid 的多数据项复杂数据展示的优点,是一种很好的处理复杂多级数据的控件。然而,无论对于 Tree 或者 TreeGrid,通常由于实现方面的种种限制,对数据的延迟加载只能是针对层级结构而言的,即在展开某一节点时即时请求该节点下的全部子节点。尽管这对于一般的应用场景来说基本可以满足需求,但在当次级节点下数据结构较复杂,节点繁多的情况下,则可能造成极其严重的性能问题。针对这一特定需求,Dojo 从 1.6 开始推出了一个全新的控件——LazyTreeGrid。

LazyTreeGrid 的结构

作为 Dojo DataGrid 的扩展,LazyTreeGrid 在整体的数据结构上采用的仍然是 MVC 结构。

图 1. LazyTreeGrid 结构模型

图 1 就是 TreeGrid 的一个基本架构模型,就整体结构而言,LazyTreeGrid 与 TreeGrid、DataGrid 并没有太大区别。视图即为用户直接可见的部分,包括了 Grid 的表头、行、列、单元格及 TreeGrid 特有的节点展开按钮等直观内容,整个 TreeGrid 通过内容视图中的虚拟滚动条的滚动事件以及节点展开按钮 Expando 的展开或关闭事件的触发来获取数据并构建内容。

然而,为了满足针对次级节点的分页延迟加载及渲染功能,LazyTreeGrid 则需要基于树状层级结构要求在 Model 和 View 部分进行相应的扩展。下面就基于 LazyTreeGrid 的数据模型及视图结构来对其设计思路及实现方式做一个简单介绍。

LazyTreeGrid 的数据模型

正常状态的树状结构数据是层级嵌套模式的,如下例所示:

复制代码
data = {
identifier: 'id',label: 'name',
items: [
{
id: 'AF',name: 'Africa',
children: [
{
id: 'EG', name: 'Egypt'
},
{
id: 'KE', name: 'Kenya',
children: [
{
id: 'Nairobi', name: 'Nairobi', type: 'city'
},
{
id: 'Mombasa', name: 'Mombasa',type: 'city'
}
]
},
...
]
},
...
]
}

与其他的可延迟加载设计的 Tree 类型应用的数据实现要求类似,为了达到延迟加载次级数据的目的,需要对父节点数据做压平处理,将其与子节点在结构上进行分离,这样才可以在进行数据的最初请求时只加载必须的父节点数据,仅在展开父节点时再延迟加载其下的次级数据。另外,在 LazyTreeGrid 中,对于某些子节点数目很少,不需要延迟加载的情况,这里也允许存在未被压平的数据节点,如下例所示:

复制代码
data = {
identifier: 'id',label: 'name',
items: [
{
id: 'AF', name: 'Africa',children: 10
},
{
id: 'EG', name: 'Egypt',children: false
},
{
id: 'KE',name: 'Kenya',
children: [
{
id: 'Nairobi',name: 'Nairobi',type: 'city'
},
{
id: 'Mombasa', name: 'Mombasa',type: 'city'
}
]
},
...
]
}

LazyTreeGrid 所要求的数据结构允许父节点将原有的嵌套数据替换为一个正整数数值或布尔值,用以代表其下的子节点数目或者是否有子节点(false,非正数或者没有相应属性都代表该节点没有子节点)。需要注意的是,LazyTreeGrid 要求数据必须拥有一个唯一的主键 id,这样才可以通过该 id 去服务器端请求相应数据条目的子数据。

在 LazyTreeGrid 数据模型(model)部分,除了 DataGrid 原有的 DataStore(Dojo 数据存储器)外,由于 Dojo 本身的 DataStore 对树形数据结构的 API 方面的支持不足,另外增加了一个 TreeModel 用于提供针对树形结构数据的特定支持。而为了能够满足分页加载次级数据的要求,LazyTreeGrid 实现了一个特殊的 TreeModel:dojox.grid.LazyTreeGridStoreModel,其主要功能就是建立一个后台数据获取协议,通过指定父节点与子节点序列来使服务器端正确返回相应的分页数据,在请求次级数据时,LazyTreeGrid 将向后台服务端发送类似如下的一条请求:

http://localhost:8080/TreeGrid/FakeDataServlet?parentId=root1&start=0&count=25

在这里,parentId 即为要请求数据的父节点 id,而 start 和 count 分别代表了请求起始的子节点序列和请求的节点个数。

下面的代码给出了如何建立一个简单的 LazyTreeGridStoreModel:

复制代码
// programmatic
var treeModel = new dojox.grid.LazyTreeGridStoreModel({
store: queryReadStore,
serverStore: true
});
// declarative
<span data-dojo-type="dojox.grid.LazyTreeGridStoreModel"
data-dojo-props="store:queryReadStore, serverStore:true" >
</span>

建立一个 LazyTreeGridStoreModel 需要确定两个参数:store 和 serverStore,store 用于指定获取数据的 dojo DataStore;serverStore 接收一个布尔值,用于确定是否数据由服务器端传递且满足数据条目是被压平存储、传输的(存储在客户端的数据没必要采用延迟加载模式)。对于次级数据量不大,不需要分页加载子数据的情况,用户也可以选择使用 Dojo 原有的 dijit.tree.ForestStoreModel。

LazyTreeGrid 的视图 (View)

在 Dojo1.6 之前存在的 dojo.grid.TreeGrid,采用的视图构建方式是认为所有的子节点都是最上级父节点的内容扩展,即在一行之内渲染出所有的展开的子节点结构,如下图所示:

图 2. Dojox.grid.TreeGrid 视图

在这个图例中,Grid 每行最左侧的就是 rowSelector——行选择按钮,根据 rowSelector 的分配情况,我们就可以清楚的看出其行结构是按第一级节点进行划分的。

尽管就整体视图结构来看这一做法并无不妥,但由于 TreeGrid 复用了 DataGrid 的按行结构进行分页的延迟加载与渲染机制,因此位于当前页的所有行的内容就都会被一次加载及渲染。那么当次级节点较多、较复杂的情况下,这种加载,尤其是渲染所带来的资源消耗以及响应时间就会变得非常突出和难以忍受了。

在 LazyTreeGrid 中,根据在数据模型中对数据进行的预处理,在保留层级信息的基础上对数据进行了分离处理,这样就可以在视图中将各条数据都作为一条独立数据行进行加载渲染。由图 3 中可以看出,每条数据都是单独的一行,因此,在复用了 DataGrid 的 Virtual Scroller 机制的前提下,即使数据中包含了很多的次级节点,也会忽略其层级结构,仅根据对 Grid 的分页配置进行划分,延迟加载与渲染数据条目,从而达到性能上的极大提升。

图 3. LazyTreeGrid 视图

LazyTreeGrid 中的其他特性

在树状结构数据中,可能会存在不同级别的数据条目中的数据列不完全相同的情况,更常见的是上级数据为概要据,而次级数据则作为详细数据存在。因此在这一类情况下,用户有可能需要对不同级别数据定制出不同的表达格式。针对这种需求,LazyTreeGrid 分别支持分级合并单元格设置和分级数据格式化设置。

图 4. LazyTreeGrid 实例

图 4 就是一个基于一个简单化的存储管理表格示例,最顶级数据是存储器群组,第二级和第三级数据分别是物理磁盘和虚拟磁盘。在这一示例中,就根据根级数据和次级数据的数据不一致性做了分级合并单元格和分级单元格格式化,使得整体视图更加清晰明了。

分级合并单元格需要在声明 LazyTreeGrid 时为其添加一个 colSpans 属性:

复制代码
colSpans: {0: [ {start: 0, end: 1},  {start: 2, end: 3, primary: 2}] 1: [], 2:… }

colSpans 属性接收一个 JSON 对象,该对象中的键 0/1/2/…分别对应各个级别,其值则用于指定单元格合并的细节,start 代表从第几个单元格开始合并,end 指合并到第几个单元格(都是以 0 作为起始值),而 primary 则指示了合并后显示第几个单元格的内容,在没有指定的情况下,其默认值等于 start 的值。

而分级单元格格式化则继承了与 DataGrid 的单元格格式化相同的方式,即分别为各个列设定 formatter 函数,不同的是,所提供的 formatter 函数的第三个参数为该行数据所在的级别,下例给出了一个简单的 formatter 函数:

复制代码
var fmtByLevel = function(value, idx, level) {
return level == 0 ? "root" : level == 1 ? "2nd" : "3rd";
};

LazyTreeGrid 的使用

下面,我们就基于图 4 中的示例,简述如何构建一个 LazyTreeGrid。

简单而言,由于 LazyTreeGrid 继承于 DataGrid,所以其基本创建过程与 DataGrid 基本一致,主要的区别就是前面提到过的需要在 Grid 和 DataStore 之间加入一个 TreeModel。下面就是以 JS 编程方式创建这一 LazyTreeGrid 的前台代码示例。

首先需要对 LazyTreeGrid 的视图结构进行定义:

复制代码
// LazyTreeGrid 结构定义
var layout = [{
name: "Name",
field: "name",
width: "150px",
formatter: fmtName
}, {
name: "Status",
field: "status",
width: "40px",
formatter: fmtStatus
}, {
name: "Capacity",
field: "capacity",
width: "80px",
formatter: fmtCapacity
}, {
name: "UID",
field: "uid",
width: "240px"
}];

Layout 主要定义了 Grid 的列名、列宽度、相应的取值数据项等,另外我们也可以看到,针对前三个列分别定义了三个 formatter 函数,用于定义分级的单元格格式化。其中的 fmtCapacity 函数采用了对第一级数据返回一个 progressbar 的小部件:

复制代码
// progress bar formatter
var fmtCapacity = function (value, idx, level) {
return level == 0
new dijit.ProgressBar({
progress: value * 100,
maximum: 100,
report: function (percent) {
return (percent * 100).toFixed(2) + "% Storage Online";
}
}): value + "GB";
};

DataStore 方面采用的是 dojox.data.QueryReadStore,通过同域中的一个 servlet 获取数据,并基于这个 DataStore 创建了一个 LazyTreeGridStoreModel 来连接到 LazyTreeGrid。如下段代码所示:

复制代码
// 创建 DataStore
var queryReadStore = new dojox.data.QueryReadStore({
id: "queryReadStore",
url: "FakeDataServlet"
});
// 创建 TreeModel
var treeModel = new dojox.grid.LazyTreeGridStoreModel({
id: "treeModel",
serverStore: true,
store: queryReadStore
});

最后,我们需要在页面中放入一个 id 为’gridContainer’的 div 作为 LazyTreeGrid 的容器元素,并在页面加载完成后对 LazyTreeGrid 的创建及初始化操作:

复制代码
// 创建 LazyTreeGrid
var grid;
dojo.addOnLoad(function () {
grid = new dojox.grid.LazyTreeGrid({
id: "grid",
rowSelector: true,
treeModel: treeModel,
structure: layout,
colSpans: {
0: [{
start: 0,
end: 1
}, & nbsp; {
start: 2,
end: 3,
primary: 2
}]
}
});
dojo.byId('gridContainer').appendChild(grid.domNode);
grid.startup();
});

至此,我们已经完成了 LazyTreeGrid 的页面创建工作,接下来的工作就是创建合适的后台代码来正确响应 LazyTreeGrid 的数据请求。

小结

作为 Dojo1.6 新引入的一个 Widget,尽管 LazyTreeGrid 仍有很多亟待解决的缺陷或问题,如尚不支持初始化及运行时的 Expand/Collapse All 的功能、可调用 API 较少等,但它作为一种呈现复杂多层级数据的 RIA 应用小部件,其对于延迟加载与延迟渲染方面所提出的解决方案还是具有一定的突破性的,具有这方面应用要求的用户不妨一试。


感谢张凯峰对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2011 年 11 月 24 日 00:004800

评论

发布
暂无评论
发现更多内容

【涂鸦物联网足迹】物联网常见通信协议

IoT云工坊

物联网 HTTP 通信协议 mqtt coap

五年Java开发经验,从传统企业月薪20k到互联网大厂月薪50k,我只做了这一件事!

Java成神之路

Java 程序员 架构 面试 编程语言

《微信小程序开发入门与实践》.pdf

田维常

面试大厂的尖兵利器!超全算法笔试模拟题精解合集,这份《程序员面试宝典》简直太牛了

Java成神之路

Java 程序员 架构 面试 编程语言

连续肝了好几个晚上,终于把网络相关知识和计算机底层操作系统知识总结整理出来了,两份文档资料分享给你!

Java成神之路

Java 程序员 架构 面试 编程语言

巨头们为什么要开源自己的技术?解析科技企业对软件开源的态度

Marilyn

开源 敏捷开发

ICT芯矿链挖矿矿机系统开发平台丨ICT芯矿链源码案例

系统开发咨询1357O98O718

ICT芯矿链矿机系统开发

2020年阿里巴巴136道高频Java面试真题(含答案),附赠Java复习资料。

Java成神之路

Java 程序员 架构 面试 编程语言

喜讯 | 拍乐云荣登2020「年度最具投资价值创新企业TOP20」榜单

拍乐云Pano

音视频 实时音视频 音视频算法 拍乐云

投行工作的本质 | 读《投行职业进阶指南:从新手到合伙人》

邓瑞恒Ryan

读书笔记 投资 金融 投行 职业第二曲线

公安大数据分析系统开发,情报研判系统搭建

t13823115967

智慧公安

熬夜整理10 万字节详细面试笔记(带完整目录) 良心分享

Crud的程序员

Java 编程 程序员 架构 java面试

我看技术人的成长路径

阿里巴巴云原生

开发者 云原生 技术人 自我思考 职场成长

北京天源迪科与重庆邮电大学移通学院成功签约

DT极客

探究神秘的SpringMVC,寻找遗失的web.xml踪迹

996小迁

Java 编程 程序员 架构 面试

GaussDB(DWS)应用实践丨负载管理与作业排队处理方法

华为云开发者社区

数据 负载 GaussDB

忒棒了!阿里P8大牛用这份技术点直接带你玩转高可用服务架构

比伯

Java 编程 架构 互联网 程序人生

产品推荐 | 还在自研?快来解锁拍乐云互动白板

拍乐云Pano

音视频 在线教育 RTC 互动白板

深入浅出理解视频编解码技术

拍乐云Pano

音视频 RTC 拍乐云 视频编解码 视频算法

构建一张音视频全球大网究竟需要多少个节点?Pano Backbone技术探秘

拍乐云Pano

音视频 RTC 实时音视频 音视频算法 拍乐云

第八周大作业

小兵

看到Mybatis源码就感到烦躁,怎么办?

田维常

mybatis

快来!开源一份阿里微服务指导手册:SpringBoot+SpringCloud+消息中间件

Java架构追梦

Java 架构 面试 微服务

H3C核心交换机故障处理通用流程

网络技术平台

Norvarm波场链系统开发方案丨Norvarm波场源码功能

系统开发咨询1357O98O718

Norvarm波场链系统开发

有道逻辑英语-时态新发现笔记

Leo

学习 前端进阶训练营 笔记 时态

区块链应用落地,物流供应链平台搭建

t13823115967

区块链应用

Gemini双子新约交易所系统软件APP开发

开發I852946OIIO

系统开发

云小课 | 需求任务还未分解,该咋整!项目管理Scrum项目工作分解的心酸谁能知?

华为云开发者社区

项目管理 敏捷 devcloud

云原生体系下的技海浮沉与理论探索

阿里巴巴云原生

Serverless 容器 微服务 云原生 k8s

太牛皮了!这份神仙级面试笔记把所有Java知识面试题都详解出来了。免费分享PDF文档,先到先得。

Java成神之路

Java 程序员 架构 面试 编程语言

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

Dojo中的LazyTreeGrid控件-InfoQ