【问题标题】:How would one create a dynamic html table from a one dimensional array, taking into account rowspan and colspan?考虑到行跨度和列跨度,如何从一维数组创建动态 html 表?
【发布时间】:2016-09-08 10:08:48
【问题描述】:

我需要从一个一维数组构造一个 html 表,为了抽象起见,它具有以下格式:

{ value: "ABC", colspan: 1, rowspan: 2 }, // etc

还有一个名为width 的属性将是动态的并表示列数。

我相信下面的代码很接近,并且可以处理“非行跨度”数据 - 但我在如何计算单元格跨越时遇到问题,而表格不会超过列数。

我觉得我需要一个“步进器”,每次有行跨度时它都会向上和向下计数,但我无法正确计算数学。

目前,任何行跨度都会导致下一行退出表格的右侧。

基本上我希望它能够将每一个都包裹起来并放在下一个可用的位置。换句话说,动态组装表格。

第 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);

第 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;

【问题讨论】:

  • 我在想当输入无效时会发生什么,我想不出能涵盖所有可能情况的东西。我唯一看到的是一条消息或某事说输入无效(可能是原因/在哪里)......您是否希望无效输入被“修复”?如果是这样,输入无效的方式有很多种,每种情况会发生什么?
  • 理想情况下,“优雅地”捕获错误是最好的 - 我倾向于在失败的情况下将所有 col/row spans 设置为 1,以便表仍然可用,并显示错误用它。
  • 由于您的数据是理想的,因此不太清楚您希望如何处理边缘情况。 IE。如果 a 中的行跨度位于 a2 上,而 colspan 为 2 的位于 b1 上,那么您要做什么?
  • @Chris 所以基本上你的第二个解决方案是可以接受的,如果基本上有一个ValidateInput 函数来检查输入是否有效,即如果有人要添加该函数,这将是你的可接受的答案问题?
  • 啊,是的,提供者输入是理想的。我现在将添加一个错误输入的示例

标签: javascript jquery html arrays


【解决方案1】:

这是 v0.0.1,它处理任何输入数据并构造 HTML 文本,前提是,就像在这种情况下,提供有意义的输入数据,因为垂直和水平跨越的单元格不相交或 colspan 不会' t 超出提供的width 值设置的限制。我还计划在以后开发一个 V0.0.2,无论存在随机 colspan 和 rowspan 值,它都能够生成有效的表格布局。我认为 v0.0.1 足以满足您的需求。

我首先开发了一个tableMap,它在二维数组中构造了表格的地图。实际上在客户端,现在构建 DOM 表相当容易。主单元格由一个名为 sp 的额外属性标记为 0,而跨单元格的 sp 属性为非零值。所以实际上 DOM 树在这个 2D 数组中很容易获得。只需反向迭代以选择具有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>');

【讨论】:

    【解决方案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} 没有任何作用。

    【讨论】:

    • 当所有单元格的行跨度大于1 时跳过行。在此处查看单元格a3、b1、c2 jsbin.com/qoxacu/edit?html,output
    • 我更新了答案,然后发现 @Mauricio Poppe 提供了相同的修复 (tr {height:20px})
    【解决方案3】:

    我认为您的替代解决方案是正确的,应该验证的两个极端情况是

    • 一个单元格可能被渲染出边界,例如当单元格的起始位置 + 它的 colspan 大于允许的 width 时(蓝色单元格被渲染出边界)

    • 可能会在已占用的位置渲染单元格(蓝色单元格试图占用红色单元格占用的空间)

    我想出了以下算法,它与您的第二个解决方案非常相似

    • 创建一个N行和width列的矩阵,N的值将在需要时分配
    • 对于输入中的每个cell
      • 从矩阵的第一行开始从左向右移动以尝试找到空白空间,请注意,如果当前行中没有空白空间,则会在此处分配新行
      • 设i和j为矩阵中第一个空格的行列,那么我们需要占用下面的i + cell.rowspace乘以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
    }
    

    Demo


    更新:用户在 cmets 中发布了一个似乎无法正常渲染的案例,上面的算法适用于这种情况,即使标记看起来不错,但它看起来像是此表中的一行被渲染的高度为零,我相信有很多方法可以解决这个问题,我通过在table tr 元素上设置一个固定高度来修复它

    Demo fixing the problem where a &lt;tr&gt; was rendered with a height = 0

    【讨论】:

    • 有趣的解决方案。但是,它将跳过仅包含行跨度大于1 的单元格的行。例如:jsbin.com/faqosenowu/edit?js,output 其中a3、c2 和d1 都应该有 2 行的高度。
    • ty 对于反馈@C14L,我首先不明白表格有什么问题,因为标记很好(我检查了tablesgenerator.com/html_tables,你的示例的标记输出是相同的),但是我注意到其中一行的渲染没有高度,我添加了一个“哨兵”单元格作为每行的第一个单元格以避免这种怪癖jsbin.com/lulevoc/edit?css,js,console,output
    • 解决这个问题的另一个简单方法是在table tr元素上设置一个固定高度,更新jsbin链接
    • 同时,如果您查看jsbin.com/qoxacu/edit?html,output,您会注意到 tr 高度不是 0。它大约是 1px(边框宽度左右)。手动创建这样的表结构时也是如此。
    猜你喜欢
    • 2018-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-23
    • 1970-01-01
    • 2021-07-23
    • 2012-10-18
    • 2014-04-15
    相关资源
    最近更新 更多