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 相关文章推荐
完美解决JS中汉字显示乱码问题(已解决)
Dec 27 Javascript
将jQuery应用于login页面的问题及解决
Oct 17 Javascript
Javascript事件实例详解
Nov 06 Javascript
jquery使用remove()方法删除指定class子元素
Mar 26 Javascript
浅谈jquery.fn.extend与jquery.extend区别
Jul 13 Javascript
BootstrapTable与KnockoutJS相结合实现增删改查功能【二】
May 10 Javascript
JS碰撞运动实现方法详解
Dec 15 Javascript
JS实现页面中所有img对象添加onclick事件及新窗口查看图片的方法
Dec 27 Javascript
JavaScript复制内容到剪贴板的两种常用方法
Feb 27 Javascript
微信小程序登录session的使用
Mar 17 Javascript
JavaScript中变量提升机制示例详解
Dec 27 Javascript
vuecli3.x中轻松4步带你使用tinymce的步骤
Jun 25 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
无线电广播与收音机发展的历史回眸
2021/03/02 无线电
PHP中使用CURL伪造来路抓取页面或文件
2011/05/04 PHP
PHP中通过HTTP_USER_AGENT判断是否为手机移动终端的函数代码
2013/02/14 PHP
ThinkPHP2.x防范XSS跨站攻击的方法
2015/09/25 PHP
IE中直接运行显示当前网页中的图片 推荐
2006/08/31 Javascript
在 IE 中调用 javascript 打开 Excel 表
2006/12/21 Javascript
jQuery中文入门指南,翻译加实例,jQuery的起点教程
2007/01/13 Javascript
javascript跨域刷新实现代码
2011/01/01 Javascript
Jquery封装tab自动切换效果的具体实现
2013/07/13 Javascript
jQuery中事件对象e的事件冒泡用法示例介绍
2014/04/25 Javascript
JS实现的鼠标跟随代码(卡通手型点击效果)
2015/10/26 Javascript
图解js图片轮播效果
2015/12/20 Javascript
Linux CentOS系统下安装node.js与express的方法
2017/04/01 Javascript
Three.js利用orbit controls插件(轨道控制)控制模型交互动作详解
2017/09/25 Javascript
angularjs的单选框+ng-repeat的实现方法
2018/09/12 Javascript
javascript简单实现深浅拷贝过程详解
2019/10/08 Javascript
vue使用自定义事件的表单输入组件用法详解【日期组件与货币组件】
2020/06/01 Javascript
[59:08]DOTA2上海特级锦标赛C组小组赛#2 LGD VS Newbee第一局
2016/02/27 DOTA
[00:32]2018DOTA2亚洲邀请赛Mineski出场
2018/04/04 DOTA
pymongo为mongodb数据库添加索引的方法
2015/05/11 Python
Python实现随机选择元素功能
2017/09/14 Python
python 字符串只保留汉字的方法
2018/11/16 Python
python3利用Socket实现通信的方法示例
2019/05/06 Python
python与字符编码问题
2019/05/24 Python
opencv 获取rtsp流媒体视频的实现方法
2019/08/23 Python
python使用matplotlib的savefig保存时图片保存不完整的问题
2021/01/08 Python
CSS3中currentColor关键字的妙用
2016/02/27 HTML / CSS
初一生物教学反思
2014/01/18 职场文书
高中数学教学反思
2014/01/30 职场文书
测绘专业大学生职业生涯规划书
2014/02/10 职场文书
2014年个人委托书范本
2014/10/13 职场文书
学生会宣传部竞选稿
2015/11/21 职场文书
2016八一建军节慰问信
2015/11/30 职场文书
终止合同协议书范本
2016/03/22 职场文书
MySQL系列之一 MariaDB-server安装
2021/07/02 MySQL
nginx刷新页面出现404解决方案(亲测有效)
2022/03/18 Servers