How to design a table using KineticJS on HTML5 Canvas?

During my software development experiences I never faced such a challenging stuff to design a table like Microsoft word or excel. Recently with my Ex-employer in a project I found me to design a table like MS-Word. The skills I used here are KineticJS, HTML5 & Jquery.

A table looks very easy to Operate but during the development of a table I faced many critical logical games to handle. In this article I am sharing the complete class for creating a table. The JavaScript class I am sharing here is TableShape which accepts row & column numbers with KineticJS stage & layer. In real-time this class is capable to draw table for you depending upon the row & column values. To implement this you need to copy this js file to your application & while creating the instance for the class TableShape you need to pass parameters only.

In the below TableShape class I have all the ready mate method to do operation with a table. createCell is the method which creates cell for table. Used Splitters to handle cells & rows adjustments. To insert a row into the table we created a function insertRow(). Where by passing the index you can add a row to your table. To delete a row in this class we implemented deleteRow() function. This function accepts index of the row which one you want to delete. Depending upon the index this external function is capable to delete row from your table. Similar to insertRow() & deleteRow() we have insertColumn() & deleteColumn() method. Using getHeight() & getWidth() external function you can retrieve table width & height in your class. During operations to retrieve the x & y positions of table in this class we declared two additional methods getX() & getY(). These methods are added to the exports array of this method. Which you can easily access from your class. Using setWidth() & setHeight() functions you can set table width & height from your external class. To select the table or it’s cells this class contains Select & Deselect methods. You just need to call them from your class to use. Like this there are plenty of functionality you can get with this class.

This class is well tested by our QA team. I don’t think you will get any problems to implement this class. What problems we faced during a table design I don’t want to give you the same pain. Let us reuse the code.

tableShape.js

/*Table Cell related default values*/
var DEFAULT_CELL_STROKE_WIDTH = 1;
var DEFAULT_CELL_WIDTH = 200;
var DEFAULT_CELL_HEIGHT = 100;
var MIN_CELL_DIM = 25;
var DEFAULT_SPANNER_WIDTH = 5;
/*Table Slitter Default Stroke & Width*/
var DEFAULT_SPLITTER_STROKE = "#000000";
var DEFAULT_SPLITTER_WIDTH = 3;

var DEFAULT_SPANNER_STROKE = "darkgray";
/*Table default cell settings*/
var DEFAULT_CELL_FILL = "#FFFFFF";
var DEFAULT_CELL_STROKE = "#000000";
var DEFAULT_CELL_STROKE_SELECTED = "#000000";

