/*global $: false, EditViewer: false, Overlay: false, HTMLElement: false, Rectangle: false, Point: false, HTMLTableCellElement: false, HTMLTableElement: false */
/*jslint white: true, browser: true, plusplus: true */
$(function() {
"use strict";
// Specify the table and the overlay div.
var viewer = new EditViewer($('#main-table'), $('.overlay'));
});
function Point(x, y) {
"use strict";
this.x = x || 0;
this.y = y || 0;
}
function Rectangle(left, top, right, bottom) {
"use strict";
this.left = left || 0;
this.top = top || 0;
this.right = right || 0;
this.bottom = bottom || 0;
}
(function() {
"use strict";
// Determine whether point locates within the rectangle.
Rectangle.prototype.contain = function(point) {
return (point.x >= this.left && point.x < this.right && point.y >= this.top && point.y < this.bottom);
};
// Get the width of the rectangle.
Rectangle.prototype.width = function() {
return this.right - this.left;
};
// Get the height of the rectangle.
Rectangle.prototype.height = function() {
return this.bottom - this.top;
};
Rectangle.prototype.leftOf = function(rect) {
return this.left <= rect.left;
};
Rectangle.prototype.rightOf = function(rect) {
return this.right >= rect.right;
};
Rectangle.prototype.topOf = function(rect) {
return this.top <= rect.top;
};
Rectangle.prototype.bottomOf = function(rect) {
return this.bottom >= rect.bottom;
};
// Merge two rectangle areas into a large area, which contains both rectangles.
Rectangle.prototype.merge = function(rect) {
var left = this.left,
top = this.top,
right = this.right,
bottom = this.bottom;
if (!rect) {
return new Rectangle(left, top, right, bottom);
}
if (rect.leftOf(this)) {
left = rect.left;
}
if (rect.rightOf(this)) {
right = rect.right;
}
if (rect.topOf(this)) {
top = rect.top;
}
if (rect.bottomOf(this)) {
bottom = rect.bottom;
}
return new Rectangle(left, top, right, bottom);
};
}());
function Overlay(elem, viewer) {
"use strict";
// overlay div
this.overlay = elem;
// table element
this.viewer = viewer;
this.init();
}
(function() {
"use strict";
// The starting rectangle area.
var start = null;
Overlay.prototype.init = function() {
var viewer = this.viewer;
// Forward click event to the viewer.
this.overlay.on('click', function(event) {
viewer.forwardEvent(event);
});
// Forward mousemove event to the viewer.
this.overlay.on('mousemove', function(event) {
viewer.forwardEvent(event);
});
};
// Draw overlay at the specified rectangle area.
Overlay.prototype.draw = function(rect) {
if ( !! rect) {
this.overlay.removeClass('hidden');
this.layout(rect);
start = rect;
}
};
// Move and resize overlay.
Overlay.prototype.layout = function(rect) {
if ( !! rect) {
if ( !! start) {
rect = start.merge(rect);
}
this.overlay.css('left', rect.left);
this.overlay.css('top', rect.top);
this.overlay.css('width', rect.width());
this.overlay.css('height', rect.height());
}
};
// Hide overlay.
Overlay.prototype.clear = function() {
start = null;
this.overlay.addClass('hidden');
};
}());
function EditViewer(viewer, overlay) {
"use strict";
// table elment
this.viewer = viewer;
// overlay object
this.overlay = new Overlay(overlay, this);
// Listen to click event and draw overlay.
this.init();
}
(function() {
"use strict";
var // First click starts the overlay drawing, until the second click ends the overlay drawing.
started = false,
// Get the rectangle area of the element.
getElemRect = function(elem) {
var left, top, right, bottom;
if (elem instanceof HTMLElement) {
left = elem.offsetLeft;
top = elem.offsetTop;
right = left + elem.offsetWidth;
bottom = top + elem.offsetHeight;
return new Rectangle(left, top, right, bottom);
}
return null;
},
// Determin whether a point locates within the rectangle area of the element.
locateWithin = function(elem, point) {
var rect = getElemRect(elem);
if (!rect) {
return false;
}
return rect.contain(point);
},
// Start drawing overlay.
startDrawing = function(self, event) {
var rect = self.cellUnderMouse(event),
viewer = self.viewer,
overlay = self.overlay;
if ( !! rect) {
started = true;
overlay.clear();
overlay.draw(rect);
viewer.on('mouseover', function(event) {
rect = self.cellUnderMouse(event);
overlay.layout(rect);
});
}
},
// End drawing overlay.
endDrawing = function(self, event) {
var viewer = self.viewer;
viewer.off('mouseover');
started = false;
};
// Register the click event handler.
EditViewer.prototype.init = function() {
var viewer = this.viewer,
self = this;
viewer.on('click', function(event) {
if (!started) {
startDrawing(self, event);
} else {
endDrawing(self, event);
}
});
};
// Dispatch mouse event on the viewer.
EditViewer.prototype.forwardEvent = function(event) {
var viewer = this.viewer.get()[0],
mouseevent, type = (event.type === "click") ? "click" : "mouseover";
if (document.createEvent) {
mouseevent = document.createEvent('MouseEvent');
mouseevent.initMouseEvent(type, event.bubbles, event.cancelable, window, 0, event.screenX, event.screenY, event.clientX, event.clientY, event.ctrlKey, event.altKey, event.shiftKey, event.metaKey, event.button, null);
viewer.dispatchEvent(mouseevent);
}
};
// Get the HD cell under mouse.
EditViewer.prototype.cellUnderMouse = function(event) {
var elem = event.target || event.srcElement,
rect = null,
point = new Point(),
tagName = "",
children, child, i;
if (elem instanceof HTMLTableCellElement) {
// A TH/TD is clicked
tagName = elem.tagName;
if (tagName.toLowerCase() === "td") {
// A TD is clicked
rect = getElemRect(elem);
}
} else if (elem instanceof HTMLTableElement) {
// Border is clicked
elem = event.target;
point.x = event.offsetX;
point.y = event.offsetY;
children = $(elem).find('td');
for (i = 0; i < children.length; i++) {
child = children[i];
if ((child instanceof HTMLTableCellElement) && locateWithin(child, point)) {
rect = getElemRect(child);
break;
}
}
}
return rect;
};
}());
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div class="viewer">
<table id="main-table">
<thead>
<tr>
<th></th>
<th scope="col">A</th>
<th scope="col">B</th>
<th scope="col">C</th>
<th scope="col">D</th>
<th scope="col">E</th>
<th scope="col">F</th>
<th scope="col">G</th>
<th scope="col">H</th>
<th scope="col">I</th>
<th scope="col">J</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">2</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">3</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">4</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">5</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">6</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">7</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">8</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">9</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th scope="row">10</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<div class="overlay hidden"></div>
</div>
</body>
</html>
body {
font: 13px arial, sans-serif;
}
table {
border-collapse: collapse;
border-spacing: 0;
color: #222;
font-size: 8pt;
}
th {
font-weight: normal;
background-color: #eee;
border: 1px solid #ccc;
text-align: center;
vertical-align: middle;
}
thead th {
height: 23px;
}
tbody th {
height: 16px;
width: 45px;
}
tbody td {
height: 16px;
width: 120px;
overflow: hidden;
padding: 0 3px;
border-width: 0 1px 1px 0;
border: 1px solid #ccc;
}
.viewer {
position: relative;
}
.overlay {
background-color: rgba(82, 146, 247, .1);
position: absolute;
z-index: 10;
}
.hidden {
display: none;
}