admin管理员组文章数量:1268438
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.
Round 1 - Not working
,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);
Round 2 - Improved, but assumes input is valid
,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(''));
Edit - Bad input
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;
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.
Round 1 - Not working
http://jsbin./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);
Round 2 - Improved, but assumes input is valid
http://jsbin./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(''));
Edit - Bad input
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;
Share
Improve this question
edited May 15, 2016 at 1:58
Chris
asked May 12, 2016 at 13:59
ChrisChris
58.3k33 gold badges157 silver badges196 bronze badges
9
- I'm thinking of what should happen when the input is invalid and I can't think of something that would cover every possible case. The only thing I see is a message or something saying the input is invalid (and possibly a reason why/where)... Do you expect the invalid input to be "fixed"? If so, there are quite a few ways the input can be invalid, what should happen in each case? – Bikonja Commented May 14, 2016 at 23:02
- Ideally "a graceful" catch of the error would be best - I am leaning towards setting all col/row spans to 1 in the case of failure so the table is still usable, and display an error along with it. – Chris Commented May 14, 2016 at 23:10
- Since your data is ideal it is not very clear how you want to handle edge cases. I.e. if the rowspan in a were on a2 and a colspan of 2 was on b1, what are you trying to make happen? – lossleader Commented May 14, 2016 at 23:37
-
@Chris so basically your second solution would be acceptable if there was basically a
ValidateInput
function to check if the input is valid i.e. if someone were to add that function it would be the acceptable answer to your question? – Bikonja Commented May 15, 2016 at 0:06 - Ah yes the provider input is ideal. I will add an example of bad input now – Chris Commented May 15, 2016 at 1:56
3 Answers
Reset to default 6 +100I think you were on the right track with your alternative solution, the two corner cases that should be validated are
- a cell might be rendered out of bounds e.g. when a cell's start position + its colspan is bigger than the
width
allowed (the blue cell is rendered out of bounds)
- a cell might be rendered in a place already occupied (the blue cell tries to occupy a space taken by the red cell)
I came up with the following algorithm which is very similar to your second solution
- Create a matrix of
N
rows andwidth
columns, the value ofN
will be allocated whenever needed - For each
cell
in your input- Move from left to right starting from the first row of the matrix trying to find an empty space, note that this is where the allocation of new rows occur if there wasn't an empty space in the current row
- Let
i
andj
be the row and column of the first empty space in the matrix, then we need to occupy the followingi + cell.rowspace
timesj + cell.colspace
cells, In the implementation I use the index of cell - If by any means
cell
tries to occupy an out of bound cell throw an error - If by any means
cell
tries to occupy a cell in the matrix which already has some value saved throw an error
The 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
}
Demo
Update: A user has posted a case in the ments 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
Demo fixing the problem where a <tr>
was rendered with a height = 0
This 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);
}
Update:
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>
Update 2
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.
Well this is the v0.0.1 which handles any input data and constructs the HTML text provided that, like in this case, a meaningful input data is supplied as in the vertical and horizontal spanned cells don't intersect or colspan won't pass beyond the limit set by the supplied width
value. I also plan to develop a V0.0.2 at a later time which will be able to produce a valid table layout whatever random colspan and rowspan values exist. I think v0.0.1 is sufficient for your needs.
I first developed a tableMap
which constructs the map of the table in a 2D array. Actually on the client side, now constructing the DOM table is fairly easy. The main cells are marked by an extra property called sp
as 0 and the spanned ones have sp property as a non-zero value. So actually the DOM tree is readily available in this 2D array. Just a reverse iteration to pick the cells with sp == 0
, is the only thing to be done to construct the DOM tree.
However since you ask for HTML table, for the server side i move one step further and convert the tableMap
into HTML string.
Sorry for my unorthodox indenting style. I prefer using arrows, ternary and short circuits a lot, hence a wide layout is more easy for me to perceive the code.
The code for you to play with @ 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>');
本文标签:
版权声明:本文标题:javascript - How would one create a dynamic html table from a one dimensional array, taking into account rowspan and colspan? - 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741041862a2329290.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论