var TableShape = function (totalRows, totalColumns, stage, layer) {

var exports = {};
var _rows = null;
var _columns = null;
var _stage = null;
var _tbllayer = null;
var _tblgroup = null;
var _embeddedShapes = [];
var _tableIndex = [];

/* Initial values related to Table */
function init() {

_tbllayer = layer;
_tblgroup = new Kinetic.Group({
x: -(_columns / 2) * DEFAULT_CELL_WIDTH,
y: -(_rows / 2) * DEFAULT_CELL_HEIGHT,
width: _columns * DEFAULT_CELL_WIDTH,
height: _rows * DEFAULT_CELL_HEIGHT,
draggable: true
});

var xDisp = (2 * DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH);
var yDisp = (2 * DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH);

for (var row = 0; row < _rows; row++) {

_tableIndex[row] = new Array();

for (var col = 0; col < _columns; col++) {

var box = createCell((col == 0) ? (2 * DEFAULT_CELL_STROKE_WIDTH) : (col * DEFAULT_CELL_WIDTH) + (col * xDisp),
(row == 0) ? (2 * DEFAULT_CELL_STROKE_WIDTH) : (row * DEFAULT_CELL_HEIGHT) + (row * yDisp),
DEFAULT_CELL_WIDTH, DEFAULT_CELL_HEIGHT);

_tblgroup.add(box);
_tableIndex[row][col] = box;
}
}

//nameCells();
createSpanners(true);

_tblgroup.on('mouseout', function (e) {
document.body.style.cursor = 'default';
});

_tblgroup.on('click', function (e) {

if (e.shape.getId() != "tableCell" &&
e.shape.getId() != "vSplitter" && e.shape.getId() != "hSplitter" &&
e.shape.getName() != "spanLineCol" && e.shape.getName() != "spanLineRow" &&
event.shiftKey) {
e.shape.parent.setX(_tblgroup.parent.getX() + e.shape.parent.getX() - _tblgroup.parent.getOffset().x);
e.shape.parent.setY(_tblgroup.parent.getY() + e.shape.parent.getY() - _tblgroup.parent.getOffset().y);
e.shape.parent.moveTo(_tbllayer);
e.shape.parentTable = null;
}
else {
// table in table
if (e.shape.getId() == "tableCell" && e.shape.parent != _tblgroup && event.shiftKey) {
//Nothing ToDo
}
else {
groupEmbeddedShapes();
}
}
});
_tblgroup.on('dblclick', function (e) {
});

_tbllayer.add(_tblgroup);
_tblgroup._id = exports._id;
selectCell(_tableIndex[0][0]);
_stage.draw();
}

/* Funtion to Create Cells in Table */
function createCell(cellX, cellY, cellWidth, cellHeight, cellName) {

var cell = new Kinetic.Rect({
id: "tableCell",
name: cellName ? cellName : randomString(5),
x: cellX,
y: cellY,
width: cellWidth,
height: cellHeight,
fill: DEFAULT_CELL_FILL,
stroke: DEFAULT_CELL_STROKE,
strokeWidth: DEFAULT_CELL_STROKE_WIDTH
});

cell.on('click', function (evt) {
selectCell(this);

if (event.ctrlLeft) {
addSplitter(cell, "vertical");
}

if (event.altLeft) {
addSplitter(cell, "horizontal");
}
});

cell.on('mouseover', function (e) {
document.body.style.cursor = 'crosshair';
});

return cell;
}

function updateSplitters(cell) {

if (cell.vSplitters) {

for (var i = 0; i < cell.vSplitters.length; i++) {
var vSplit = cell.vSplitters[i];
var points = vSplit.getPoints();

var curSpltrX = points[0].x + vSplit.getX();

if (curSpltrX < cell.getX()) {
vSplit.setX(cell.getX() - points[0].x + DEFAULT_SPANNER_WIDTH);
}

if (curSpltrX > cell.getX() + cell.getWidth()) {
vSplit.setX(cell.getX() + cell.getWidth() - points[0].x - DEFAULT_SPANNER_WIDTH);
}
points[0].y = cell.getY();
points[1].y = cell.getY() + cell.getHeight();
vSplit.setPoints(points);
vSplit.setY(cell.getY() - points[0].y);
}
}

if (cell.hSplitters) {
for (var i = 0; i < cell.hSplitters.length; i++) {
var hSplit = cell.hSplitters[i];
var points = hSplit.getPoints();

var curSpltrY = points[0].y + hSplit.getY();

if (curSpltrY < cell.getY()) {
hSplit.setY(cell.getY() - points[0].y + DEFAULT_SPANNER_WIDTH);
}

if (curSpltrY > cell.getY() + cell.getHeight()) {
hSplit.setY(cell.getY() + cell.getHeight() - points[0].y - DEFAULT_SPANNER_WIDTH);
}
points[0].x = cell.getX();
points[1].x = cell.getX() + cell.getWidth();
hSplit.setPoints(points);
hSplit.setX(cell.getX() - points[0].x);
}
}
}

function addSplitter(cell, type, evt) {

switch (type) {
case "vertical":
var x, y;

if (evt && evt.x) {
transformEventCoordsToCellCoords(cell, evt);
x = cell.getX() + evt.x;
y = cell.getY();
}
else {
x = cell.getX() + cell.getWidth() / 2;
y = cell.getY();
}

var lineVSplitter = new Kinetic.Line({
id: "vSplitter",
name: cell.getName() + "vSplitter",
points: [x, y, x, y + cell.getHeight()],
stroke: cell.getStroke(),
strokeWidth: DEFAULT_SPLITTER_WIDTH,
draggable: true,
dragOnTop: false
});

lineVSplitter.on('mouseover', function (e) {
$("body").css("cursor", "col-resize");
});

lineVSplitter.on('dragmove', function (e) {
this.setY(0);

var curSpltrX = this.getPoints()[0].x + this.getX();

if (curSpltrX < cell.getX()) {
this.setX(cell.getX() - this.getPoints()[0].x + DEFAULT_SPANNER_WIDTH);
return;
}

if (curSpltrX > cell.getX() + cell.getWidth()) {
this.setX(cell.getX() + cell.getWidth() - this.getPoints()[0].x - DEFAULT_SPANNER_WIDTH);
return;
}
});

lineVSplitter.on('mouseout', function (e) {
$("body").css("cursor", "default");
});

lineVSplitter.on("mousedown dragstart", function () {
_tblgroup.setDraggable(false);
_tblgroup.parent.setDraggable(false);
});

lineVSplitter.on("mouseup dragend", function () {
_tblgroup.setDraggable(true);
_tblgroup.parent.setDraggable(true);
});

_tblgroup.add(lineVSplitter);
if (!cell.vSplitters) cell.vSplitters = [];
cell.vSplitters.push(lineVSplitter);
break;

case "horizontal":
var x, y;

if (evt && evt.y) {
transformEventCoordsToCellCoords(cell, evt);
x = cell.getX();
y = cell.getY() + evt.y;
}
else {
x = cell.getX();
y = cell.getY() + cell.getHeight() / 2;
}

var lineHSplitter = new Kinetic.Line({
id: "hSplitter",
name: cell.getName() + "hSplitter",
points: [x, y, x + cell.getWidth(), y],
stroke: cell.getStroke(),
strokeWidth: DEFAULT_SPLITTER_WIDTH,
draggable: true,
dragOnTop: false
});

lineHSplitter.on('mouseover', function (e) {
$("body").css("cursor", "row-resize");
});

lineHSplitter.on('dragmove', function (e) {
this.setX(0);

var curSpltrY = this.getPoints()[0].y + this.getY();

if (curSpltrY < cell.getY()) {
this.setY(cell.getY() - this.getPoints()[0].y + DEFAULT_SPANNER_WIDTH);
return;
}

if (curSpltrY > cell.getY() + cell.getHeight()) {
this.setY(cell.getY() + cell.getHeight() - this.getPoints()[0].y - DEFAULT_SPANNER_WIDTH);
return;
}
});

lineHSplitter.on('mouseout', function (e) {
$("body").css("cursor", "default");
});

lineHSplitter.on("mousedown dragstart", function () {
_tblgroup.setDraggable(false);
_tblgroup.parent.setDraggable(false);
});

lineHSplitter.on("mouseup dragend", function () {
_tblgroup.setDraggable(true);
_tblgroup.parent.setDraggable(true);
});

_tblgroup.add(lineHSplitter);
if (!cell.hSplitters) cell.hSplitters = [];
cell.hSplitters.push(lineHSplitter);
break;

default:
break;
}
}

function removeSplitters(cell, type, evt) {

switch (type) {
case "vertical":

if (cell.vSplitters) {
if (evt && evt.x) {
transformEventCoordsToCellCoords(cell, evt);
var nearest = 0;

for (var i = 1; i < cell.vSplitters.length; i++) {
var vSplit = cell.vSplitters[i];
var vSplitNearest = cell.vSplitters[nearest];
var splitX = vSplit.getPoints()[0].x + vSplit.getX();
var splitXnearest = vSplitNearest.getPoints()[0].x + vSplitNearest.getX();

if (Math.abs(evt.x - splitXnearest) > Math.abs(evt.x - splitX)) {
nearest = i;
}
}

cell.vSplitters[nearest].remove();
cell.vSplitters.splice(nearest, 1);
if (0 == cell.vSplitters.length) cell.vSplitters = null;
}
else {
for (var i = 0; i < cell.vSplitters.length; i++) {
cell.vSplitters[i].remove();
}

cell.vSplitters = null;
}
}
break;

case "horizontal":
if (cell.hSplitters) {

if (evt && evt.y) {
transformEventCoordsToCellCoords(cell, evt);
var nearest = 0;

for (var i = 1; i < cell.hSplitters.length; i++) {
var hSplit = cell.hSplitters[i];
var hSplitNearest = cell.hSplitters[nearest];
var splitY = hSplit.getPoints()[0].y + hSplit.getY();
var splitYnearest = hSplitNearest.getPoints()[0].y + hSplitNearest.getY();

if (Math.abs(evt.y - splitYnearest) > Math.abs(evt.y - splitY)) {
nearest = i;
}
}

cell.hSplitters[nearest].remove();
cell.hSplitters.splice(nearest, 1);
if (0 == cell.hSplitters.length) cell.hSplitters = null;
}
else {
for (var i = 0; i < cell.hSplitters.length; i++) {
cell.hSplitters[i].remove();
}

cell.hSplitters = null;
}
}
break;

default:
break;
}
}

function moveSplitters(cell, disp) {

if (cell.vSplitters) {
for (var i = 0; i < cell.vSplitters.length; i++) {
cell.vSplitters[i].move(disp.x, disp.y);
}
}

if (cell.hSplitters) {
for (var i = 0; i < cell.hSplitters.length; i++) {
cell.hSplitters[i].move(disp.x, disp.y);
}
}

updateSplitters(cell);
}

function createSpanners(init) {

if (!init) {
// remove previous ones
var rowSpans = _tblgroup.get(".spanLineRow");

for (var i = 0; i < rowSpans.length; i++) {
rowSpans[i].remove();
}

var colSpans = _tblgroup.get(".spanLineCol");

for (var i = 0; i < colSpans.length; i++) {
colSpans[i].remove();
}
}

var row = 0;
for (var col = 1; col < _tableIndex[row].length; col++) {

var cell = _tableIndex[row][col];

var lineCol = new Kinetic.Line({
name: "spanLineCol",
id: col,
points: [cell.getX() - DEFAULT_SPANNER_WIDTH + (2 * DEFAULT_CELL_STROKE_WIDTH), cell.getY(),
cell.getX() - DEFAULT_SPANNER_WIDTH + (2 * DEFAULT_CELL_STROKE_WIDTH), exports.getHeight()],

stroke: DEFAULT_SPANNER_STROKE,
strokeWidth: DEFAULT_SPANNER_WIDTH,
dashArray: [DEFAULT_SPANNER_WIDTH, DEFAULT_SPANNER_WIDTH, DEFAULT_SPANNER_WIDTH, DEFAULT_SPANNER_WIDTH],
lineCap: 'round',
lineJoin: 'round',
draggable: true,
dragOnTop: false
});

lineCol.on('mouseover', function (e) {
$("body").css("cursor", "col-resize");
});

lineCol.on('dragmove', function (e) {
this.setY(0);
spanColumn(e.shape);
});

lineCol.on('mouseout', function (e) {
$("body").css("cursor", "default");
});

lineCol.on("mousedown dragstart", function () {
_tblgroup.setDraggable(false);
_tblgroup.parent.setDraggable(false);
});

lineCol.on("mouseup dragend", function () {
_tblgroup.setDraggable(true);
_tblgroup.parent.setDraggable(true);
});

_tblgroup.add(lineCol);
}

var col = 0;
for (var row = 1; row < _tableIndex.length; row++) {

var cell = _tableIndex[row][col];

var lineRow = new Kinetic.Line({
name: "spanLineRow",
id: row,
points: [cell.getX(), cell.getY() - DEFAULT_SPANNER_WIDTH + (2 * DEFAULT_CELL_STROKE_WIDTH),
exports.getWidth(), cell.getY() - DEFAULT_SPANNER_WIDTH + (2 * DEFAULT_CELL_STROKE_WIDTH)],
stroke: DEFAULT_SPANNER_STROKE,
strokeWidth: DEFAULT_SPANNER_WIDTH,
dashArray: [DEFAULT_SPANNER_WIDTH, DEFAULT_SPANNER_WIDTH, DEFAULT_SPANNER_WIDTH, DEFAULT_SPANNER_WIDTH],
lineCap: 'round',
lineJoin: 'round',
draggable: true,
dragOnTop: false
});

lineRow.on('mouseover', function (e) {
$("body").css("cursor", "row-resize");
});

lineRow.on('dragmove', function (e) {
this.setX(0);
spanRow(e.shape);
});

lineRow.on('mouseout', function (e) {
$("body").css("cursor", "default");
});

lineRow.on("mousedown dragstart", function () {
_tblgroup.setDraggable(false);
_tblgroup.parent.setDraggable(false);
});

lineRow.on("mouseup dragend", function () {
_tblgroup.setDraggable(true);
_tblgroup.parent.setDraggable(true);
});

_tblgroup.add(lineRow);
}

if (exports.selectedCell) {
exports.selectedCell.show();
}
}

function spanColumn(spanner) {

var col = Number(spanner.getId());
var curSpanX = spanner.getPoints()[0].x + spanner.getX();

for (var row = 0; row < _tableIndex.length; row++) {

var lCell = _tableIndex[row][col - 1];
var rCell = _tableIndex[row][col];

if (lCell.getWidth() <= MIN_CELL_DIM && (curSpanX - DEFAULT_SPANNER_WIDTH) <= (lCell.getX() + lCell.getWidth())) {
spanner.setX((lCell.getX() + lCell.getWidth() + (4 * DEFAULT_CELL_STROKE_WIDTH)) - spanner.getPoints()[0].x);
return;
}

if (rCell.getWidth() <= MIN_CELL_DIM && curSpanX >= rCell.getX()) {
spanner.setX((rCell.getX() - (4 * DEFAULT_CELL_STROKE_WIDTH)) - spanner.getPoints()[0].x);
return;
}

var diff = curSpanX - rCell.getX() + (4 * DEFAULT_CELL_STROKE_WIDTH);

lCell.setWidth(lCell.getWidth() + diff);
rCell.setWidth(rCell.getWidth() - diff);

var embShapes = _tblgroup.get("." + rCell.getName());

for (var i = 0; i < embShapes.length; i++) {
embShapes[i].setX(embShapes[i].getX() + diff);
}

updateSplitters(lCell);
updateSplitters(rCell);
}
}

function spanRow(spanner) {

var row = Number(spanner.getId());
var curSpanY = spanner.getPoints()[0].y + spanner.getY();

for (var col = 0; col < _tableIndex[row].length; col++) {

var tCell = _tableIndex[row - 1][col];
var bCell = _tableIndex[row][col];

if (tCell.getHeight() <= MIN_CELL_DIM && (curSpanY - DEFAULT_SPANNER_WIDTH) <= (tCell.getY() + tCell.getHeight())) {
spanner.setY((tCell.getY() + tCell.getHeight() + (4 * DEFAULT_CELL_STROKE_WIDTH)) - spanner.getPoints()[0].y);
return;
}

if (bCell.getHeight() <= MIN_CELL_DIM && curSpanY >= bCell.getY()) {
spanner.setY((bCell.getY() - (4 * DEFAULT_CELL_STROKE_WIDTH)) - spanner.getPoints()[0].y);
return;
}

var diff = curSpanY - bCell.getY() + (4 * DEFAULT_CELL_STROKE_WIDTH);

tCell.setHeight(tCell.getHeight() + diff);
bCell.setHeight(bCell.getHeight() - diff);

var embShapes = _tblgroup.get("." + bCell.getName());

for (var i = 0; i < embShapes.length; i++) {
embShapes[i].setY(embShapes[i].getY() + diff);
}

updateSplitters(tCell);
updateSplitters(bCell);
}
}

/* Function to Select Indivisual Cell in Table*/
function selectCell(cell) {

if (exports.selectedCell && exports.selectedCell.sCell() == cell) return;

if (exports.selectedCell) {
exports.selectedCell.hide();
}
exports.selectedCell = new SelectedCell(cell, null);
exports.selectedCell.show();
_stage.draw();
}

function selectTable() {

if (exports.selectedCell) {
exports.selectedCell.hide();
}

exports.selectedCell = new SelectedCell(null, _tableIndex);
exports.selectedCell.show();
_stage.draw();
}

function resizeRowSpanners(diff, bLeft) {

var rowSpans = _tblgroup.get(".spanLineRow");

for (var i = 0; i < rowSpans.length; i++) {
rowSpans[i].attrs.points[1].x += diff;
}

if (bLeft) {
var colSpans = _tblgroup.get(".spanLineCol");

for (var i = 0; i < colSpans.length; i++) {
colSpans[i].attrs.points[0].x += diff;
colSpans[i].attrs.points[1].x += diff;
}
}

_tblgroup.setWidth(_tblgroup.getWidth() + diff);
}

function resizeColSpanners(diff, bTop) {
var colSpans = _tblgroup.get(".spanLineCol");

for (var i = 0; i < colSpans.length; i++) {
colSpans[i].attrs.points[1].y += diff;
}

if (bTop) {
var rowSpans = _tblgroup.get(".spanLineRow");

for (var i = 0; i < rowSpans.length; i++) {
rowSpans[i].attrs.points[0].y += diff;
rowSpans[i].attrs.points[1].y += diff;
}
}

_tblgroup.setHeight(_tblgroup.getHeight() + diff);
}

function groupEmbeddedShapes() {

var oldRot = _tblgroup.parent.getRotation();
_tblgroup.parent.setRotation(0);

for (var key in _embeddedShapes) {

if (_embeddedShapes.hasOwnProperty(key)) {
var shape = _embeddedShapes[key];
var oldChld = _tblgroup.children.length;

shape.moveTo(_tblgroup);

if (oldChld != _tblgroup.children.length) {

shape.setX(shape.getX() - _tblgroup.parent.getX() + _tblgroup.parent.getOffset().x);
shape.setY(shape.getY() - _tblgroup.parent.getY() + _tblgroup.parent.getOffset().y);

shape.setName("");
bInside = false;

for (var row = 0; row < _tableIndex.length && bInside == false; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {
cell = _tableIndex[row][col];
if (exports.areShapesOverlapping(cell, shape)) {
shape.setName(cell.getName());
bInside = true;
break;
}
}
}
}

if (!bInside) {
shape.setX(_tblgroup.parent.getX() + shape.getX() - _tblgroup.parent.getOffset().x);
shape.setY(_tblgroup.parent.getY() + shape.getY() - _tblgroup.parent.getOffset().y);
shape.moveTo(_tbllayer);
}
}
}

_tblgroup.parent.setRotation(oldRot);
}

function transformEventCoordsToCellCoords(cell, evt) {

if (cell && evt) {
var m = cell.getAbsoluteTransform();
var n = new Kinetic.Transform();
n.multiply(m);
n.invert();
n.translate(evt.x, evt.y)
evt.x = n.getMatrix()[4];
evt.y = n.getMatrix()[5];
}
}

/* Function to insert Row into the Table */
exports.insertRow = function (index) {

if (index < 0) index = 0;
if (index == undefined || index >= _rows) index = _rows;

var row = new Array();

if (index >= _tableIndex.length) {
for (var col = 0; col < _tableIndex[index - 1].length; col++) {
var refCell = _tableIndex[index - 1][col];
var cell = createCell(refCell.getX(), refCell.getY() + DEFAULT_SPANNER_WIDTH + refCell.getHeight(), refCell.getWidth(), DEFAULT_CELL_HEIGHT);
row.push(cell);
_tblgroup.add(cell);
}

_tableIndex.splice(index, 0, row);
}
else {
for (var col = 0; col < _tableIndex[index].length; col++) {
var refCell = _tableIndex[index][col];
var cell = createCell(refCell.getX(), refCell.getY(), refCell.getWidth(), DEFAULT_CELL_HEIGHT);
row.push(cell);
_tblgroup.add(cell);
}

_tableIndex.splice(index, 0, row);

for (var rowInd = index + 1; rowInd < _tableIndex.length; rowInd++) {
for (var col = 0; col < _tableIndex[rowInd].length; col++) {
var cell = _tableIndex[rowInd][col];

// move linked shapes too
var embShapes = _tblgroup.get("." + cell.getName());

for (var i = 0; i < embShapes.length; i++) {
embShapes[i].move(0, DEFAULT_CELL_HEIGHT + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH);
}

moveSplitters(cell, { x: 0, y: DEFAULT_CELL_HEIGHT + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH });
}
}
}

_rows++;

createSpanners();
_stage.draw();
}

/* Function to delete Row from the Table */
exports.deleteRow = function (index) {

if (_rows == 1) return;

if (index == undefined) index = _rows - 1;

var diff = _tableIndex[index][0].getHeight();

for (var row = index; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {

var cell = _tableIndex[row][col];
var embShapes = _tblgroup.get("." + cell.getName());

if (row == index) {
// delete linked shapes too
for (var i = 0; i < embShapes.length; i++) {
embShapes[i].remove();
}

removeSplitters(cell, "vertical");
removeSplitters(cell, "horizontal");
}
else {
for (var i = 0; i < embShapes.length; i++) {
embShapes[i].move(0, -(diff + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH));
}

moveSplitters(cell, { x: 0, y: -(diff + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH) });
}
}
}

_tableIndex.splice(index, 1);
_rows--;

createSpanners();
_stage.draw();
}

/* Function to insert Column into the Table */
exports.insertColumn = function (index) {

if (index < 0) index = 0;
if (index == undefined || index >= _columns) index = _columns;

for (var row = 0; row < _tableIndex.length; row++) {

if (index >= _tableIndex[row].length) {

var refCell = _tableIndex[row][index - 1];
var cell = createCell(refCell.getX() + refCell.getWidth() + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH, refCell.getY(), DEFAULT_CELL_WIDTH, refCell.getHeight());
_tableIndex[row].push(cell);
_tblgroup.add(cell);
}
else {
var refCell = _tableIndex[row][index];
var newCell = createCell(refCell.getX(), refCell.getY(), DEFAULT_CELL_WIDTH, refCell.getHeight());
_tableIndex[row].splice(index, 0, newCell);
_tblgroup.add(newCell);

for (var colInd = index + 1; colInd < _tableIndex[row].length; colInd++) {
var cell = _tableIndex[row][colInd];

// move linked shapes too
var embShapes = _tblgroup.get("." + cell.getName());

for (var i = 0; i < embShapes.length; i++) {
embShapes[i].move(DEFAULT_CELL_WIDTH + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH, 0);
}

moveSplitters(cell, { x: DEFAULT_CELL_WIDTH + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH, y: 0 });
}
}
}

_columns++;
createSpanners();
_stage.draw();
}

/* Function to delete column from the Table */
exports.deleteColumn = function (index) {

if (_columns == 1) return;

if (index == undefined) index = _columns - 1;

var diff = _tableIndex[0][index].getWidth();

for (var row = 0; row < _tableIndex.length; row++) {
for (var col = index; col < _tableIndex[row].length; col++) {

var cell = _tableIndex[row][col];

var embShapes = _tblgroup.get("." + cell.getName());

if (col == index) {
// delete linked shapes too
for (var i = 0; i < embShapes.length; i++) {
embShapes[i].remove();
}

removeSplitters(cell, "vertical");
removeSplitters(cell, "horizontal");
}
else {
for (var i = 0; i < embShapes.length; i++) {
embShapes[i].move(-(diff + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH), 0);
}

moveSplitters(cell, { x: -(diff + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH), y: 0 });
}
}

_tableIndex[row].splice(index, 1);
}

_columns--;
createSpanners();
_stage.draw();
}

exports.rows = function () {
return _rows;
}

exports.columns = function () {
return _columns;
}

exports.group = function () {
return _tblgroup;
}

exports.getCell = function (row, col) {
if (row >= 0 && row < _rows && col >= 0 && col < _columns) {
return _tableIndex[row][col];
}
}

exports.addShape = function (shape) {

_embeddedShapes[shape._id] = shape;

shape.parentTable = exports;
}

exports.removeShape = function (shape) {

if (_embeddedShapes[shape._id]) {
delete _embeddedShapes[shape._id]
}
}

exports.getSelectedIndex = function () {

var cell = exports.selectedCell.sCell();

if (cell) {
for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {
if (_tableIndex[row][col] == cell) {
return { row: row, column: col };
}
}
}
}

return null;
}

