diff --git a/src/table-layout.js b/src/table-layout.js index 9a8ea81..7b9661a 100644 --- a/src/table-layout.js +++ b/src/table-layout.js @@ -1,10 +1,12 @@ var _ = require('lodash'); -var Cell = require('./cell'); +(function(){ +var Cell = require('./cell'); function makeTableLayout(rows){ var cellRows = generateCells(rows); expandCells(cellRows); + fillInTable(cellRows); return cellRows; } @@ -82,8 +84,91 @@ function maxWidth(rows){ },0); } +function iterateColumn(rows,col,fn,ctx){ + for(var i = 0; i < rows.length; i++){ + fn.call(ctx,rows[i][col],i); + } +} + function getCell(rows,y,x){ + return rows[y][x]; + } + + function getCellPivot(rows,x,y){ + return rows[y][x]; + } + + function iterateRow(rows,row,fn,ctx){ + var columns = rows[row]; + for(var i = 0; i < columns.length; i++){ + fn.call(ctx,columns[i],i); + } + } + function maxHeight(rows){ + return rows.length; + } + module.exports = { makeTableLayout:makeTableLayout, maxWidth:maxWidth, - fillInTable:fillInTable -}; \ No newline at end of file + fillInTable:fillInTable, + computeWidths:makeComputeWidths(iterateColumn,maxWidth,'colSpan','desiredWidth',getCell), + computeHeights:makeComputeWidths(iterateRow,maxHeight,'rowSpan','desiredHeight',getCellPivot) +}; + +})(); + +function makeComputeWidths(iterateColumn,maxWidth,colSpan,desiredWidth,getCell){ + return function (vals,rows){ + var width = maxWidth(rows); + var result = []; + var spanners = []; + for(var columnIndex = 0; columnIndex < width; columnIndex++){ + if(!_.isNumber(vals[columnIndex])){ + var maxDesired = 0; + iterateColumn(rows,columnIndex,function(cell,rowIndex){ + if(cell[colSpan] && cell[colSpan] > 1){ + spanners.push({row:rowIndex,column:columnIndex}); + } + else { + maxDesired = Math.max(maxDesired,cell[desiredWidth] || 0); + } + }); + result[columnIndex] = maxDesired; + } + else { + result[columnIndex] = vals[columnIndex]; + iterateColumn(rows,columnIndex,function(cell,rowIndex) { + if (cell[colSpan] && cell[colSpan] > 1) { + spanners.push({row: rowIndex, column: columnIndex}); + } + }); + } + } + while(spanners.length){ + var coords = spanners.pop(); + var cell = getCell(rows,coords.row,coords.column); + var span = cell[colSpan]; + var existingWidth = result[coords.column]; + var editableCols = _.isNumber(vals[coords.column]) ? 0 : 1; + for(var i = 1; i < span; i ++){ + existingWidth += 1 + result[coords.column + i]; + if(!_.isNumber(vals[coords.column + i])){ + editableCols++; + } + } + if(cell[desiredWidth] > existingWidth){ + i = 0; + while(editableCols > 0 && cell[desiredWidth] > existingWidth){ + if(!_.isNumber(vals[coords.column+i])){ + var dif = Math.round( (cell[desiredWidth] - existingWidth) / editableCols ); + existingWidth += dif; + result[coords.column + i] += dif; + editableCols--; + } + i++; + } + } + } + _.extend(vals,result); + } +} \ No newline at end of file diff --git a/test/table-layout-test.js b/test/table-layout-test.js index 95a4621..e8be7d8 100644 --- a/test/table-layout-test.js +++ b/test/table-layout-test.js @@ -4,6 +4,8 @@ describe('tableLayout', function () { var makeTableLayout = tableLayout.makeTableLayout; var maxWidth = tableLayout.maxWidth; var fillInTable = tableLayout.fillInTable; + var computeWidths = tableLayout.computeWidths; + var computeHeights = tableLayout.computeHeights; var chai = require('chai'); var expect = chai.expect; var _ = require('lodash'); @@ -159,7 +161,192 @@ describe('tableLayout', function () { ['','b',{spannerFor:[1,2]}] ]); }); + }); + + describe('computeWidths',function() { + function mc(desiredWidth, colSpan) { + return {desiredWidth: desiredWidth, colSpan: colSpan}; + } + + it('finds the maximum desired width of each column', function () { + var widths = []; + var cells = [ + [mc(7), mc(3), mc(5)], + [mc(8), mc(5), mc(2)], + [mc(6), mc(9), mc(1)] + ]; + + computeWidths(widths, cells); + + expect(widths).to.eql([8, 9, 5]); + }); + + it('won\'t touch hard coded values', function () { + var widths = [null, 3]; + var cells = [ + [mc(7), mc(3), mc(5)], + [mc(8), mc(5), mc(2)], + [mc(6), mc(9), mc(1)] + ]; + + computeWidths(widths, cells); + + expect(widths).to.eql([8, 3, 5]); + }); + + it('assumes undefined desiredWidth is 0', function () { + var widths = []; + var cells = [[{}], [{}], [{}]]; + computeWidths(widths, cells); + expect(widths).to.eql([0]) + }); + + it('takes into account colSpan and wont over expand', function () { + var widths = []; + var cells = [ + [mc(10, 2), mc(5), mc(5)], + [mc(5), mc(3), mc(2)], + [mc(4), mc(2), mc(1)] + ]; + computeWidths(widths, cells); + expect(widths).to.eql([5, 5, 5]); + }); + + it('will expand rows involved in colSpan in a balanced way', function () { + var widths = []; + var cells = [ + [mc(13, 2), mc(), mc(5)], + [mc(5), mc(5), mc(2)], + [mc(4), mc(2), mc(1)] + ]; + computeWidths(widths, cells); + expect(widths).to.eql([6, 6, 5]); + }); + + it('expands across 3 cols', function () { + var widths = []; + var cells = [ + [mc(25, 3), mc(), mc()], + [mc(5), mc(5), mc(2)], + [mc(4), mc(2), mc(1)] + ]; + computeWidths(widths, cells); + expect(widths).to.eql([9, 9, 5]); + }); + + it('multiple spans in same table', function () { + var widths = []; + var cells = [ + [mc(25, 3), mc(), mc()], + [mc(30, 3), mc(), mc()], + [mc(4), mc(2), mc(1)] + ]; + computeWidths(widths, cells); + expect(widths).to.eql([11, 9, 8]); + }); + + it('spans will only edit uneditable tables',function(){ + var widths = [null, 3]; + var cells = [ + [mc(20,3),mc(),mc()], + [mc(4),mc(20),mc(5)] + ]; + computeWidths(widths, cells); + expect(widths).to.eql([7,3,8]) + }); + + it('spans will only edit uneditable tables - first column uneditable',function(){ + var widths = [3]; + var cells = [ + [mc(20,3),mc(), mc()], + [mc(4), mc(3), mc(5)] + ]; + computeWidths(widths, cells); + expect(widths).to.eql([3,7,8]) + }); + }); + + describe('computeHeights',function(){ + function mc(desiredHeight,colSpan){ + return {desiredHeight:desiredHeight,rowSpan:colSpan}; + } + + it('finds the maximum desired height of each row',function(){ + var heights = []; + var cells = [ + [mc(7), mc(3), mc(5)], + [mc(8), mc(5), mc(2)], + [mc(6), mc(9), mc(1)] + ]; + + computeHeights(heights,cells); + + expect(heights).to.eql([7,8,9]); + }); + + it('won\'t touch hard coded values',function(){ + var heights = [null,3]; + var cells = [ + [mc(7), mc(3), mc(5)], + [mc(8), mc(5), mc(2)], + [mc(6), mc(9), mc(1)] + ]; + + computeHeights(heights,cells); + + expect(heights).to.eql([7,3,9]); + }); + + it('assumes undefined desiredHeight is 0',function(){ + var heights = []; + var cells = [[{},{},{}]]; + computeHeights(heights,cells); + expect(heights).to.eql([0]) + }); + + it('takes into account rowSpan and wont over expand',function(){ + var heights = []; + var cells = [ + [mc(10,2), mc(5), mc(5)], + [mc(5), mc(3), mc(2)], + [mc(4), mc(2), mc(1)] + ]; + computeHeights(heights,cells); + expect(heights).to.eql([5,5,4]); + }); + + it('will expand rows involved in rowSpan in a balanced way',function(){ + var heights = []; + var cells = [ + [mc(13,2), mc(), mc(5)], + [mc(5), mc(5), mc(2)], + [mc(4), mc(2), mc(1)] + ]; + computeHeights(heights,cells); + expect(heights).to.eql([6,6,4]); + }); + it('expands across 3 rows',function(){ + var heights = []; + var cells = [ + [mc(25,3), mc(5), mc(4)], + [mc(), mc(5), mc(2)], + [mc(), mc(2), mc(1)] + ]; + computeHeights(heights,cells); + expect(heights).to.eql([9,9,5]); + }); + + it('multiple spans in same table',function(){ + var heights = []; + var cells = [ + [mc(25,3), mc(30,3), mc(4)], + [mc(), mc(), mc(2)], + [mc(), mc(), mc(1)] + ]; + computeHeights(heights,cells); + expect(heights).to.eql([11,9,8]); + }); }); /**