JS组件系列——自己动手扩展BootstrapTable的treegrid功能
前言:上篇 JS组件系列——自己动手封装bootstrap-treegrid组件 博主自己动手封装了下treegrid的功能,但毕竟那个组件只是一个单独针对树形表格做的,适用性还比较有限。关注博主的园友应该知道,博主的博客里面写了很多bootstrapTable的扩展,今天打算在直接在bootstrapTable的基础上扩展一个treegrid的功能,很多长期关注博主博客的园友一直在问我怎么在bootstrapTable里面直接使用treegrid的功能,所以今天还是带来点福利。有兴趣的可以捧个人场!
本文原创地址:http://www.cnblogs.com/landeanfen/p/6924895.html
一、效果预览
全部折叠
展开一级
全部展开
二、代码示例
怎么样?效果还行吧。给出js的源码供大家参考。
(function ($) {
'use strict'; var sprintf = function (str) {
var args = arguments,
flag = true,
i = 1; str = str.replace(/%s/g, function () {
var arg = args[i++]; if (typeof arg === 'undefined') {
flag = false;
return '';
}
return arg;
});
return flag ? str : '';
}; var getFieldIndex = function (columns, field) {
var index = -1; $.each(columns, function (i, column) {
if (column.field === field) {
index = i;
return false;
}
return true;
});
return index;
}; var calculateObjectValue = function (self, name, args, defaultValue) {
var func = name; if (typeof name === 'string') {
var names = name.split('.'); if (names.length > 1) {
func = window;
$.each(names, function (i, f) {
func = func[f];
});
} else {
func = window[name];
}
}
if (typeof func === 'object') {
return func;
}
if (typeof func === 'function') {
return func.apply(self, args);
}
if (!func && typeof name === 'string' && sprintf.apply(this, [name].concat(args))) {
return sprintf.apply(this, [name].concat(args));
}
return defaultValue;
}; var getItemField = function (item, field) {
var value = item; if (typeof field !== 'string' || item.hasOwnProperty(field)) {
return item[field];
}
var props = field.split('.');
for (var p in props) {
value = value[props[p]];
}
return value;
}; var getParent = function (node, source, field) {
var data = [];
var items = $.grep(source, function (item, index) {
return node.ParentId == item[field];
});
$.each(items, function (index, item) {
data.splice(0, 0, item);
var child = getParent(item, source, field);
$.each(child, function (i, n) {
data.splice(0, 0, n);
});
});
return data;
}; var getChild = function (node, source, field) {
var data = [];
var items = $.grep(source, function (item, index) {
return item.ParentId == node[field];
}); $.each(items, function (index, item) {
data.push(item);
var child = getChild(item, source, field);
$.each(child, function (i, n) {
data.push(n);
});
});
return data;
}; //调用bootstrapTable组件的构造器得到对象
var BootstrapTable = $.fn.bootstrapTable.Constructor,
_initData = BootstrapTable.prototype.initData,
_initPagination = BootstrapTable.prototype.initPagination,
_initBody = BootstrapTable.prototype.initBody; //重写bootstrapTable的initData方法
BootstrapTable.prototype.initData = function () {
_initData.apply(this, Array.prototype.slice.apply(arguments));
var that = this;
if (that.options.treeView && this.data.length > 0) {
var rows = [];
var roots = $.grep(this.data, function (row, index) {
return row.Level == that.options.treeRootLevel;
});
$.each(roots, function (index, item) {
rows.push(item);
var child = getChild(item, that.data, that.options.treeId);
$.each(child, function (i, n) {
if (that.options.treeCollapseAll) {
n.hidden = true;
}
rows.push(n);
});
});
that.options.data = that.data = rows;
}
}; //重写bootstrapTable的initPagination方法
BootstrapTable.prototype.initPagination = function () {
//理论情况下,treegrid是不支持分页的,所以默认分页参数为false
this.options.pagination = false;
//调用“父类”的“虚方法”
_initPagination.apply(this, Array.prototype.slice.apply(arguments));
}; //重写bootstrapTable的initBody方法
BootstrapTable.prototype.initBody = function (fixedScroll) {
var that = this,
html = [],
data = this.getData(); this.trigger('pre-body', data); this.$body = this.$el.find('tbody');
if (!this.$body.length) {
this.$body = $('<tbody></tbody>').appendTo(this.$el);
} if (!this.options.pagination || this.options.sidePagination === 'server') {
this.pageFrom = 1;
this.pageTo = data.length;
} for (var i = this.pageFrom - 1; i < this.pageTo; i++) {
var key,
item = data[i],
style = {},
csses = [],
data_ = '',
attributes = {},
htmlAttributes = [];
if (item.hidden) continue; style = calculateObjectValue(this.options, this.options.rowStyle, [item, i], style); if (style && style.css) {
for (key in style.css) {
csses.push(key + ': ' + style.css[key]);
}
} attributes = calculateObjectValue(this.options,
this.options.rowAttributes, [item, i], attributes); if (attributes) {
for (key in attributes) {
htmlAttributes.push(sprintf('%s="%s"', key, escapeHTML(attributes[key])));
}
} if (item._data && !$.isEmptyObject(item._data)) {
$.each(item._data, function (k, v) {
if (k === 'index') {
return;
}
data_ += sprintf(' data-%s="%s"', k, v);
});
} html.push('<tr',
sprintf(' %s', htmlAttributes.join(' ')),
sprintf(' id="%s"', $.isArray(item) ? undefined : item._id),
sprintf(' class="%s"', style.classes || ($.isArray(item) ? undefined : item._class)),
sprintf(' data-index="%s"', i),
sprintf(' data-uniqueid="%s"', item[this.options.uniqueId]),
sprintf('%s', data_),
'>'
); if (this.options.cardView) {
html.push(sprintf('<td colspan="%s">', this.header.fields.length));
} if (!this.options.cardView && this.options.detailView) {
html.push('<td>',
'<a class="detail-icon" href="javascript:">',
sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.detailOpen),
'</a>',
'</td>');
} $.each(this.header.fields, function (j, field) {
var text = '',
value = getItemField(item, field),
type = '',
cellStyle = {},
id_ = '',
class_ = that.header.classes[j],
data_ = '',
rowspan_ = '',
title_ = '',
column = that.columns[getFieldIndex(that.columns, field)]; if (!column.visible) {
return;
} style = sprintf('style="%s"', csses.concat(that.header.styles[j]).join('; ')); value = calculateObjectValue(column,
that.header.formatters[j], [value, item, i], value); if (item['_' + field + '_id']) {
id_ = sprintf(' id="%s"', item['_' + field + '_id']);
}
if (item['_' + field + '_class']) {
class_ = sprintf(' class="%s"', item['_' + field + '_class']);
}
if (item['_' + field + '_rowspan']) {
rowspan_ = sprintf(' rowspan="%s"', item['_' + field + '_rowspan']);
}
if (item['_' + field + '_title']) {
title_ = sprintf(' title="%s"', item['_' + field + '_title']);
}
cellStyle = calculateObjectValue(that.header,
that.header.cellStyles[j], [value, item, i], cellStyle);
if (cellStyle.classes) {
class_ = sprintf(' class="%s"', cellStyle.classes);
}
if (cellStyle.css) {
var csses_ = [];
for (var key in cellStyle.css) {
csses_.push(key + ': ' + cellStyle.css[key]);
}
style = sprintf('style="%s"', csses_.concat(that.header.styles[j]).join('; '));
} if (item['_' + field + '_data'] && !$.isEmptyObject(item['_' + field + '_data'])) {
$.each(item['_' + field + '_data'], function (k, v) {
if (k === 'index') {
return;
}
data_ += sprintf(' data-%s="%s"', k, v);
});
} if (column.checkbox || column.radio) {
type = column.checkbox ? 'checkbox' : type;
type = column.radio ? 'radio' : type; text = [that.options.cardView ?
'<div class="card-view">' : '<td class="bs-checkbox">',
'<input' +
sprintf(' data-index="%s"', i) +
sprintf(' name="%s"', that.options.selectItemName) +
sprintf(' type="%s"', type) +
sprintf(' value="%s"', item[that.options.idField]) +
sprintf(' checked="%s"', value === true ||
(value && value.checked) ? 'checked' : undefined) +
sprintf(' disabled="%s"', !column.checkboxEnabled ||
(value && value.disabled) ? 'disabled' : undefined) +
' />',
that.header.formatters[j] && typeof value === 'string' ? value : '',
that.options.cardView ? '</div>' : '</td>'
].join(''); item[that.header.stateField] = value === true || (value && value.checked);
} else { value = typeof value === 'undefined' || value === null ?
that.options.undefinedText : value;
var indent, icon;
if (that.options.treeView && column.field == that.options.treeField) {
var indent = item.Level == that.options.Level ? '' : sprintf('<span style="margin-left: %spx;"></span>', (item.Level - that.options.treeRootLevel) * 15);
var child = $.grep(data, function (d, i) {
return d.ParentId == item[that.options.treeId] && !d.hidden;
});
icon = sprintf('<span class="tree-icon %s" style="cursor: pointer; margin: 0px 5px;"></span>', child.length > 0 ? that.options.expandIcon : that.options.collapseIcon);
//icon = sprintf('<span class="tree-icon %s" style="cursor: pointer; margin: 0px 5px;"></span>', child.length > 0 ? that.options.expandIcon : "");
}
text = that.options.cardView ? ['<div class="card-view">',
that.options.showHeader ? sprintf('<span class="title" %s>%s</span>', style,
getPropertyFromOther(that.columns, 'field', 'title', field)) : '',
sprintf('<span class="value">%s</span>', value),
'</div>'
].join('') : [sprintf('<td%s %s %s %s %s %s>', id_, class_, style, data_, rowspan_, title_),
indent,
icon,
value,
'</td>'
].join(''); if (that.options.cardView && that.options.smartDisplay && value === '') {
text = '';
}
} html.push(text);
}); if (this.options.cardView) {
html.push('</td>');
} html.push('</tr>');
} if (!html.length) {
html.push('<tr class="no-records-found">',
sprintf('<td colspan="%s">%s</td>',
this.$header.find('th').length, this.options.formatNoMatches()),
'</tr>');
} this.$body.html(html.join('')); if (!fixedScroll) {
this.scrollTo(0);
} this.$body.find('> tr[data-index] > td').off('click dblclick').on('click dblclick', function (e) {
var $td = $(this),
$tr = $td.parent(),
item = that.data[$tr.data('index')],
index = $td[0].cellIndex,
field = that.header.fields[that.options.detailView && !that.options.cardView ? index - 1 : index],
column = that.columns[getFieldIndex(that.columns, field)],
value = getItemField(item, field); if ($td.find('.detail-icon').length) {
return;
} that.trigger(e.type === 'click' ? 'click-cell' : 'dbl-click-cell', field, value, item, $td);
that.trigger(e.type === 'click' ? 'click-row' : 'dbl-click-row', item, $tr); if (e.type === 'click' && that.options.clickToSelect && column.clickToSelect) {
var $selectItem = $tr.find(sprintf('[name="%s"]', that.options.selectItemName));
if ($selectItem.length) {
$selectItem[0].click();
}
}
}); this.$body.find('> tr[data-index] > td > .detail-icon').off('click').on('click', function () {
debugger;
var $this = $(this),
$tr = $this.parent().parent(),
index = $tr.data('index'),
row = data[index]; if ($tr.next().is('tr.detail-view')) {
$this.find('i').attr('class', sprintf('%s %s', that.options.iconsPrefix, that.options.icons.detailOpen));
$tr.next().remove();
that.trigger('collapse-row', index, row);
} else {
$this.find('i').attr('class', sprintf('%s %s', that.options.iconsPrefix, that.options.icons.detailClose));
$tr.after(sprintf('<tr class="detail-view"><td colspan="%s">%s</td></tr>',
$tr.find('td').length, calculateObjectValue(that.options,
that.options.detailFormatter, [index, row], '')));
that.trigger('expand-row', index, row, $tr.next().find('td'));
}
that.resetView();
}); this.$body.find('> tr[data-index] > td > .tree-icon').off('click').on('click', function (e) {
debugger;
e.stopPropagation();
var $this = $(this),
$tr = $this.parent().parent(),
index = $tr.data('index'),
row = data[index];
var icon = $(this);
var child = getChild(data[index], data, that.options.treeId);
$.each(child, function (i, c) {
$.each(that.data, function (index, item) {
if (item[that.options.treeId] == c[that.options.treeId]) {
item.hidden = icon.hasClass(that.options.expandIcon);
that.uncheck(index);
return;
}
});
});
if (icon.hasClass(that.options.expandIcon)) {
icon.removeClass(that.options.expandIcon).addClass(that.options.collapseIcon);
} else {
icon.removeClass(that.options.collapseIcon).addClass(that.options.expandIcon);
}
that.options.data = that.data;
that.initBody(true);
}); this.$selectItem = this.$body.find(sprintf('[name="%s"]', this.options.selectItemName));
this.$selectItem.off('click').on('click', function (event) {
event.stopImmediatePropagation(); var $this = $(this),
checked = $this.prop('checked'),
row = that.data[$this.data('index')]; if (that.options.maintainSelected && $(this).is(':radio')) {
$.each(that.options.data, function (i, row) {
row[that.header.stateField] = false;
});
} row[that.header.stateField] = checked; if (that.options.singleSelect) {
that.$selectItem.not(this).each(function () {
that.data[$(this).data('index')][that.header.stateField] = false;
});
that.$selectItem.filter(':checked').not(this).prop('checked', false);
} that.updateSelected();
that.trigger(checked ? 'check' : 'uncheck', row, $this);
}); $.each(this.header.events, function (i, events) {
if (!events) {
return;
}
if (typeof events === 'string') {
events = calculateObjectValue(null, events);
} var field = that.header.fields[i],
fieldIndex = $.inArray(field, that.getVisibleFields()); if (that.options.detailView && !that.options.cardView) {
fieldIndex += 1;
} for (var key in events) {
that.$body.find('tr').each(function () {
var $tr = $(this),
$td = $tr.find(that.options.cardView ? '.card-view' : 'td').eq(fieldIndex),
index = key.indexOf(' '),
name = key.substring(0, index),
el = key.substring(index + 1),
func = events[key]; $td.find(el).off(name).on(name, function (e) {
var index = $tr.data('index'),
row = that.data[index],
value = row[field]; func.apply(this, [e, value, row, index]);
});
});
}
}); this.updateSelected();
this.resetView(); this.trigger('post-body');
}; //给组件增加默认参数列表
$.extend($.fn.bootstrapTable.defaults, {
treeView: false,//treeView视图
treeField: "id",//treeView视图字段
treeId: "id",
treeRootLevel: 0,//根节点序号
treeCollapseAll: false,//是否全部展开
collapseIcon: "glyphicon glyphicon-chevron-right",//折叠样式
expandIcon: "glyphicon glyphicon-chevron-down"//展开样式
});
})(jQuery);
组件的使用如下:
1、首先引用这个js文件。
2、然后初始化组件
$('#tb').bootstrapTable({
url: ActionUrl + 'GetMenuList',
toolbar: '#toolbar',
sidePagination: 'client',
pagination: false,
treeView: true,
treeId: "Id",
treeField: "Name",
treeRootLevel: 1,
clickToSelect: true,//collapseIcon: "glyphicon glyphicon-triangle-right",//折叠样式
//expandIcon: "glyphicon glyphicon-triangle-bottom"//展开样式
});
treeView:true表示启用树表格模式;
treeId:'Id'表示每一行tree的id;
treeField:'Name'表示要对那一列进行展开;
treeRootLevel:1表示树根的级别。
还有一个地方需要注意,要建立记录之间的父子级关系,必然后有一个ParentId的概念,所以在从后端返回的结果集里面,每条记录势必有一个ParentId的属性,如果是根节点,ParentId为null。比如我们后台得到的结果集的json格式如下:
[{Id: 1, Name: "系统设置", Url: null, ParentId: null, Level: 1, CreateTime: null, Status: 1, SortOrder: 1,…},
{Id: 2, Name: "菜单管理", Url: "/Systems/Menu/Index", ParentId: 1, Level: 2, CreateTime: null, Status: 1,…},
{Id: 3, Name: "订单管理", Url: null, ParentId: null, Level: 1, CreateTime: "2017-05-31 17:05:27",…},
{Id: 4, Name: "基础数据", Url: null, ParentId: null, Level: 1, CreateTime: "2017-05-31 17:05:55",…},
{Id: 5, Name: "新增订单", Url: "/order/add", ParentId: 3, Level: 2, CreateTime: "2017-05-31 17:07:03",…}]
三、组件需要完善的地方
上述封装给大家提供一个扩展bootstrapTable组件treeview功能,还有很多地方需要完善,比如:
1、我们的叶子节点前面的图标可以去掉;
2、增加展开所有、折叠所有的功能;
3、Level字段可以去掉,通过ParentId为null来确定根节点。
有兴趣的园友可以自己试试。
四、总结
至此本文就结束了,这篇针对上篇做了一个补充,使得我们可以根据项目的需求自己选择用哪种方式,如果我们项目使用的是bootstrapTable作为数据展示的组件,可以考虑上述扩展;如果没有使用bootstrapTable,可以试试上篇的组件。如果你觉得本文能够帮助你,可以右边随意 打赏 博主,打赏后可以获得博主永久免费的技术支持。
本文原创出处:http://www.cnblogs.com/landeanfen/
欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利
JS组件系列——自己动手扩展BootstrapTable的treegrid功能的更多相关文章
- JS组件系列——自己动手扩展BootstrapTable的 冻结列 功能:彻底解决高度问题
前言:一年前,博主分享过一篇关于bootstrapTable组件冻结列的解决方案 JS组件系列——Bootstrap Table 冻结列功能IE浏览器兼容性问题解决方案 ,通过该篇,确实可以实现bo ...
- JS组件系列——自己动手封装bootstrap-treegrid组件
前言:最近产品需要设计一套相对完整的组织架构的解决方案,由于组织架构涉及到层级关系,在表格里面展示层级关系,自然就要用到所谓的treegrid.可惜的是,一些轻量级的表格组件本身并没有自带树形表格的功 ...
- 扩展BootstrapTable的treegrid功能
扩展BootstrapTable的treegrid功能 阅读目录 一.效果预览 二.代码示例 三.组件需要完善的地方 四.总结 正文 前言:上篇 JS组件系列——自己动手封装bootstrap-tr ...
- JS组件系列——BootstrapTable 行内编辑解决方案:x-editable
前言:之前介绍bootstrapTable组件的时候有提到它的行内编辑功能,只不过为了展示功能,将此一笔带过了,罪过罪过!最近项目里面还是打算将行内编辑用起来,于是再次研究了下x-editable组件 ...
- [转]JS组件系列——BootstrapTable 行内编辑解决方案:x-editable
本文转自:http://www.cnblogs.com/landeanfen/p/5821192.html 阅读目录 一.x-editable组件介绍 二.bootstrapTable行内编辑初始方案 ...
- JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(一)
前言:出于某种原因,需要学习下Knockout.js,这个组件很早前听说过,但一直没尝试使用,这两天学习了下,觉得它真心不错,双向绑定的机制简直太爽了.今天打算结合bootstrapTable和Kno ...
- JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(四):自定义T4模板快速生成页面
前言:上篇介绍了下ko增删改查的封装,确实节省了大量的js代码.博主是一个喜欢偷懒的人,总觉得这些基础的增删改查效果能不能通过一个什么工具直接生成页面效果,啥代码都不用写了,那该多爽.于是研究了下T4 ...
- JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(三):两个Viewmodel搞定增删改查
前言:之前博主分享过knockoutJS和BootstrapTable的一些基础用法,都是写基础应用,根本谈不上封装,仅仅是避免了html控件的取值和赋值,远远没有将MVVM的精妙展现出来.最近项目打 ...
- JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(二)
前言:上篇 JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(一) 介绍了下knockout.js的一些基础用法,由于篇幅的关系,所以只能分成两篇,望见谅!昨天就 ...
随机推荐
- 理解C++中的头文件和源文件的作用【转】
一.C++编译模式通常,在一个C++程序中,只包含两类文件--.cpp文件和.h文件.其中,.cpp文件被称作C++源文件,里面放的都是C++的源代码:而.h文件则被称作C++头文件,里面放的也是C+ ...
- 本地yum服务搭建
1.准备linux ISO系统镜像文件 (例如:rhel-server-5.5-i386-dvd.iso) 2.linux虚拟机(centos 7 192.168.50.24 ),启动sshd服务 ...
- 解决CentOS虚拟机克隆后无法上网(网卡信息不一致)的问题
一.问题描述 虚拟机克隆后,由于网卡信息不一致的问题,导致不能上网或者执行"sercice network restart"命令失败 [root@lyy 桌面]# ifconfig ...
- 关于String类和String[]数组的获取长度方法细节
一.在Java中,以下代码段有错误的是第( )行 public static void main(String[] args) { String name = "小新"; ...
- bzoj3064 Tyvj 1518 CPU监控
Description Bob需要一个程序来监视CPU使用率.这是一个很繁琐的过程,为了让问题更加简单,Bob会慢慢列出今天会在用计算机时做什么事. Bob会干很多事,除了跑暴力程序看视频之外,还会做 ...
- hibernate 多对多关系总结
hibernate中,对对象关系的映射处理估计是最让人迷惑和头疼的,特别是cascade和inverse属性的使用,不知已经杀死了我多少个脑细胞了,好记性永远比不上烂笔头,为了能节省自己的脑细胞,降低 ...
- 【算法系列学习】状压dp [kuangbin带你飞]专题十二 基础DP1 D - Doing Homework
https://vjudge.net/contest/68966#problem/D http://blog.csdn.net/u010489389/article/details/19218795 ...
- poj2594最小顶点覆盖+传递闭包
传递闭包最开始是在Floyd-Warshall算法里面出现的,当时这算法用的很少就被我忽视了.. 传递闭包是指如果i能到达k,并且k能到达j,那么i就能到达j Have you ever read a ...
- poj2502最短路!
have just moved from a quiet Waterloo neighbourhood to a big, noisy city. Instead of getting to ride ...
- CAS单点登录(SSO)服务端的部署和配置---连接MySQL进行身份认证
一.修改系统host,加入 127.0.0.1 server.test.com127.0.0.1 client1.test.com127.0.0.1 client2.test.com 二.安装grad ...