Skip to content Skip to sidebar Skip to footer

How Would One Create A Dynamic Html Table From A One Dimensional Array, Taking Into Account Rowspan And Colspan?

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 als

Solution 1:

I 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)

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)

cell occupied

I came up with the following algorithm which is very similar to your second solution

  • Create a matrix of N rows and width columns, the value of N 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 and j be the row and column of the first empty space in the matrix, then we need to occupy the following i + cell.rowspace times j + 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

classMatrix{
  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)
    returnthis.data[i][j]
  }

  findNextEmpty(i, j) {
    while (true) {
      if (this.get(i, j) === undefined) {
        return [i, j]
      }
      j += 1if (j === this.width) {
        i += 1
        j = 0
      }
    }
  }

  fromData(data) {
    let i = 0
    let j = 0data.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)
        }
      }
    })
    returnthis.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 comments 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

Solution 2:

This is straightforward solution of the question.

functionbuildTbl() {
        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}
    .tbltr{height:20px}
    .tbltd{border:solid 1px#fcc}

Generated HTML:

<tableclass="tbl"><tbody><tr><td>a1</td><td>a2</td><tdrowspan="3">a3</td></tr><tr><tdrowspan="2">b1</td><td>b2</td></tr><tr><tdrowspan="2">c2</td></tr><tr><td>d1</td><td>d3</td></tr><tr><td>e1</td><tdcolspan="2">e2</td></tr></tbody></table>

Update 2

If you have enough content then there is no need for fixed height of tr.

constinput= [
  { 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}*/.tbltd{border:solid 1px#fcc}

Even more, .tbl tr{height:20px} has no effect.

Solution 3:

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) => { vargetNextRC = (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;}, newArray(rowCount).fill(true).map(e =>newArray(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>');

Post a Comment for "How Would One Create A Dynamic Html Table From A One Dimensional Array, Taking Into Account Rowspan And Colspan?"