Use stickyheaders in payment calendar, minimise reflows in stickyheaders

rel-1.0
Vitaliy Filippov 2016-08-12 15:29:58 +03:00
parent 557a5a817b
commit f48f8cbdec
4 changed files with 153 additions and 62 deletions

View File

@ -1,10 +1,12 @@
// Simple Sticky Header and Column implementation for HTML tables
// (c) Vitaliy Filippov 2016
// License: MPL 2.0+
// Version: 2016-08-12
// USAGE:
// makeStickyHeaders(table): add sticky header and footer to table
// table.parentNode is assumed to be the scroll container
// initStickyRow(table, tr): initialise table row (tr) if not yet
// fixStickyRow(table, tr): fix table row (tr) after modifications
// fixStickyHeader(table): fix table header after modifications
@ -15,7 +17,9 @@
// WARNING 2: this plugin does NOT support border-collapse because it is implemented by non-integer border
// sizes O_o and slightly differently in different browsers which leads to problems calculating cell positions.
// WARNING 3: all table cells should obviously have non-transparent background
// WARNING 3: for these hacks to work correctly, you need 'position: relative' on your table rows.
// WARNING 4: all table cells should obviously have non-transparent background
// because fixed ones are positioned over normal.
var _scri = 0;
@ -29,6 +33,8 @@ function makeStickyHeaders(table)
table._scri = _scri++;
}
var rs = table.rows;
if (!rs.length || !rs[0].children.length)
return;
var w = [], l = [], h = rs[0].offsetHeight;
var sr = rs[0].cloneNode(true);
sr.insertBefore(document.createElement('td'), sr.firstChild);
@ -58,20 +64,21 @@ function makeStickyHeaders(table)
e.style.display = 'block';
e.style.zIndex = '1';
}
// Try to minimise reflows: (1) make some changes
for (var i = rs.length-1; i >= 0; i--)
{
if (rs[i] == sr)
continue;
if (!rs[i].children.length)
continue;
var e = rs[i].children[0];
var d = e.cloneNode(true);
d.style.visibility = 'hidden';
e.parentNode.insertBefore(d, e.nextSibling);
if (i != 0)
{
e.style.height = e.parentNode.offsetHeight+'px';
e.style.position = 'absolute';
e.style.display = 'block';
e.style.width = w[0]+'px';
e.style.zIndex = '1';
}
else
@ -81,6 +88,22 @@ function makeStickyHeaders(table)
}
e.className += ' _scri'+table._scri;
}
// 2) <REFLOW> (offsetHeight causes reflow)
// 3) remember all heights
var hs = [];
for (var i = rs.length-1; i >= 1; i--)
hs[i] = rs[i].offsetHeight;
// 4) make other changes
for (var i = rs.length-1; i >= 1; i--)
{
if (rs[i] == sr)
continue;
if (!rs[i].children.length)
continue;
var e = rs[i].children[0];
e.style.height = hs[i]+'px';
e.style.width = w[0]+'px';
}
table.parentNode.addEventListener('scroll', function(e)
{
var l = this.scrollLeft, t = this.scrollTop;
@ -89,47 +112,77 @@ function makeStickyHeaders(table)
});
}
// handle non-header row change
function fixStickyRow(table, tr, nocols)
// check if row(s) are not yet initialised and fix them
function initStickyRows(table, trs, nocols)
{
var e = tr.children[0];
if (e.className.indexOf(' _scri'+table._scri) >= 0)
var ntrs = [];
for (var i = 0; i < trs.length; i++)
if (trs[i].children[0].className.indexOf(' _scri'+table._scri) < 0)
ntrs.push(trs[i]);
if (ntrs.length)
fixStickyRows(table, ntrs, nocols);
}
// handle non-header row(s) change with O(1) reflow count
function fixStickyRows(table, trs, nocols)
{
// (1) make some changes
var e, d;
for (var i = 0; i < trs.length; i++)
{
// refix
tr.removeChild(tr.children[1]);
var d = e.cloneNode(true);
d.className = d.className.replace(' _scri'+table._scri, '');
d.style.visibility = 'hidden';
d.style.width = '';
d.style.height = '';
d.style.position = '';
d.style.display = '';
d.style.zIndex = '';
e.parentNode.insertBefore(d, e.nextSibling);
// fix size
e.style.height = tr.offsetHeight+'px';
e.style.width = d.offsetWidth+'px';
// force layouting
if (!nocols)
table.parentNode.scrollTop = table.parentNode.scrollTop;
e = trs[i].children[0];
if (e.className.indexOf(' _scri'+table._scri) >= 0)
{
// refix
trs[i].removeChild(trs[i].children[1]);
d = e.cloneNode(true);
d.className = d.className.replace(' _scri'+table._scri, '');
d.style.visibility = 'hidden';
d.style.width = '';
d.style.height = '';
d.style.position = '';
d.style.display = '';
d.style.zIndex = '';
e.parentNode.insertBefore(d, e.nextSibling);
}
else
{
// fix new
d = e.cloneNode(true);
d.style.visibility = 'hidden';
e.style.position = 'absolute';
e.style.display = 'block';
e.style.zIndex = '1';
trs[i].insertBefore(d, e.nextSibling);
e.className += ' _scri'+table._scri;
}
}
else
// (2) <REFLOW> caused by offsetWidth/offsetHeight
// (3) remember sizes
var w = [], h = [];
for (var i = 0; i < trs.length; i++)
{
// fix new
var d = e.cloneNode(true);
d.style.visibility = 'hidden';
e.parentNode.insertBefore(d, e.nextSibling);
e.style.height = e.parentNode.offsetHeight+'px';
e.style.width = e.offsetWidth+'px';
e.style.position = 'absolute';
e.style.display = 'block';
e.style.zIndex = '1';
e.className += ' _scri'+table._scri;
e = trs[i].children[1];
w[i] = e.offsetWidth;
h[i] = e.offsetHeight;
}
// (4) apply sizes
for (var i = 0; i < trs.length; i++)
{
e = trs[i].children[0];
e.style.width = w[i]+'px';
e.style.height = h[i]+'px';
}
if (!nocols)
fixStickyColumnSizes(table);
}
// handle non-header row change
function fixStickyRow(table, tr, nocols)
{
fixStickyRows(table, [ tr ], nocols);
}
function fixStickyColumnSizes(table)
{
// check&fix column sizes
@ -160,11 +213,15 @@ function fixStickyColumnSizes(table)
if (changed)
{
// reposition fixed header
for (var i = 1; i < table._sizerow.children.length; i++)
var b = table._sizerow.children[1];
table.rows[0].children[0].style.width = b.offsetWidth+'px';
table.rows[0].children[0].style.height = b.offsetHeight+'px';
for (var i = 2; i < table._sizerow.children.length; i++)
{
table.rows[0].children[i].style.left = table._sizerow.children[i].offsetLeft+'px';
table.rows[0].children[i].style.width = table._sizerow.children[i].offsetWidth+'px';
table.rows[0].children[i].style.height = table._sizerow.children[i].offsetHeight+'px';
b = table._sizerow.children[i];
table.rows[0].children[i].style.left = b.offsetLeft+'px';
table.rows[0].children[i].style.width = b.offsetWidth+'px';
table.rows[0].children[i].style.height = b.offsetHeight+'px';
}
}
}
@ -191,8 +248,6 @@ function fixStickyHeader(table, nocols)
e.style.zIndex = '';
sr.appendChild(e);
}
// force layouting
table.parentNode.scrollTop = table.parentNode.scrollTop;
if (!nocols)
fixStickyColumnSizes(table);
}

