var GRID = {};
GRID.setup = function(root,data) {
var dom = DOM.byName(root);
var grid = {};
// Adjust size of container and layout top/left/center components.
grid.sizer = function(container) {
container = container || dom.container;
var ddx = 0, ddy = 0;
var o = { dx: container.offsetWidth, dy: container.offsetHeight };
var resize = function() {
container.style.width = (ddx + (0 | o.dx)) + "px";
container.style.height = (ddy + (0 | o.dy)) + "px";
var now = { dx: container.offsetWidth, dy: container.offsetHeight };
ddx += o.dx - now.dx;
ddy += o.dy - now.dy;
o.dx = now.dx;
o.dy = now.dy;
};
o.apply = function() {
resize();
grid.layout();
}
o.adjust = function() {
var want = grid.sizer(container);
resize();
o.dx = want.dx;
o.dy = want.dy;
resize();
}
return o;
};
var idResize = 0;
grid.align = function() {
// Reset any scroll of the headers.
dom.top.container.style.marginLeft = 0;
dom.left.container.style.marginTop = 0;
// Gather the elements we will touch most often.
var headersColumn = dom.top.strip.getElementsByTagName("div");
var samplesColumn = DOM.asArray(dom.center.cells.rows[0].getElementsByTagName("div"));
var headersRow = dom.left.strip.getElementsByTagName("div");
var samplesRow = DOM.asArray(dom.center.cells.rows).each(function(tr){
var divs = tr.cells[0].getElementsByTagName("div");
return divs[0];
});
// Tweak sizing to account for off-by-one in browsers.
// Firebox in particular makes cells one pixel taller, for no obvious reason.
// Assumes the
XX | structure.
var tweak = function() {
idResize = 0;
samplesColumn.apply(function(cell,i){
var head = headersColumn[i];
if (head) {
var dx = cell.parentNode.offsetWidth - head.parentNode.offsetWidth;
if (dx < 0) {
cell.style.width = (cell.offsetWidth - dx) + "px";
} else if (0 < dx) {
head.style.width = (head.offsetWidth + dx) + "px";
}
}
});
samplesRow.apply(function(cell,i){
var head = headersRow[i];
if (head) {
var dy = cell.parentNode.offsetHeight - head.parentNode.offsetHeight;
if (dy < 0) {
cell.style.height = (cell.offsetHeight - dy) + "px";
} else if (0 < dy) {
head.style.height = (head.offsetHeight + dy) + "px";
}
}
});
dom.wrap.style.visibility = "visible";
};
// Size table data and header cells to match.
// Will follow by a call to "tweak" to account for differences in browser layout.
var resize = function() {
idResize = 0;
samplesColumn.apply(function(cell,i){
var head = headersColumn[i];
if (head) {
var dxCell = cell.offsetWidth;
var dxHead = head.offsetWidth;
cell.style.width = head.style.width = Math.max(dxCell,dxHead) + "px";
}
});
samplesRow.apply(function(cell,i){
var head = headersRow[i];
if (head) {
var dyCell = cell.offsetHeight;
var dyHead = head.offsetHeight;
cell.style.height = head.style.height = Math.max(dyCell,dyHead) + "px";
}
});
// Give the browser a chance to do layout, then check for any errors.
idResize = window.setTimeout(tweak,1);
};
var dt = 200;
if (idResize) {
// Cancel pending resize to reduce flicker.
window.clearTimeout(idResize);
// Wait a bit longer as there is a good chance we want to cancel this resize also.
dt = 400;
} else {
// Unsize columns so table gets normal layout.
//grid.frame.style.visibility = "hidden";
samplesColumn.apply(function(cell,i){
var head = headersColumn[i];
if (head) {
cell.style.width = head.style.width = "";
}
});
samplesRow.apply(function(cell,i){
var head = headersRow[i];
if (head) {
cell.style.height = head.style.height = "";
}
});
}
// IE does not immediately redo the layout, so adjust later.
// The 200ms delay seems sufficient to kill flicker on resize in IE.
idResize = window.setTimeout(resize,dt);
};
// Arrange grid within the container so top and left strips align with center content.
grid.arrange = function() {
var dx = dom.left.container.offsetWidth;
var dy = dom.top.container.offsetHeight;
// Have to set the size (minus border) on the wrapper as Firefox has a bug.
// With .corner .wrap { height: 100% } the height is too tall (by the border width).
dom.corner.wrap.style.height = (dy - 1) + 'px';
dom.corner.wrap.style.width = (dx - 1) + 'px';
dom.left.container.style.top = (dy + 0) + 'px';
dom.top.container.style.left = (dx + 0) + 'px';
dom.center.container.style.top = (dy + 0) + 'px';
dom.center.container.style.left = (dx + 0) + 'px';
dx = dom.wrap.offsetWidth - dx;
dy = dom.wrap.offsetHeight - dy;
dom.top.container.style.width = (dx + 0) + 'px';
dom.left.container.style.height = (dy + 0) + 'px';
dom.center.container.style.width = (dx + 0) + 'px';
dom.center.container.style.height = (dy + 0) + 'px';
};
// Layout grid within the container and align rows and columns.
grid.layout = function() {
dom.wrap.style.visibility = "hidden";
dom.wrap.style.display = "block";
grid.arrange();
grid.align();
};
//
// Support for formatting table from data.
//
var clearTABLE = function(t) {
if (t) {
while (0 < t.rows.length) {
t.deleteRow(0);
}
}
};
var formatTop = function() {
var nHead = data.top.length;
if (nHead < 1) {
return;
}
var a = data.top[--nHead];
var n = a.length;
var TR = dom.top.strip.insertRow(0);
while (0 < n) {
var o = a[--n];
TR.insertCell(0).innerHTML = '' + DOM.encode(o.s) + '
';
}
while (0 < nHead) {
a = data.top[--nHead];
n = a.length;
TR = dom.top.strip.insertRow(0);
while (0 < n) {
var o = a[--n];
var TD = TR.insertCell(0);
TD.innerHTML = DOM.encode(o.s);
TD.colSpan = o.n;
TD.className = "head";
}
}
};
var formatLeft = function() {
var nHead = data.left.length;
if (nHead < 1) {
return;
}
var a = data.left[--nHead];
var n = a.length;
while (0 < n) {
var o = a[--n];
dom.left.strip.insertRow(0).insertCell(0).innerHTML = '' + DOM.encode(o.s) + '
';
}
var TR, TD, iRow;
while (0 < nHead) {
a = data.left[--nHead];
n = a.length;
iRow = dom.left.strip.rows.length;
while (0 < n) {
var o = a[--n];
iRow -= o.n;
TR = dom.left.strip.rows[iRow];
TD = TR.insertCell(0);
TD.innerHTML = DOM.encode(o.s);
TD.rowSpan = o.n;
TD.className = "head";
}
}
};
var formatCenter = function() {
var iRow = data.table.length;
var doc = dom.center.cells.ownerDocument;
var div = doc.createElement('div');
while (0 < iRow) {
var aRow = data.table[--iRow];
var TR = dom.center.cells.insertRow(0);
var iColumn = aRow.length;
while (0 < iColumn) {
var o = div.cloneNode(false);
o.appendChild(doc.createTextNode(aRow[--iColumn]));
TR.insertCell(0).appendChild(o);
}
}
};
// Format the table from the data.
grid.format = function() {
//dom.wrap.style.display = "none";
//dom.wrap.style.visibility = "hidden";
clearTABLE(dom.top.strip);
clearTABLE(dom.left.strip);
clearTABLE(dom.center.cells);
formatTop();
formatLeft();
formatCenter();
//dom.empty.style.display = ((0 == data.table.length) ? "block" : "none");
};
grid.reformat = function() {
grid.format();
grid.layout();
};
var asInteger = function(v) {
return (v ? parseInt(v) : 0);
}
var sync = { TIMEOUT: 50, LIMIT: 3000, id: 0, dt: 0 };
sync.start = function() {
if (sync.id) {
window.clearTimeout(sync.id);
}
sync.dt = 0;
sync.poll();
};
sync.poll = function() {
var dxTable = dom.center.container.scrollLeft;
var dyTable = dom.center.container.scrollTop;
var dxHeader = -asInteger(dom.top.container.style.marginLeft);
var dyHeader = -asInteger(dom.left.container.style.marginTop);
var bDX = dxTable != dxHeader;
var bDY = dyTable != dyHeader;
if (bDX) {
dom.top.container.style.marginLeft = (-dxTable) + "px";
}
if (bDY) {
dom.left.container.style.marginTop = (-dyTable) + "px";
}
if (bDX || bDY) {
sync.dt = 0;
} else {
sync.dt += sync.TIMEOUT;
if (sync.LIMIT < sync.dt) {
return; // stop polling
}
}
sync.id = window.setTimeout(sync.poll,sync.TIMEOUT);
};
DOM.forEvent(dom.container,'mouseover',sync.start);
DOM.forEvent(document,'mousemove',sync.start);
DOM.forEvent(document,'mousewheel',sync.start);
DOM.forEvent(document,'DOMMouseScroll',sync.start);
DOM.forEvent(document,'keyup',sync.start);
return grid;
};