JS 组件系列之BootstrapTable的treegrid功能


Posted in Javascript onJune 16, 2017

上篇给大家介绍了JS 组件系列之 bootstrap treegrid 组件封装过程,下面重点给大家介绍JS 组件系列之BootstrapTable的treegrid功能,需要的的朋友一起学习吧!

一、效果预览

全部折叠

JS 组件系列之BootstrapTable的treegrid功能

展开一级

JS 组件系列之BootstrapTable的treegrid功能

全部展开

JS 组件系列之BootstrapTable的treegrid功能

二、代码示例

怎么样?效果还行吧。给出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:" rel="external nofollow" >',
   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,可以试试上篇的组件。

Javascript 相关文章推荐
jQuery之$(document).ready()使用介绍
Apr 05 Javascript
JavaScript高级程序设计(第3版)学习笔记 概述
Oct 11 Javascript
javascript当中的代码嗅探扩展原生对象和原型(prototype)
Jan 11 Javascript
元素绑定click点击事件方法
Jun 08 Javascript
JS实现表单中checkbox对勾选中增加边框显示效果
Aug 21 Javascript
图片懒加载插件实例分享(含解析)
Jan 09 Javascript
jQuery鼠标悬停内容动画切换效果
Apr 27 jQuery
基于ExtJs在页面上window再调用Window的事件处理方法
Jul 26 Javascript
小程序使用wxs解决wxml保留2位小数问题
Dec 13 Javascript
VUE table表格动态添加一列数据,新增的这些数据不可以编辑(v-model绑定的数据不能实时更新)
Apr 03 Javascript
vue 避免变量赋值后双向绑定的操作
Nov 07 Javascript
js不常见操作运算符总结
Nov 20 Javascript
vue之数据交互实例代码
Jun 16 #Javascript
基于jQuery和CSS3实现APPLE TV海报视差效果
Jun 16 #jQuery
JS基于正则实现数字千分位用逗号分隔的方法
Jun 16 #Javascript
利用jquery去掉时光轴头尾部线条的方法实例
Jun 16 #jQuery
基于JS实现网页中的选项卡(两种方法)
Jun 16 #Javascript
angular ng-click防止重复提交实例
Jun 16 #Javascript
vue.js实现数据动态响应 Vue.set的简单应用
Jun 15 #Javascript
You might like
PHP获取MAC地址的具体实例
2013/12/13 PHP
PHP中$_FILES的使用方法及注意事项说明
2014/02/14 PHP
对比分析php中Cookie与Session的异同
2016/02/19 PHP
document 和 document.all 分别什么时候用
2006/06/22 Javascript
javascript学习笔记(十三) js闭包介绍(转)
2012/06/20 Javascript
windows系统下简单nodejs安装及环境配置
2013/01/08 NodeJs
jQuery实现的漂亮表单效果代码
2015/08/18 Javascript
jQuery实现打开页面渐现效果示例
2016/07/27 Javascript
jQuery插件jquery.kxbdmarquee.js实现无缝滚动效果
2017/02/15 Javascript
基于JS实现翻书效果的页面切换样式
2017/02/16 Javascript
监控Nodejs的性能实例代码
2019/07/02 NodeJs
vue 实现滚动到底部翻页效果(pc端)
2019/07/31 Javascript
使用Angular material主题定义自己的组件库的配色体系
2019/09/04 Javascript
vue-cli+iview项目打包上线之后图标不显示问题及解决方法
2019/10/16 Javascript
vue实现户籍管理系统
2020/05/29 Javascript
Vue页面渲染中key的应用实例教程
2021/01/12 Vue.js
[01:14:55]EG vs Spirit Supermajor 败者组 BO3 第三场 6.4
2018/06/05 DOTA
python实现ftp客户端示例分享
2014/02/17 Python
python画出三角形外接圆和内切圆的方法
2018/01/25 Python
pytorch中tensor的合并与截取方法
2018/07/26 Python
浅谈python连续赋值可能引发的错误
2018/11/10 Python
python实现名片管理系统项目
2019/04/26 Python
Python列表的深复制和浅复制示例详解
2021/02/12 Python
英国花园药房: The Garden Pharmacy
2017/12/28 全球购物
沃达丰英国有限公司:Vodafone英国
2019/04/16 全球购物
C&A巴西网上商店:时尚、衣服、手机和鞋子
2020/06/07 全球购物
EJB2和EJB3在架构上的不同点
2014/09/29 面试题
大学辅导员事迹材料
2014/02/05 职场文书
保护动物的标语
2014/06/11 职场文书
党员自评材料范文
2014/12/17 职场文书
2015年高中生国庆节演讲稿
2015/07/30 职场文书
《飘》英文读后感五篇
2019/10/11 职场文书
解决Navicat for Mysql连接报错1251的问题(连接失败)
2021/05/27 MySQL
TV动画《神废柴☆偶像》公布先导PV
2022/03/20 日漫
CI Games宣布《堕落之王2》使用虚幻引擎5制作 预计将于2023年正式发售
2022/04/11 其他游戏
AndroidStudio图片压缩工具ImgCompressPlugin使用实例
2022/08/05 Java/Android