View File

@ -4,7 +4,7 @@ select, input, textarea { box-sizing: border-box; font-size: 100%; }
table, td, th { vertical-align: top; font-size: 100%; }
img { vertical-align: middle; }
body { -moz-text-size-adjust: none; } /* do not scale fonts in mobile firefox */
table { border-collapse: collapse; }
/*table { border-collapse: collapse; }*/
/* scroll div styles */
.scroller
@ -15,16 +15,6 @@ table { border-collapse: collapse; }
width: 100%;
}
.scroller > .inner
{
position: absolute;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/* fixed header table styles */
table.grid tbody tr:hover, table.grid tbody tr:hover td
{
@ -51,17 +41,27 @@ table.grid th
table.grid
{
width: auto;
border-collapse: separate;
border-spacing: 0;
border-left: 1px solid #ccc;
border-top: 1px solid #ccc;
}
table.grid, table.grid tr, table.grid td, table.grid th
{
box-sizing: border-box;
position: relative;
}
table.grid td, table.grid th
{
border: 1px solid #ccc;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
background-clip: padding-box; /* O_o firefox draws background over borders */
padding: 3px;
font-size: 13px;
position: relative;
overflow: visible;
background: white;
}
/* treegrid styles */

View File

@ -1,7 +1,7 @@
/**
* Very simple and fast tree grid/table, compatible with dynamic loading and jQuery fixedHeaderTable
* Very simple and fast tree grid/table, compatible with dynamic loading and stickyheaders.js
* License: MPL 2.0+, (c) Vitaliy Filippov 2016+
* Version: 2016-06-06
* Version: 2016-08-12
*/
/**
@ -82,6 +82,14 @@ function TreeGrid(options)
new TreeGridNode({ children: options.items }, this);
}
TreeGrid.prototype.initStickyHeaders = function()
{
this.sticky = true;
this.stickyInit = this.header && this.header.length;
if (this.stickyInit)
makeStickyHeaders(this.table);
}
TreeGrid.prototype.setHeader = function(newHeader)
{
var tr = this.thead.rows[0];
@ -97,6 +105,16 @@ TreeGrid.prototype.setHeader = function(newHeader)
// header change clears the whole grid by now
if (this.root)
this.root.setChildren(false, []);
if (this.sticky)
{
if (!this.stickyInit)
{
makeStickyHeaders(this.table);
this.stickyInit = true;
}
else
fixStickyHeader(this.table);
}
}
TreeGrid.prototype.onExpand = function(node)
@ -373,6 +391,7 @@ function TreeGridNode(node, grid, level, insertBefore, startHidden)
this.childrenByKey = {};
if (node.children)
{
var trs = [];
for (var i = 0; i < node.children.length; i++)
{
var child = new TreeGridNode(node.children[i], grid, this.level+1, insertBefore, this.collapsed);
@ -380,7 +399,11 @@ function TreeGridNode(node, grid, level, insertBefore, startHidden)
this.children.push(child);
if (child.key !== undefined)
this.childrenByKey[child.key] = child;
if (grid.stickyInit && !this.collapsed)
trs.push(child.tr);
}
if (trs.length)
fixStickyRows(grid.table, trs);
}
}
@ -431,12 +454,13 @@ TreeGridNode.prototype._hashEqual = function(a, b)
TreeGridNode.prototype._renderCell = function(i, cell)
{
if (!this.tr.cells[i])
var ri = this.stickyInit ? (i > 0 ? i+1 : 0) : i;
if (!this.tr.cells[ri])
this.tr.appendChild(document.createElement('td'));
// virtualDOM-like approach: compare old HTML properties
if (!this._oldCells[i] || cell && !this._hashEqual(this._oldCells[i], cell))
{
this.grid._setProps(this.tr.cells[i], cell);
this.grid._setProps(this.tr.cells[ri], cell);
if (i == 0)
this._addCollapser();
}
@ -493,14 +517,21 @@ TreeGridNode.prototype.toggle = function()
{
// expand all children except children of collapsed subnodes
var st = this.children.concat([]);
var trs = [];
while (st.length)
{
var e = st.pop();
e.tr.style.display = '';
if (!e.collapsed)
st = st.concat(e.children);
if (this.grid.stickyInit)
trs.push(e.tr);
}
if (trs.length)
initStickyRows(this.grid.table, trs, true);
}
if (this.grid.stickyInit)
fixStickyColumnSizes(this.grid.table);
}
TreeGridNode.prototype.setChildren = function(isLeaf, newChildren)
@ -531,7 +562,7 @@ TreeGridNode.prototype.setChildren = function(isLeaf, newChildren)
this.addChildren(newChildren);
}
// experimental & broken
/*// experimental & broken
TreeGridNode.prototype._syncChildren = function()
{
var i, j;
@ -593,7 +624,7 @@ TreeGridNode.prototype._syncChildren = function()
k.parentNode.removeChild(k);
}
}
}
}*/
TreeGridNode.prototype.removeChild = function(nodeOrIndex)
{
@ -639,6 +670,7 @@ TreeGridNode.prototype.addChildren = function(nodes, insertBefore)
}
else
e = this.children[insertBefore].tr;
var trs = [];
for (var i = 0; i < nodes.length; i++)
{
var child = new TreeGridNode(nodes[i], this.grid, this.level+1, e, this.collapsed);
@ -646,8 +678,12 @@ TreeGridNode.prototype.addChildren = function(nodes, insertBefore)
this.children.splice(insertBefore+i, 0, child);
if (child.key !== undefined)
this.childrenByKey[child.key] = child;
if (this.grid.stickyInit && !this.collapsed)
trs.push(child.tr);
}
for (var i = insertBefore+nodes.length; i < this.children.length; i++)
this.children[i]._index += nodes.length;
if (trs.length)
fixStickyRows(this.grid.table, trs);
return this.children.slice(insertBefore, nodes.length);
}

View File

@ -47,7 +47,7 @@ onDomReady(function()
} ]);
}, 1000);
setTimeout(function() {
makeStickyHeaders(TG.table);
TG.initStickyHeaders();
}, 3000);
TG.onExpand = function(node)
{