Use stickyheaders in payment calendar, minimise reflows in stickyheaders
parent
557a5a817b
commit
f48f8cbdec
137
stickyheaders.js
137
stickyheaders.js
|
@ -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);
|
||||
}
|
||||
|
|
28
treegrid.css
28
treegrid.css
|
@ -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 */
|
||||
|
|
48
treegrid.js
48
treegrid.js
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ onDomReady(function()
|
|||
} ]);
|
||||
}, 1000);
|
||||
setTimeout(function() {
|
||||
makeStickyHeaders(TG.table);
|
||||
TG.initStickyHeaders();
|
||||
}, 3000);
|
||||
TG.onExpand = function(node)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue