Как создать динамическую таблицу html из одномерного массива с учетом rowspan и colspan?
Мне нужно построить таблицу html из одномерного массива, который для абстракций имеет следующий формат:
{ value: "ABC", colspan: 1, rowspan: 2 }, // etc
Существует также свойство width
, которое будет динамическим и будет представлять количество столбцов.
Код ниже, я считаю, близок и может обрабатывать данные "non-rowspan", но я получаю ответы на вопрос о том, как учитывать ячейки, охватывающие, без таблицы, превышающей количество столбцов.
Мне кажется, что мне нужен "степпер", который подсчитывает вверх и вниз каждый раз, когда есть строка, но я не могу получить математику правильно.
В настоящий момент любые rowspan заставляют следующую строку покидать правую часть таблицы.
По сути, я бы хотел, чтобы он обернулся и упал каждый в следующем доступном месте. В других словах динамически анализируется таблица.
Раунд 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);
Round 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;
Ответы
Ответ 1
Я думаю, что вы были на правильном пути с вашим альтернативным решением, два угловых случая, которые должны быть проверены,
- ячейка может быть выведена за пределы, например. когда позиция начала ячейки + ее совпадение больше, чем разрешено
width
(синяя ячейка отображается за пределами границ).
![вне границ]()
- ячейка может быть отображена в уже занятом месте (синяя ячейка пытается занять пространство, занимаемое красной ячейкой).
![ячейка занята]()
Я придумал следующий алгоритм, который очень похож на ваше второе решение
- Создайте матрицу столбцов
N
и столбцов width
, значение N
будет выделено при необходимости
- Для каждого
cell
на вашем входе
- Переместитесь слева направо, начиная с первой строки матрицы, пытаясь найти пустое пространство, обратите внимание, что это место, где происходит распределение новых строк, если в текущей строке не было пустого пространства
- Пусть
i
и j
- строка и столбец первого пустого пространства в матрице, тогда нам нужно занять следующие ячейки i + cell.rowspace
times 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
}
Обновление: пользователь опубликовал случай в комментариях, которые, казалось, не отображались отлично, алгоритм выше работает для этого случая, даже разметка выглядит отлично, но похоже, что строка в этой таблице был сделан с высотой, равной нулю, я уверен, что есть много способов исправить это, я исправил ее, установив фиксированную высоту над элементами table tr
Ответ 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, тогда таблица будет отображаться как ожидалось (желательно).
.tbl{border:solid 1px #ccc}
.tbl tr{height:20px}
.tbl td{border: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:
.tbl{border:solid 1px #ccc;width:300px}
/*.tbl tr{height:20px}*/
.tbl td{border:solid 1px #fcc}
Более того, .tbl tr{height:20px}
не имеет эффекта.
Ответ 3
Ну, это v0.0.1, который обрабатывает любые входные данные и конструирует текст HTML при условии, что, как и в этом случае, вводятся осмысленные входные данные, так как в вертикальных и горизонтальных развернутых ячейках не пересекаются, или colspan won ' t выходит за пределы, установленные заданным значением width
. Я также планирую разработать V0.0.2 в более позднее время, которое будет иметь возможность создавать правильную табличную компоновку, какими бы ни были случайные значения colspan и rowspan. Я думаю, что v0.0.1 достаточно для ваших нужд.
Сначала я разработал tableMap
, который строит карту таблицы в 2D-массиве. Фактически на стороне клиента, теперь создание таблицы DOM довольно просто. Основные ячейки отмечены дополнительным свойством, называемым sp
, как 0, а спаренные имеют свойство sp как ненулевое значение. Таким образом, фактически дерево DOM легко доступно в этом двумерном массиве. Просто обратная итерация для выбора ячеек с помощью 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>');