exports.update = function () {
groupEmbeddedShapes();
}

exports.initDone = function () {

if (_tblgroup.parent && _tblgroup.parent.getId() == "marquee") {
var borderLine = _tblgroup.parent.get(".borderLines")[0];

if (borderLine) {
borderLine.on("click", function (e) {
selectTable();
});
}
}
}

exports.splitCellVertical = function (row, col, evt) {
if (row >= 0 && row < _rows && col >= 0 && col < _columns) {
var cell = _tableIndex[row][col];

addSplitter(cell, "vertical", evt);
}
}

exports.mergeCellVertical = function (row, col, evt) {
if (row >= 0 && row < _rows && col >= 0 && col < _columns) {
var cell = _tableIndex[row][col];

removeSplitters(cell, "vertical", evt);
}
}

exports.splitCellHorizontal = function (row, col, evt) {
if (row >= 0 && row < _rows && col >= 0 && col < _columns) {
var cell = _tableIndex[row][col];

addSplitter(cell, "horizontal", evt);
}
}

exports.mergeCellHorizontal = function (row, col, evt) {
if (row >= 0 && row < _rows && col >= 0 && col < _columns) {
var cell = _tableIndex[row][col];

removeSplitters(cell, "horizontal", evt);
}
}

exports.hasVertSplitters = function (row, col) {
if (row >= 0 && row < _rows && col >= 0 && col < _columns) {
var cell = _tableIndex[row][col];

if (cell.vSplitters) return true;
}

return false;
}

