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 相关文章推荐
JavaScipt基本教程之前言
Jan 16 Javascript
Jquery替换已存在于element上的event的方法
Mar 09 Javascript
JS实现仿QQ面板的手风琴效果折叠菜单代码
Sep 11 Javascript
基于Javascript实现返回顶部按钮
Feb 29 Javascript
30分钟快速掌握Bootstrap框架
May 24 Javascript
JQuery点击行tr实现checkBox选中的简单实例
May 26 Javascript
Bootstrap编写一个在当前网页弹出可关闭的对话框 非弹窗
Jun 30 Javascript
Node.js开发教程之基于OnceIO框架实现文件上传和验证功能
Nov 30 Javascript
ES6学习笔记之map、set与数组、对象的对比
Mar 01 Javascript
layer 刷新某个页面的实现方法
Sep 05 Javascript
vue总线机制(bus)知识点详解
May 10 Javascript
javaScript实现一个队列的方法
Jul 14 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
如何过滤高亮显示非法字符
2006/10/09 PHP
PHP生成RSS文件类实例
2014/12/05 PHP
PHP利用APC模块实现文件上传进度条的方法
2015/01/26 PHP
浅谈PHP中foreach/in_array的使用
2015/11/02 PHP
php实现图片上传时添加文字和图片水印技巧
2020/04/18 PHP
php桥接模式应用案例分析
2019/10/23 PHP
js获取变量
2006/08/24 Javascript
编辑浪子版表单验证类
2007/05/12 Javascript
定义select的边框颜色
2008/04/28 Javascript
javascript 节点排序 2
2011/01/31 Javascript
jquery实现勾选复选框触发事件给input赋值
2015/02/01 Javascript
JavaScript中神奇的call()方法
2015/03/12 Javascript
JavaScript对Cookie进行读写操作实例
2015/07/25 Javascript
js实现仿MSN带关闭功能的右下角弹窗代码
2015/09/04 Javascript
JavaScript继承模式粗探
2016/01/12 Javascript
关于function类中定义变量this的简单说明
2016/05/28 Javascript
Vue.JS入门教程之处理表单
2016/12/01 Javascript
JavaScript中splice与slice的区别
2017/05/09 Javascript
利用node.js如何搭建一个简易的即时响应服务器
2017/05/28 Javascript
你可能不知道的JSON.stringify()详解
2017/08/17 Javascript
react.js组件实现拖拽复制和可排序的示例代码
2018/08/20 Javascript
详解ES6 Promise对象then方法链式调用
2018/10/20 Javascript
vue随机验证码组件的封装实现
2020/02/19 Javascript
JavaScript this关键字指向常用情况解析
2020/09/02 Javascript
python脚本实现数据导出excel格式的简单方法(推荐)
2016/12/30 Python
python pandas.DataFrame选取、修改数据最好用.loc,.iloc,.ix实现
2018/06/11 Python
python使用turtle绘制国际象棋棋盘
2019/05/23 Python
python 求1-100之间的奇数或者偶数之和的实例
2019/06/11 Python
Python 实现一个手机号码获取妹子名字的功能
2019/09/25 Python
基于numpy中的expand_dims函数用法
2019/12/18 Python
美国二手复古奢侈品包包购物网站:LXRandCo
2019/06/18 全球购物
党的群众路线批评与自我批评范文
2014/10/16 职场文书
干部作风建设年活动剖析材料
2014/10/23 职场文书
教师师德师风整改措施
2014/10/24 职场文书
课程设计感想范文
2015/08/11 职场文书
上班旷工检讨书
2015/08/15 职场文书