Как создать динамическую таблицу 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

Демонстрация проблемы, при которой <tr> была отображена с высотой = 0

Ответ 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>');