exports.hasHorizSplitters = function (row, col) {
if (row >= 0 && row < _rows && col >= 0 && col < _columns) {
var cell = _tableIndex[row][col];

if (cell.hSplitters) return true;
}
return false;
}

// getters
exports.getId = function () {
return null;
}

exports.getName = function () {
return null;
}

exports.getLayer = function () {
return _tbllayer;
}

exports.getStage = function () {
return _stage;
}

exports.getZIndex = function () {
return _tblgroup.getZIndex();
}

exports.getOffset = function () {
if (_tblgroup.parent && _tblgroup.parent.getId() == "marquee") {
return _tblgroup.parent.getOffset();
}
else {
return _tblgroup.getOffset();
}
}

exports.getScale = function () {
return _tblgroup.getScale();
}

/* Function to get width of the Table */
exports.getWidth = function () {
var row = _rows - 1;
var width = 0 - (2 * DEFAULT_CELL_STROKE_WIDTH);

for (var i = 0; i < _tableIndex[row].length; i++) {
width += _tableIndex[row][i].getWidth() + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH;
}

_tblgroup.setWidth(width);

return width;
}

/* Function to get height of the Table */
exports.getHeight = function () {
var col = _columns - 1
var height = 0 - (2 * DEFAULT_CELL_STROKE_WIDTH);

for (var i = 0; i < _tableIndex.length; i++) {
height += _tableIndex[i][col].getHeight() + DEFAULT_CELL_STROKE_WIDTH + DEFAULT_SPANNER_WIDTH;
}

_tblgroup.setHeight(height);

return height;
}

