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