索引
UI 库提供了表格组件类 ht.ui.TableView,用于显示 DataModel 数据容器中 Data 类型对象的属性信息,支持排序和过滤等功能
通过 tableView = new ht.ui.TableView(dataModel); 初始化构建一个表格组件对象,dataModel 参数为表格组件绑定的数据模型,该模型为空时表格组件构造函数内部将创建一个新的数据模型进行绑定
表格组件的 getColumnModel() 函数返回列模型对象,其本质也是个 DataModel 类型对象,只不过该对象是仅用于添加 ht.ui.Column 类型对象(注意不要和 ht.Column 混淆,ht.Column 用于 ht.widget.TableView),
ht.ui.Column 类型的父类为 ht.Data,增加了属性定义相关的函数接口。
因此用户所需要做的工作是,根据要显示的属性信息构建出 ht.ui.Column 对象,然后添加到 tableView.getColumnModel() 函数返回的列模型,
这样 tableView.getDataModel() 的数据模型中的 Data 的相关属性信息,就会根据 tableView.getColumnModel() 上存储的
ht.ui.Column 对象的配置进行显示。
ht.ui.Column 列类继承于 ht.Data,也可是设置父子关系实现表头嵌套功能,具备以下属性函数:
getName() 和 setName(name) 用于存取 name 属性,该属性结合 accessType 属性最终实现对 Data 属性的存取getDisplayName() 和 setDisplayName(displayName) 用于存取表头的列名内容,若为空则显示 name 值getIcon() 和 setIcon('icon') 存取表头的列名左侧显示的图标getWidth() 和 setWidth(80) 存取列宽度isVisible() 和 setVisible(true/false) 存取列是否可见isEditable(data) 和 setEditable(true/false) 存取该列是否可编辑,默认为 falseisBatchEditable() 和 setBatchEditable(true/false) 存取该列是否允许多选时批量编辑,默认为 truegetEditorClass() 和 setEditorClass(editor) 存取该列的编辑器类名(必须是 ht.ui.editor.Editor 的子类)getAccessType() 和 setAccessType(type) 操作存取列的属性类型:null: 默认类型,如 name 为 age,采用 getAge() 和 setAge(98) 的 get/set 或 is/set 方式存取style:如 name 为 age,采用 getStyle('age') 和 setStyle('age', 98) 的方式存取field:如 name 为 age,采用 data.age 和 data.age = 98 的方式存取attr:如 name为 age,采用 getAttr('age') 和 setAttr('age', 98) 的方式存取valueType 值类型用于提示组件提供合适的 renderer 渲染,合适的编辑控件,及改变值时必要的类型转换:null:默认类型,显示为文本方式string:字符串类型,显示为文本方式boolean:布尔类型,显示为勾选框color:颜色类型,以填充背景色的方式显示int:整型类型,显示时自动通过 parseInt 进行转换number:浮点数类型,显示时自动通过 parseFloat 转换getAlign() 和 setAlign('left'/'center'/'right') 决定以文本方式进行渲染时的水平对齐方式,默认为 leftgetSortOrder() 和 setSortOrder('desc'/'asc') 存取列升降序状态,点击表头排序时会自动改变其值isSortable() 和 setSortable(true/false) 存取列是否可排序标志,默认为 truegetSortFunc() 和 setSortFunc(function(v1, v2, d1, d2){return 1/-1/0}) 存取列排序函数,用于自定义排序逻辑column.getValue = function(data, column, view){return value} 自定义获取值方式column.setValue = function(data, column, value, view){} 自定义设置值方式column.formatValue = function(value, data) 将获取到的值格式化用来显示,可重写自定义column.getLabelColor = function(value, data, state) 返回单元格文字颜色(state 值为 undefiend 或 hover 或 select),可重写自定义column.getCellBackground = function(value, data) 返回单元格背景(返回值为 Drawable 对象或可转为 Drawable 对象的原始数据),可重写自定义column.getLabelFont = function(data) 返回当前列中单元格文字字体,可重写自定义column.drawCell = function (g, data, selected, column, x, y, w, h, view){} 自定义单元格渲染方式(用画笔绘制或返回 DOM )column.getToolTip = function(data, tableView) 自定义文字提示内容枚举是较为常见的属性编辑应用,在编辑时候呈现下拉列表,因此 UI 库提供了 ht.ui.EnumColumn( 从 ht.ui.Column 继承) 处理枚举展示和编辑需求(内置下拉框作为编辑器)
setDatas(['C','C++','JS']),设置枚举数据(参数格式与 ht.ui.ComboBox 的数据格式保持一致)setReadOnly(true/false),设置下拉框编辑器是否是只读的setDropDownConfig(config) 设置下拉框编辑器的属性(参数格式与 ht.ui.ComboBox#setDropDownConfig 保持一致)下面各种 Column 的例子(需要访问外网请求 jQuery 类库):
上面例子中,我们创建一个 TableView 组件,并且创建了八个列:
第一个列只是简单的显示节点的 id 属性:
var column = new ht.ui.Column();
column.setName('id');
column.setDisplayName('ID');
columnModel.add(column);
第二列显示 name 属性,并且将其设置为可编辑(双击启动编辑):
column = new ht.ui.Column();
column.setName('name');
column.setDisplayName('Title');
// 设置可编辑
column.setEditable(true);
// 使用字符串编辑器
column.setEditorClass('ht.ui.editor.StringEditor');
columnModel.add(column);
第三列用于显示 duration 属性,这个属性为 int 类型,但是我们重写了 formatValue 函数,通过拼接字符串返回了新的格式:
column = new ht.ui.Column();
column.setName('duration');
column.setAccessType('attr'); // 指示通过 data.getAttr('duration') 获取值
column.setDisplayName('Duration');
// 使用整数编辑器,编辑时只能输入整数
column.setEditorClass('ht.ui.editor.IntEditor');
column.formatValue = function (value, data) {
return value + ' Days';
};
column.setEditable(true);
columnModel.add(column);
第四列显示 progress 进度,通过重写 drawCell 绘制进度条
column = new ht.ui.Column();
column.setName('progress');
column.setAccessType('attr'); // 指示通过 data.getAttr('progress') 获取值
column.setDisplayName('% Complete');
column.drawCell = function (g, data, selected, column, x, y, w, h, view) {
var value = data.a('progress'),
rectY = y + (h - 20) / 2;
g.beginPath();
g.fillStyle = 'white';
g.fillRect(x, rectY, w, 20);
g.beginPath();
g.fillStyle = '#139dd7';
g.fillRect(x, rectY, w * (value / 100), 20);
ht.Default.drawText(g, value + '%', '12px Arial', 'black', x, y, w, h, 'center', 'middle');
}
column.setEditable(true);
column.setEditorClass('ht.ui.editor.NumberEditor');
columnModel.add(column);
第五列创建了一个 boolean 类型的列,自动用复选框渲染:
column = new ht.ui.Column();
column.setName('important');
column.setDisplayName('Important');
column.setAccessType('attr');
column.setValueType('boolean');// valueType 设置为 boolean 类型后,无需再设置 editorClass,只要 editable 设为 true 即可编辑
column.setEditable(true);
columnModel.add(column);
第六列显示颜色值:
column = new ht.ui.Column();
column.setName('color');
column.setDisplayName('Color');
column.setAccessType('attr');
// 颜色类型
column.setValueType('color');
column.setEditable(true);
column.setEditorClass('ht.ui.editor.ColorEditor');
columnModel.add(column);
第七列是一个枚举列(ht.ui.EnumColumn)
// 创建枚举列
column = new ht.ui.EnumColumn();
// 设置枚举数据
column.setDatas(belongTos);
column.setName('belongTo');
column.setDisplayName('Belong To');
column.setAccessType('attr');
// 只需要设置可编辑即可,EnumColumn 内置了下拉框类型的编辑器
column.setEditable(true);
columnModel.add(column);
第八列重写了 drawCell 自定义使用 DOM 对象渲染单元格:
column = new ht.ui.EnumColumn();
column.setName('popularity');
column.setDisplayName('Popularity');
column.setAccessType('attr');
column.setEditable(true);
columnModel.add(column);
// 返回 DOM 对象
column.drawCell = function (g, data, selected, column, x, y, width, height) {
var chartDiv = data.chartDiv;
// 为了节省性能,chartDiv 需要缓存在 data 上
if (!chartDiv) {
// 设置 div 属性
chartDiv = data.chartDiv = document.createElement('div');
chartDiv.style.position = 'absolute';
chartDiv.style.top = (height - 20) / 2 + 'px';
chartDiv.style.width = width + 'px';
chartDiv.style.height = '20px';
}
if (data._timeId != null) {
clearTimeout(data._timeId);
delete data._timeId;
}
// 使用 sparkline 填充 div,注意要 setTimeout,因为 sparkline 填充 div 时要求 div 已经显示在页面中
data._timeId = setTimeout(function () {
$(chartDiv).sparkline(data.a('popularity'),
{ width: width, height: 20, type: 'bar', barColor: '#00a65a', negBarColor: '#ff4444' });
delete data._timeId;
}, 50);
return chartDiv;
};
单元格编辑组件的属性可以通过 Column 的 editorViewProperties 配置:
// 修改某一列数据的编辑组件属性
column.s('editorViewProperties', {
background: 'red', // 这里可以配置编辑组件的任意属性
color: 'white'
});
上面表格中列都是固定宽度,通过简单的代码即可实现百分比宽度(独立运行示例然后调整浏览器窗口大小查看效果):
当 Column 的宽度为负数表示当前列的宽度占表格剩余宽度(总宽度减去固定宽度)的百分比
// column 列的宽度为表格剩余宽度的 10%
column.setWidth(-0.1);
表格支持使用 visibleFunc 过滤只显示部分数据:
tableView.setVisibleFunc(function(data){
if (data.a('belongTo') === 'Yancy')
return true;
else
return false;
});
重写 drawRowBackground 可实现斑马线效果:
table.drawRowBackground = function (drawable, x, y, width, height, data) {
var g = table.getRootContext();
if (table.isSelected(data)) {
g.fillStyle = '#87A6CB';
}
else if (table.getRowIndex(data) % 2 === 0) {
g.fillStyle = '#F1F4F7';
}
else {
g.fillStyle = '#FAFAFA';
}
g.beginPath();
g.rect(x, y, width, height);
g.fill();
};
表格支持每行使用不同的高度,参考下面的例子:
例子中重写了第二列的 drawCell 函数创建一个 div 作为文本渲染器
column.drawCell = function (g, data, selected, column, x, y, width, height) {
var self = this,
div = data.div;
if (!div) {
div = data.div = document.createElement('div');
div.style.position = 'absolute';
div.style.wordWrap = 'break-word';
div.style.wordBreak = 'break-all';
div.style.paddingLeft = '4px';
div.style.touchAction = 'none';
div.style.cursor = 'default';
div.style.font = table.getLabelFont();
div.innerHTML = data.a('content');
}
div.style.color = ht.ui.uiTheme.textColor;
return div;
};
每一行的高度由文字内容确定,所以行高需要存储在每个 Data 上:
// 通过临时 DOM 的方式计算文本高度
function initRowsHeight(table) {
var dataModel = table.dm(),
div = table._tempDiv;
if (!div) {
var div = table._tempDiv = document.createElement('div');
div.style.position = 'absolute';
div.style.visibility = 'hidden';
div.style.wordWrap = 'break-word';
div.style.wordBreak = 'break-all';
div.style.paddingLeft = '4px';
}
div.style.width = table.getColumnModel().getDatas().get(1).getWidth() + 'px';
div.style.font = table.getLabelFont();
document.body.appendChild(div);
dataModel.getDatas().each(function (data) {
div.innerHTML = data.a('content');
// 高度值存储在 _rowHeight 私有变量上
data._rowHeight = div.scrollHeight;
});
document.body.removeChild(div);
}
然后通过 setRowHeightFunc 指定获取每行高度的方式:
table.setRowHeightFunc(function (data) {
return data._rowHeight;
});
上面例子中同时演示了 TableHeader 表头组件的用法:
// 使用 splitLayout 布局表头和表格组件
splitLayout.addView(tableHeader, {
region: 'first'
});
splitLayout.addView(table, {
region: 'second'
});
下面例子演示了如何使用利用自定义行高和动画动态插入数据:
表格的拖拽和 ListView 组件类似,如有需求请参考 ListView 手册;
表格默认会自动生成拖拽图片,但是如果表格中重写了 drawCell 函数使用 DOM 渲染单元格,自动生成的拖拽图片中不会包含 DOM 对象,此时一般需要重写 createDragImage 函数,
返回自定义的拖拽图片:
这个例子中,重写了 createDragImage 生成自定义的拖拽图片(通过这种方式,可实现任意效果的拖拽图片):
tableView.createDragImage = function (data, selection) {
// 返回矢量对象
return {
width: 80,
height: 24,
comps: [
{
type: 'rect',
relative: true,
rect: [17, 1, 1],
background: '#006dc8'
},
{
type: 'text',
relative: true,
rect: [17, 1, 1],
text: data.getName(),
color: 'white'
}
]
}
}
自定义行高的例子中演示了使用 SplitLayout 布局表头和表格;为了简化布局操作,UI 库提供了表格面板容器(ht.ui.TablePane),这个容器内部自动创建表头组件、表格组件、表尾组件:
这个例子中创建了 TablePane 实例,并且获取 TableView 和 DataModel 再处理:
tablePane = new ht.ui.TablePane();
tableView = tablePane.getTableView();
dataModel = tableView.getDataModel();
columnModel = tableView.getColumnModel();
//...
// 加到页面中
tablePane.addToDOM();
TableFooter 表尾组件默认是隐藏的,通过下面的方式将其显示出来:
tableFooter = tablePane.getTableFooter();
tableFooter.setVisible(true);
// ...
// 设置列的统计方式:
column.setSummary('max');
另外可以看到例子中第二、三、四列有同一个分组列:
// GroupColumn 用来对列进行分组
groupColumn = new ht.ui.GroupColumn();
groupColumn.setAlign('center');
groupColumn.setName('RGB');
再一个需要关注的地方是第二、三、四列使用了自定义的 SliderEditor 作为编辑器,单击即可使用 Slider 编辑颜色值的三原色:
column = new ht.ui.Column();
column.setName('r');
column.setDisplayName('R');
column.setAccessType('attr');
column.setEditable(true);
column.setEditorClass(SliderEditor);
columnModel.add(column);
为了拖拽拉条时能实时改变第一列的颜色值,我们监听了 r、g、b 三个属性变化:
dataModel.addDataPropertyChangeListener(function (e) {
var property = e.property,
data = e.data;
if (property === 'a:r' || property === 'a:g' || property === 'a:b') {
var r = data.a('r'),
g = data.a('g'),
b = data.a('b');
data.a('color', 'rgb(' + r + ',' + g + ',' + b + ')');
}
});
表格面板支持使用 addColumns 增加多列(等同于 columnModel.add):
tablePane.addColumns([
{
name: 'id',
displayName: '序号'
},
{
name: 'background',
accessType: 'style'
},
{
name: 'belongTo',
displayName: 'Belong To',
className: 'ht.ui.EnumColumn',
editable: true,
accessType: 'attr',
datas: ['Eric', 'Yancy', 'David']
},
{
name: 'Group Column', // 创建一个分组列
className: 'ht.ui.GroupColumn',
id: 'groupId1'
},
{
name: 'childColumn',
displayName: 'Child Column',
accessType: 'attr',
parentId: 'groupId1' // 指定从属的分组列
}
]);
UI 提供了树表组件类 ht.ui.TreeTableView,该组件融合了树和表格的展示方式,显示 DataModel 数据容器中 Data 对象的父子关系和属性信息,支持排序和过滤等功能
TreeTableView 和 TableView 非常相似:列模型、数据模型、单元格渲染、单元格编辑的使用方式基本一致,但是增加了父子关系呈现(需要访问外网请求 jQuery 类库):
TreeTableView 默认会自动构建一个 treeColumn 列,通过 treeTableView.getTreeColumn() 可得到该列进行属性修改。
treeTableView = treeTablePane.getTableView();
treeColumn = treeTableView.getTreeColumn();
treeColumn.setDisplayName('目录结构');
treeColumn.setAlign('center');
treeColumn.setWidth(260);
调用 treeTableView.expandAll() 可张开所有树节点,需注意要在模型数据添加完后进行:
treeTableView.expandAll();
表格分页也是很常见的需求,请看下面的例子:
这个例子中我们创建了 ht.ui.Pagination 组件表示分页组件,这个组件可以使用布局器自由布局到任意位置,使用时需要设置对应的表格、数据行数等:
pagination = new ht.ui.Pagination();
pagination.setRowCount(paginationTableDatas.length);
var pageButtonGroup = pagination.getPageButtonGroup();
pageButtonGroup.setButtonCount(4);
pagination.setRowsPerPage(10);
pagination.fit(tableView);
// 请求分页数据, 需要在这里根据当前页填充表格数据
pagination.on('execRequest', function(e) {
var tableView = e.tableView;
var pageIndex = e.currentPage;
var rowsPerPage = e.rowsPerPage;
var sortColumn = tableView.getSortColumn();
var datas = paginationTableDatas.slice(0);
// sort datas
if (sortColumn) {
var sortName = sortColumn.getName();
if (sortName === 'renderingEngine') {
if (sortColumn.getSortOrder() === 'asc') {
datas.sort(function(d1, d2) {
if (d1[0] > d2[0]) return 1;
else if (d1[0] < d2[0]) return -1;
else return 0;
});
}
else if (sortColumn.getSortOrder() === 'desc') {
datas.sort(function(d1, d2) {
if (d1[0] > d2[0]) return -1;
else if (d1[0] < d2[0]) return 1;
else return 0;
});
}
}
}
var start = pageIndex * rowsPerPage;
tableView.dm().clear();
for (var i = start; i < (pageIndex + 1) * rowsPerPage; i++) {
var row = datas[i];
if (row) {
var htData = new ht.Data();
htData.a('id', i);
htData.a('renderingEngine', row[0]);
htData.a('browser', row[1]);
htData.a('platform', row[2]);
htData.a('engineVersion', row[3]);
htData.a('cssGrade', row[4]);
tableView.dm().add(htData);
}
}
});
注意 execRequest 事件,每次点击分页组件中页数按钮(切换当前页)和表头列(排序)都会派发事件,上面例子中我们直接操作了内存数据,另外一种常见的需求是服务器端分页,示范代码:
paginationView.on('execRequest', function(e) {
var tableView = e.tableView;
var pageIndex = e.currentPage;
var rowsPerPage = e.rowsPerPage;
var sortColumn = tableView.getSortColumn();
var sortName,
sortOrder;
if (sortColumn) {
sortName = sortColumn.getName();
sortOrder = sortColumn.getSortOrder() || '';
}
ht.Default.xhrLoad('getDatas?pageIndex=' + pageIndex + '&sortName=' + sortName + '&sortOrder=' + sortOrder, function(datas) {
// 重新填充表格数据
tableView.dm().clear();
for (var i = 0; i < datas.length; i++) {
var row = datas[i];
if (row) {
var htData = new ht.Data();
htData.a('id', i);
htData.a('renderingEngine', row[0]);
htData.a('browser', row[1]);
htData.a('platform', row[2]);
htData.a('engineVersion', row[3]);
htData.a('cssGrade', row[4]);
tableView.dm().add(htData);
}
}
});
});