考虑到行跨度和列跨度,如何从一维数组创建动态 html 表?
Posted
技术标签:
【中文标题】考虑到行跨度和列跨度,如何从一维数组创建动态 html 表?【英文标题】:How would one create a dynamic html table from a one dimensional array, taking into account rowspan and colspan? 【发布时间】:2016-09-08 10:08:48 【问题描述】:我需要从一个一维数组构造一个 html 表,为了抽象起见,它具有以下格式:
value: "ABC", colspan: 1, rowspan: 2 , // etc
还有一个名为width
的属性将是动态的并表示列数。
我相信下面的代码很接近,并且可以处理“非行跨度”数据 - 但我在如何计算单元格跨越时遇到问题,而表格不会超过列数。
我觉得我需要一个“步进器”,每次有行跨度时它都会向上和向下计数,但我无法正确计算数学。
目前,任何行跨度都会导致下一行退出表格的右侧。
基本上我希望它能够将每一个都包裹起来并放在下一个可用的位置。换句话说,动态组装表格。
第 1 轮 - 不工作
http://jsbin.com/zopoxaqato/edit?js,console,output
const input = [
value: "a1", colspan: 1, rowspan: 1 ,
value: "a2", colspan: 1, rowspan: 1 ,
value: "a3", colspan: 1, rowspan: 3 ,
value: "b1", colspan: 1, rowspan: 1 ,
value: "b2", colspan: 1, rowspan: 1 ,
value: "c1", colspan: 1, rowspan: 1 ,
value: "c2", colspan: 1, rowspan: 2 ,
value: "d1", colspan: 1, rowspan: 1 ,
value: "d3", colspan: 1, rowspan: 1 ,
value: "e1", colspan: 1, rowspan: 1 ,
value: "e2", colspan: 2, rowspan: 1 ,
];
const width = 3;
const trs = [];
let tds = [];
let rowSpanOffset = 0;
// Loops over entries
input.forEach((cell, index) =>
// Stock standard td
tds.push(`<td colspan="$cell.colspan" rowspan="$cell.rowspan">$cell.value</td>`);
// New row time
if(index % width === width - 1 || rowSpanOffset < 0)
trs.push("<tr>" + tds.join('') + "</tr>");
// Reset for next row
tds = [];
);
const leTable = "<table class='table'>"+trs.join('')+"</table>";
$("body").append(leTable);
第 2 轮 - 改进,但假设输入有效
http://jsbin.com/solesiyuro/edit?js,output
const input = [
value: "a1", colspan: 1, rowspan: 1 , // 1
value: "a2", colspan: 1, rowspan: 1 , // 2
value: "a3", colspan: 1, rowspan: 3 , // 3
value: "b1", colspan: 1, rowspan: 1 , // 1
value: "b2", colspan: 1, rowspan: 1 , // 1
value: "c1", colspan: 1, rowspan: 1 , // 1
value: "c2", colspan: 1, rowspan: 2 , // 2
value: "d1", colspan: 1, rowspan: 1 , // 1
value: "d3", colspan: 1, rowspan: 1 , // 1
value: "e1", colspan: 1, rowspan: 1 , // 1
value: "e2", colspan: 1, rowspan: 1 , // 2
];
const width = 3;
const totalCellCount = _.reduce(input, (sum, c) => sum + c.colspan * c.rowspan, 0);
const grid = _.chunk(_.fill(new Array(totalCellCount), -1), width);
_.each(input, cell =>
let start = [-1, -1];
outerLoop:
for(let y = 0; y < grid.length; y++)
for(let x = 0; x < width; x++)
if(grid[y][x] === -1)
start = [x, y];
break outerLoop;
for(let y = 0; y < cell.rowspan; y++)
for(let x = 0; x < cell.colspan; x++)
grid[start[1] + y][start[0] + x] = null;
grid[start[1]][start[0]] = cell;
);
let trs = [];
let tds = [];
for(let y = 0; y < grid.length; y++)
for(let x = 0; x < grid[y].length; x++)
const cell = grid[y][x];
if(cell)
const value = cell.value;
tds.push('<td colspan="'+cell.colspan+'" rowspan="'+cell.rowspan+'">'+cell.value+'</td>');
trs.push('<tr>'+tds.join('')+'</tr>');
tds = [];
$(".table").append(trs.join(''));
编辑 - 输入错误
错误输入的一个例子是拆分单元格:
const input = [
value: "a1", colspan: 1, rowspan: 1 ,
value: "a2", colspan: 1, rowspan: 2 ,
value: "a3", colspan: 1, rowspan: 1 ,
value: "b1", colspan: 3, rowspan: 1 ,
];
const width = 3;
【问题讨论】:
我在想当输入无效时会发生什么,我想不出能涵盖所有可能情况的东西。我唯一看到的是一条消息或某事说输入无效(可能是原因/在哪里)......您是否希望无效输入被“修复”?如果是这样,输入无效的方式有很多种,每种情况会发生什么? 理想情况下,“优雅地”捕获错误是最好的 - 我倾向于在失败的情况下将所有 col/row spans 设置为 1,以便表仍然可用,并显示错误用它。 由于您的数据是理想的,因此不太清楚您希望如何处理边缘情况。 IE。如果 a 中的行跨度位于 a2 上,而 colspan 为 2 的位于 b1 上,那么您要做什么? @Chris 所以基本上你的第二个解决方案是可以接受的,如果基本上有一个ValidateInput
函数来检查输入是否有效,即如果有人要添加该函数,这将是你的可接受的答案问题?
啊,是的,提供者输入是理想的。我现在将添加一个错误输入的示例
【参考方案1】:
这是 v0.0.1,它处理任何输入数据并构造 HTML 文本,前提是,就像在这种情况下,提供有意义的输入数据,因为垂直和水平跨越的单元格不相交或 colspan 不会' t 超出提供的width
值设置的限制。我还计划在以后开发一个 V0.0.2,无论存在随机 colspan 和 rowspan 值,它都能够生成有效的表格布局。我认为 v0.0.1 足以满足您的需求。
我首先开发了一个tableMap
,它在二维数组中构造了表格的地图。实际上在客户端,现在构建 DOM 表相当容易。主单元格由一个名为 sp
的额外属性标记为 0,而跨单元格的 sp 属性为非零值。所以实际上 DOM 树在这个 2D 数组中很容易获得。只需反向迭代以选择具有sp == 0
的单元格,是构建 DOM 树唯一要做的事情。
但是,既然您要求 HTML 表格,对于服务器端,我更进一步,将 tableMap
转换为 HTML 字符串。
对不起,我的非正统缩进风格。我更喜欢使用箭头,三进制和短路很多,因此宽布局更容易让我感知代码。
供你玩的代码@repl.it
var input = [
value: "a1", colspan: 1, rowspan: 1 ,
value: "a2", colspan: 1, rowspan: 1 ,
value: "a3", colspan: 1, rowspan: 3 ,
value: "b1", colspan: 1, rowspan: 1 ,
value: "b2", colspan: 1, rowspan: 1 ,
value: "c1", colspan: 1, rowspan: 1 ,
value: "c2", colspan: 1, rowspan: 2 ,
value: "d1", colspan: 1, rowspan: 1 ,
value: "d3", colspan: 1, rowspan: 1 ,
value: "e1", colspan: 1, rowspan: 1 ,
value: "e2", colspan: 2, rowspan: 1 ,
],
width = 3,
cellCount = input.reduce((p,c) => p += c.colspan * c.rowspan,0),
rowCount = Math.ceil(cellCount/width),
rc = r:0,c:0,
tableMap = input.reduce((t,e) => var getNextRC = (rc) => rc.r = rc.c == 2 ? ++rc.r : rc.r;
rc.c = ++rc.c%width;
return rc,
insertCell = (rc) => if (!t[rc.r][rc.c])
for (var c = 0; c < e.colspan; c++)
for (var r = 0; r < e.rowspan; r++)t[rc.r+r][rc.c+c] = "td": e, "sp": r+c;
getNextRC(rc);
else
getNextRC(rc);
insertCell(rc);
return rc;
;
rc = insertCell(rc);
return t;, new Array(rowCount).fill(true).map(e => new Array(width).fill(false))),
tableHTML = tableMap.reduceRight((t,r,i) => var dt = r.reduceRight((t,d,i) => t = !d.sp ? i > 0 ? '</td><td colspan = "' +
d.td.colspan +
'" rowspan = "' +
d.td.rowspan +
'">' + d.td.value + t
: '<td colspan = "' +
d.td.colspan +
'" rowspan = "' +
d.td.rowspan +
'">' + d.td.value + t
: t, '</td>');
t = i > 0 ? '</tr><tr>' + dt + t
: '<tr>' + dt + t;
return t;
, '</tr>');
document.write("<style>table, th, td border: 1px solid black;</style>");
document.write('<table>' + tableHTML + '</table>');
【讨论】:
【参考方案2】:这是问题的直接解决方案。
function buildTbl()
var tbl = document.createElement('table');
tbl.className = 'tbl';
var cols = width, tr = null, td = null, i = 0, inp = null, rowspan = [];
while (inp = input[i])
if (cols >= width)
tr = tbl.insertRow(-1);
cols = 0;
for (var j = 0, n = rowspan.length; j < n; j++)
if (rowspan[j] > 1)
cols++;
rowspan[j]--;
td = tr.insertCell(-1);
td.innerHTML = inp.value;
if (inp.colspan > 1)
td.setAttribute('colspan', inp.colspan);
if (inp.rowspan > 1)
td.setAttribute('rowspan', inp.rowspan);
rowspan.push(inp.rowspan);
cols += inp.colspan;
i++;
document.getElementById('content').appendChild(tbl);
更新:
如果我添加 css,那么表格会按预期(期望)呈现。
.tblborder:solid 1px #ccc
.tbl trheight:20px
.tbl tdborder:solid 1px #fcc
生成的 HTML:
<table class="tbl">
<tbody>
<tr>
<td>a1</td>
<td>a2</td>
<td rowspan="3">a3</td>
</tr>
<tr>
<td rowspan="2">b1</td>
<td>b2</td>
</tr>
<tr>
<td rowspan="2">c2</td>
</tr>
<tr>
<td>d1</td>
<td>d3</td>
</tr>
<tr>
<td>e1</td>
<td colspan="2">e2</td>
</tr>
</tbody>
</table>
更新 2
如果你有足够的内容,那么就不需要固定高度的tr。
const input = [
value: "a1 long content long content long content long content long content long content long content ", colspan: 1, rowspan: 1 ,
value: "a2 long content long content long content long content long content long content", colspan: 1, rowspan: 1 ,
value: "a3 long content long content long content long content long content long content", colspan: 1, rowspan: 3 ,
value: "b1 long content long content long content long content long content long content long content long content long content long content", colspan: 1, rowspan: 2 ,
value: "b2 long content long content long content long content long content long content", colspan: 1, rowspan: 1 ,
// value: "c1", colspan: 1, rowspan: 1 ,
value: "c2 long content long content long content long content long content long content long content", colspan: 1, rowspan: 2 ,
value: "d1 long content long content long content long content long content long content", colspan: 1, rowspan: 1 ,
value: "d3 long content long content long content long content long content long content", colspan: 1, rowspan: 1 ,
value: "e1 long content long content long content long content long content", colspan: 1, rowspan: 1 ,
value: "e2 long content long content long content long content long content long content", colspan: 2, rowspan: 1 ,
];
CSS:
.tblborder:solid 1px #ccc;width:300px
/*.tbl trheight:20px*/
.tbl tdborder:solid 1px #fcc
更何况,.tbl trheight:20px
没有任何作用。
【讨论】:
当所有单元格的行跨度大于1
时跳过行。在此处查看单元格a3
、b1
、c2
jsbin.com/qoxacu/edit?html,output
我更新了答案,然后发现 @Mauricio Poppe 提供了相同的修复 (tr height:20px
)【参考方案3】:
我认为您的替代解决方案是正确的,应该验证的两个极端情况是
一个单元格可能被渲染出边界,例如当单元格的起始位置 + 它的 colspan 大于允许的width
时(蓝色单元格被渲染出边界)
可能会在已占用的位置渲染单元格(蓝色单元格试图占用红色单元格占用的空间)
我想出了以下算法,它与您的第二个解决方案非常相似
创建一个N
行和width
列的矩阵,N
的值将在需要时分配
对于输入中的每个cell
从矩阵的第一行开始从左向右移动以尝试找到空白空间,请注意,如果当前行中没有空白空间,则会在此处分配新行
设i
和j
为矩阵中第一个空格的行列,那么我们需要占用下面的i + cell.rowspace
乘以j + cell.colspace
单元格,在实现中我使用了单元格的索引
如果cell
试图占用超出范围的单元格,则会引发错误
如果cell
试图占用矩阵中已经保存了一些值的单元格,则会引发错误
实现如下
class Matrix
constructor(width)
this.width = width
this.data = []
set(i, j, d)
if (j >= width) throw Error(`set was run out of bounds index ($i, $j)`)
var value = this.get(i, j)
if (value !== undefined) throw Error(`cell ($i, $j) is occupied with $value`)
this.data[i][j] = d
get(i, j)
this.data[i] = this.data[i] || Array(this.width)
return this.data[i][j]
findNextEmpty(i, j)
while (true)
if (this.get(i, j) === undefined)
return [i, j]
j += 1
if (j === this.width)
i += 1
j = 0
fromData(data)
let i = 0
let j = 0
data.forEach((meta, metaIndex) =>
[i, j] = this.findNextEmpty(i, j)
for (var ci = i; ci < i + meta.rowspan; ci += 1)
for (var cj = j; cj < j + meta.colspan; cj += 1)
this.set(ci, cj, metaIndex)
)
return this.data
try
const table = new Matrix(width).fromData(input)
catch (err)
// the input was invalid
Demo
更新:用户在 cmets 中发布了一个似乎无法正常渲染的案例,上面的算法适用于这种情况,即使标记看起来不错,但它看起来像是此表中的一行被渲染的高度为零,我相信有很多方法可以解决这个问题,我通过在table tr
元素上设置一个固定高度来修复它
Demo fixing the problem where a <tr>
was rendered with a height = 0
【讨论】:
有趣的解决方案。但是,它将跳过仅包含行跨度大于1
的单元格的行。例如:jsbin.com/faqosenowu/edit?js,output 其中a3
、c2
和d1
都应该有 2 行的高度。
ty 对于反馈@C14L,我首先不明白表格有什么问题,因为标记很好(我检查了tablesgenerator.com/html_tables,你的示例的标记输出是相同的),但是我注意到其中一行的渲染没有高度,我添加了一个“哨兵”单元格作为每行的第一个单元格以避免这种怪癖jsbin.com/lulevoc/edit?css,js,console,output
解决这个问题的另一个简单方法是在table tr
元素上设置一个固定高度,更新jsbin链接
同时,如果您查看jsbin.com/qoxacu/edit?html,output,您会注意到 tr 高度不是 0。它大约是 1px(边框宽度左右)。手动创建这样的表结构时也是如此。以上是关于考虑到行跨度和列跨度,如何从一维数组创建动态 html 表?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Ag-Grid 中将相同的相邻行与动态行跨度合并? [关闭]