exports.getX = function () {
if (_tblgroup.parent && _tblgroup.parent.getId() == "marquee") {
return _tblgroup.parent.getX();
}
else {
return _tblgroup.getX();
}
}

exports.getY = function () {
if (_tblgroup.parent && _tblgroup.parent.getId() == "marquee") {
return _tblgroup.parent.getY();
}
else {
return _tblgroup.getY();
}
}

// setters
exports.setX = function (val) {
_tblgroup.setX(val);
}

exports.setY = function (val) {
_tblgroup.setY(val);
}

exports.moveTo = function (shp) {
_tblgroup.moveTo(shp);
}

exports.setDraggable = function (val) {
_tblgroup.setDraggable(val);
}

exports.setWidth = function (val, anchor) {

var diff = val - exports.getWidth();

if (anchor) {
var anchorName = anchor.getName();

switch (anchorName) {

case "cpLeft":
for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {

var cell = _tableIndex[row][col];

if (col == 0) {
var newWidth = cell.getWidth() + diff;

if (newWidth > MIN_CELL_DIM) {
cell.setWidth(newWidth);
if (row == 0) resizeRowSpanners(diff, true);
}
else {
_tblgroup.parent.move(diff, 0);
diff = 0;
}
}
else {
var embShapes = _tblgroup.get("." + cell.getName());

for (var i = 0; i < embShapes.length; i++) {
embShapes[i].move(diff, 0);
}
}

updateSplitters(cell);
}
}
break;

case "cpRight":
var col = _columns - 1;

for (var row = 0; row < _tableIndex.length; row++) {
var cell = _tableIndex[row][col];
var newWidth = cell.getWidth() + diff;

if (newWidth > MIN_CELL_DIM) {
cell.setWidth(newWidth);
if (row == 0) resizeRowSpanners(diff, false);
}

updateSplitters(cell);
}
break;

case "cpTopRight":
case "cpBottomRight":
case "cpTopLeft":
case "cpBottomLeft":
var frac = diff / _columns;
var action = false;

for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {

var cell = _tableIndex[row][col];
var newWidth = cell.getWidth() + frac;

if (newWidth > MIN_CELL_DIM) {
action = true;
cell.setWidth(newWidth);

for (var cc = col + 1; cc < _tableIndex[row].length; cc++) {
var nCell = _tableIndex[row][cc];

var embShapes = _tblgroup.get("." + nCell.getName());

for (var i = 0; i < embShapes.length; i++) {
embShapes[i].move(frac, 0);
}
}
}

updateSplitters(cell);
}
}

if (action) {
createSpanners();
}
else {
if (anchorName != "cpBottomRight" && anchorName != "cpTopRight") {
_tblgroup.parent.move(diff, 0);
}
}

break;

default:
break;
}
}
}

