最近在项目中有这样一个需求,需要将DataTable中的某些行合并,展示为单行对应多行格式:
技术栈是BootStrap3.x+DataTable1.x且不可随意变更(一分钱难倒英雄汉)
刚开始做时前端隐藏行采用了博主@雨萱521 的思路
在后端拼接对应的行的rowspan数值:
private List<Demo> getRowDemos(List<Demo> demos){
//逆序
Collections.reverse(demos);
//循环的上一个id
String previousId = "";
//重复次数
int index = 1;
//是否重复且需要置rowspan为1
boolean isRepeat = false;
boolean isNotContinue = true;
for (int i = 0; i < demos.size(); i++) {
Demo demo = demos.get(i);
if (demo.getId().equals(previousId)) {
index++;
demo.setRowspan(index);
}else {
previousId = demo.getId();
index = 1;
}
}
//逆序
Collections.reverse(demos);
for (int i = 0; i < demos.size(); i++) {
Demo demo = demos.get(i);
if (isNotContinue) {
if (demo.getRowspan() != 1) {
isRepeat = true;
isNotContinue = false;
continue;
}
}
if (isRepeat) {
if (demo.getRowspan() == 1) {
demo.setRowspan(0);
isRepeat = false;
isNotContinue = true;
continue;
}
demo.setRowspan(0);
}
}
return demos;
}
其中Demo类实例的默认rowspan是 1。
DataTable部分TS使用columnDefs
"columnDefs": [{
targets: [1,3,4,5,7,8,9],
createdCell: function (td, cellData, rowData, row, col) {
let rowspan = rowData.rowspan;
if (rowspan > 1) {
$(td).attr('rowspan', rowspan)
$(td).attr('style', 'vertical-align:middle;text-align:center');
}
if (rowspan == 0) {
//$(td).remove();
$(td).attr('style', 'display:none;');
}
}
}],
完成测试发现有个很无语的问题,如果被合并的单元格是第10行和第11行,分页选项是每页10条数据,那么11行显示在下一页时只能显示未被隐藏的列:
如图 10条数据时:
如图 5条数据时:
可以看到5条数据的第二页没法正常显示了。
做到这里正好下班,苦恼一直没法解决这个问题,于是坛子发了个贴,走人了,至此也没找到合理的思路。
在DataTable官网,github,stackoverflow转了几圈,参考了几种插件,github找到了前端JS的一种解决思路。
废话不多说,上JS:
(function ($) {
ShowedDataSelectorModifier = {
order: 'current',
page: 'current',
search: 'applied',
}
GroupedColumnsOrderDir = 'asc';
var RowspanPro = function (dt, columnsForGrouping) {
this.table = dt.table();
this.columnsForGrouping = columnsForGrouping;
this.orderOverrideNow = false;
this.mergeCellsNeeded = false;
this.order = []
var self = this;
dt.on('order.dt', function (e, settings) {
if (!self.orderOverrideNow) {
self.orderOverrideNow = true;
self._updateOrderAndDraw()
} else {
self.orderOverrideNow = false;
}
})
dt.on('preDraw.dt', function (e, settings) {
if (self.mergeCellsNeeded) {
self.mergeCellsNeeded = false;
self._mergeCells()
}
})
dt.on('column-visibility.dt', function (e, settings) {
self.mergeCellsNeeded = true;
})
this._updateOrderAndDraw();
};
RowspanPro.prototype = {
_getOrderWithGroupColumns: function (order, groupedColumnsOrderDir) {
if (groupedColumnsOrderDir === undefined)
groupedColumnsOrderDir = GroupedColumnsOrderDir
var self = this;
var groupedColumnsIndexes = this.columnsForGrouping.map(function (columnSelector) {
return self.table.column(columnSelector).index()
})
var groupedColumnsKnownOrder = order.filter(function (columnOrder) {
return groupedColumnsIndexes.indexOf(columnOrder[0]) >= 0
})
var nongroupedColumnsOrder = order.filter(function (columnOrder) {
return groupedColumnsIndexes.indexOf(columnOrder[0]) < 0
})
var groupedColumnsKnownOrderIndexes = groupedColumnsKnownOrder.map(function (columnOrder) {
return columnOrder[0]
})
var groupedColumnsOrder = groupedColumnsIndexes.map(function (iColumn) {
var iInOrderIndexes = groupedColumnsKnownOrderIndexes.indexOf(iColumn)
if (iInOrderIndexes >= 0)
return [iColumn, groupedColumnsKnownOrder[iInOrderIndexes][1]]
else
return [iColumn, groupedColumnsOrderDir]
})
groupedColumnsOrder.push.apply(groupedColumnsOrder, nongroupedColumnsOrder)
return groupedColumnsOrder;
},
_getInjectedMonoSelectWorkaround: function (order) {
if (order.length === 1) {
// got mono order - workaround here
var orderingColumn = order[0][0]
var previousOrder = this.order.map(function (val) {
return val[0]
})
var iColumn = previousOrder.indexOf(orderingColumn);
if (iColumn >= 0) {
return [[orderingColumn, this._toogleDirection(this.order[iColumn][1])]]
}
}
return order;
},
_mergeCells: function () {
var columnsIndexes = this.table.columns(this.columnsForGrouping, ShowedDataSelectorModifier).indexes().toArray()
var showedRowsCount = this.table.rows(ShowedDataSelectorModifier)[0].length
this._mergeColumn(0, showedRowsCount - 1, columnsIndexes)
},
_mergeColumn: function (iStartRow, iFinishRow, columnsIndexes) {
var columnsIndexesCopy = columnsIndexes.slice()
currentColumn = columnsIndexesCopy.shift()
currentColumn = this.table.column(currentColumn, ShowedDataSelectorModifier)
var columnNodes = currentColumn.nodes()
var columnValues = currentColumn.data()
var newSequenceRow = iStartRow,
iRow;
for (iRow = iStartRow + 1; iRow <= iFinishRow; ++iRow) {
if (columnValues[iRow] === columnValues[newSequenceRow]) {
$(columnNodes[iRow]).hide()
} else {
$(columnNodes[newSequenceRow]).show()
$(columnNodes[newSequenceRow]).attr('rowspan', (iRow - 1) - newSequenceRow + 1)
if (columnsIndexesCopy.length > 0)
this._mergeColumn(newSequenceRow, (iRow - 1), columnsIndexesCopy)
newSequenceRow = iRow;
}
}
$(columnNodes[newSequenceRow]).show()
$(columnNodes[newSequenceRow]).attr('rowspan', (iRow - 1) - newSequenceRow + 1)
if (columnsIndexesCopy.length > 0)
this._mergeColumn(newSequenceRow, (iRow - 1), columnsIndexesCopy)
},
_toogleDirection: function (dir) {
return dir == 'asc' ? 'desc' : 'asc';
},
_updateOrderAndDraw: function () {
this.mergeCellsNeeded = true;
var currentOrder = this.table.order();
currentOrder = this._getInjectedMonoSelectWorkaround(currentOrder);
this.order = this._getOrderWithGroupColumns(currentOrder)
this.table.order($.extend(true, Array(), this.order))
this.table.draw()
},
};
$.fn.dataTable.RowspanPro = RowspanPro;
$.fn.DataTable.RowspanPro = RowspanPro;
$(document).on('init.dt', function (e, settings) {
if (e.namespace !== 'dt') {
return;
}
var api = new $.fn.dataTable.Api(settings);
if (settings.oInit.RowspanPro ||
$.fn.dataTable.defaults.RowspanPro) {
options = settings.oInit.RowspanPro ?
settings.oInit.RowspanPro :
$.fn.dataTable.defaults.RowspanPro;
new RowspanPro(api, options);
}
});
}(jQuery));
由于这个JS太多了,所以抽离出来了,可以封装包压缩之后引入,也可以拿出来直接塞进主体JS中。
使用炒鸡简单:
let table = $('#example').DataTable({
columns: [
{
data:'id',
name:'id',
title: 'id',
},
{
data:'name',
name:'name',
title: 'name',
}
],
data: data,
RowspanPro: [
'id:name',
'name:name'
]
});
用rowsGroup作为属性引入即可,如Demo所示,先对id进行分组,然后对name分组,这样id不同name相同的列就不会把name全部合在一起了。
但是这个js有两个致命缺陷,一是作者多年未更新,对高版本dt支持很不好,第二是参与合并的属性无法做隐藏处理。
后端分页,将分页的数据组装出rowspan然后传到前台,前台datatables 的 columnDefs来处理rowspan对应的行。
构建思路:每页N条数据,将需要合并的行数据第一条设置rowspan = 1,不需要合并的设置rowspan = 0
后端构建rowspan代码:
/**
* 构建datatable合并行的标志
* @param demos
* @return
*/
private List<Demo> getRowDemos(List<Demo> demos) {
Collections.reverse(demos);
//循环的上一个id
String previousId = "";
//重复次数
int index = 1;
//是否重复且需要置rowspan为1
boolean isRepeat = false;
boolean isNotContinue = true;
for (int i = 0; i < demos.size(); i++) {
Demo Demo = demos.get(i);
if (Demo.getId().equals(previousId)) {
index++;
Demo.setRowspan(index);
} else {
previousId = Demo.getId();
index = 1;
}
}
Collections.reverse(demos);
for (int i = 0; i < demos.size(); i++) {
Demo Demo = demos.get(i);
if (isNotContinue) {
if (Demo.getRowspan() != 1) {
isRepeat = true;
isNotContinue = false;
continue;
}
}
if (isRepeat) {
if (Demo.getRowspan() == 1) {
Demo.setRowspan(0);
isRepeat = false;
isNotContinue = true;
continue;
}
Demo.setRowspan(0);
}
}
return demos;
}
前端:
private initBlockStoneTable() {
let currentClass = this;
currentClass.configMap.blockStoneTable = currentClass.jqueryMap.$blockStoneTable.DataTable({
"dom": 'rt<"row"<"col-md-3"<"pull-left"i><"pull-left"l>><"col-md-3"<"pull-left">><"col-md-6"p>><"clear">',
"sPaginationType": "full_numbers",//设置分页按钮
"searching": true,
"destroy": true,
"reset": true,
"serverSide": true,
"ordering": false,
"bAutoWidth": false,
"lengthMenu": [10, 20, 50, 100],
"ajax": {
"url": currentClass.configMap.path + '/transfer/initQuarryStoneData',
"type": "POST",
"contentType" : 'application/json; charset=utf-8',
"data": function (d) {
for(let key in d){
if(key.indexOf("columns")==0||key.indexOf("order")==0||key.indexOf("search")==0){
delete d[key];
}
}
let searchParams= {
companyName: currentClass.jqueryMap.$companyName.val(),
beginTime: moment(currentClass.jqueryMap.$myContainer.find("#dtp_input_start").val() + " 00:00:00").valueOf(),
endTime: moment(currentClass.jqueryMap.$myContainer.find("#dtp_input_end").val() + " 00:00:00").valueOf(),
missionType:'stone',
driverName: currentClass.jqueryMap.$driver_name.val(),
isComplete: currentClass.configMap.is_complete_id,
toCompanyName: currentClass.configMap.toCompanyName
};
//附加查询参数
if(searchParams){
$.extend(d,searchParams);
}
return JSON.stringify(d);
},
"dataType" : "JSON",
"dataFilter": function (json) {
json = JSON.parse(json);
let returnData = {
draw:'',
recordsTotal:'',
recordsFiltered:'',
data:{}
};
returnData.draw = json.draw;
returnData.recordsTotal = json.total;
returnData.recordsFiltered = json.total;
returnData.data = json.list;
currentClass.configMap.totalVol = json.totalVolume;
currentClass.configMap.size = json.total;
return JSON.stringify(returnData);
}
},
"columns": [
{
name: 'number',
data: 'number',
width: 70
},
{
name: 'companyName',
data: 'company_name'
},
{
name: 'q_companyName',
data: 'q_company_name'
},
{
name: 'driverName',
data: 'driver_name'
},
{
name: 'volume',
data: 'volume'
},
{
name: 'registerTime',
data: 'register_time',
render:function (data) {
return moment(data).format('YYYY-MM-DD')
}
},
{
name: 'qIsComplete',
data: 'qIsComplete',
render: function (data) {
if (data == 1) {
return '已完成';
} else {
return '<span style="color:red">未完成</span>';
}
}
},
{
name: 'isComplete',
data: 'isComplete',
render: function (data) {
return currentClass.configMap.viewBtn_html;
}
},
{
name: 'statBtn',
"render": function (data, type, row) {
return currentClass.configMap.statBtn_html;
}
},
{
data: 'id',
name: 'id'
}
],
"createdRow": function (row, data, index) {
$(row).children('td').eq(9).attr('style', 'display:none');
},
"columnDefs": [{
targets: [1,3,4,5,7,8,9],
createdCell: function (td, cellData, rowData, row, col) {
let rowspan = rowData.rowspan;
if (rowspan > 1) {
$(td).attr('rowspan', rowspan)
$(td).attr('style', 'vertical-align:middle;text-align:center');
}
if (rowspan == 0) {
$(td).attr('style', 'display:none;');
}
}
}],
"drawCallback": function (row) { // 数据加载完成后执行
/*this.api().column(0).nodes().each(function (cell, i) {
cell.innerHTML = i + 1;
});*/
let tootipContainer = $('[data-toggle="tooltip"]');
let editContainer = $('[data-type="bs_edit"]');
let viewContainer = $('[data-type="bs_view"]');
let statContainer = $('[data-type="bs_stat"]');
if (tootipContainer.length > 0) {
tootipContainer.tooltip();
}
if (editContainer.length > 0) {
editContainer.off('click').on('click', function () {
currentClass.mission_edit(currentClass, $(this))
});
}
if (viewContainer.length > 0) {
viewContainer.off('click').on('click', function () {
currentClass.mission_view(currentClass, $(this));
});
}
if (statContainer.length > 0) {
statContainer.off('click').on('click', function () {
currentClass.stat_view(currentClass, $(this));
});
}
currentClass.jqueryMap.$myContainer.find('#mission_table_wrapper').find('.pull-left').eq(2).html('总计:' + currentClass.configMap.size + '条' + ' ' + '合计运输量:' + currentClass.configMap.totalVol.toFixed(1) + 'm³');
currentClass.jqueryMap.$myContainer.find('#mission_table_wrapper').find('.pull-left').eq(2).addClass('dataTables_info');
}
});
}
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- oldu.cn 版权所有 浙ICP备2024123271号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务