I need to construct a html table from a one dimensional array which, for abstractions sake, has the following format:
{ value: "ABC", colspan: 1, rowspan: 2 }, // etc
There is also a property called width which will be dynamic and represent the number of columns.
The code below, I believe is close, and can handle "non-rowspan" data - but I am getting tripped up on how to account for cells spanning, without the table exceeding the column count.
I feel like I need a "stepper" which counts up and down everytime there is a rowspan, but I can't get the maths correct.
At the moment, any rowspan causes the next row to exit the right of the table.
Essentially I would like it to wrap and drop each one in the next available spot. In otherwords assmeble the table dynamically.
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);
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(''));
An example of bad input would be splitting cells:
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;
Specifies the number of rows a cell should span. Note: rowspan="0" tells the browser to span the cell to the last row of the table section (thead, tbody, or tfoot). Chrome, Firefox, and Opera 12 (and earlier versions) support rowspan="0".
You can use rowspan="n" on a td element to make it span n rows, and colspan="m" on a td element to make it span m columns. Looks like your first td needs a rowspan="2" and the next td needs a colspan="4" . Show activity on this post. Show activity on this post.
I think you were on the right track with your alternative solution, the two corner cases that should be validated are
width allowed (the blue cell is rendered out of bounds)

I came up with the following algorithm which is very similar to your second solution
N rows and width columns, the value of N will be allocated whenever neededcell in your input
i and j be the row and column of the first empty space in the matrix, then we need to occupy the following i + cell.rowspace times j + cell.colspace cells, In the implementation I use the index of cellcell tries to occupy an out of bound cell throw an errorcell tries to occupy a cell in the matrix which already has some value saved throw an errorThe implementation looks as follows
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
}
Update: A user has posted a case in the comments which seemed not to render fine, the algorithm above works for this case, even the markup looks fine however it seems like a row in this table was rendered with a height equal to zero, I'm sure there are a lot of ways to fix this, I fixed it by setting a fixed height over the table tr elements
<tr> was rendered with a height = 0This is straightforward solution of the question.
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);
}
If I add css then the table is rendered as expected (desired).
.tbl{border:solid 1px #ccc}
.tbl tr{height:20px}
.tbl td{border:solid 1px #fcc}
Generated 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>
If you have enough content then there is no need for fixed height of 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:
.tbl{border:solid 1px #ccc;width:300px}
/*.tbl tr{height:20px}*/
.tbl td{border:solid 1px #fcc}
Even more, .tbl tr{height:20px} has no effect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With