Introduction
The purpose of this post is to present a JavaScript-based tool (utility, add-on) named JSGrid. The tool overlays the HTML page (when opened by a browser) with a grid.
The grid is basically an HTML table with a varying number of rows and columns (i.e., the cell (<td>) width and height is controlled by the user).
The grid permits easier visualization of positioning coordinates and sizes of block elements. Thus, the tool can be useful for web designers, especially in
situations where fixed-layout or CSS positioning is employed. Furthermore, the source code (and the explanation given here) can serve as
a good example of using JavaScript for DOM and CSS manipulation.
The tool can be run from here.
How to Use the Script
To use the script, add the following line to the <head> section of your HTML page:
<script src="grid.js" ></script>
Ensure that the src attribute points to an accessible location of the file "grid.js".
When the page loads in the browser, the grid (and its control form) will automatically appear, as in the figure shown above.
How the Script Works
One approach to implement a grid is to use an HTML div for each cell. However, I have encountered few problems and decided to use an HTML table instead.
The following listing shows the beginning part of the source code of my script.
var $gridStyle = "div#gridForm { z-index:21; color:black;padding:2px;margin:0;font-size:11pt;font-family:Tahoma;position:fixed; right:0; top:0; " +
"background:lightgray; border:1px solid gray; border-top:0;border-right:0;width:90px; height:90px;} " +
"div#gridForm input {font-size: 10pt; margin:3px; color:black;} " +
"div#gridForm input[type=text] { width:30px; padding-left:2px; } " +
"div#gridForm input[type=button] { padding:6px; margin:8px;border:1px solid gray } " +
"div#grid { z-index:20; font-size:3pt; position:absolute; left:-1px; top:-1px; background:transparent; padding:0;margin:0; } " +
"div#grid table { font-size:0.8em; border-collapse: collapse; border-spacing:0; padding:0;margin:0; table-layout:fixed; } " +
"div#grid table td { border:1px solid black; margin:0; padding:0; -moz-box-sizing:border-box; box-sizing:border-box;}" ;
var $gridFormHTML = "Height <input type='text' id='cellHeight' value='200' />" +
"Width <input type='text' id='cellWidth' value='100' />" +
"<input type='button' onclick='createGrid()' value='Set Grid' />"
function createGrid()
{
var gridForm = document.getElementById("gridForm");
if (gridForm ==null)
{ var formdiv = document.createElement("div");
formdiv.id = 'gridForm';
formdiv.innerHTML = $gridFormHTML;
var docbody = document.body;
docbody.insertBefore(formdiv, docbody.firstChild);
var griddiv = document.createElement("div");
griddiv.id = 'grid';
griddiv.innerHTML = "<table id='gridTable' ></table>";
docbody.insertBefore(griddiv, docbody.firstChild);
var gridst = document.createElement("style");
gridst.id = 'gridstyle';
gridst.innerHTML = $gridStyle;
var gridExt = document.createElement("style");
gridExt.id = 'gridExtra';
document.head.appendChild(gridExt);
document.head.appendChild(gridst);
}
var cellWidth = parseInt(document.getElementById("cellWidth").value);
var cellHeight = parseInt(document.getElementById("cellHeight").value);
var griddiv = document.getElementById("grid");
if ((cellWidth==0) && (cellHeight==0))
{ griddiv.style.display = "none"; setCanvas(); return; }
griddiv.style.display ="block";
griddiv.style.width = window.innerWidth + "px";
if (cellWidth== 0) cellWidth = window.innerWidth;
if (cellHeight==0) cellHeight = window.innerHeight;
var cols= Math.floor(window.innerWidth/cellWidth);
var rows= Math.floor(window.innerHeight/cellHeight);
var lastcellWidth = window.innerWidth - cols*cellWidth;
var lastcellHeight = 0;
if (cellWidth*cols < window.innerWidth) cols++;
if (cellHeight*rows < window.innerHeight)
{ lastcellHeight = window.innerHeight - rows*cellHeight;
rows++;
}
var tbody = document.getElementById("gridTable");
tbody.innerHTML = "";
for(var i=0; i < rows; i++)
{ var row = document.createElement("tr");
for(var j=0; j < cols; j++)
{ var cell= document.createElement("td");
if ((cols >1) && (j== cols-1)) cell.setAttribute("style","width:" + lastcellWidth + "px");
row.appendChild(cell);
}
tbody.appendChild(row);
}
var lastRowStyle = "";
if (lastcellHeight != 0)
{ lastRowStyle = "#grid table tr:last-child td { height:"+ lastcellHeight + "px; }"; }
document.getElementById("gridExtra").innerHTML= "#grid table td { width:" + cellWidth +"px; height:"+ cellHeight + "px; } " + lastRowStyle ;
setCanvas();
}
In accordance with the preceding code, the grid is an HTML table with empty cells (<td>
s). The table is enclosed within a div with
id="grid"
.
The input text-boxes and "Set Grid" button are part of a div with id="gridForm"
. This latter div uses fixed positioning to anchor it to the top-right corner of the browser window.
The code consists primarily of the function CreateGrid()
followed by the line:
window.addEventListener("load", createGrid, false);
Thus, the function CreateGrid()
is executed when the page finishes loading. The function relies on two DIV elements (which are created by the function):
- <div id="gridForm" >The HTML form goes here</div>
- <div id="grid" >The HTML table for the grid goes here</div>
To create the "gridForm
" div, I use the code (this code runs once, only if "gridforom
" does not exist):
var formdiv = document.createElement("div");
formdiv.id = 'gridForm';
formdiv.innerHTML = $gridFormHTML;
var docbody = document.body;
docbody.insertBefore(formdiv, docbody.firstChild);
To create the "grid" div, we use the following code:
var griddiv = document.createElement("div");
griddiv.id = 'grid';
griddiv.innerHTML = "<table id='gridTable' ></table>";
docbody.insertBefore(griddiv, docbody.firstChild);
The two divs have some associated CSS styles using their ID attributes as selectors.
We use two style sections (created in code), each with its own id
as follows:
- One style section with
id="gridstyle"
is for use by the grid and the form divisions (this is only done during initialization). - The other style section with
id="gridExtra"
(created at initialization and filled later) is used to set the cell width and height.
Note that both the grid and gridForm divisions are assigned high z-index values (20 for grid and 21 for gridForm) to have them appear atop of any other content.
To prevent the page content from being hidden, the grid is assigned a transparent background color.
Important Observations
I have noticed that browsers often do not honor the assigned cell width.
To get the cell width working, I had to add table-layout:fixed
to <table> styles and box-sizing:border-box;
(or -moz-box-sizing:border-box
for FireFox) to <td> styles. Also, to prevent scroll bars from unnecessarily showing, I had to set the position of "grid" div to left:-1px; top:-1px;
.
Tracking Mouse Clicks
It is desirable to get the (x,y)-coordinates of locations (on the web page) chosen by the user; specifically, identify two successively clicked locations and draw a line between them. This is now supported through the setCanvas()
function:
var lastX = -1;
var lastY;
function setCanvas()
{
lastX = -1;
var canvas = document.getElementById("myCanvas");
if (!canvas)
{ canvas = document.createElement("canvas");
canvas.id = 'myCanvas';
canvas.setAttribute("style","position:absolute; left:0; top:0;");
document.body.insertBefore(canvas, document.body.firstChild);
canvas.parentNode.addEventListener("click", handleClick, false);
}
canvas.setAttribute("width", window.innerWidth);
canvas.setAttribute("height", window.innerHeight);
}
function handleClick()
{ oEvent = arguments[0];
var tagName =oEvent.target.tagName;
if (!((tagName=="TD") || (tagName=="CANVAS"))) return;
if (lastX==-1)
{ lastX = oEvent.pageX;
lastY = oEvent.pageY;
return;
}
var canvas = document.getElementById('myCanvas');
var ctx=canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle="#FF0000";
ctx.lineWidth=1;
ctx.lineStyle="#ffff00";
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(oEvent.pageX,oEvent.pageY);
ctx.stroke();
ctx.fillStyle="#CC00FF";
ctx.font="14px sans-serif";
ctx.fillText(lastX + "," + lastY , lastX-20, lastY+20);
ctx.fillText(oEvent.pageX + "," + oEvent.pageY, oEvent.pageX-20, oEvent.pageY+20);
lastX = oEvent.pageX;
lastY = oEvent.pageY;
}
The setCanvas()
function overlays a canvas over the entire page. The handleClick()
function traps mouse click events on the canvas and draws a line between the last two successively clicked locations.
Limitations, suggestions
The tool (version 1.2) now works perfectly in latest versions of IE (version 11), Chrome (version 30), and Firefox
(version 24 for MS Windows OS). In earlier versions I was using the innerText
property for setting style elements. This does not work in FireFox. It turns out that using the innerHTML
property for setting style elements works cross browser.
The current version does not handle the resize event because the window.innerWidth
and window.innerHeight
continuously change during resizing and cause createGrid()
to malfunction.
As a workaround, if the browser window is resized, the "Set Grid" button can be clicked to redraw the "correct" grid.
Vertical [horizontal] grid lines can be removed by setting the cell width [height] to 0.
If both the cell width and height are set to 0, then the grid is not displayed
(in the code, the display
property is set to "none
").
This allows for inspecting page elements (using the browser developer tool) that
would otherwise be covered by the grid.
Certainly it is more efficient to have the grid implemented directly by the browser.
For this, it is suggested that a CSS grid property be introduced for block elements.
This can use syntax similar to the border property. As an example, the CSS rule
"grid: 10px 20px black;"
creates a grid with cell width of 10px, cell height of 20px and black color for the grid lines.
Alternatively, the example can be specified using expanded CSS syntax: "grid-cellwidth:10px;grid-cellheight:20px;grid-linecolor:black;"
.
History
- 26thOctober, 2013: Version 1.0.
- 2nd November, 2013: Version 1.1. Added a function setCanvas() to setup canvas and handle click events.
- 4th November, 2013: Version 1.2. Enhanced the script to work in FireFox.