Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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 < grid.length; y++) {
        for(let x = 0; x < columnCount; x++) {
            if(grid[y][x] === -1) {
                // Found our starting position, save 
                // it and exit out
                start = { x, y };
                break outerLoop;
            }
        }
    }

    // No more spots. There is no scenario where it 
    // could only be -1 on x or y, so just check 
    // the `x`
    if(start.x === -1) {
        return false;
    }

    // Fill area with null basically saying, don't 
    // do anything in this area when we access 
    // the grid later for processing
    for(let y = 0; y < cell.rowSpan; y++) {
        for(let x = 0; x < cell.colSpan; x++) {
            grid[start.y + y][start.x + x] = null;
        }
    }
    // Except the upper left spot, that is the actual 
    // cell. The final result for a cell of 2 x 3 
    // would look like:
    //
    //   +------+------+  
    //   | cell | null |
    //   | null | null |
    //   | null | null |
    //   +------+------+  
    //
    grid[start.y][start.x] = cell;

});

// At this point, we now have a grid populated 
// with workable data - cells and null based
// on the surface area of each. Lets convert
// this to html table td's + tr's. This, 
// imo, is the "easy" bit
let trs = [];
let tds = [];

for(let y = 0; y < grid.length; y++) {
  for(let x = 0; x < grid[y].length; x++) {
    // Fetch the cell at this location. If 
    // it is null, its the consumed space
    // of a colspan or a rowspan, so we
    // can just ignore it
    const cell = grid[y][x];
    if(cell) {
      const { value, rowspan, colspan } = cell;
      tds.push('<td colspan="'+colspan+'" rowspan="'+rowspan+'">'+value+'</td>');
    }
  }    

  // End of this row, assemble and push into 
  // the tr array
  trs.push('<tr>'+tds.join('')+'</tr>');
  // 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 although performance improvements are welcome, stability and edges cases would be preferred.

share|improve this question
    
What do you mean by edge case? Can the input data include bad rowspan (intersects with columns of below rows) and colspan (extends beyond the width of the table) values..? – Redu Jun 1 at 11:30

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 => `<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);

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

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.