HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Generating an HTML table with colspan and rowspan from a one-dimensional array

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
colspanwitharraydimensionalgeneratingoneandfromhtmltable

Problem

I will be given the following input:

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;


And from such, I need to construct an HTML table, taking into account colspan and rowspans. The resulting table would look like:

*----+----+----*
| a1 | a2 | a3 |
+----+----+    |
| b1 | b2 |    |
+----+----+    |
| c1 | c2 |    |
+----+    +----+
| d1 |    | d2 |
+----+----+----+
| e1 | e2      |
*----+---------*


This works, but I'm not sure if it's the best approach or if it could be tackled better:

// Define the total possible cell count
// This poorly assumes no gaps 
const totalCellCount = reduce(input, (sum, c) => sum + (c.colSpan * c.rowSpan), 0);

// Fill a 2d array with placeholders so the below section 
// can find the next available "spot". Chunk is being 
// used to create the 2d array
const grid = chunk(fill(new Array(totalCellCount), -1), columnCount);

each(input, cell => {
    // A default "not found" state. 
    let start = { x: -1, y: -1 };

    // Search for the next available spot working from 
    // left to right, top to bottom
    outerLoop: for(let y = 0; y '+value+'');
    }
  }    

  // End of this row, assemble and push into 
  // the tr array
  trs.push(''+tds.join('')+'');
  // Reset the td array for the next row
  tds = [];
}

// Simple join and attach to screen
$(".table").append(trs.join(''));


JS Bin

I'm using ES6/ES2015, although I could use template strings. Ignore them for now because it seems to freak out jsbin.

The data set is reasonably small, so

Solution

I really wanted to find a purely functional solution to this problem, but I wasn't able to. Nevertheless, your basic idea is pretty simple, and can be made even shorter and more declarative:

// Cell calculations as pure data.  "cells" is the variable containing
// all cells, which we are filling in
var width     = 3,
    numCells  = input.reduce((m,x) => m += x.rowspan + x.colspan -1, 0),
    cells     = new Array(numCells).fill(false),
    dummy     = { dummy: true}, 
    addAcross = (i,n) => R.times(j => cells[i+j+1] = dummy, n),
    addDown   = (i,n) => R.times(j => cells[i+(j+1)*width] = dummy, n),
    nextIndex = () => cells.findIndex(x => !x);

 // Everything happens here.  We fill in "cells" with the real
 // cells and dummy cells for the span cells

 input.forEach(x => {
   var i = nextIndex();
   cells[i] = x;
   addAcross(i, x.colspan-1);
   addDown(i, x.rowspan-1);
 })

// HTML View Helpers
// Once we have the data, just split the array into subarrays
// for each row, and filter out the dummy cells.  In this format
// constructing the html itself is trivial

var rows = R.splitEvery(width, cells).map(r => r.filter(x => !x.dummy)),
    asTd = c => `${c.value}`,
    asTr = row => `${row.map(asTd).join('')}`,
    asTable = rows => `${rows.map(asTr).join('')}`;

document.querySelector('body').innerHTML = asTable(rows);


Note: I used one ramda library function just to break the array into subarrays. Lodash has a similar one, but I prefer ramda.

Here's a working bin of the final solution:
http://jsbin.com/nimuca/2/edit?js,output

Code Snippets

// Cell calculations as pure data.  "cells" is the variable containing
// all cells, which we are filling in
var width     = 3,
    numCells  = input.reduce((m,x) => m += x.rowspan + x.colspan -1, 0),
    cells     = new Array(numCells).fill(false),
    dummy     = { dummy: true}, 
    addAcross = (i,n) => R.times(j => cells[i+j+1] = dummy, n),
    addDown   = (i,n) => R.times(j => cells[i+(j+1)*width] = dummy, n),
    nextIndex = () => cells.findIndex(x => !x);

 // Everything happens here.  We fill in "cells" with the real
 // cells and dummy cells for the span cells

 input.forEach(x => {
   var i = nextIndex();
   cells[i] = x;
   addAcross(i, x.colspan-1);
   addDown(i, x.rowspan-1);
 })

// HTML View Helpers
// Once we have the data, just split the array into subarrays
// for each row, and filter out the dummy cells.  In this format
// constructing the html itself is trivial

var rows = R.splitEvery(width, cells).map(r => r.filter(x => !x.dummy)),
    asTd = c => `<td rowspan=${c.rowspan} colspan=${c.colspan}>${c.value}</td>`,
    asTr = row => `<tr>${row.map(asTd).join('')}</tr>`,
    asTable = rows => `<table border=1>${rows.map(asTr).join('')}</table>`;

document.querySelector('body').innerHTML = asTable(rows);

Context

StackExchange Code Review Q#128248, answer score: 2

Revisions (0)

No revisions yet.