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 相关文章推荐
JavaScript isArray()函数判断对象类型的种种方法
Oct 11 Javascript
javascript 闭包
Sep 15 Javascript
js 输出内容到新窗口具体实现代码
May 31 Javascript
jQuery Animation实现CSS3动画示例介绍
Aug 14 Javascript
使表格的标题列可左右拉伸jquery插件封装
Nov 24 Javascript
Vue响应式添加、修改数组和对象的值
Mar 20 Javascript
使用jQuery实现购物车结算功能
Aug 15 jQuery
anime.js 实现带有描边动画效果的复选框(推荐)
Dec 24 Javascript
AngularJS创建一个上传照片的指令实例代码
Feb 24 Javascript
浅谈vue.use()方法从源码到使用
May 12 Javascript
JavaScript实现筛选数组
Mar 02 Javascript
JavaScript高级程序设计之变量与作用域
Nov 17 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高级应用技巧果断收藏
2015/09/25 PHP
jquery中prop()方法和attr()方法的区别浅析
2013/09/06 Javascript
键盘KeyCode值列表汇总
2013/11/26 Javascript
Node.js编码规范
2014/07/14 Javascript
网页运行时提示对象不支持abigimage属性或方法
2014/08/10 Javascript
javascript使用数组的push方法完成快速排序
2014/09/15 Javascript
浅谈时钟的生成(js手写简洁代码)
2016/08/20 Javascript
功能强大的jquery.validate表单验证插件
2016/11/07 Javascript
微信小程序 开发经验整理
2017/02/15 Javascript
MUI实现上拉加载和下拉刷新效果
2017/06/30 Javascript
微信小程序开发教程之增加mixin扩展
2017/08/09 Javascript
react-native使用react-navigation进行页面跳转导航的示例
2017/09/07 Javascript
javascript中UMD规范的代码推演
2018/08/29 Javascript
angular 未登录状态拦截路由跳转的方法
2018/10/09 Javascript
小程序实现左滑删除效果
2019/07/25 Javascript
JS 设计模式之:工厂模式定义与实现方法浅析
2020/05/06 Javascript
[01:11:46]DOTA2-DPC中国联赛 正赛 iG vs Magma BO3 第一场 2月23日
2021/03/11 DOTA
让 python 命令行也可以自动补全
2014/11/30 Python
Python 正则表达式(转义问题)
2014/12/15 Python
让python 3支持mysqldb的解决方法
2017/02/14 Python
Python实现字符串格式化输出的方法详解
2017/09/20 Python
Python+matplotlib实现计算两个信号的交叉谱密度实例
2018/01/08 Python
Python实现二维数组输出为图片
2018/04/03 Python
Python闭包函数定义与用法分析
2018/07/20 Python
python爬虫简单的添加代理进行访问的实现代码
2019/04/04 Python
python 实现12bit灰度图像映射到8bit显示的方法
2019/07/08 Python
Python3+Django get/post请求实现教程详解
2021/02/16 Python
纯CSS实现的大小渐变、渐远效果
2014/04/15 HTML / CSS
利用 CSS3 实现的无缝轮播功能代码
2017/09/25 HTML / CSS
Cult Gaia官网:美国生活方式品牌
2019/08/16 全球购物
英国领先的在线鱼贩:The Fish Society
2020/08/12 全球购物
运动会入场词100字
2014/02/06 职场文书
大学生通用个人的自我评价
2014/02/10 职场文书
高校教师岗位职责
2014/03/18 职场文书
MySQL基础(一)
2021/04/05 MySQL
Python+OpenCV实现图片中的圆形检测
2022/04/07 Python