这一章我们要实现是一个网格组件,该组件除了最基本的数据展示功能外,还提供排序以及数据过滤功能。
![](https://files.jb51.cc/file_images/article/201705/201755111115399.jpg?201745111123)
数据源
为了测试我们即将编写好网格组件,我们采用如下格式的数据源。此数据源包含两部分的内容,分别是表头数据集和表体数据集。网格组件实例最终的列数由表头数据集的长度决定。
顶层设计
从视觉上,我们很自然地把网格组件划分为表头与表体。此网格组件有三个功能,所以应该提供三个动态接口。但我们注意到排序功能是通过点击表头进行的,而表头属于网格组件的一部分,所以该功能应该内置。从而,实际上我们的网格组件对外只暴露两个动态接口:一个用于过滤,另一个用于接收数据源。所以我们可以得到如下的一个顶层设计。
设计表头
表头只有一行,所以可以直接给它提供一个 tr 元素。tr 元素的子级项 th 的个数取决于表头数据集的长度,所以需要动态创建。由于 th 元素包含了排序功能,所以需要另行封装。下面是我们给出的表头的设计。
`,opts) {
function setValue(value) {
sys.tr.children().call("remove");
data.forEach(item => sys.tr.append("Th").value().val(item));
}
return { val: setValue };
}
}
表头数据项组件提供一个文本设置接口。该组件本身并不负责排序,它只完成自身视图状态的变更以及排序命令的派发。排序命令的派发需要携带两个数据:一个是排序关键字,也就是表头文本;另一个排序方向,升或者降。
\
\
",opts) {
var order = "#asc";
this.watch("sort",function (e,key,order) {
sys.key.text().toLowerCase() == key || sys.th.removeClass("#active");
});
this.on("click",function (e) {
sys.th.addClass("#active");
sys.arrow.removeClass(order);
order = order == "#asc" ? "#dsc" : "#asc";
sys.arrow.addClass(order).notify("sort",[sys.key.text().toLowerCase(),order]);
});
sys.arrow.addClass("#asc");
return { val: sys.key.text };
}
}
设计表体
表体可以有多行,但表体只负责展示数据,所以实现起来比表头要简单的多。
`,opts) {
function setValue(gridColumns,gridData) {
sys.tbody.children().call("remove");
gridData.forEach(data =>
tr = sys.tbody.append("tr");
gridColumns.forEach(key => tr.append("td").text(data[key]));
));
}
return { val: setValue };
}
}
为了便于管理,我们把排序功能单独封装成一个组件,该组件提供一个排序接口,同时侦听一个排序消息。一旦接收到排序消息,则记录下关键字与排序方向,并派发一个表体刷新命令。
与排序功能的加入流程类似,我们把过滤功能单独封装成一个组件,该组件提供一个过滤接口,同时侦听一个过滤消息。一旦接收到消息,则记录下过滤关键字,并派发一个表体刷新命令。
另外需要对组件 DataGrid 作一些修正,修正内容与上述的排序功能的加入类似,区别在于额外完善了 filter 接口以及对消息作用域进行了限制。下面是我们最终的网格组件。
",opts) {
items.table.val(data);
sys.search.on("input",e => items.table.filter(sys.search.prop("value")));
}
}