exports.setHeight = function (val, anchor) {

var diff = val - exports.getHeight();

if (anchor) {
var anchorName = anchor.getName();

switch (anchorName) {

case "cpTop":

for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {
var cell = _tableIndex[row][col];

if (row == 0) {
var newHeight = cell.getHeight() + diff;

if (newHeight > MIN_CELL_DIM) {
cell.setHeight(newHeight);
if (col == 0) resizeColSpanners(diff, true);
}
else {
_tblgroup.parent.move(0, diff);
diff = 0;
}
}
else {
var embShapes = _tblgroup.get("." + cell.getName());

for (var i = 0; i < embShapes.length; i++) {
embShapes[i].move(0, diff);
}
}

updateSplitters(cell);
}
}
break;

case "cpBottom":
var row = _rows - 1;

for (var col = 0; col < _tableIndex[row].length; col++) {
var cell = _tableIndex[row][col];
var newHeight = cell.getHeight() + diff;

if (newHeight > MIN_CELL_DIM) {
cell.setHeight(newHeight);
if (col == 0) resizeColSpanners(diff, false);
}

updateSplitters(cell);
}
break;

case "cpBottomRight":
case "cpBottomLeft":
case "cpTopLeft":
case "cpTopRight":
var frac = diff / _rows;
var action = false;

for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {

var cell = _tableIndex[row][col];
var newHeight = cell.getHeight() + frac;

if (newHeight > MIN_CELL_DIM) {
action = true;
cell.setHeight(newHeight);

for (var rr = row + 1; rr < _tableIndex.length; rr++) {
var nCell = _tableIndex[rr][col];

var embShapes = _tblgroup.get("." + nCell.getName());

for (var i = 0; i < embShapes.length; i++) {
embShapes[i].move(0, frac);
}
}
}

updateSplitters(cell);
}
}

if (action) {
createSpanners();
}
else {
if (anchorName != "cpBottomLeft" && anchorName != "cpBottomRight") {
_tblgroup.parent.move(0, diff);
}
}

break;

default:
break;
}
}
}

