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中创建类/对象的几种方法总结
Nov 29 Javascript
jquery validate demo 基础
Oct 29 Javascript
jquery实现转盘抽奖功能
Jan 06 Javascript
浅谈Angular2 模块懒加载的方法
Oct 04 Javascript
全站最详细的Vuex教程
Apr 13 Javascript
bootstrap treeview 树形菜单带复选框及级联选择功能
Jun 08 Javascript
vue实现动态添加数据滚动条自动滚动到底部的示例代码
Jul 06 Javascript
vue初始化动画加载的实例
Sep 01 Javascript
jQuery实现的五星点评功能【案例】
Feb 18 jQuery
jQuery层叠选择器用法实例分析
Jun 28 jQuery
解决Vue中 父子传值 数据丢失问题
Aug 27 Javascript
vue 页面跳转的实现方式
Jan 12 Vue.js
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
《猛禽小队》:DC宇宙的又一超级大烂片
2020/04/09 欧美动漫
phpinfo() 中 Local Value(局部变量)Master Value(主变量) 的区别
2016/02/03 PHP
JS date对象的减法处理实现代码
2010/12/28 Javascript
js加强的经典分页实例
2013/03/15 Javascript
8个超实用的jQuery功能代码分享
2015/01/08 Javascript
jQuery unbind()方法实例详解
2016/01/19 Javascript
jQuery针对input的class属性写了多个值情况下的选择方法
2016/06/03 Javascript
浅析BootStrap Treeview的简单使用
2016/10/12 Javascript
JavaScript实现前端实时搜索功能
2020/03/26 Javascript
JS中LocalStorage与SessionStorage五种循序渐进的使用方法
2017/07/12 Javascript
基于React Native 0.52实现轮播图效果
2020/08/25 Javascript
浅谈react-router@4.0 使用方法和源码分析
2019/06/04 Javascript
vue如何限制只能输入正负数及小数
2019/07/04 Javascript
angularjs自定义过滤器demo示例
2019/08/24 Javascript
Antd的Table组件嵌套Table以及选择框联动操作
2020/10/24 Javascript
nestjs返回给前端数据格式的封装实现
2021/02/22 Javascript
详解Django+Uwsgi+Nginx的生产环境部署
2018/06/25 Python
利用Pyhton中的requests包进行网页访问测试的方法
2018/12/26 Python
pycharm 将python文件打包为exe格式的方法
2019/01/16 Python
Python Pexpect库的简单使用方法
2019/01/29 Python
用Python中的turtle模块画图两只小羊方法
2019/04/09 Python
Python中面向对象你应该知道的一下知识
2019/07/10 Python
python中类的输出或类的实例输出为这种形式的原因
2019/08/12 Python
关于Python3 类方法、静态方法新解
2019/08/30 Python
pygame库实现俄罗斯方块小游戏
2019/10/29 Python
利用Python绘制Jazz网络图的例子
2019/11/21 Python
Css3+Js制作漂亮时钟(附源码)
2013/04/24 HTML / CSS
检测浏览器是否支持html5视频的代码
2013/03/28 HTML / CSS
HTML5 Canvas——用路径描画线条实例介绍
2013/06/09 HTML / CSS
苏宁红孩子母婴商城:redbaby
2017/02/12 全球购物
英国电子产品购物网站:TobyDeals
2018/07/30 全球购物
北美最大的零售退货翻新商:VIP Outlet
2019/11/21 全球购物
黄河象教学反思
2014/02/10 职场文书
2015年毕业实习工作总结
2014/12/12 职场文书
小学同学聚会感言
2015/07/30 职场文书
七年级作文之我的梦想
2019/10/16 职场文书