bootstrap-table-treeview.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. /**
  2. * created by lise
  3. * @date 2018-01-18
  4. */
  5. (function ($) {
  6. 'use strict';
  7. var sprintf = function (str) {
  8. var args = arguments,
  9. flag = true,
  10. i = 1;
  11. str = str.replace(/%s/g, function () {
  12. var arg = args[i++];
  13. if (typeof arg === 'undefined') {
  14. flag = false;
  15. return '';
  16. }
  17. return arg;
  18. });
  19. return flag ? str : '';
  20. };
  21. var getFieldIndex = function (columns, field) {
  22. var index = -1;
  23. $.each(columns, function (i, column) {
  24. if (column.field === field) {
  25. index = i;
  26. return false;
  27. }
  28. return true;
  29. });
  30. return index;
  31. };
  32. var escapeHTML = function (text) {
  33. if (typeof text === 'string') {
  34. return text
  35. .replace(/&/g, '&')
  36. .replace(/</g, '&lt;')
  37. .replace(/>/g, '&gt;')
  38. .replace(/"/g, '&quot;')
  39. .replace(/'/g, '&#039;')
  40. .replace(/`/g, '&#x60;');
  41. }
  42. return text;
  43. };
  44. var calculateObjectValue = function (self, name, args, defaultValue) {
  45. var func = name;
  46. if (typeof name === 'string') {
  47. var names = name.split('.');
  48. if (names.length > 1) {
  49. func = window;
  50. $.each(names, function (i, f) {
  51. func = func[f];
  52. });
  53. } else {
  54. func = window[name];
  55. }
  56. }
  57. if (typeof func === 'object') {
  58. return func;
  59. }
  60. if (typeof func === 'function') {
  61. return func.apply(self, args);
  62. }
  63. if (!func && typeof name === 'string' && sprintf.apply(this, [name].concat(args))) {
  64. return sprintf.apply(this, [name].concat(args));
  65. }
  66. return defaultValue;
  67. };
  68. var getItemField = function (item, field) {
  69. var value = item;
  70. if (typeof field !== 'string' || item.hasOwnProperty(field)) {
  71. return item[field];
  72. }
  73. var props = field.split('.');
  74. for (var p in props) {
  75. value = value[props[p]];
  76. }
  77. return value;
  78. };
  79. var getParent = function (node, source, field) {
  80. var data = [];
  81. var items = $.grep(source, function (item, index) {
  82. return node.parentId == item[field];
  83. });
  84. $.each(items, function (index, item) {
  85. data.splice(0, 0, item);
  86. var child = getParent(item, source, field);
  87. $.each(child, function (i, n) {
  88. data.splice(0, 0, n);
  89. });
  90. });
  91. return data;
  92. };
  93. var getChild = function (node, source, field) {
  94. var items = $.grep(source, function (item, index) {
  95. return item.parentId == node[field];
  96. });
  97. return items;
  98. };
  99. var getAllChild = function (node, source, field) {
  100. var data = [];
  101. var g=function(child){
  102. $.each(child, function (i, n) {
  103. data.push(n);
  104. var subChild = getChild(n, source, field);
  105. if(subChild!=null && subChild.length>0){
  106. g(subChild);
  107. }
  108. });
  109. }
  110. var child = getChild(node, source, field);
  111. g(child);
  112. return data;
  113. };
  114. //调用bootstrapTable组件的构造器得到对象
  115. var BootstrapTable = $.fn.bootstrapTable.Constructor,
  116. _initData = BootstrapTable.prototype.initData,
  117. _initPagination = BootstrapTable.prototype.initPagination;
  118. //重写bootstrapTable的initData方法
  119. BootstrapTable.prototype.initData = function () {
  120. _initData.apply(this, Array.prototype.slice.apply(arguments));
  121. var that = this;
  122. //初始化数据,添加level,isLeaf 属性
  123. if (that.options.treeView && this.data.length > 0) {
  124. var rows = [],levelStep=1;
  125. var roots = $.grep(this.data, function (row, index) {
  126. return row.parentId == null;
  127. });
  128. var g=function(child){
  129. var childLevel=that.options.treeRootLevel+levelStep;
  130. $.each(child, function (i, n) {
  131. n.level=childLevel;
  132. if (that.options.treeCollapseAll) {
  133. n.hidden = true;
  134. }
  135. var subChild = getChild(n, that.data, that.options.treeId);
  136. if(subChild==null || subChild.length==0){
  137. n.isLeaf=true;
  138. }
  139. rows.push(n);
  140. if(subChild!=null && subChild.length>0){
  141. levelStep++;
  142. g(subChild);
  143. }else{
  144. levelStep=1;
  145. }
  146. });
  147. }
  148. $.each(roots, function (index, item) {
  149. item.level=that.options.treeRootLevel;
  150. var child = getChild(item, that.data, that.options.treeId);
  151. if(child==null || child.length==0){
  152. item.isLeaf=true;
  153. }
  154. rows.push(item);
  155. g(child);
  156. });
  157. that.options.data = that.data = rows;
  158. }
  159. };
  160. //重写bootstrapTable的initPagination方法
  161. BootstrapTable.prototype.initPagination = function () {
  162. //理论情况下,treegrid是不支持分页的,所以默认分页参数为false
  163. if(this.options.treeView){
  164. this.options.pagination = false;
  165. }
  166. //调用“父类”的“虚方法”
  167. _initPagination.apply(this, Array.prototype.slice.apply(arguments));
  168. };
  169. //重写bootstrapTable的initRow方法
  170. BootstrapTable.prototype.initRow = function(item, i, data, parentDom) {
  171. var that=this,
  172. key,
  173. html = [],
  174. style = {},
  175. csses = [],
  176. data_ = '',
  177. attributes = {},
  178. htmlAttributes = [];
  179. if ($.inArray(item, this.hiddenRows) > -1) {
  180. return;
  181. }
  182. style = calculateObjectValue(this.options, this.options.rowStyle, [item, i], style);
  183. if (style && style.css) {
  184. for (key in style.css) {
  185. csses.push(key + ': ' + style.css[key]);
  186. }
  187. }
  188. attributes = calculateObjectValue(this.options,
  189. this.options.rowAttributes, [item, i], attributes);
  190. if (attributes) {
  191. for (key in attributes) {
  192. htmlAttributes.push(sprintf('%s="%s"', key, escapeHTML(attributes[key])));
  193. }
  194. }
  195. if (item._data && !$.isEmptyObject(item._data)) {
  196. $.each(item._data, function(k, v) {
  197. // ignore data-index
  198. if (k === 'index') {
  199. return;
  200. }
  201. data_ += sprintf(' data-%s="%s"', k, v);
  202. });
  203. }
  204. html.push('<tr',
  205. sprintf(' %s', htmlAttributes.join(' ')),
  206. sprintf(' id="%s"', $.isArray(item) ? undefined : item._id),
  207. sprintf(' class="%s"', style.classes || ($.isArray(item) ? undefined : item._class)),
  208. sprintf(' data-index="%s"', i),
  209. sprintf(' data-uniqueid="%s"', item[this.options.uniqueId]),
  210. sprintf('%s', data_),
  211. '>'
  212. );
  213. if (this.options.cardView) {
  214. html.push(sprintf('<td colspan="%s"><div class="card-views">', this.header.fields.length));
  215. }
  216. if (!this.options.cardView && this.options.detailView) {
  217. html.push('<td>',
  218. '<a class="detail-icon" href="#">',
  219. sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.detailOpen),
  220. '</a>',
  221. '</td>');
  222. }
  223. $.each(this.header.fields, function(j, field) {
  224. var text = '',
  225. value_ = getItemField(item, field, that.options.escape),
  226. value = '',
  227. type = '',
  228. cellStyle = {},
  229. id_ = '',
  230. class_ = that.header.classes[j],
  231. data_ = '',
  232. rowspan_ = '',
  233. colspan_ = '',
  234. title_ = '',
  235. column = that.columns[j];
  236. if (that.fromHtml && typeof value_ === 'undefined') {
  237. return;
  238. }
  239. if (!column.visible) {
  240. return;
  241. }
  242. if (that.options.cardView && (!column.cardVisible)) {
  243. return;
  244. }
  245. if (column.escape) {
  246. value_ = escapeHTML(value_);
  247. }
  248. style = sprintf('style="%s"', csses.concat(that.header.styles[j]).join('; '));
  249. // handle td's id and class
  250. if (item['_' + field + '_id']) {
  251. id_ = sprintf(' id="%s"', item['_' + field + '_id']);
  252. }
  253. if (item['_' + field + '_class']) {
  254. class_ = sprintf(' class="%s"', item['_' + field + '_class']);
  255. }
  256. if (item['_' + field + '_rowspan']) {
  257. rowspan_ = sprintf(' rowspan="%s"', item['_' + field + '_rowspan']);
  258. }
  259. if (item['_' + field + '_colspan']) {
  260. colspan_ = sprintf(' colspan="%s"', item['_' + field + '_colspan']);
  261. }
  262. if (item['_' + field + '_title']) {
  263. title_ = sprintf(' title="%s"', item['_' + field + '_title']);
  264. }
  265. cellStyle = calculateObjectValue(that.header,
  266. that.header.cellStyles[j], [value_, item, i, field], cellStyle);
  267. if (cellStyle.classes) {
  268. class_ = sprintf(' class="%s"', cellStyle.classes);
  269. }
  270. if (cellStyle.css) {
  271. var csses_ = [];
  272. for (var key in cellStyle.css) {
  273. csses_.push(key + ': ' + cellStyle.css[key]);
  274. }
  275. style = sprintf('style="%s"', csses_.concat(that.header.styles[j]).join('; '));
  276. }
  277. value = calculateObjectValue(column,
  278. that.header.formatters[j], [value_, item, i], value_);
  279. if (item['_' + field + '_data'] && !$.isEmptyObject(item['_' + field + '_data'])) {
  280. $.each(item['_' + field + '_data'], function(k, v) {
  281. // ignore data-index
  282. if (k === 'index') {
  283. return;
  284. }
  285. data_ += sprintf(' data-%s="%s"', k, v);
  286. });
  287. }
  288. if (column.checkbox || column.radio) {
  289. type = column.checkbox ? 'checkbox' : type;
  290. type = column.radio ? 'radio' : type;
  291. text = [sprintf(that.options.cardView ?
  292. '<div class="card-view %s">' : '<td class="bs-checkbox %s">', column['class'] || ''),
  293. '<input' +
  294. sprintf(' data-index="%s"', i) +
  295. sprintf(' name="%s"', that.options.selectItemName) +
  296. sprintf(' type="%s"', type) +
  297. sprintf(' value="%s"', item[that.options.idField]) +
  298. sprintf(' checked="%s"', value === true ||
  299. (value_ || value && value.checked) ||
  300. (item && item.checked)? 'checked' : undefined) +
  301. sprintf(' disabled="%s"', !column.checkboxEnabled ||
  302. (value && value.disabled) ? 'disabled' : undefined) +
  303. ' />',
  304. that.header.formatters[j] && typeof value === 'string' ? value : '',
  305. that.options.cardView ? '</div>' : '</td>'
  306. ].join('');
  307. item[that.header.stateField] = value === true || (value && value.checked);
  308. } else {
  309. value = typeof value === 'undefined' || value === null ?
  310. that.options.undefinedText : value;
  311. //渲染tree展开图标,下面text中添加了indent和icon。
  312. var indent, icon;
  313. if (that.options.treeView && column.field == that.options.treeField) {
  314. var indent = item.level == that.options.treeRootLevel ? '' : sprintf('<span style="margin-left: %spx;"></span>', (item.level - that.options.treeRootLevel) * 15);
  315. var child = $.grep(data, function (d, i) {
  316. return d.parentId == item[that.options.treeId] && !d.hidden;
  317. });
  318. icon = sprintf('<span class="tree-icon %s" style="cursor: pointer; margin: 0px 5px;"></span>', child.length > 0 ? that.options.expandIcon : that.options.collapseIcon);
  319. if(item.isLeaf){
  320. icon = sprintf('<span class="tree-icon %s" style="cursor: pointer; margin: 0px 5px;"></span>', that.options.leafIcon);
  321. }
  322. }
  323. //end
  324. text = that.options.cardView ? ['<div class="card-view">',
  325. that.options.showHeader ? sprintf('<span class="title" %s>%s</span>', style,
  326. getPropertyFromOther(that.columns, 'field', 'title', field)) : '',
  327. sprintf('<span class="value">%s</span>', value),
  328. '</div>'
  329. ].join('') : [sprintf('<td%s %s %s %s %s %s %s>',
  330. id_, class_, style, data_, rowspan_, colspan_, title_),
  331. indent,icon,
  332. value,
  333. '</td>'
  334. ].join('');
  335. // Hide empty data on Card view when smartDisplay is set to true.
  336. if (that.options.cardView && that.options.smartDisplay && value === '') {
  337. // Should set a placeholder for event binding correct fieldIndex
  338. text = '<div class="card-view"></div>';
  339. }
  340. }
  341. html.push(text);
  342. });
  343. if (this.options.cardView) {
  344. html.push('</div></td>');
  345. }
  346. html.push('</tr>');
  347. return html.join(' ');
  348. };
  349. //重写bootstrapTable的initBody方法
  350. BootstrapTable.prototype.initBody = function (fixedScroll) {
  351. var that = this,
  352. html = [],
  353. data = this.getData();
  354. this.trigger('pre-body', data);
  355. this.$body = this.$el.find('>tbody');
  356. if (!this.$body.length) {
  357. this.$body = $('<tbody></tbody>').appendTo(this.$el);
  358. }
  359. //Fix #389 Bootstrap-table-flatJSON is not working
  360. if (!this.options.pagination || this.options.sidePagination === 'server') {
  361. this.pageFrom = 1;
  362. this.pageTo = data.length;
  363. }
  364. var trFragments = $(document.createDocumentFragment());
  365. var hasTr;
  366. for (var i = this.pageFrom - 1; i < this.pageTo; i++) {
  367. var item = data[i];
  368. if (item.hidden) continue;//hidden属性,当前行不渲染
  369. var tr = this.initRow(item, i, data, trFragments);
  370. hasTr = hasTr || !!tr;
  371. if (tr&&tr!==true) {
  372. trFragments.append(tr);
  373. }
  374. }
  375. // show no records
  376. if (!hasTr) {
  377. trFragments.append('<tr class="no-records-found">' +
  378. sprintf('<td colspan="%s">%s</td>',
  379. this.$header.find('th').length,
  380. this.options.formatNoMatches()) +
  381. '</tr>');
  382. }
  383. this.$body.html(trFragments);
  384. if (!fixedScroll) {
  385. this.scrollTo(0);
  386. }
  387. // click to select by column
  388. this.$body.find('> tr[data-index] > td').off('click dblclick').on('click dblclick', function (e) {
  389. var $td = $(this),
  390. $tr = $td.parent(),
  391. item = that.data[$tr.data('index')],
  392. index = $td[0].cellIndex,
  393. fields = that.getVisibleFields(),
  394. field = fields[that.options.detailView && !that.options.cardView ? index - 1 : index],
  395. column = that.columns[getFieldIndex(that.columns, field)],
  396. value = getItemField(item, field, that.options.escape);
  397. if ($td.find('.detail-icon').length) {
  398. return;
  399. }
  400. that.trigger(e.type === 'click' ? 'click-cell' : 'dbl-click-cell', field, value, item, $td);
  401. that.trigger(e.type === 'click' ? 'click-row' : 'dbl-click-row', item, $tr, field);
  402. // if click to select - then trigger the checkbox/radio click
  403. if (e.type === 'click' && that.options.clickToSelect && column.clickToSelect) {
  404. var $selectItem = $tr.find(sprintf('[name="%s"]', that.options.selectItemName));
  405. if ($selectItem.length) {
  406. $selectItem[0].click(); // #144: .trigger('click') bug
  407. }
  408. }
  409. });
  410. this.$body.find('> tr[data-index] > td > .detail-icon').off('click').on('click', function () {
  411. var $this = $(this),
  412. $tr = $this.parent().parent(),
  413. index = $tr.data('index'),
  414. row = data[index]; // Fix #980 Detail view, when searching, returns wrong row
  415. // remove and update
  416. if ($tr.next().is('tr.detail-view')) {
  417. $this.find('i').attr('class', sprintf('%s %s', that.options.iconsPrefix, that.options.icons.detailOpen));
  418. that.trigger('collapse-row', index, row);
  419. $tr.next().remove();
  420. } else {
  421. $this.find('i').attr('class', sprintf('%s %s', that.options.iconsPrefix, that.options.icons.detailClose));
  422. $tr.after(sprintf('<tr class="detail-view"><td colspan="%s"></td></tr>', $tr.find('td').length));
  423. var $element = $tr.next().find('td');
  424. var content = calculateObjectValue(that.options, that.options.detailFormatter, [index, row, $element], '');
  425. if($element.length === 1) {
  426. $element.append(content);
  427. }
  428. that.trigger('expand-row', index, row, $element);
  429. }
  430. that.resetView();
  431. return false;
  432. });
  433. //treeicon点击事件
  434. this.$body.find('> tr[data-index] > td > .tree-icon').off('click').on('click', function (e) {
  435. e.stopPropagation();
  436. var $this = $(this),
  437. $tr = $this.parent().parent(),
  438. index = $tr.data('index'),
  439. row = data[index];
  440. var icon = $(this);
  441. if(icon.hasClass(that.options.expandIcon)){
  442. //展开状态
  443. icon.removeClass(that.options.expandIcon).addClass(that.options.collapseIcon);
  444. var child = getAllChild(data[index], data, that.options.treeId);
  445. $.each(child, function (i, c) {
  446. $.each(that.data, function (index, item) {
  447. if (item[that.options.treeId] == c[that.options.treeId]) {
  448. item.hidden = true;
  449. return;
  450. }
  451. });
  452. });
  453. }else{
  454. icon.removeClass(that.options.collapseIcon).addClass(that.options.expandIcon);
  455. var child = getChild(data[index], data, that.options.treeId);
  456. $.each(child, function (i, c) {
  457. $.each(that.data, function (index, item) {
  458. if (item[that.options.treeId] == c[that.options.treeId]) {
  459. item.hidden = false;
  460. return;
  461. }
  462. });
  463. });
  464. }
  465. that.options.data = that.data;
  466. that.initBody(true);
  467. });
  468. //end
  469. this.$selectItem = this.$body.find(sprintf('[name="%s"]', this.options.selectItemName));
  470. this.$selectItem.off('click').on('click', function (event) {
  471. event.stopImmediatePropagation();
  472. var $this = $(this),
  473. checked = $this.prop('checked'),
  474. row = that.data[$this.data('index')];
  475. if (that.options.maintainSelected && $(this).is(':radio')) {
  476. $.each(that.options.data, function (i, row) {
  477. row[that.header.stateField] = false;
  478. });
  479. }
  480. row[that.header.stateField] = checked;
  481. if (that.options.singleSelect) {
  482. that.$selectItem.not(this).each(function () {
  483. that.data[$(this).data('index')][that.header.stateField] = false;
  484. });
  485. that.$selectItem.filter(':checked').not(this).prop('checked', false);
  486. }
  487. var child = getAllChild(row, that.options.data, that.options.treeId);
  488. $.each(child, function (i, c) {
  489. $.each(that.data, function (index, item) {
  490. if (item[that.options.treeId] == c[that.options.treeId]) {
  491. item.checked = checked ? true : false;
  492. return;
  493. }
  494. });
  495. });
  496. that.options.data = that.data;
  497. that.initBody(true);
  498. that.updateSelected();
  499. that.trigger(checked ? 'check' : 'uncheck', row, $this);
  500. });
  501. $.each(this.header.events, function (i, events) {
  502. if (!events) {
  503. return;
  504. }
  505. // fix bug, if events is defined with namespace
  506. if (typeof events === 'string') {
  507. events = calculateObjectValue(null, events);
  508. }
  509. var field = that.header.fields[i],
  510. fieldIndex = $.inArray(field, that.getVisibleFields());
  511. if (that.options.detailView && !that.options.cardView) {
  512. fieldIndex += 1;
  513. }
  514. for (var key in events) {
  515. that.$body.find('>tr:not(.no-records-found)').each(function () {
  516. var $tr = $(this),
  517. $td = $tr.find(that.options.cardView ? '.card-view' : 'td').eq(fieldIndex),
  518. index = key.indexOf(' '),
  519. name = key.substring(0, index),
  520. el = key.substring(index + 1),
  521. func = events[key];
  522. $td.find(el).off(name).on(name, function (e) {
  523. var index = $tr.data('index'),
  524. row = that.data[index],
  525. value = row[field];
  526. func.apply(this, [e, value, row, index]);
  527. });
  528. });
  529. }
  530. });
  531. this.updateSelected();
  532. this.resetView();
  533. this.trigger('post-body', data);
  534. };
  535. /**
  536. * 展开所有树节点
  537. */
  538. BootstrapTable.prototype.expandAllTree = function ()
  539. {
  540. var that=this;
  541. var roots = $.grep(this.data, function (row, index) {
  542. return row.parentId == null;
  543. });
  544. $.each(roots, function (index, item) {
  545. var child = getAllChild(item, that.options.data, that.options.treeId);
  546. $.each(child, function (i, n) {
  547. n.hidden=false;
  548. });
  549. });
  550. that.initBody(true);
  551. }
  552. /**
  553. * 闭合所有树节点
  554. */
  555. BootstrapTable.prototype.collapseAllTree = function ()
  556. {
  557. var that=this;
  558. var roots = $.grep(this.data, function (row, index) {
  559. return row.parentId == null;
  560. });
  561. $.each(roots, function (index, item) {
  562. var child = getAllChild(item, that.options.data, that.options.treeId);
  563. $.each(child, function (i, n) {
  564. n.hidden=true;
  565. });
  566. });
  567. that.initBody(true);
  568. }
  569. //给组件增加默认参数列表
  570. $.extend($.fn.bootstrapTable.defaults, {
  571. treeView: false,//treeView视图
  572. treeField: "id",//treeView视图字段
  573. treeId: "id",
  574. treeRootLevel: 1,//根节点序号
  575. treeCollapseAll: true,//是否全部展开,默认不展开
  576. collapseIcon: "glyphicon glyphicon-chevron-right",//折叠样式
  577. expandIcon: "glyphicon glyphicon-chevron-down",//展开样式
  578. leafIcon:"glyphicon glyphicon-leaf"//叶子节点样式
  579. });
  580. $.fn.bootstrapTable.methods.push('expandAllTree', 'collapseAllTree');
  581. })(jQuery);