exports.deSelect = function () {
exports.selectedCell.hide();
_stage.draw();
}

exports.select = function () {
exports.selectedCell.show();
_stage.draw();
}

function randomString() {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 3;
var randomstring = '';

for (var i = 0; i < string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}

return randomstring;
}

exports.checkTableIntersections = function(marquees, shape, core) {

if (core && core.shapeType == "Table") {
return;
}

for (var key in marquees) {
if (marquees.hasOwnProperty(key) && marquees[key].shape().shapeType == "Table") {
var tab = marquees[key].shape();

if (exports.areShapesOverlapping(shape, tab)) {
tab.addShape(shape);
}
else {
tab.removeShape(shape);
}
}
}
}

exports.areShapesOverlapping = function(shape1, shape2) {

var shape1Width = shape1.getWidth();
var shape1Height = shape1.getHeight();
var shape1X = shape1.getX() - shape1.getOffset().x;
var shape1XW = shape1X + shape1Width;
var shape1Y = shape1.getY() - shape1.getOffset().y;
var shape1YH = shape1Y + shape1Height;

var shape2Width = shape2.getWidth();
var shape2Height = shape2.getHeight();
var shape2X = shape2.getX() - shape2.getOffset().x;
var shape2XW = shape2X + shape2Width;
var shape2Y = shape2.getY() - shape2.getOffset().y;
var shape2YH = shape2Y + shape2Height;

// any part of shape on the table
if (((shape1X > shape2X && shape1X < shape2XW) || (shape2X > shape1X && shape2X < shape1XW)) &&
((shape1Y > shape2Y && shape1Y < shape2YH) || (shape2Y > shape1Y && shape2Y < shape1YH))) {
return true;
}
else {
return false;
}
}

