virtual scroll v1

master
Vitaliy Filippov 2017-06-25 16:19:41 +03:00
parent 7aa4acc719
commit 72d11e887c
2 changed files with 283 additions and 180 deletions

View File

@ -144,6 +144,7 @@ function TreeGrid(options)
self.syncStickyHeaders(true);
});
}
var syncTimer;
addListener(this.tableWrapper, 'scroll', function()
{
if (self.scrolling != 2)
@ -154,6 +155,10 @@ function TreeGrid(options)
}
else
self.scrolling = 0;
if (!syncTimer)
{
syncTimer = setTimeout(function() { self.syncView(); syncTimer = null; }, 100);
}
});
addListener(this.fixedColWrapper, 'scroll', function()
{
@ -169,58 +174,31 @@ function TreeGrid(options)
addListener(this.wrapper||this.table, 'mouseover', function(ev) { self.nodeOnHover(true, ev); });
addListener(this.wrapper||this.table, 'mouseout', function(ev) { self.nodeOnHover(false, ev); });
this.setHeader(options.header);
new TreeGridNode({ children: options.items }, this);
this.nodeCount = 0;
new TreeGridNode({ children: options.items }, this, null, 0);
}
function TreeGridNode(node, grid, level, insertBeforeNode, startHidden, skipSync)
function TreeGridNode(node, grid, parentNode, insertIndex, skipSync)
{
this.grid = grid;
this.level = level;
this.level = parentNode ? parentNode.level + 1 : -1;
this.key = node.key;
if (this.key !== undefined)
parentNode.childrenByKey[this.key] = this;
this.data = node.data;
if (this.level === undefined)
this.level = -1;
if (!grid.root)
grid.root = this;
else
if (parentNode)
{
this.leaf = node.leaf;
this.collapsed = node.collapsed || !node.leaf && (!node.children || !node.children.length);
this._oldCells = [];
this.tr = [];
for (var i = 0; i < (node.rows || 1); i++)
{
var tr = document.createElement('tr');
if (startHidden)
tr.style.display = 'none';
tr._node = this;
this.tr.push(tr);
}
if (grid.stickyHeaders)
{
this.col_tr = [];
for (var i = 0; i < (node.rows || 1); i++)
{
var tr = document.createElement('tr');
if (startHidden)
tr.style.display = 'none';
tr._node = this;
this.col_tr.push(tr);
}
}
this.render(undefined, undefined, false, true);
for (var i = 0; i < this.tr.length; i++)
{
insertBeforeNode ? grid.tbody.insertBefore(this.tr[i], insertBeforeNode.tr[0]) : grid.tbody.appendChild(this.tr[i]);
}
if (grid.stickyHeaders)
{
for (var i = 0; i < this.col_tr.length; i++)
{
insertBeforeNode ? grid.fixedColBody.insertBefore(this.col_tr[i], insertBeforeNode.col_tr[0]) : grid.fixedColBody.appendChild(this.col_tr[i]);
}
grid.toSync.push(this);
}
this.collapsed = node.collapsed || !node.leaf && (!node.children || !node.children.length);
this.visible = parentNode.visible && !parentNode.collapsed;
grid.nodeCount += this.visible ? 1 : 0;
parentNode.children.splice(insertIndex, 0, this);
}
else
{
this.visible = true;
grid.root = this;
}
this.children = [];
this.childrenByKey = {};
@ -228,15 +206,14 @@ function TreeGridNode(node, grid, level, insertBeforeNode, startHidden, skipSync
{
for (var i = 0; i < node.children.length; i++)
{
var child = new TreeGridNode(node.children[i], grid, this.level+1, insertBeforeNode, this.collapsed, true);
child._index = i;
this.children.push(child);
if (child.key !== undefined)
this.childrenByKey[child.key] = child;
new TreeGridNode(node.children[i], grid, this, i, true);
}
}
if (!skipSync)
{
this.grid.syncView();
this.grid.syncStickyHeaders();
}
}
(function() {
@ -250,6 +227,189 @@ function htmlspecialchars(text)
.replace(/>/g, '&gt;');
}
function findNodes(node, start, count, result)
{
result = result || [];
var skipped = 0, added = 0, counts;
for (var i = 0; i < node.children.length && added < count; i++)
{
if (skipped < start)
{
skipped++;
}
else
{
result.push(node.children[i]);
added++;
}
if (!node.children[i].collapsed)
{
counts = findNodes(node.children[i], start-skipped, count-added, result);
skipped += counts[0];
added += counts[1];
}
}
return [ skipped, added ];
}
TreeGrid.prototype.syncView = function()
{
if (!this.table.offsetParent)
return;
var itemheight = 24;
var reserve = 300;
var offsetHeight = this.wrapper.offsetHeight - this.thead.offsetHeight;
var scrollTop = this.tableWrapper.scrollTop;
var scrollHeight = itemheight*this.nodeCount + 2*reserve;
var effheight = (scrollHeight - offsetHeight+1) / this.nodeCount;
var first = Math.floor(scrollTop / effheight);
var firstOffset = scrollTop - first*effheight;
var count = Math.floor(offsetHeight/itemheight);
var nodes = [];
findNodes(this.root, first, count, nodes);
var prefix_tr, prefix_col_tr, suffix_tr, suffix_col_tr;
if (this.tbody.firstChild && this.tbody.firstChild.is_prefix)
{
prefix_tr = this.tbody.firstChild;
if (this.stickyHeaders)
prefix_col_tr = this.fixedColBody.firstChild;
}
if (this.tbody.lastChild && this.tbody.lastChild.is_suffix)
{
suffix_tr = this.tbody.lastChild;
if (this.stickyHeaders)
suffix_col_tr = this.fixedColBody.lastChild;
}
this.tbody.innerHTML = '';
if (this.stickyHeaders)
this.fixedColBody.innerHTML = '';
var tr, placeholderHeight;
if (first > 0)
{
placeholderHeight = Math.floor(first * itemheight)+'px';
if (!prefix_tr)
{
prefix_tr = document.createElement('tr');
prefix_tr.is_prefix = true;
prefix_tr.appendChild(document.createElement('td'));
}
this.tbody.appendChild(prefix_tr);
prefix_tr.firstChild.style.height = placeholderHeight;
if (this.stickyHeaders)
{
if (!prefix_col_tr)
{
prefix_col_tr = document.createElement('tr');
prefix_col_tr.is_prefix = true;
prefix_col_tr.appendChild(document.createElement('td'));
}
this.fixedColBody.appendChild(prefix_col_tr);
prefix_col_tr.firstChild.style.height = placeholderHeight;
}
}
if (this.renderedNodes)
{
for (var i = 0; i < this.renderedNodes.length; i++)
this.renderedNodes[i]._reuse = false;
}
for (var i = 0; i < nodes.length; i++)
{
if (!nodes[i].tr)
{
nodes[i].tr = [];
for (var j = 0; j < (nodes[i].rows||1); j++)
{
tr = document.createElement('tr');
tr._node = nodes[i];
nodes[i].tr.push(tr);
}
if (this.stickyHeaders)
{
nodes[i].col_tr = [];
for (var j = 0; j < (nodes[i].rows||1); j++)
{
var tr = document.createElement('tr');
tr._node = nodes[i];
nodes[i].col_tr.push(tr);
}
}
}
else
nodes[i]._reuse = true;
nodes[i].render(undefined, undefined, false, true);
for (var j = 0; j < nodes[i].tr.length; j++)
this.tbody.appendChild(nodes[i].tr[j]);
if (this.stickyHeaders)
for (var j = 0; j < nodes[i].col_tr.length; j++)
this.fixedColBody.appendChild(nodes[i].col_tr[j]);
}
if (this.renderedNodes)
{
for (var i = 0; i < this.renderedNodes.length; i++)
{
if (!this.renderedNodes[i]._reuse && this.renderedNodes[i].tr)
{
this.renderedNodes[i].tr = null;
this.renderedNodes[i].col_tr = null;
this.renderedNodes[i]._oldCells = [];
}
}
}
this.renderedNodes = nodes;
var totalHeight = 0;
if (this.stickyHeaders)
{
if (this.fixedCol.offsetWidth)
this.setStickyColumnWidth(this.fixedCol.offsetWidth+'px');
var h1, h2;
var h = [];
for (var i = first > 0 ? 1 : 0; i < this.tbody.rows.length; i++)
{
h1 = this.tbody.rows[i].offsetHeight||0;
h2 = this.fixedColBody.rows[i].offsetHeight||0;
h[i] = h1 < h2 ? h2 : h1;
totalHeight += h[i];
}
for (var i = first > 0 ? 1 : 0; i < this.tbody.rows.length; i++)
{
if (h[i])
{
this.tbody.rows[i].style.height = h[i]+'px';
this.fixedColBody.rows[i].style.height = h[i]+'px';
}
}
this.syncStickyRow();
}
else
{
for (var i = first > 0 ? 1 : 0; i < this.tbody.rows.length; i++)
totalHeight += this.tbody.rows[i].offsetHeight||0;
}
if (first+nodes.length < this.nodeCount)
{
placeholderHeight = Math.floor(scrollHeight - totalHeight - first*itemheight)+'px';
if (!suffix_tr)
{
suffix_tr = document.createElement('tr');
suffix_tr.is_suffix = true;
suffix_tr.appendChild(document.createElement('td'));
}
this.tbody.appendChild(suffix_tr);
suffix_tr.firstChild.style.height = placeholderHeight;
if (this.stickyHeaders)
{
if (!suffix_col_tr)
{
suffix_col_tr = document.createElement('tr');
suffix_col_tr.is_prefix = true;
suffix_col_tr.appendChild(document.createElement('td'));
}
this.fixedColBody.appendChild(suffix_col_tr);
suffix_col_tr.firstChild.style.height = placeholderHeight;
}
}
}
TreeGrid.prototype.nodeOnHover = function(hover, ev)
{
ev = ev||window.event;
@ -338,6 +498,11 @@ TreeGrid.prototype.syncStickyHeaders = function(sync_all)
}
}
this.toSync = [];
this.syncStickyRow();
}
TreeGrid.prototype.syncStickyRow = function()
{
var trh = this.fixedRow.rows[0], w = [];
for (var i = 0; i < trh.cells.length; i++)
w[i] = this.sizeRow.cells[i] && this.sizeRow.cells[i].offsetWidth || 0;
@ -849,6 +1014,23 @@ TreeGridNode.prototype._toggleHandler = function()
this.toggle();
}
function setHiddenNodes(node)
{
if (!node.children)
return;
var nv;
for (var i = 0; i < node.children.length; i++)
{
nv = node.visible && !node.collapsed;
if (nv != node.children[i].visible)
{
node.grid.nodeCount += nv ? 1 : -1;
node.children[i].visible = nv;
setHiddenNodes(node.children[i]);
}
}
}
TreeGridNode.prototype.toggle = function()
{
if (this.leaf)
@ -856,159 +1038,51 @@ TreeGridNode.prototype.toggle = function()
this.collapsed = !this.collapsed;
(this.col_tr || this.tr)[0].cells[0].firstChild.className =
this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
if (this.collapsed)
{
// collapse all children
var c = this.tr[this.tr.length-1].nextSibling;
while (c && c._node.level > this.level)
{
c.style.display = 'none';
c = c.nextSibling;
}
if (this.col_tr)
{
c = this.col_tr[this.col_tr.length-1].nextSibling;
while (c && c._node.level > this.level)
{
c.style.display = 'none';
c = c.nextSibling;
}
}
}
else
{
// expand all children except children of collapsed subnodes
var st = this.children.concat([]);
var trs = [];
while (st.length)
{
var e = st.pop();
for (var i = 0; i < e.tr.length; i++)
{
e.tr[i].style.display = '';
if (this.grid.stickyHeaders)
{
e.col_tr[i].style.display = '';
this.grid.toSync.push(e);
}
}
if (!e.collapsed)
st = st.concat(e.children);
}
}
this.grid.syncStickyHeaders();
setHiddenNodes(this);
this.grid.syncView();
}
TreeGridNode.prototype.setChildren = function(isLeaf, newChildren)
TreeGridNode.prototype.setChildren = function(isLeaf, newChildren, skipSync)
{
if (!this.tr)
{
// root node
this.grid.tbody.innerHTML = '';
if (this.grid.fixedColBody)
this.grid.fixedColBody.innerHTML = '';
}
else
{
var tr = this.tr[this.tr.length-1];
while (tr.nextSibling && tr.nextSibling._node.level > this.level)
tr.parentNode.removeChild(tr.nextSibling);
if (this.col_tr)
{
tr = this.col_tr[this.col_tr.length-1];
while (tr.nextSibling && tr.nextSibling._node.level > this.level)
tr.parentNode.removeChild(tr.nextSibling);
}
if (this.leaf != isLeaf)
{
var collapser = (this.col_tr || this.tr)[0].cells[0].firstChild;
if (isLeaf)
{
collapser.className = 'collapser collapser-inactive';
removeListener(collapser, 'click', this._getToggleHandler());
}
else
{
collapser.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
addListener(collapser, 'click', this._getToggleHandler());
}
}
}
this.leaf = isLeaf;
this.children = [];
this.childrenByKey = {};
this.addChildren(newChildren);
this.addChildren(newChildren, null, skipSync);
}
TreeGridNode.prototype.removeChild = function(nodeOrIndex)
TreeGridNode.prototype.removeChild = function(index, skipSync)
{
if (nodeOrIndex instanceof TreeGridNode)
{
if (this.children[nodeOrIndex._index] == nodeOrIndex)
nodeOrIndex = nodeOrIndex._index;
else
return false;
}
if (!this.children[nodeOrIndex])
if (!this.children[index])
return false;
var tr = this.children[nodeOrIndex].tr[0];
var col_tr = this.children[nodeOrIndex].col_tr && this.children[nodeOrIndex].col_tr[0];
var l = this.children[nodeOrIndex].level;
if (this.children[nodeOrIndex].key !== undefined)
delete this.childrenByKey[this.children[nodeOrIndex].key];
this.children.splice(nodeOrIndex, 1);
for (var i = nodeOrIndex; i < this.children.length; i++)
this.children[i]._index--;
var k, e;
e = tr;
do
{
k = e;
e = e.nextSibling;
k.parentNode.removeChild(k);
} while (e && e._node.level > l);
if (col_tr)
{
e = col_tr;
do
{
k = e;
e = e.nextSibling;
k.parentNode.removeChild(k);
} while (e && e._node.level > l);
}
if (this.children[index].key !== undefined)
delete this.childrenByKey[this.children[index].key];
this.children.splice(index, 1);
if (!skipSync)
this.grid.syncView();
return true;
}
TreeGridNode.prototype.addChildren = function(nodes, insertBefore)
TreeGridNode.prototype.addChildren = function(nodes, insertBefore, skipSync)
{
var e;
if (insertBefore < 0)
insertBefore = 0;
if (insertBefore === undefined || insertBefore === null || insertBefore === false ||
insertBefore >= this.children.length)
{
insertBefore = this.children.length;
// тут нужно получить следующий узел после текущего...
e = this;
while (e.children.length)
e = e.children[e.children.length-1];
e = e.tr && e.tr[e.tr.length-1].nextSibling ? e.tr[e.tr.length-1].nextSibling._node : null;
}
else if (insertBefore < 0)
insertBefore = 0;
else
e = this.children[insertBefore];
var trs = [];
for (var i = 0; i < nodes.length; i++)
{
var child = new TreeGridNode(nodes[i], this.grid, this.level+1, e, this.collapsed, true);
child._index = insertBefore+i;
var child = new TreeGridNode(nodes[i], this.grid, this, i, true);
this.children.splice(insertBefore+i, 0, child);
if (child.key !== undefined)
this.childrenByKey[child.key] = child;
}
for (var i = insertBefore+nodes.length; i < this.children.length; i++)
this.children[i]._index += nodes.length;
this.grid.syncStickyHeaders();
if (!skipSync)
this.grid.syncView();
return this.children.slice(insertBefore, nodes.length);
}

View File

@ -16,10 +16,26 @@ th, td { border-width: 0 1px 1px 0 !important; }
<!--
(function()
{
var i = {
data: [ 'Payment from Gazprom to Shell 2', '4', '5' ]
};
var TG = window.TG = new TreeGrid({ header: [], items: [], bind: { rowSpan: true }, stickyHeaders: true, stickyColumnWidth: '200px', renderer: function(node)
var items = [];
for (var i = 0; i < 1000; i++)
{
items[i] = {
data: [ 'Payment from Gazprom to Shell '+i, (Math.random()+'').substr(0, 6), '', (Math.random()+'').substr(0, 6) ],
collapsed: true,
children: []
};
for (var j = 0; j < 5; j++)
{
items[i].children[j] = {
data: [ 'Subpayment '+i+' / '+j, 231, 231 ],
leaf: true
};
}
}
//var TG = window.TG = new TreeGrid({ header: [], items: [], bind: { rowSpan: true }, stickyHeaders: true, stickyColumnWidth: '200px', renderer: function(node)
var TG = window.TG = new TreeGrid({ header: [
'', '15 фев', '16 фев', '17 фев', '18 фев'
], items: items, bind: { rowSpan: true }, stickyHeaders: true, stickyColumnWidth: '200px', renderer: function(node)
{
var cells = [];
for (var i = 0; i < node.data.length; i++)
@ -33,10 +49,23 @@ th, td { border-width: 0 1px 1px 0 !important; }
return cells;
} });
document.body.appendChild(TG.wrapper);
setTimeout(function() {
TG.syncView();
/*setTimeout(function() {
TG.setHeader([ '', '15 фев', '16 фев', '17 фев', '18 фев', '19 фев', '20 фев', '21 фев', '15 фев', '16 фев', '17 фев', '18 фев', '19 фев', '20 фев', '21 фев', '15 фев', '16 фев', '17 фев', '18 фев', '19 фев', '20 фев', '21 фев', '15 фев', '16 фев', '17 фев', '18 фев', '19 фев', '20 фев', '21 фев' ]);
TG.root.setChildren(false, [ {
data: [ 'Payment from Gazprom to Shell', '2', '3' ]
data: [ 'Payment from Gazprom to Shell', '2', '3' ],
children: [ {
data: [ 'Payment from Gazprom to Shell', '2', '3' ],
}, {
data: [ 'Payment from Gazprom to Shell', '2', '31203' ],
rows: 2
}, {
data: [ 'Payment from Gazprom to Shell', '2', '329x<br>2' ]
}, {
data: [ 'Payment from Gazprom to Shell', '2', '239000' ]
}, {
data: [ 'Payment from Gazprom to Shell', '2', 'aaaaa' ]
} ]
}, {
data: [ 'Payment from Gazprom to Shell', '2', '31203' ],
rows: 2
@ -72,7 +101,7 @@ th, td { border-width: 0 1px 1px 0 !important; }
TG.onCellSelect = function(node, i, td)
{
return i > 0;
};
};*/
})();
//-->
</script>