考虑到rowspan和colspan,如何从一维数组创建动态html表?

时间:2016-05-12 13:59:19

标签: javascript jquery html arrays

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

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

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

下面的代码,我相信很接近,可以处理"非rowpan"数据 - 但我正在惹恼如何考虑跨越的单元格,而不会超出列数。

我觉得我需要一个"步进器"每次有行数时,它会上下调整,但我无法正确计算数学。

目前,任何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);

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

3 个答案:

答案 0 :(得分:6)

我认为你的替代解决方案走在正确的轨道上,应该验证的两个角落案例是

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

out of bounds

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

cell occupied

我提出了以下算法,这与你的第二个解决方案非常相似

  • 创建N行和width列的矩阵,N的值将在需要时分配
  • 输入中的每个cell
    • 从矩阵的第一行开始,从左向右移动,试图找到一个空格,请注意,如果当前行中没有空格,则会出现新行的分配
    • ij成为矩阵中第一个空白空间的行和列,然后我们需要占用以下i + cell.rowspacej + cell.colspace个单元格,In实现我使用单元格索引
    • 如果以任何方式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

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

Demo fixing the problem where a <tr> was rendered with a height = 0

答案 1 :(得分:3)

这是问题的直接解决方案。

    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}无效。

答案 2 :(得分:1)

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

我首先开发了一个tableMap,它在2D数组中构建表的映射。实际上在客户端,现在构建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>');