/** * * @name: 子表格扩展 * @author: yelog * @link: https://github.com/yelog/layui-soul-table * @license: MIT * @version: v1.6.4 */ layui.define(['table', 'element', 'form', 'laytpl'], function (exports) { var $ = layui.jquery, table = layui.table, laytpl = layui.laytpl, tableChildren = {}, HIDE = 'layui-hide', ELEM_HOVER = 'soul-table-hover'; // 封装方法 var mod = { /** * 渲染入口 * @param myTable */ render: function (myTable) { var _this = this, $table = $(myTable.elem), $tableBox = $table.next().children('.layui-table-box'), tableId = myTable.id, $tableHead = $tableBox.children('.layui-table-header').children('table'), $fixedBody = $tableBox.children('.layui-table-fixed').children('.layui-table-body').children('table'), $noFixedBody = $tableBox.children('.layui-table-body').children('table'), $tableBody = $.merge($tableBox.children('.layui-table-body').children('table'), $fixedBody), columns = _this.getCompleteCols(myTable.cols), childIndex = [], soulSort = typeof myTable.soulSort === 'undefined' || myTable.soulSort, i; // 修复hover样式 _this.fixHoverStyle(myTable) // 获取子表配置信息 for (i = 0; i < columns.length; i++) { if (columns[i].children && columns[i].children.length > 0) { childIndex.push(i); } } if (typeof $table.attr('lay-filter') === 'undefined') { $table.attr('lay-filter', tableId); } // 绑定一下主表事件 if ($table.parents('.childTr').length === 0) { // 行单击事件 if (typeof myTable.rowEvent === 'function') { table.on('row(' + $table.attr('lay-filter') + ')', function (obj) { myTable.rowEvent(_this.commonMember.call(this, _this, myTable, obj)); }) } // 行双击事件 if (typeof myTable.rowDoubleEvent === 'function') { table.on('rowDouble(' + $table.attr('lay-filter') + ')', function (obj) { myTable.rowDoubleEvent(_this.commonMember.call(this, _this, myTable, obj)); }) } // 绑定 checkbox 事件 if (typeof myTable.checkboxEvent === 'function') { table.on('checkbox(' + $table.attr('lay-filter') + ')', function (obj) { myTable.checkboxEvent(_this.commonMember.call(this, _this, myTable, obj)); }) } // 绑定 edit 事件 if (typeof myTable.editEvent === 'function') { table.on('edit(' + $table.attr('lay-filter') + ')', function (obj) { myTable.editEvent(_this.commonMember.call(this, _this, myTable, obj)); }) } // 绑定 tool 事件 if (typeof myTable.toolEvent === 'function') { table.on('tool(' + $table.attr('lay-filter') + ')', function (obj) { myTable.toolEvent(_this.commonMember.call(this, _this, myTable, obj)); }) } // 绑定 toolbar 事件 if (typeof myTable.toolbarEvent === 'function') { table.on('toolbar(' + $table.attr('lay-filter') + ')', function (obj) { myTable.toolbarEvent(_this.commonMember.call(this, _this, myTable, obj)); }) } } if (childIndex.length > 0) { for (i = 0; i < childIndex.length; i++) { (function f() { var child = columns[childIndex[i]] , curIndex = childIndex[i] , icon = child.icon || ['layui-icon layui-icon-right', 'layui-icon layui-icon-down']; if (soulSort && !(myTable.url && myTable.page)) { // 前台排序 table.on('sort(' + $table.attr('lay-filter') + ')', function () { _this.render(myTable) }); } if (child.isChild && typeof child.isChild === 'function') { $tableBody.find('tr').find('td[data-key$="' + child.key + '"]>div').each(function () { if (child.isChild(layui.table.cache[tableId][$(this).parents('tr:eq(0)').data('index')])) { if (child.field) { $(this).prepend(''); } else { $(this).html(''); } } }) } else { if (child.field) { $tableBody.find('tr').find('td[data-key$="' + child.key + '"]>div').prepend(''); } else { $tableBody.find('tr').find('td[data-key$="' + child.key + '"]>div').html(''); } } $tableBody.children('tbody').children('tr').each(function () { $(this).children('td:eq(' + curIndex + ')').find('.childTable').on('click', function (e) { layui.stope(e) var rowIndex = $(this).parents('tr:eq(0)').data('index'), tableId = myTable.id, key = $(this).parents('td:eq(0)').data('key'), $this = $noFixedBody.children('tbody').children('tr[data-index=' + rowIndex + ']').children('td[data-key="' + key + '"]').find('.childTable:eq(0)'), $fixedThis = $fixedBody.find('tr[data-index=' + rowIndex + ']').children('td[data-key="' + key + '"]').find('.childTable:eq(0)'), data = table.cache[tableId][rowIndex], children = child.children, isTpl = false, tplContent = '', tr = $tableBody.children('tbody').children('tr[data-index="' + rowIndex + '"]'), obj = { data: data, tr: tr, del: function () { table.cache[tableId][rowIndex] = []; _this.destroyChildren(rowIndex, myTable, icon) tr.remove(); table.resize(tableId); }, update: function (fields) { fields = fields || {}; layui.each(fields, function (key, value) { if (key in data) { var templet, td = tr.children('td[data-field="' + key + '"]'); data[key] = value; table.eachCols(tableId, function (i, item2) { if (item2.field == key && item2.templet) { templet = item2.templet; } }); td.children('.layui-table-cell').html(function () { return templet ? function () { return typeof templet === 'function' ? templet(data) : laytpl($(templet).html() || value).render(data) }() : value; }()); td.data('content', value); } }); }, close: function () { _this.destroyChildren(rowIndex, myTable, icon) table.resize(tableId); } }; if ($this.hasClass(icon[1])) { if (typeof child.childClose === 'function') { if (child.childClose(obj) === false) { return; } } } else { // 展开事件 if (typeof child.childOpen === 'function') { if (child.childOpen(obj) === false) { return; } } } if (typeof children === 'function') { children = children(data) } // 如果是 templet 自定义内容 if (typeof children === 'string') { isTpl = true tplContent = _this.parseTempData(child, child.field ? data[child.field] : null, data) } if (child.show === 2) { // 弹窗模式 child.layerOption ? (typeof child.layerOption.title === 'function' ? (child.layerOption.title = child.layerOption.title(data)) : null) : null; layer.open($.extend({ type: 1, title: '子表', maxmin: true, content: _this.getTables(this, data, child, myTable, children, isTpl, tplContent), area: '1000px', offset: '100px', cancel: function () { if (typeof child.childClose === 'function') { if (child.childClose(obj) === false) { return; } } } }, child.layerOption || {})); if (!isTpl) { _this.renderTable(this, data, child, myTable, children, icon); } } else { // 展开模式 // 开启手风琴模式 if (!$this.hasClass(icon[1]) && child.collapse) { $tableBody.children('tbody').children('tr').children('td').find('.childTable').each(function () { if ($(this).hasClass(icon[1])) { _this.destroyChildren($(this).parents('tr:eq(0)').data('index'), myTable, icon) } }) } // 多个入口时,关闭其他入口 if (!$this.hasClass(icon[1])) { $this.parents('tr:eq(0)').children('td').find('.childTable').each(function () { if ($(this).hasClass(icon[1])) { $(this).removeClass(icon[1]).addClass(icon[0]) _this.destroyChildren($(this).parents('tr:eq(0)').data('index'), myTable, icon) } }) } if ($this.hasClass(icon[1])) { $this.removeClass(icon[1]).addClass(icon[0]) $fixedThis.removeClass(icon[1]).addClass(icon[0]) } else { $this.removeClass(icon[0]).addClass(icon[1]) $fixedThis.removeClass(icon[0]).addClass(icon[1]) } var rowspanIndex = $this.parents('td:eq(0)').attr("rowspan"); if ($this.hasClass(icon[1])) { var newTr = []; newTr.push(''); newTr.push(_this.getTables(this, data, child, myTable, children, isTpl, tplContent)); newTr.push(''); if (rowspanIndex) { var index = parseInt($this.parents('tr:eq(0)').data("index")) + parseInt(rowspanIndex) - 1; $this.parents('table:eq(0)').children().children("[data-index='" + index + "']").after(newTr.join('')); } else { $this.parents('tr:eq(0)').after(newTr.join('')); } layui.element.init('tab') if (!isTpl) { _this.renderTable(this, data, child, myTable, children, icon); // 阻止事件冒泡 $this.parents('tr:eq(0)').next().children('td').children('.layui-tab').children('.layui-tab-content').on('click', function (e) { // 不阻止 tab 切换点击事件 if (!$(e.target.parentElement).hasClass('layui-tab-title')) { e.stopPropagation() } }).off('dblclick').on('dblclick', function (e) { e.stopPropagation() }).on('mouseenter', 'td', function (e) { e.stopPropagation() }).on('change', function (e) { layui.stope(e) }) } if ($fixedBody.length > 0) { var $tr = $this.parents('tr:eq(0)').next(), height = $tr.children('td').height(), $patchDiv = '
'; $tr.children('td').children('.soul-table-child-wrapper').css({ position: 'absolute', top: 0, width: '100%', background: 'white', 'z-index': 200 }) $tr.children('td').append($patchDiv); $fixedBody.find('tr[data-index="' + rowIndex + '"]').each(function () { $(this).after('' + $patchDiv + '') }) table.resize(tableId) } if (child.show === 3) { $this.parents('tr:eq(0)').next().find('.layui-table-view').css({margin: 0, 'border-width': 0}); $this.parents('tr:eq(0)').next().find('.layui-table-header').css('display', 'none'); } } else { _this.destroyChildren(rowIndex, myTable, icon); table.resize(tableId) } } }) }) if (child.spread && child.show !== 2) { $tableBody.children('tbody').children('tr').children('td').find('.childTable').trigger('click'); } })() } } }, /** * 生成子表内容 * @param _this * @param data * @param child * @param myTable * @param children 子表配置 * @param isTpl 是否是自定义模版 * @param tplContent 自定义模版内容 * @returns {string} */ getTables: function (_this, data, child, myTable, children, isTpl, tplContent) { var tables = [], $table = $(myTable.elem), $tableBox = $table.next().children('.layui-table-box'), tableId = myTable.id, rowTableId = tableId + $(_this).parents('tr:eq(0)').data('index'), $tableHead = $tableBox.children('.layui-table-header').children('table'), $tableMain = $table.next().children('.layui-table-box').children('.layui-table-body'), $tableBody = $tableMain.children('table'), scrollWidth = 0, i; if (isTpl) { tables.push('
') } else if (child.show === 3) { //不限制宽度 tables.push('">') } else { if (child.childWidth === 'full') { //不限制宽度 tables.push('">') } else { // 如果有滚动条 if ($tableMain.prop('scrollHeight') + (children.length > 0 ? children[0].height : 0) > $tableMain.height()) { scrollWidth = this.getScrollWidth(); } var maxWidth = $tableMain.width() - 1 - scrollWidth; tables.push('max-width: ' + (maxWidth > $tableHead.width() ? $tableHead.width() : maxWidth) + 'px">') } } if (isTpl) { tables.push(tplContent) } else { if (child.show !== 3 && (typeof child.childTitle === 'undefined' || child.childTitle)) { tables.push('') } if (child.show === 3) { tables.push('
'); } else { tables.push('
'); } for (i = 0; i < children.length; i++) { var childTableId = rowTableId + i; tables.push('
'); } tables.push('
'); } tables.push('
'); return tables.join('') }, /** * 渲染子表 * @param _this * @param data 父表当前行数据 * @param child 子表列 * @param myTable 父表配置 * @param children 子表配置 * @param icon 自定义图标 */ renderTable: function (_this, data, child, myTable, children, icon) { var tables = [] , _that = this , tableId = myTable.id , rowTableId = tableId + $(_this).parents('tr:eq(0)').data('index'); if (child.lazy) { tables.push(renderChildTable(_that, _this, data, child, myTable, 0, children, icon)); } else { for (var i = 0; i < children.length; i++) { tables.push(renderChildTable(_that, _this, data, child, myTable, i, children, icon)); } } tableChildren[rowTableId] = tables; layui.element.on('tab(table-child-tab-' + rowTableId + ')', function (tabData) { if (child.lazy) { var isRender = false; // 是否已经渲染 for (i = 0; i < tableChildren[rowTableId].length; i++) { if (tableChildren[rowTableId][i].config.id === (rowTableId + tabData.index)) { isRender = true; break; } } if (!isRender) { tableChildren[rowTableId].push(renderChildTable(_that, _this, data, child, myTable, tabData.index, children)) } } var rowIndex = $(_this).parents('tr:eq(0)').data('index'), height = $(tabData.elem).height(); $(_this).parents('.layui-table-box:eq(0)').children('.layui-table-body').children('table').children('tbody').children('tr[data-index=' + rowIndex + ']').next().children().children('.soul-table-child-patch').css('height', height) $(_this).parents('.layui-table-box:eq(0)').children('.layui-table-fixed').children('.layui-table-body').children('table').children('tbody').children('tr[data-index=' + rowIndex + ']').next().children().children('.soul-table-child-patch').css('height', height) table.resize(tableId) }); function renderChildTable(_that, _this, data, child, myTable, i, children, icon) { var param = _that.deepClone(children[i]), thisTableChild, tableId = myTable.id, rowIndex = $(_this).parents('tr:eq(0)').data('index'), childTableId = tableId + rowIndex + i, $table = $(myTable.elem), $tableBox = $table.next().children('.layui-table-box'), $tableBody = $.merge($tableBox.children('.layui-table-body').children('table'), $tableBox.children('.layui-table-fixed').children('.layui-table-body').children('table')), tr = $tableBody.children('tbody').children('tr[data-index="' + rowIndex + '"]'), row = table.cache[tableId][rowIndex], // 父表当前行对象 pobj = { data: row, tr: tr, del: function () { table.cache[tableId][rowIndex] = []; _that.destroyChildren(rowIndex, myTable, icon) tr.remove(); table.resize(tableId); }, update: function (fields) { fields = fields || {}; layui.each(fields, function (key, value) { if (key in row) { var templet, td = tr.children('td[data-field="' + key + '"]'); row[key] = value; table.eachCols(tableId, function (i, item2) { if (item2.field == key && item2.templet) { templet = item2.templet; } }); td.children('.layui-table-cell').html(function () { return templet ? function () { return typeof templet === 'function' ? templet(row) : laytpl($(templet).html() || value).render(row) }() : value; }()); td.data('content', value); } }); }, close: function () { _that.destroyChildren(rowIndex, myTable, icon) table.resize(tableId); } }; param.id = childTableId; param.elem = '#' + childTableId; typeof param.where === 'function' && (param.where = param.where(data)); typeof param.data === 'function' && (param.data = param.data(data)); typeof param.url === 'function' && (param.url = param.url(data)); thisTableChild = table.render(param); if (!child.lazy && i !== 0) { $('#' + childTableId).parents('.layui-tab-item:eq(0)').removeClass('layui-show'); //解决隐藏时计算表格高度有问题 } // 绑定 checkbox 事件 if (typeof param.checkboxEvent === 'function') { table.on('checkbox(' + childTableId + ')', function (obj) { param.checkboxEvent(_that.commonMember.call(this, _that, param, obj), pobj) }) } // 绑定 edit 事件 if (typeof param.editEvent === 'function') { table.on('edit(' + childTableId + ')', function (obj) { param.editEvent(_that.commonMember.call(this, _that, param, obj), pobj) }) } // 绑定 tool 事件 if (typeof param.toolEvent === 'function') { table.on('tool(' + childTableId + ')', function (obj) { param.toolEvent(_that.commonMember.call(this, _that, param, obj), pobj) }) } // 绑定 toolbar 事件 if (typeof param.toolbarEvent === 'function') { table.on('toolbar(' + childTableId + ')', function (obj) { param.toolbarEvent(_that.commonMember.call(this, _that, param, obj), pobj) }) } // 绑定单击行事件 if (typeof param.rowEvent === 'function') { table.on('row(' + childTableId + ')', function (obj) { param.rowEvent(_that.commonMember.call(this, _that, param, obj), pobj) }) } // 绑定双击行事件 if (typeof param.rowDoubleEvent === 'function') { table.on('rowDouble(' + childTableId + ')', function (obj) { param.rowDoubleEvent(_that.commonMember.call(this, _that, param, obj), pobj) }) } return thisTableChild; } }, destroyChildren: function (rowIndex, myTable, icon) { var tableId = myTable.id, $table = $(myTable.elem), $tableBox = $table.next().children('.layui-table-box'), $fixedBody = $tableBox.children('.layui-table-fixed').children('.layui-table-body').children('table'), $tableBody = $.merge($tableBox.children('.layui-table-body').children('table'), $fixedBody), $tr = $tableBody.children('tbody').children('tr[data-index="' + rowIndex + '"]'), isTpl = $tr.next().data('tpl'); $tr.find('.childTable').removeClass(icon[1]).addClass(icon[0]); // 暂时不处理 rowspan 情况 // var rowspanIndex = $this.parents('td:eq(0)').attr("rowspan"); // if(rowspanIndex){ // var index=$this.parents('tr:eq(0)').index()+parseInt(rowspanIndex); // $this.parents('table:eq(0)').children().children('tr:eq('+index+')').remove() // }else{ // $this.parents('tr:eq(0)').next().remove(); // } $tr.next().remove() if (isTpl === 'false') { var tables = tableChildren[tableId + rowIndex]; if (layui.tableFilter) { //如果使用了筛选功能,同时清理筛选渲染的数据 layui.tableFilter.destroy(tables); } if (layui.soulTable) { // 清除记忆 for (var i = 0; i < tableChildren[tableId + rowIndex].length; i++) { layui.soulTable.clearOriginCols(tableChildren[tableId + rowIndex][i].config.id) } } } delete tableChildren[tableId + rowIndex] }, cloneJSON: function (obj) { var JSON_SERIALIZE_FIX = { PREFIX: "[[JSON_FUN_PREFIX_", SUFFIX: "_JSON_FUN_SUFFIX]]" }; var sobj = JSON.stringify(obj, function (key, value) { if (typeof value === 'function') { return JSON_SERIALIZE_FIX.PREFIX + value.toString() + JSON_SERIALIZE_FIX.SUFFIX; } return value; }); return JSON.parse(sobj, function (key, value) { if (typeof value === 'string' && value.indexOf(JSON_SERIALIZE_FIX.SUFFIX) > 0 && value.indexOf(JSON_SERIALIZE_FIX.PREFIX) === 0) { return eval("(" + value.replace(JSON_SERIALIZE_FIX.PREFIX, "").replace(JSON_SERIALIZE_FIX.SUFFIX, "") + ")"); } return value; }) || {}; }, fixHoverStyle: function (myTable) { var $table = $(myTable.elem) , $tableBody = $table.next().children('.layui-table-box').children('.layui-table-body').children('table') , $tableFixed = $table.next().children('.layui-table-box').children('.layui-table-fixed').children('.layui-table-body').children('table') , style = $table.next().find('style')[0], sheet = style.sheet || style.styleSheet || {}; // 屏蔽掉layui原生 hover 样式 this.addCSSRule(sheet, '.layui-table-hover', 'background-color: inherit'); this.addCSSRule(sheet, '.layui-table-hover.soul-table-hover', 'background-color: #F2F2F2'); $.merge($tableFixed.children('tbody').children('tr'), $tableBody.children('tbody').children('tr')) .on('mouseenter', function () { var othis = $(this) , index = $(this).data('index'); if (othis.data('off')) return; $tableFixed.children('tbody').children('tr[data-index=' + index + ']').addClass(ELEM_HOVER); $tableBody.children('tbody').children('tr[data-index=' + index + ']').addClass(ELEM_HOVER); }).on('mouseleave', function () { var othis = $(this) , index = $(this).data('index'); if (othis.data('off')) return; $tableFixed.children('tbody').children('tr[data-index=' + index + ']').removeClass(ELEM_HOVER); $tableBody.children('tbody').children('tr[data-index=' + index + ']').removeClass(ELEM_HOVER); }) }, addCSSRule: function (sheet, selector, rules, index) { if ('insertRule' in sheet) { sheet.insertRule(selector + '{' + rules + '}', index) } else if ('addRule' in sheet) { sheet.addRule(selector, rules, index) } }, // 深度克隆-不丢失方法 deepClone: function (obj) { var newObj = Array.isArray(obj) ? [] : {} if (obj && typeof obj === "object") { for (var key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = (obj && typeof obj[key] === 'object') ? this.deepClone(obj[key]) : obj[key]; } } } return newObj }, getCompleteCols: function (origin) { var cols = this.deepClone(origin); var i, j, k, cloneCol; for (i = 0; i < cols.length; i++) { for (j = 0; j < cols[i].length; j++) { if (!cols[i][j].exportHandled) { if (cols[i][j].rowspan > 1) { cloneCol = this.deepClone(cols[i][j]) cloneCol.exportHandled = true; k = i + 1; while (k < cols.length) { cols[k].splice(j, 0, cloneCol) k++ } } if (cols[i][j].colspan > 1) { cloneCol = this.deepClone(cols[i][j]) cloneCol.exportHandled = true; for (k = 1; k < cols[i][j].colspan; k++) { cols[i].splice(j, 0, cloneCol) } j = j + parseInt(cols[i][j].colspan) - 1 } } } } return cols[cols.length - 1]; }, getScrollWidth: function (elem) { var width = 0; if (elem) { width = elem.offsetWidth - elem.clientWidth; } else { elem = document.createElement('div'); elem.style.width = '100px'; elem.style.height = '100px'; elem.style.overflowY = 'scroll'; document.body.appendChild(elem); width = elem.offsetWidth - elem.clientWidth; document.body.removeChild(elem); } return width; }, //解析自定义模板数据 parseTempData: function (item3, content, tplData, text) { //表头数据、原始内容、表体数据、是否只返回文本 var str = item3.children ? function () { return typeof item3.children === 'function' ? item3.children(tplData) : laytpl($(item3.children).html() || String(content)).render(tplData) }() : content; return text ? $('
' + str + '
').text() : str; }, commonMember: function (_this, myTable, sets) { var othis = $(this) , tableId = myTable.id , $table = $(myTable.elem) , $tableBox = $table.next().children('.layui-table-box') , $fixedBody = $tableBox.children('.layui-table-fixed').children('.layui-table-body').children('table') , $tableBody = $.merge($tableBox.children('.layui-table-body').children('table'), $fixedBody) , index = othis[0].tagName === 'TR' ? $(this).data('index') : othis.parents('tr:eq(0)').data('index') , tr = $tableBody.children('tbody').children('tr[data-index="' + index + '"]') , data = table.cache[tableId] || []; data = data[index] || {}; return $.extend(sets, { tr: tr //行元素 , oldValue: othis.prev() ? othis.prev().text() : null , del: function () { //删除行数据 table.cache[tableId][index] = []; tr.remove(); _this.scrollPatch(myTable); } , update: function (fields) { //修改行数据 fields = fields || {}; layui.each(fields, function (key, value) { if (key in data) { var templet, td = tr.children('td[data-field="' + key + '"]'); data[key] = value; table.eachCols(tableId, function (i, item2) { if (item2.field == key && item2.templet) { templet = item2.templet; } }); td.children('.layui-table-cell').html(_this.parseTempData({ templet: templet }, value, data)); td.data('content', value); } }); } }); }, scrollPatch: function (myTable) { var $table = $(myTable.elem), layHeader = $table.next().children('.layui-table-box').children('.layui-table-header'), layTotal = $table.next().children('.layui-table-total'), layMain = $table.next().children('.layui-table-box').children('.layui-table-main'), layFixed = $table.next().children('.layui-table-box').children('.layui-table-fixed'), layFixRight = $table.next().children('.layui-table-box').children('.layui-table-fixed-r'), layMainTable = layMain.children('table'), scollWidth = layMain.width() - layMain.prop('clientWidth'), scollHeight = layMain.height() - layMain.prop('clientHeight'), outWidth = layMainTable.outerWidth() - layMain.width() //表格内容器的超出宽度 //添加补丁 , addPatch = function (elem) { if (scollWidth && scollHeight) { elem = elem.eq(0); if (!elem.find('.layui-table-patch')[0]) { var patchElem = $('
'); //补丁元素 patchElem.find('div').css({ width: scollWidth }); elem.find('tr').append(patchElem); } } else { elem.find('.layui-table-patch').remove(); } } addPatch(layHeader); addPatch(layTotal); //固定列区域高度 var mainHeight = layMain.height() , fixHeight = mainHeight - scollHeight; layFixed.find('.layui-table-body').css('height', layMainTable.height() >= fixHeight ? fixHeight : 'auto'); //表格宽度小于容器宽度时,隐藏固定列 layFixRight[outWidth > 0 ? 'removeClass' : 'addClass'](HIDE); //操作栏 layFixRight.css('right', scollWidth - 1); } }; // 输出 exports('tableChild', mod); });