I am trying to code a builder for table tennis exercise graphics. I want it to show a table for each rally of the exercise, with arrows on it that show the movement path of the ball and rectangles of areas where to play.
Right now it looks like this: https://codepen.io/graNite/pen/grqXOo
You can add new tables, remove the last one, and draw two fixed arrows on the same canvas layer on the first table.
What I want to enable is to draw arrows* on each table via click-and-drag and delete them with right-click on them.
*(like they are drawn in the drawArrows function)
What is the best way to do this?
I already implemented a canvas layer for the arrows and could even do so for every single arrow on each table, but how can I access an arrow that is in a middle layer if I right click on it to delete it?
HTML
<button onclick="addTable()">Add table</button>
<button onclick="removeTable()">Remove table</button>
<button onclick="drawArrow(50, 50, 150, 250)">Draw arrow</button>
<button onclick="drawArrow(50, 300, 180, 20)">Draw arrow2</button>
</br>
<div id="tables">
</div>
CSS
body {
background-color: #982439;
}
#table {
padding: 10px;
}
canvas {
position: absolute;
}
JS
function drawTable(table) {
"use strict";
var draw = table.getContext("2d");
draw.shadowBlur = 20;
draw.shadowColor = 'rgba(0,0,0,0.3)'; // shadow
draw.fillStyle = "#2e3f73"; // table
draw.fillRect(35.25, 20, 152.5, 274);
draw.fillStyle = "#ffffff"; // lines
draw.fillRect(111.35, 20, 0.3, 274); // middle line
draw.fillRect(35.25, 20, 2, 274); // lift side
draw.fillRect(185.75, 20, 2, 274); // right side
draw.fillRect(35.25, 20, 152.5, 2); // top base line
draw.fillRect(35.25, 292, 152.5, 2); // bottom base line
draw.fillRect(20, 156, 183, 2); // net
}
function addTable() {
"use strict";
var container = document.createElement("div"),
table = document.createElement("canvas"),
arrowLayer = document.createElement("canvas"),
width = 223,
height = 314;
container.appendChild(table);
container.appendChild(arrowLayer);
container.style.width = width + "px";
container.style.height = height + "px";
container.style.display = "inline-block";
document.getElementById("tables").appendChild(container);
table.width = width;
table.height = height;
table.className = "table";
table.style.zIndex = "0";
drawTable(table);
arrowLayer.width = width;
arrowLayer.height = height;
arrowLayer.className = "arrow";
arrowLayer.style.zIndex = "1";
arrowLayer.id = "arrow1";
}
function removeTable() {
"use strict";
var child = document.getElementById("tables").lastChild;
child.parentNode.removeChild(child);
}
function drawArrow(start_x, start_y, end_x, end_y) {
"use strict";
var draw = document.getElementById('arrow1').getContext('2d'),
angle = Math.atan((end_y - start_y) / (end_x - start_x)),
length = Math.sqrt(Math.pow((end_x - start_x), 2) + Math.pow((end_y - start_y), 2));
// set colors and style
draw.strokeStyle = "#ffb900";
draw.fillStyle = "#ffb900";
draw.lineWidth = 9;
// draw arrow line
draw.beginPath();
draw.translate(start_x, start_y);
draw.moveTo(0, 0);
draw.rotate(angle);
draw.lineTo(length - 23, 0); // note: arrowhead is 24px long and total arrow is line+head
draw.stroke();
draw.moveTo(-start_x, -start_y);
// draw arrow head
draw.beginPath();
draw.moveTo(length, 0);
draw.lineTo(length - 24, -7.5); // ^ see note above
draw.lineTo(length - 24, 7.5);
draw.fill();
//reset context
draw.rotate(-angle);
draw.moveTo(-start_x, -start_y);
draw.translate(-start_x, -start_y);
}
Good interface design
A good quality interface should not have ugly buttons, it should be intuitive and easy to use. There should always be feed back (cursors, highlighting, roll over effects). The effects are not as much for show but to provide information users need to interact with the app. Thus the FX's do not have to be major, just enough for a user to see that everything is working and what is clickable and what is not.
I was going to add it without help as it should make sense but I have added help just incase it does not. We all have a different approch to interface usage.
UPDATE : I have added some more code. Help now only shown until the help feature is use. Once use the help is not display.
Added comments though code where I thought it needed it (basicly everywhere)
Code
Tables are in an array tableArray. Tables are added with addTable() which returns a table object. Tables default to inactive table.active=false. To activate click the mouse on the table or table.active = true; table.draw(); Arrows are in the array table.arrows as {x:?,y?,xx:?,yy:?,highlight:false} to remove use array splice or via the interface. To remove a table deactivate it table.active = false; (or click close) it will stay in the DOM untill updateTables() is called. (when closed by user mouse click update is called automatically)
There will always be one inactive table visible that can be used to add a active table.
Each table in the array has everything needed to do its thing. Mouseevent, render events, close, etc.
Each table is rendered at full frame rate (when it has internal mouse focus) so you can add nice animations if you want. When the mouse is not over Table it is not updated unless you call the table.draw() function.
The code is a bit messy, as it got a little out of hand. Consts define most things (at top of code). Table, close icon, and empty table are pre-rendered. Arrows and help are rendered as needed.
To use
Click an empty table to add (activate). Click on active tables close icon to close. Click drag to add arrow, Right click when near arrow to delete. Arrow near mouse will highlight.
Note The number of tables has no restriction. This is not a good idea and you should limit the number of tables. Also there is a bit of a layout issue when added tables cause page scroll bars to appear. Removing tables does not revert to what was. As I do not know what you want I left that for you to sort out.
Also added
GLOBAL_SCALEconst that is applied to all constants just for the fun of it so it is a little smaller that the original
// contains an array of tables.
var tableArray = [];
// App constants all up top
const GLOBAL_SCALE = 0.7;
const SHOW_HELP = true; // set to false to have the help turned off
const SHADOW = 'rgba(0,0,0,0.8)';
const WHITE = "white";
const TABLE_REFRESH_DELAY = 50; // Time in millisecond befor updating DOM for table add and remove
const FONT = {
face : "px Arial",
size : Math.max(10,18 * GLOBAL_SCALE),
fill : WHITE,
};
const TABLE = {
width : 223 * GLOBAL_SCALE, // size of table
height : 314 * GLOBAL_SCALE,
tables : document.getElementById("tables"),
image : { // table image styles
shadow : SHADOW,
shadowBlur : 20 * GLOBAL_SCALE,
fill : "#2e3f73",
lines : WHITE,
font : FONT,
cursor : "default",
},
empty : { // empty table styles
inset : 30 * GLOBAL_SCALE, // amount box is inset
lines : 'rgba(255,255,255,0.5)',
lineWidth : 8 * GLOBAL_SCALE,
shadow : SHADOW,
shadowBlur : 20 * GLOBAL_SCALE,
font : FONT,
cursor : "pointer",
highlightAmount : 0.3, // amount to highlight empty table when mouse over 0 none 1 full
},
arrow : { // arrow styles
width : 15 * GLOBAL_SCALE, // arrow width
shadow : SHADOW,
shadowBlur : 10 * GLOBAL_SCALE,
// custom cursor
cursor : "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADcAAAAVCAYAAADiv3Z7AAACtUlEQVRYha2Ye1NTMRDFf/KqLY/SlvIolAJFqSKOoqI4o+P3/1LXP3oi23XvTUrpzJm52Zxs9mRzczeFl/u9KkDp2FV5xb/IURT42hIoGbcsp1hMGrxegw1hU9gCWsDrDFriWrTc2FYDL/K1qZiS2KywdRO0DcAiTdIBtoEdYFfYC7Arzo7426a96+B53n/idBTDlha7VmAStiGyD9wjiegCPWAAHABDg0NhqL6B0BcGZpyF5fQM98D52lcc7SaBXlhbArrGeRTQEDgCKoNzwdsq4Aw4BUZqn8qWeGOhMryRGWt9jrRwfSMwbdF/72F6SFuxLXLPORppwspMPgYq+0v93mb63rh2OFbPU9sO5hlrcXtKRstnz2atxXwb9oCqEhYmWrRdR4F6mxdQIzqyfWjwOQOutMgHzF+RlL0FcTZrXaV77MU4YZ+EXIC/InuTuIL+B809Ay6UvX3mZ4TdmgviOiIdARPgxooywr4BX4TGbRkFvoo40/4MvHPi/HtXK+5CKxOJ+y6BXwsC/ePav18gcz+AeyeuS2ZbenFN2zJlLxuUa4fbtIkTtFfOnBfzENjum8TZbWQ4j5nt9jPgPEY+KXzn1ng6UPaYnz7p+1JphW7R6WVsM6FS/x1Ph41d5dT+KB+3at/JVrn+9/Jf6fnWjEm4ofC0jD4Fhxo4kZOpwxVwaTBl/g17q4lnDjfqnxp/17IlXMt+qYxc6NnyLafoO1f3ER8Cxyx+xG3lcKL+E9N/pknPtTATPY/VNwr8nbFYvRw7nDj+qWzZCsVnr6T8snVfj//rv1SaRbVlPxhj0Wf+/lhE3MTL1paRwFzh7Kv2qMqPbgUR3/vsOET+l7oVWIElV56Su9ky9znvczPjf+n7nBUZ3YKjW/FzbuO5MXV/U6x8E68TmrP5vuf+h1IaT5b7F+ZaSjBzrT+rAAAAAElFTkSuQmCC') 10 11, pointer",
fill : "#ffb900",
highlight : "#ffdc44",
lineWidth : 1,
line : "#ffdc44",
lineHigh : "#ffed55",
head : 30 * GLOBAL_SCALE, // arrow head width
minSize : 5, // min size arrow can be if smaller then arrow is not created
},
DOM : { // variouse dom setting for table canvas and div tags
display : "inline-block",
canvasClass : "table",
zIndex : 1,
},
closeIcon : { // styles for reandering and display close icon
size : 32 * GLOBAL_SCALE,
fill : "red",
lines : WHITE,
lineWidth : Math.max(1,2 * GLOBAL_SCALE),
shadow : SHADOW,
shadowBlur : 20 * GLOBAL_SCALE,
cursor : "pointer",
pos : {
x : 1, // as fractions
y : 0,
}
},
help : { // text help
empty : "Click here to|add a new table".split("|"),
active : "Click to drag arrows".split("|"),
activeArrow : "Right click on arrow|to remove it".split("|"),
closeTable : "To close table|move to top right|click Close Icon".split("|"),
}
}
const MOUSE = { // event contains a list of mouse event to listen to
buttonMasks : [1, 2, 4, 6, 5, 3],
events : "mousemove,mousedown,mouseup,mouseout,mouseover,contextmenu".split(","),
}; // contextmenu is included as that needs to be blocked for right button events
var helpItemsUsed = {
empty : false,
active : false,
activeArrow : false,
closeTable : false,
};
const turnOffHelp = function(){
helpItemsUsed.empty = true;
helpItemsUsed.active = true;
helpItemsUsed.activeArrow = true;
helpItemsUsed.closeTable = true;
};
if(!SHOW_HELP){turnOffHelp();};
// returns distance of point p to line segment x, y,xx,yy
const distFromLine = function(px,py,x,y,xx,yy){
var vx,vy,pvx,pvy,lx,ly,u;
vx = xx - x;
vy = yy - y;
pvx = px - x;
pvy = py - y;
u = (pvx * vx + pvy * vy)/(vy * vy + vx * vx);
if(u >= 0 && u <= 1){
lx = vx * u;
ly = vy * u;
return Math.sqrt(Math.pow(ly - pvy,2) + Math.pow(lx - pvx,2));
}
// closest point past ends of line so get dist to closest end
return Math.min(
Math.sqrt(Math.pow(xx - px,2)+ Math.pow(yy - py,2)),
Math.sqrt(Math.pow(x - px,2)+ Math.pow(y - py,2))
);
}
// set up functions create images and do other general setup
function setupContext(ctx,descript){ // sets common context settings
ctx.shadowBlur = descript.shadowBlur;
ctx.shadowColor = descript.shadow;
ctx.strokeStyle = descript.lines;
ctx.fillStyle = descript.fill;
ctx.lineWidth = descript.lineWidth;
ctx.lineCap = "round";
if(descript.font){
ctx.font = descript.font.size + descript.font.face;
}
}
function createTableImage() { // create image of table but why write a comment when the function tells it all???
var table = document.createElement("canvas");
table.width = TABLE.width;
table.height= TABLE.height;
var ctx = table.getContext("2d");
setupContext(ctx,TABLE.image);
var scaleX = table.width / 223; /// get the scale compared to original layout
var scaleY = table.height / 314; /// get the scale compared to original layout
ctx.fillStyle = TABLE.image.fill;
ctx.fillRect(35.25 * scaleX, 20 * scaleY, 152.5 * scaleX, 274 * scaleY);
ctx.fillStyle = TABLE.image.lines; // lines
ctx.fillRect(111.35 * scaleX, 20 * scaleY, 0.3, 274 * scaleY); // middle line
ctx.fillRect(35.25 * scaleX, 20 * scaleY, 2, 274 * scaleY); // lift side
ctx.fillRect(185.75 * scaleX, 20 * scaleY, 2, 274 * scaleY); // right side
ctx.fillRect(35.25 * scaleX, 20 * scaleY, 152.5 * scaleX, 2); // top base line
ctx.fillRect(35.25 * scaleX, 292 * scaleY, 152.5 * scaleX, 2); // bottom base line
ctx.fillRect(20 * scaleX, 156 * scaleY, 183 * scaleX, 2); // net
return table
}
function createEmptyImage() { // empty table image
var i = TABLE.empty.inset;
var image = document.createElement("canvas");
var w = image.width = TABLE.width;
var h = image.height = TABLE.height;
var ctx = image.getContext("2d");
setupContext(ctx,TABLE.empty);
ctx.strokeRect(i, i, w - i * 2, h - i * 2);
ctx.beginPath();
ctx.moveTo(i * 2, i * 2);
ctx.lineTo(w - i * 2, h - i * 2);
ctx.moveTo(i * 2, h - i * 2);
ctx.lineTo(w - i * 2, i * 2);
ctx.stroke();
return image
}
function createCloseImage() { // create close icon
var S = TABLE.closeIcon.size;
var s = S * 0.5;
var c = s * 0.4; // cross dist from center
var sb = TABLE.closeIcon.shadowBlur;
var l = TABLE.closeIcon.lineWidth;
var image = document.createElement("canvas");
// Image must include shadowblur
image.width = S+sb; // add blur to size
image.height= S+sb;
var ctx = image.getContext("2d");
setupContext(ctx,TABLE.closeIcon);
ctx.beginPath();
var cx = s + sb / 2; // add half blur to get center
var cy = s + sb / 2;
ctx.arc(cx, cy, s - l, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx - c, cy - c)
ctx.lineTo(cx + c, cy + c)
ctx.moveTo(cx - c, cy + c)
ctx.lineTo(cx + c, cy - c)
ctx.stroke();
return image
}
// create the images
var tableImage = createTableImage();
var closeIcon = createCloseImage();
var emptyTableImage = createEmptyImage();
// draws a arrow a is the arrow object
function drawArrow(ctx,a){
var s = TABLE.arrow; // get arrow style
var vx,vy;
var x,y;
x = a.x;
y = a.y;
vx = a.xx-x;
vy = a.yy-y;
var dir = Math.atan2(vy,vx);
var len = Math.sqrt(vx * vx + vy * vy);
// ctx.save();
ctx.setTransform(1,0,0,1,x,y);
ctx.rotate(dir);
var w = s.width/2;
var h = Math.min(len,s.head); // ensure arrow head no bigger than arrow length
h /=2;
if(a.highlight){
ctx.fillStyle = s.highlight;
ctx.strokeStyle = s.lineHigh;
}else{
ctx.fillStyle = s.fill;
ctx.strokeStyle = s.line;
}
ctx.lineWidth = s.lineWidth;
ctx.save();
ctx.shadowBlur = s.shadowBlur;
ctx.shadowColor = s.shadow;
ctx.beginPath();
ctx.moveTo(0,-w/2);
ctx.lineTo(len-h-h,-w);
ctx.lineTo(len-h-h,-h);
ctx.lineTo(len,0);
ctx.lineTo(len-h-h,h);
ctx.lineTo(len-h-h,w);
ctx.lineTo(0,w/2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
}
// display help text for table
function drawHelpText(ctx,text,style){
ctx.font = style.font.size + style.font.face;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var i,len;
len = text.length;
var y = ctx.canvas.height / 2 - len * style.font.size * 1.2;
var yy = y + 1;
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
for(i = 0; i < len; i++){
ctx.strokeText(text[i], ctx.canvas.width / 2 + 1, yy);
yy += TABLE.empty.font.size * 1.2;
}
ctx.fillStyle = style.font.fill;
for(i = 0; i < len; i++){
ctx.fillText(text[i], ctx.canvas.width / 2, y);
y += TABLE.empty.font.size * 1.2;
}
}
//------------------------------------------------------------
// functions for table
function drawClose(){ // draws close icon. Fades in the close mouse is
var ctx = this.ctx;
var w = closeIcon.width;
var grow = w * 0.1;
var x = (this.width - w) * TABLE.closeIcon.pos.x ;
var y = (this.height - w) * TABLE.closeIcon.pos.y ;
x += w/2; // get icon center
y += w/2;
var dist = Math.sqrt(Math.pow(this.mouse.x - x, 2) + Math.pow(this.mouse.y - y, 2));
if(dist < TABLE.closeIcon.size / 2){
this.mouseOverClose = true;
}else{
this.mouseOverClose = false;
}
x -= w/2; // back to icon top left
y -= w/2;
ctx.globalAlpha = 1-(Math.min(100,(dist - w * 2)) / 100);
if(this.mouseOverClose){
ctx.drawImage(closeIcon,x-grow,y-grow,w + grow * 2,w + grow * 2);
}else{
ctx.drawImage(closeIcon,x,y);
}
ctx.globalAlpha = 1;
}
function drawEmpty(){ // draw empty table and handle click on empty table
var ctx = this.ctx;
ctx.drawImage(emptyTableImage,0,0);
if(this.mouse.over){
ctx.globalCompositeOperation = "lighter";
ctx.globalAlpha = TABLE.empty.highlightAmount;
ctx.drawImage(emptyTableImage,0,0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
if(!helpItemsUsed.empty){ // show help is the help action has not yet been done
drawHelpText(ctx,TABLE.help.empty,TABLE.empty);
}
this.cursor = TABLE.empty.cursor;
if(this.mouse.button & 1){ // bit field
this.buttonDown = true;
}else if( this.buttonDown){
this.active = true;
setTimeout(addTable,TABLE_REFRESH_DELAY);
this.buttonDown = false;
helpItemsUsed.empty = true; // flag this help as not needed as user has complete that task
}
}else{
this.cursor = "default";
}
}
function drawTable(){ // darw the table all states
var ctx = this.ctx;
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
if(this.active){
ctx.drawImage(tableImage,0,0);
if(this.mouse.over){
if(!this.dragging){ // Dont draw close icon while draggin
this.drawCloseIcon();
}
if(this.mouseOverClose && ! this.dragging){ // if not dragging and mouse over close
this.cursor = TABLE.closeIcon.cursor; // set cursor
if(this.mouse.button & 1){ // bit field is mouse left down
this.buttonDown = true;
}else if(this.buttonDown){ // only close if mouse moves up while over close.
this.active = false;
helpItemsUsed.closeTable = true;
this.buttonDown = false;
setTimeout(updateTables,TABLE_REFRESH_DELAY);
}
}else{ // not over close
// if near a arrow and mouse button right is down delete the arrow
if(this.closestArrowIndex > -1 && (this.mouse.button & 4) === 4){ // but field Only button right down
this.arrows.splice(this.closestArrowIndex,1);
this.closestArrowIndex = -1;
this.mouse.button = 0; // turn mouse click off
helpItemsUsed.activeArrow = true; // flag arrow delete help as used
}else // if not near line or close then check for mouse left
if(this.mouse.button & 1){ // bit field if down start dragging new arroe
if(!this.dragging){ // Start of drag create arrow
this.arrows.push({
x: this.mouse.x,
y: this.mouse.y,
xx : this.mouse.x,
yy : this.mouse.y,
});
this.currentArrow = this.arrows[this.arrows.length-1];
this.dragging = true;
}else{ // during drag move arrow endpoint
helpItemsUsed.active = true; // flag arrow help as used
this.currentArrow.xx = this.mouse.x;
this.currentArrow.yy = this.mouse.y;
}
}else{ // mouse up
if(this.dragging){ // is dragging then must be a arrow
// if arrow added is smaller than 2 pixels then remove it;
if(Math.abs(this.currentArrow.xx-this.currentArrow.x) < TABLE.arrow.minSize && Math.abs(this.currentArrow.y-this.currentArrow.yy) < TABLE.arrow.minSize){
this.arrows.length -= 1;
}
this.currentArrow = null;
this.dragging = false;
}
}
this.cursor = TABLE.image.cursor; // set cursor tp table standard
}
}
if(this.closestArrowIndex > -1 && ! this.dragging){ // is mouse near arrow
this.cursor = TABLE.arrow.cursor; // yes set cursor for arrow
}
// find arrow closest to mouse
var minDist = TABLE.arrow.width; // this sets the max distance mouse can be for it to highlight an arrow
var dist = 0;
this.closestArrowIndex = -1;
for(var i = 0; i < this.arrows.length; i++){ // test all arrow
var a = this.arrows[i];
drawArrow(ctx,a); // draw the arrow
a.highlight = false; // turn off highlight
dist = distFromLine(this.mouse.x,this.mouse.y,a.x,a.y,a.xx,a.yy); // get distance from mouse
if(dist < minDist){ // is closer than any other arrow
this.closestArrowIndex = i; // yes remember the index
minDist = dist;
}
}
if(this.closestArrowIndex > -1 && this.mouse.over){ // is a arror close to mouse
this.arrows[this.closestArrowIndex].highlight = true; // highlight it
}
ctx.setTransform(1,0,0,1,0,0); // reset transform after arrows drawn
// show help
if(this.mouse.over){
if(this.arrows.length === 0 && !helpItemsUsed.active){
drawHelpText(ctx,TABLE.help.active,TABLE.image);
}else
if(this.closestArrowIndex > -1 && !helpItemsUsed.activeArrow){
drawHelpText(ctx,TABLE.help.activeArrow,TABLE.image);
}else
if(this.closestArrowIndex === -1 && !helpItemsUsed.closeTable){
drawHelpText(ctx,TABLE.help.closeTable,TABLE.image);
}
}
}else{
this.drawEmpty();
}
}
// renders a table. Stops rendering if the mouse is not over
function tableUpdate(){
if(this.mouse.over){
this.updating = true;
requestAnimationFrame(this.update);
}else{
this.buttonDown = false; // turn of button if dragged off
this.div.style.cursor = "default";
this.updating = false;
this.draw(); // draw another time. This alows for the visual state to be correct
}
this.draw();
this.div.style.cursor = this.cursor;
}
// Mousecallback starts a table rendering if not allready doing so.
function mouseInOutCallback(){
if(this.mouse.over){
if(!this.updating){
this.update();
}
}else{
this.div.style.cursor = "default";
}
}
// function to handle mouse events
function mouseEvent(e) {
var m =this; // lazy programer short cut
var t = e.type;
var bounds = m.element.getBoundingClientRect();
m.x = e.clientX - bounds.left;
m.y = e.clientY - bounds.top;
if (t === "mousedown") {
m.button |= MOUSE.buttonMasks[e.which-1];
} else if (t === "mouseup") {
m.button &= MOUSE.buttonMasks[e.which + 2];
} else if (t === "mouseout") {
m.button = 0;
m.over = false;
m.table.mouseOver();
} else if (t === "mouseover") {
m.over = true;
m.table.mouseOver();
}
e.preventDefault();
}
// create the mouse inteface for a table
function createMouse(table){
var mouse = {
x : 0,
y : 0,
over : false,
table : table,
element : table.div,
button : 0,
};
mouse.event = mouseEvent.bind(mouse);
mouse.start = function(){
MOUSE.events.forEach( n => { this.element.addEventListener(n, this.event); } );
}
mouse.remove = function(){
MOUSE.events.forEach( n => { this.element.removeEventListener(n, this.event); } );
}
return mouse;
}
function createAddTable(){ // Creates a table. Tables default in inactive
var table = {};
var div = document.createElement("div");
div.style.width = TABLE.width+ "px";
div.style.height = TABLE.height + "px";
div.style.display = TABLE.DOM.display;
var canvas = document.createElement("canvas");
canvas.width = TABLE.width;
canvas.height = TABLE.height;
canvas.className = TABLE.DOM.tableClass
canvas.style.zIndex = TABLE.DOM.zIndex;
var ctx = canvas.getContext("2d");
table.div = div;
table.canvas = canvas;
table.ctx = ctx;
table.arrows = [];
table.width = TABLE.width;
table.height = TABLE.height;
table.mouseOverClose = false
table.drawCloseIcon = drawClose;
table.draw = drawTable;
table.dragging = false;
table.active = false;
table.update = tableUpdate.bind(table);
table.mouseOver = mouseInOutCallback; // called by mouseEvent when mouse over out
table.drawEmpty = drawEmpty.bind(table);
table.dead = false; // when removed and not needed it is dead and can then be removed from table array
table.updating = false; // true is animation requests are happening
div.appendChild(canvas); // add canvas
table.mouse = createMouse(table);
table.draw();
return table;
}
function removeTable(table){ // remove table from dom
table.mouse.remove(); // deactivate moue events
TABLE.tables.removeChild(table.div); // remove from DOM
table.dead = true;// flag as dead to be removed from table array
}
function addTable(){ // Adds a table to table array and DOM
var table = createAddTable(); // create new table
TABLE.tables.appendChild(table.div); // add to the dom
table.mouse.start(); // start the mouse
tableArray.push(table); // add to table array
return table;
}
function updateTables(){ // Updates tables. Removes any dead tables from table array
var closeTables = [];
closeTables = tableArray.filter(t => !t.active);
while(closeTables.length > 1){
removeTable(closeTables.shift());
}
for(var i = 0; i < tableArray.length; i ++){
if(tableArray[i].dead){
tableArray.splice(i,1);
i -= 1;
}
}
}
addTable();
body {
background-color: #982439;
}
#table {
padding: 10px;
}
canvas {
position: absolute;
}
<div id="tables">
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With