在我们项目中,一般用ztree和treegrid插件来展示分层级的部门树(表),有一次客户的部门体量比较大(数十万),导致ztree和treegrid都会直接卡死,于是有了改造为异步的需求;
直接看改造
首先是ztree
首先是ztree,ztree插件原生是支持异步使用的,只是初始化方法和同步略有区别;详细描述和api可以查看官网文档:http://www.treejs.cn/v3/api.php
官网demo示例:
代码:
//设置参数
var setting = {
data: {
simpleData: {
enable: true,
idKey: "id",
pIdKey: "parentId",
rootPId: -1
},
key: {
url:"nourl"
}
},
async: {
enable: true,
url: baseURL + "sys/region/selectAsync",
dataType: "json",
autoParam: ["id"],
//regionList才是真正节点
dataFilter: function(treeId, parentNode, childNodes) {
return childNodes.regionList;
}
},
callback: {
onAsyncSuccess: function () {
ztree = $.fn.zTree.getZTreeObj("regionTree");
var node = ztree.getNodeByParam("id", vm.region.parentId);
//ztree.selectNode(node);
if(node!=null){
vm.region.parentName = node.name;
}
//分行管理员默认选中城市
if((vm.user.userLevel == 2 || vm.user.userLevel == 4) && vm.region.method == "add") {
var zNodes = ztree.getNodes();
vm.region.parentId = zNodes[0].id;
vm.region.parentName = zNodes[0].name;
vm.city.parentId = zNodes[0].cityId;
vm.getcity();
}
}
}
};
//初始化
getRegion: function(){
$.fn.zTree.init($("#regionTree"), setting);
//加载分行树(同步)
/*$.get(baseURL + "sys/region/select", function(r){
ztree = $.fn.zTree.init($("#regionTree"), setting, r.regionList);
var node = ztree.getNodeByParam("id", vm.region.parentId);
ztree.selectNode(node);
if(node!=null){
vm.region.parentName = node.name;
}
})*/
},
然后是treegrid
treegrid目前官方是没有提供异步使用方法的,所以它的改造要复杂很多;
需要自己重写扩展代码jquery.treegrid.extension.js
文件,重些行加载流程,逻辑,点击事件等等;
直接上代码留底,为了保证项目中其他同步的树表正常使用,我重新写了一个扩展命名为 jquery.treegrid.ajax.js
,在不通的页面(有的不需要异步)调用不同的扩展进行初始化就可以了。
(function($) {
"use strict";
$.fn.ajaxTreeTable = function(options, param) {
// 如果是调用方法
if (typeof options == 'string') {
return $.fn.ajaxTreeTable.methods[options](this, param);
}
// 如果是初始化组件
options = $.extend({}, $.fn.ajaxTreeTable.defaults, options || {});
// 是否有radio或checkbox
var hasSelectItem = false;
var target = $(this);
// 在外层包装一下div,样式用的bootstrap-table的
var _main_div = $("<div class='fixed-table-container'></div>");
target.before(_main_div);
_main_div.append(target);
target.addClass("table table-hover treegrid-table table-bordered");
if (options.striped) {
target.addClass('table-striped');
}
// 工具条在外层包装一下div,样式用的bootstrap-table的
if(options.toolbar){
var _tool_div = $("<div class='fixed-table-toolbar' style='display:none;'></div>");
var _tool_left_div = $("<div class='bs-bars pull-left'></div>");
_tool_left_div.append($(options.toolbar));
_tool_div.append(_tool_left_div);
_main_div.before(_tool_div);
}
var j = 0;
target.renderChildRows = function(data, parentNode, parentIndex, tbody){
$.each(data, function(i, item) {
var tr = $('<tr></tr>');
tr.addClass('treegrid-' + parentIndex + '_' + i);
tr.addClass('treegrid-parent-' + parentIndex);
//tr.addClass('unknow');
if(item[options.isParent] != true && item[options.isParent] != 'true'){
tr.attr('isLeaf','true');
}
tr.attr('trid',item[options.id]);
target.renderRow(tr,item);
item.isShow = true;
tbody.after(tr);
});
};
// 绘制行
target.renderRow = function(tr,item){
var treeColumn = options.expandColumn?options.expandColumn:(hasSelectItem?1:0)
$.each(options.columns, function(index, column) {
// 判断有没有选择列
if(index==0&&column.field=='selectItem'){
hasSelectItem = true;
var td = $('<td style="text-align:center;width:36px"></td>');
if(column.radio){
var _ipt = $('<input name="select_item" type="radio" value="'+item[options.id]+'"></input>');
td.append(_ipt);
}
if(column.checkbox){
var _ipt = $('<input name="select_item" type="checkbox" value="'+item[options.id]+'"></input>');
td.append(_ipt);
}
tr.append(td);
}else{
var td = $('<td mainid='+item[options.id]+' style="text-align:'+column.align+';'+((column.width)?('width:'+column.width):'')+'"></td>');
// 增加formatter渲染
if (column.formatter) {
td.html(column.formatter.call(this, item, index));
} else {
td.text(item[column.field]);
}
if(index == treeColumn){
var indent = '';
var strs = tr.attr('class').split(" ")[0].split("_");
var level = strs.length - 1;
for(var i=0; i<level-1; i++){
indent = indent + '<span class="treegrid-indent"></span>';
}
var span = indent + '<span class="treegrid-expander"></span>';
td.html(span+item[column.field]);
}
tr.append(td);
}
//绘制展开图标
target.painExpend(tr);
//添加行内点击事件
target.addTrEvent(tr);
});
}
// 加载数据
target.load = function(parms){
// 加载数据前先清空
target.html("");
// 构造表头
var thr = $('<tr></tr>');
$.each(options.columns, function(i, item) {
var th = null;
// 判断有没有选择列
if(i==0&&item.field=='selectItem'){
hasSelectItem = true;
th = $('<th style="text-align:'+item.valign+';width:36px"></th>');
}else{
th = $('<th style="text-align:'+item.valign+';padding:10px;'+((item.width)?('width:'+item.width):'')+'"></th>');
}
th.text(item.title);
thr.append(th);
});
var thead = $('<thead class="treegrid-thead"></thead>');
thead.append(thr);
target.append(thead);
// 构造表体
var tbody = $('<tbody class="treegrid-tbody"></tbody>');
target.append(tbody);
// 添加加载loading
var _loading = '<tr><td colspan="'+options.columns.length+'"><div style="display: block;text-align: center;">正在努力地加载数据中,请稍候……</div></td></tr>'
tbody.html(_loading);
// 默认高度
if(options.height){
tbody.css("height",options.height);
}
$.ajax({
type : options.type,
url : options.url,
data : parms?parms:options.ajaxParams,
dataType : "JSON",
success : function(data, textStatus, jqXHR) {
// 加载完数据先清空
tbody.html("");
if(!data||data.length<=0){
var _empty = '<tr><td colspan="'+options.columns.length+'"><div style="display: block;text-align: center;">没有记录</div></td></tr>'
tbody.html(_empty);
return;
}
$.each(data, function(i, item) {
var tr = $('<tr></tr>');
tr.addClass('treegrid-' + (j + "_" + i));
//tr.attr('load','false');
//tr.addClass('unknow');
if(item[options.isParent] != true && item[options.isParent] != 'true'){
tr.attr('isLeaf','true');
}
tr.attr('trid', item[options.id]);
target.renderRow(tr,item);
item.isShow = true;
tbody.append(tr);
});
target.append(tbody);
},
error:function(xhr,textStatus){
var _errorMsg = '<tr><td colspan="'+options.columns.length+'"><div style="display: block;text-align: center;">'+xhr.responseText+'</div></td></tr>'
tbody.html(_errorMsg);
debugger;
},
});
}
/*** 绘制展开图标 ***/
/*target.painExpends = function(trs) {
$.each(trs, function (index, tr) {
if (tr.attr('isLeaf') != 'true' && tr.attr('load') == 'true') { //$(item).hasClass('know')
tr.find("span:last-child").addClass('glyphicon-chevron-down').addClass('glyphicon');
} else if (tr.attr('isLeaf') != 'true' && tr.hasClass('unknow')) {
tr.find("span:last-child").addClass('glyphicon-chevron-right').addClass('glyphicon');
}
if (tr.attr('isLeaf') != 'true' && tr.attr('load') != 'true') {
tr.find("span.glyphicon-chevron-right").unbind();
tr.find("span.glyphicon-chevron-right").click(function () {
target.loadChilds(tr,
{
"id": tr.attr('trid'),
"parentIndex": tr.attr('class').split(" ")[0].split("-")[1]
});
}
);
}
});
}*/
/*** 绘制展开图标(单个) ***/
target.painExpend = function(tr) {
if (tr.attr('isLeaf') != 'true'){
var span = tr.find("span.treegrid-expander");
span.addClass('glyphicon-chevron-right').addClass('glyphicon');
span.unbind().click(function () {
target.loadChilds(tr,
{
"id": tr.attr('trid'),
"parentIndex": tr.attr('class').split(" ")[0].split("-")[1]
});
})
}
}
//添加行内点击事件
target.addTrEvent = function(tr){
tr.click(function(){
if(hasSelectItem){
var _ipt = $(this).find("input[name='select_item']");
if(_ipt.attr("type")=="radio"){
_ipt.prop('checked',true);
target.find("tbody").find("tr.treegrid-selected").removeClass("treegrid-selected");
$(this).addClass("treegrid-selected");
}else{
if(_ipt.prop('checked')){
_ipt.prop('checked',false);
$(this).removeClass("treegrid-selected");
}else{
_ipt.prop('checked',true);
$(this).addClass("treegrid-selected");
}
}
}
});
}
//隐藏子节点
target.hideChild = function(tr){
var parentIndex = tr.attr('class').split(" ")[0].split("-")[1];
var classArrtr = 'treegrid-parent-' + parentIndex;
var trs = target.find("tr."+classArrtr);
if(trs.length>=0){
$.each(trs, function (index, tr) {
$(tr).css("display","none");
target.hideChild($(tr));
});
}
var span = tr.find("span.glyphicon-chevron-down");
span.removeClass('glyphicon-chevron-down');
span.addClass('glyphicon-chevron-right');
span.unbind();
span.click(function() {
target.showChild(tr)
});
}
//展开子节点
target.showChild = function(tr){
var parentIndex = tr.attr('class').split(" ")[0].split("-")[1];
var classArrtr = 'treegrid-parent-' + parentIndex;
var trs = target.find("tr."+classArrtr);
if(trs.length>=0){
$.each(trs, function (index, tr) {
$(tr).css("display","");
});
}
var span = tr.find("span.glyphicon-chevron-right");
span.removeClass('glyphicon-chevron-right');
span.addClass('glyphicon-chevron-down');
span.unbind();
span.click(function() {
target.hideChild(tr)
});
}
/****** 加载子节点数据 start ***************/
target.loadChilds = function(parentTR,parms){
console.log("~~~~loadChilds~~~~~~");
$.ajax({
type : options.type,
url : options.url,
data : parms?parms:options.ajaxParams,
dataType : "JSON",
success : function(data, textStatus, jqXHR) {
//parentTR.removeClass('unknow').addClass('know');
//parentTR.attr('load','true');
//处理图标 和 点击事件
var parentSpan = parentTR.find("span.glyphicon-chevron-right");
parentSpan.removeClass('glyphicon-chevron-right').addClass('glyphicon-chevron-down');
parentSpan.unbind().click(function () {
target.hideChild(parentTR);
});
if(!data||data.length<=0){
return;
}
target.renderChildRows(data, {"mainId":parms.id}, parms.parentIndex, parentTR);
},
error:function(xhr,textStatus){
var _errorMsg = '<tr><td colspan="'+options.columns.length+'"><div style="display: block;text-align: center;">'+xhr.responseText+'</div></td></tr>'
tbody.html(_errorMsg);
debugger;
},
});
}
/****** 加载子节点数据 end ***************/
if (options.url) {
target.load();
} else {
// 也可以通过defaults里面的data属性通过传递一个数据集合进来对组件进行初始化....有兴趣可以自己实现,思路和上述类似
}
return target;
};
// 组件方法封装........
$.fn.ajaxTreeTable.methods = {
// 返回选中记录的id(返回的id由配置中的id属性指定)
// 为了兼容bootstrap-table的写法,统一返回数组,这里只返回了指定的id
getSelections : function(target, data) {
// 所有被选中的记录input
var _ipt = target.find("tbody").find("tr").find("input[name='select_item']:checked");
var chk_value =[];
// 如果是radio
if(_ipt.attr("type")=="radio"){
chk_value.push({id:_ipt.val()});
}else{
_ipt.each(function(_i,_item){
chk_value.push({id:$(_item).val()});
});
}
return chk_value;
},
// 刷新记录
refresh : function(target, parms) {
console.log("~~~~refresh~~~~~");
if(parms){
target.load(parms);
}else{
target.load();
}
},
// 重置表格视图
resetHeight : function(target, height) {
target.find("tbody").css("height", height + 'px');
}
// 组件的其他方法也可以进行类似封装........
};
$.fn.ajaxTreeTable.defaults = {
id : 'menuId',// 选取记录返回的值
code : 'menuId',// 用于设置父子关系
parentCode : 'parentId',// 用于设置父子关系
rootCodeValue: null,//设置根节点code值----可指定根节点,默认为null,"",0,"0"
data : [], // 构造table的数据集合
type : "GET", // 请求数据的ajax类型
url : null, // 请求数据的ajax的url
ajaxParams : {}, // 请求数据的ajax的data属性
expandColumn : null,// 在哪一列上面显示展开按钮
expandAll : true, // 是否全部展开
striped : false, // 是否各行渐变色
columns : [],
toolbar: null,//顶部工具条
height: 0,
expanderExpandedClass : 'glyphicon glyphicon-chevron-down',// 展开的按钮的图标
expanderCollapsedClass : 'glyphicon glyphicon-chevron-right',// 缩起的按钮的图标
isParent: 'isParent' //是否有子节点的标识
};
})(jQuery);
然后是基于tree.table.js改造的tree.table.ajax.js
,增加了一些标记(isParent),切换扩展为刚才我们写的ajax.js;
/**
* 初始化 Tree Table 的封装
*/
(function () {
var TreeTable = function (tableId, url, columns) {
this.btInstance = null; //jquery和ajaxTreeTable绑定的对象
this.bstableId = tableId;
this.url = url;
this.method = "GET";
this.columns = columns;
this.data = {};// ajax的参数
this.expandColumn = null;// 展开显示的列
this.id = 'menuId';// 选取记录返回的值
this.code = 'menuId';// 用于设置父子关系
this.parentCode = 'parentId';// 用于设置父子关系
this.expandAll = false;// 是否默认全部展开
this.toolbarId = tableId + "Toolbar";
this.height = 430;
this.isParent = 'isParent'; //是否有子节点的标识
};
TreeTable.prototype = {
/**
* 初始化bootstrap table
*/
init: function () {
var tableId = this.bstableId;
this.btInstance =
$('#'+tableId).ajaxTreeTable({
id: this.id,// 选取记录返回的值
code: this.code,// 用于设置父子关系
parentCode: this.parentCode,// 用于设置父子关系
rootCodeValue: this.rootCodeValue,//设置根节点code值----可指定根节点,默认为null,"",0,"0"
type: this.method, //请求数据的ajax类型
url: this.url, //请求数据的ajax的url
ajaxParams: this.data, //请求数据的ajax的data属性
expandColumn: this.expandColumn,//在哪一列上面显示展开按钮,从0开始
striped: true, //是否各行渐变色
expandAll: this.expandAll, //是否全部展开
columns: this.columns, //列数组
toolbar: "#" + this.toolbarId,//顶部工具条
height: this.height,
isParent: this.isParent //是否有子节点的标识
});
return this;
},
/**
* 设置在哪一列上面显示展开按钮,从0开始
*/
setExpandColumn: function (expandColumn) {
this.expandColumn = expandColumn;
},
/**
* 设置记录返回的id值
*/
setIdField: function (id) {
this.id = id;
},
/**
* 设置记录分级的字段
*/
setCodeField: function (code) {
this.code = code;
},
/**
* 设置记录分级的父级字段
*/
setParentCodeField: function (parentCode) {
this.parentCode = parentCode;
},
/**
* 设置根节点code值----可指定根节点,默认为null,"",0,"0"
*/
setRootCodeValue: function (rootCodeValue) {
this.rootCodeValue = rootCodeValue;
},
/**
* 设置是否默认全部展开
*/
setExpandAll: function (expandAll) {
this.expandAll = expandAll;
},
/**
* 设置表格高度
*/
setHeight: function (height) {
this.height = height;
},
/**
* 设置ajax post请求时候附带的参数
*/
set: function (key, value) {
if (typeof key == "object") {
for (var i in key) {
if (typeof i == "function")
continue;
this.data[i] = key[i];
}
} else {
this.data[key] = (typeof value == "undefined") ? $("#" + key).val() : value;
}
return this;
},
/**
* 设置ajax get请求时候附带的参数
*/
setData: function (data) {
this.data = data;
return this;
},
/**
* 清空ajax post请求参数
*/
clear: function () {
this.data = {};
return this;
},
/**
* 刷新表格
*/
refresh: function (parms) {
if (typeof parms != "undefined") {
this.btInstance.ajaxTreeTable('refresh', parms.query);// 为了兼容bootstrap-table的写法
} else {
this.btInstance.ajaxTreeTable('refresh');
}
}
};
window.TreeTable = TreeTable;
}());
最后是页面js中的引用
var Region = {
id: "regionTable",
table: null,
layerIndex: -1
};
/**
* 初始化表格的列
*/
Region.initColumn = function () {
var columns = [
{field: 'selectItem', radio: true},
// {title: '分行id', field: 'id', visible: false, align: 'center', valign: 'middle', width: '80px'},
{title: '分行名称', field: 'name', align: 'left', valign: 'middle', sortable: true, width: '200px'},
{title: '分行号', field: 'branchNo', align: 'center', valign: 'middle', sortable: true, width: '100px'},
{title: '上级归属', field: 'parentName', align: 'center', valign: 'middle', sortable: true, width: '100px'},
{title: '简称', field: 'simpleName', align: 'center', valign: 'middle', sortable: true, width: '100px'},
{title: '负责人', field: 'chargeMan', align: 'center', valign: 'middle', sortable: true, width: '100px'},
{title: '联系电话', field: 'phone', align: 'center', valign: 'middle', sortable: true, width: '100px'}];
return columns;
}
$(function () {
$.get(baseURL + "sys/region/info", function(r){
var colunms = Region.initColumn();
var table = new TreeTable(Region.id, baseURL + "sys/region/listAsync", colunms);
table.setRootCodeValue(r.id);
table.setExpandColumn(1);
table.setIdField("id");
table.setCodeField("id");
table.setParentCodeField("parentId");
table.setExpandAll(false);
table.init();
Region.table = table;
});
});
//获取已选行的id
function getRegionId () {
var selected = $('#regionTable').ajaxTreeTable('getSelections');
if (selected.length == 0) {
alertSuccess("请选择一条记录");
return;
} else {
return selected[0].id;
}
}