exports.shapeType = "Table";
exports._id = randomString(3);
exports.selectedCell = null;

_rows = totalRows;
_columns = totalColumns;
_stage = stage;

init();

exports.parent = _tblgroup;
return exports;
}

var SelectedCell = function (cell, tblIndex) {
var exports = {};
var _selectedCell = cell;
var _tableIndex = tblIndex;

exports.getFill = function () {
if (_selectedCell) {
return _selectedCell.getFill();
}

if (_tableIndex) {
return _tableIndex[0][0].getFill();
}
}

exports.getStroke = function () {
if (_selectedCell) {
return _selectedCell.getStroke();
}

if (_tableIndex) {
return _tableIndex[0][0].getStroke();
}
}

exports.getStrokeWidth = function () {
if (_selectedCell) {
return _selectedCell.getStrokeWidth();
}

if (_tableIndex) {
return _tableIndex[0][0].getStrokeWidth();
}
}

exports.setFill = function (val) {
if (_selectedCell) {
_selectedCell.setFill(val);
return;
}

if (_tableIndex) {
for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {
_tableIndex[row][col].setFill(val);
}
}
}
}

/* Funtion to set stroke for selected table cell */
exports.setStroke = function (val) {
if (_selectedCell) {
_selectedCell.setStroke(val);

if (_selectedCell.vSplitters) {
for (var i = 0; i < _selectedCell.vSplitters.length; i++) {
_selectedCell.vSplitters[i].setStroke(val);
}
}

if (_selectedCell.hSplitters) {
for (var i = 0; i < _selectedCell.hSplitters.length; i++) {
_selectedCell.hSplitters[i].setStroke(val);
}
}
return;
}

if (_tableIndex) {
for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {
var cell = _tableIndex[row][col];
cell.setStroke(val);

if (cell.vSplitters) {
for (var i = 0; i < cell.vSplitters.length; i++) {
cell.vSplitters[i].setStroke(val);
}
}

if (cell.hSplitters) {
for (var i = 0; i < cell.hSplitters.length; i++) {
cell.hSplitters[i].setStroke(val);
}
}
}
}
}
}

exports.setStrokeWidth = function (val) {
if (_selectedCell) {
_selectedCell.setStrokeWidth(val);
return;
}

if (_tableIndex) {
for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {
_tableIndex[row][col].setStrokeWidth(val);
}
}
}
}

exports.sCell = function () {
return _selectedCell;
}

exports.show = function () {

if (_selectedCell) {

_selectedCell.setStrokeWidth(DEFAULT_CELL_STROKE_WIDTH * 4);
_selectedCell.setStroke(_selectedCell.getStroke());
}
else {

for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {
cl = _tableIndex[row][col];
cl.setStrokeWidth(DEFAULT_CELL_STROKE_WIDTH * 4);
cl.setStroke(cl.getStroke());
}
}
}
}

exports.hide = function () {

if (_selectedCell) {

_selectedCell.setStrokeWidth(DEFAULT_CELL_STROKE_WIDTH);
_selectedCell.setStroke(_selectedCell.getStroke());
}
else {

for (var row = 0; row < _tableIndex.length; row++) {
for (var col = 0; col < _tableIndex[row].length; col++) {
cl = _tableIndex[row][col];
cl.setStrokeWidth(DEFAULT_CELL_STROKE_WIDTH);
cl.setStroke(cl.getStroke());
}
}
}
}

return exports;
}