virtual scroll v2
parent
72d11e887c
commit
bcc9a26ef2
319
treegrid.js
319
treegrid.js
|
@ -95,6 +95,7 @@ function TreeGrid(options)
|
|||
if (options.stickyHeaders)
|
||||
{
|
||||
this.stickyHeaders = true;
|
||||
this.minItemHeight = 24;
|
||||
this.toSync = [];
|
||||
this.wrapper = document.createElement('div');
|
||||
// table for fixed cell
|
||||
|
@ -227,9 +228,8 @@ function htmlspecialchars(text)
|
|||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function findNodes(node, start, count, result)
|
||||
function findSubNodes(node, start, count, result)
|
||||
{
|
||||
result = result || [];
|
||||
var skipped = 0, added = 0, counts;
|
||||
for (var i = 0; i < node.children.length && added < count; i++)
|
||||
{
|
||||
|
@ -244,7 +244,7 @@ function findNodes(node, start, count, result)
|
|||
}
|
||||
if (!node.children[i].collapsed)
|
||||
{
|
||||
counts = findNodes(node.children[i], start-skipped, count-added, result);
|
||||
counts = findSubNodes(node.children[i], start-skipped, count-added, result);
|
||||
skipped += counts[0];
|
||||
added += counts[1];
|
||||
}
|
||||
|
@ -252,66 +252,15 @@ function findNodes(node, start, count, result)
|
|||
return [ skipped, added ];
|
||||
}
|
||||
|
||||
TreeGrid.prototype.syncView = function()
|
||||
TreeGrid.prototype._findNodes = function(node, start, count)
|
||||
{
|
||||
var result = [];
|
||||
findSubNodes(node, start, count, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
TreeGrid.prototype._renderNodes = function(nodes)
|
||||
{
|
||||
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)
|
||||
|
@ -337,42 +286,227 @@ TreeGrid.prototype.syncView = function()
|
|||
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]);
|
||||
this.renderedNodes.push(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TreeGrid.prototype._beginSync = function()
|
||||
{
|
||||
if (this.renderedNodes)
|
||||
{
|
||||
for (var i = 0; i < this.renderedNodes.length; i++)
|
||||
this.renderedNodes[i]._reuse = false;
|
||||
}
|
||||
this.oldRenderedNodes = this.renderedNodes;
|
||||
this.renderedNodes = [];
|
||||
}
|
||||
|
||||
TreeGrid.prototype._syncEndNode = function(tr, col_tr)
|
||||
{
|
||||
var before = this.lastSyncChild, beforeCol = this.lastSyncColChild;
|
||||
if (!this.lastSyncChild)
|
||||
{
|
||||
this.lastSyncChild = this.tbody.lastChild;
|
||||
this.lastSyncColChild = this.fixedColBody && this.fixedColBody.lastChild;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.lastSyncChild = this.lastSyncChild.previousSibling;
|
||||
if (this.fixedColBody)
|
||||
this.lastSyncColChild = this.lastSyncColChild.previousSibling;
|
||||
}
|
||||
if (this.lastSyncChild != tr)
|
||||
{
|
||||
this.tbody.insertBefore(tr, before);
|
||||
this.lastSyncChild = tr;
|
||||
if (this.fixedColBody)
|
||||
{
|
||||
if (!this.renderedNodes[i]._reuse && this.renderedNodes[i].tr)
|
||||
this.fixedColBody.insertBefore(col_tr, beforeCol);
|
||||
this.lastSyncColChild = col_tr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeGrid.prototype._syncEnd = function(nodes)
|
||||
{
|
||||
this.lastSyncChild = undefined;
|
||||
this.lastSyncColChild = undefined;
|
||||
for (var i = nodes.length-1; i >= 0; i--)
|
||||
{
|
||||
for (var j = nodes[i].tr.length-1; j >= 0; j--)
|
||||
{
|
||||
this._syncEndNode(nodes[i].tr[j], this.fixedColBody && nodes[i].col_tr[j]);
|
||||
}
|
||||
}
|
||||
this.firstSyncChild = this.tbody.firstChild;
|
||||
this.firstSyncColChild = this.fixedColBody && this.fixedColBody.firstChild;
|
||||
}
|
||||
|
||||
TreeGrid.prototype._syncStartNode = function(tr, col_tr)
|
||||
{
|
||||
if (this.firstSyncChild != tr)
|
||||
{
|
||||
this.tbody.insertBefore(tr, this.firstSyncChild);
|
||||
if (this.fixedColBody)
|
||||
this.fixedColBody.insertBefore(col_tr, this.firstSyncColChild);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.firstSyncChild = this.firstSyncChild.nextSibling;
|
||||
if (this.fixedColBody)
|
||||
this.firstSyncColChild = this.firstSyncColChild.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
TreeGrid.prototype._syncStart = function(nodes)
|
||||
{
|
||||
for (var i = 0; i < nodes.length; i++)
|
||||
{
|
||||
for (var j = 0; j < nodes[i].tr.length; j++)
|
||||
{
|
||||
this._syncStartNode(nodes[i].tr[j], this.fixedColBody && nodes[i].col_tr[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeGrid.prototype._addPlaceholder = function(elems)
|
||||
{
|
||||
if (elems <= 0)
|
||||
return;
|
||||
var height = elems*this.minItemHeight;
|
||||
var tr = document.createElement('tr'), col_tr;
|
||||
tr.is_placeholder = true;
|
||||
tr.appendChild(document.createElement('td'));
|
||||
tr.style.height = height+'px';
|
||||
if (this.fixedColBody)
|
||||
{
|
||||
col_tr = document.createElement('tr');
|
||||
col_tr.is_placeholder = true;
|
||||
col_tr.appendChild(document.createElement('td'));
|
||||
col_tr.style.height = height+'px';
|
||||
}
|
||||
this._syncStartNode(tr, col_tr);
|
||||
}
|
||||
|
||||
TreeGrid.prototype._finishSync = function()
|
||||
{
|
||||
if (this.oldRenderedNodes)
|
||||
{
|
||||
for (var i = 0; i < this.oldRenderedNodes.length; i++)
|
||||
{
|
||||
if (!this.oldRenderedNodes[i]._reuse && this.oldRenderedNodes[i].tr)
|
||||
{
|
||||
this.renderedNodes[i].tr = null;
|
||||
this.renderedNodes[i].col_tr = null;
|
||||
this.renderedNodes[i]._oldCells = [];
|
||||
this.oldRenderedNodes[i].tr = null;
|
||||
this.oldRenderedNodes[i].col_tr = null;
|
||||
this.oldRenderedNodes[i]._oldCells = [];
|
||||
}
|
||||
delete this.oldRenderedNodes[i]._reuse;
|
||||
}
|
||||
this.oldRenderedNodes = null;
|
||||
}
|
||||
while (this.firstSyncChild && this.firstSyncChild != this.lastSyncChild)
|
||||
{
|
||||
var next = this.firstSyncChild.nextSibling;
|
||||
this.tbody.removeChild(this.firstSyncChild);
|
||||
this.firstSyncChild = next;
|
||||
}
|
||||
if (this.fixedColBody)
|
||||
{
|
||||
while (this.firstSyncColChild && this.firstSyncColChild != this.lastSyncColChild)
|
||||
{
|
||||
var next = this.firstSyncColChild.nextSibling;
|
||||
this.fixedColBody.removeChild(this.firstSyncColChild);
|
||||
this.firstSyncColChild = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeGrid.prototype._findVisibleNodeOffset = function(nodes, offsetHeight)
|
||||
{
|
||||
var h = 0, nh = 0, h1, h2;
|
||||
for (var i = nodes.length-1; i >= 0; i--)
|
||||
{
|
||||
nh = 0;
|
||||
if (this.fixedColBody)
|
||||
{
|
||||
for (var j = 0; j < nodes[i].tr.length; j++)
|
||||
{
|
||||
h1 = nodes[i].tr[j].offsetHeight||0;
|
||||
h2 = nodes[i].col_tr[j].offsetHeight||0;
|
||||
nh += h1 < h2 ? h2 : h1;
|
||||
}
|
||||
}
|
||||
else
|
||||
for (var j = 0; j < nodes[i].tr.length; j++)
|
||||
nh += nodes[i].tr[j].offsetHeight||0;
|
||||
h += nh;
|
||||
if (h >= offsetHeight)
|
||||
{
|
||||
return i + (h-offsetHeight)/nh;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
TreeGrid.prototype.syncView = function()
|
||||
{
|
||||
if (!this.table.offsetParent)
|
||||
return;
|
||||
var scrollPos = this.tableWrapper.scrollTop / (this.tableWrapper.scrollHeight - this.tableWrapper.offsetHeight + 1);
|
||||
var offsetHeight = this.wrapper.offsetHeight - this.thead.offsetHeight;
|
||||
var visibleItems = Math.ceil(offsetHeight/this.minItemHeight);
|
||||
var endStart = this.nodeCount - visibleItems;
|
||||
if (endStart < 0)
|
||||
endStart = 0;
|
||||
// Always render last items
|
||||
var lastNodes = this._findNodes(this.root, endStart, this.nodeCount-endStart);
|
||||
this._beginSync();
|
||||
this._renderNodes(lastNodes);
|
||||
this._syncEnd(lastNodes);
|
||||
if (endStart > 0)
|
||||
{
|
||||
// Calculate virtual scroll
|
||||
var lastFirst = endStart+this._findVisibleNodeOffset(lastNodes, offsetHeight);
|
||||
var firstVisible = scrollPos*lastFirst;
|
||||
var rangeStart = Math.floor(firstVisible);
|
||||
var rangeCount = visibleItems;
|
||||
this._addPlaceholder(rangeStart);
|
||||
if (rangeStart >= endStart)
|
||||
{
|
||||
// Nothing more to render
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rangeStart+rangeCount > endStart)
|
||||
{
|
||||
rangeCount = endStart-rangeStart;
|
||||
}
|
||||
var visibleNodes = this._findNodes(this.root, rangeStart, rangeCount);
|
||||
this._renderNodes(visibleNodes);
|
||||
this._syncStart(visibleNodes);
|
||||
if (endStart > rangeStart+rangeCount)
|
||||
{
|
||||
this._addPlaceholder(endStart-rangeStart-rangeCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.renderedNodes = nodes;
|
||||
var totalHeight = 0;
|
||||
this._finishSync();
|
||||
// Then sync row sizes for fixed column to work properly
|
||||
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++)
|
||||
for (var i = 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++)
|
||||
for (var i = 0; i < this.tbody.rows.length; i++)
|
||||
{
|
||||
if (h[i])
|
||||
if (h[i] && !this.tbody.rows[i].is_placeholder)
|
||||
{
|
||||
this.tbody.rows[i].style.height = h[i]+'px';
|
||||
this.fixedColBody.rows[i].style.height = h[i]+'px';
|
||||
|
@ -380,34 +514,6 @@ TreeGrid.prototype.syncView = function()
|
|||
}
|
||||
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)
|
||||
|
@ -1018,10 +1124,9 @@ function setHiddenNodes(node)
|
|||
{
|
||||
if (!node.children)
|
||||
return;
|
||||
var nv;
|
||||
var nv = node.visible && !node.collapsed;
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue