Support multi-row nodes, remove experimental & dead syncNodes
parent
d782fcd9a5
commit
ec7a3e702d
179
treegrid.js
179
treegrid.js
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Very simple and fast tree grid/table, compatible with dynamic loading and stickyheaders.js
|
* Very simple and fast tree grid/table, compatible with dynamic loading and stickyheaders.js
|
||||||
* License: MPL 2.0+, (c) Vitaliy Filippov 2016+
|
* License: MPL 2.0+, (c) Vitaliy Filippov 2016+
|
||||||
* Version: 2016-09-07
|
* Version: 2016-10-07
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,6 +11,7 @@
|
||||||
* document.body.appendChild(TG.table);
|
* document.body.appendChild(TG.table);
|
||||||
*
|
*
|
||||||
* // renderer is a function that generates cell HTML properties for a node
|
* // renderer is a function that generates cell HTML properties for a node
|
||||||
|
* // for multi-row nodes renderer must return array of multiple rows
|
||||||
* renderer = function(node) { return [ { innerHTML, style, className, title }, ... ] }
|
* renderer = function(node) { return [ { innerHTML, style, className, title }, ... ] }
|
||||||
*
|
*
|
||||||
* items: [ node={
|
* items: [ node={
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
* children: [ node... ],
|
* children: [ node... ],
|
||||||
* leaf: true/false,
|
* leaf: true/false,
|
||||||
* collapsed: true/false,
|
* collapsed: true/false,
|
||||||
|
* [ rows: <number of rows in this node> ],
|
||||||
* data: <user data>
|
* data: <user data>
|
||||||
* }, ... ]
|
* }, ... ]
|
||||||
*
|
*
|
||||||
|
@ -70,6 +72,9 @@ function TreeGrid(options)
|
||||||
{
|
{
|
||||||
this.renderer = options.renderer;
|
this.renderer = options.renderer;
|
||||||
this.bindProps = { 'style': 1, 'className': 1, 'title': 1, 'innerHTML': 1 };
|
this.bindProps = { 'style': 1, 'className': 1, 'title': 1, 'innerHTML': 1 };
|
||||||
|
if (options.bind)
|
||||||
|
for (var i in options.bind)
|
||||||
|
this.bindProps[i] = 1;
|
||||||
this.levelIndent = 20;
|
this.levelIndent = 20;
|
||||||
this.table = document.createElement('table');
|
this.table = document.createElement('table');
|
||||||
this.table.className = 'grid';
|
this.table.className = 'grid';
|
||||||
|
@ -395,13 +400,21 @@ function TreeGridNode(node, grid, level, insertBefore, startHidden)
|
||||||
{
|
{
|
||||||
this.leaf = node.leaf;
|
this.leaf = node.leaf;
|
||||||
this.collapsed = node.collapsed || !node.leaf && (!node.children || !node.children.length);
|
this.collapsed = node.collapsed || !node.leaf && (!node.children || !node.children.length);
|
||||||
this.tr = document.createElement('tr');
|
this.tr = [];
|
||||||
if (startHidden)
|
for (var i = 0; i < (node.rows || 1); i++)
|
||||||
this.tr.style.display = 'none';
|
{
|
||||||
this.tr._node = this;
|
var tr = document.createElement('tr');
|
||||||
|
if (startHidden)
|
||||||
|
tr.style.display = 'none';
|
||||||
|
tr._node = this;
|
||||||
|
this.tr.push(tr);
|
||||||
|
}
|
||||||
this._oldCells = [];
|
this._oldCells = [];
|
||||||
this.render();
|
this.render();
|
||||||
insertBefore ? grid.tbody.insertBefore(this.tr, insertBefore) : grid.tbody.appendChild(this.tr);
|
for (var i = 0; i < this.tr.length; i++)
|
||||||
|
{
|
||||||
|
insertBefore ? grid.tbody.insertBefore(this.tr[i], insertBefore) : grid.tbody.appendChild(this.tr[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.childrenByKey = {};
|
this.childrenByKey = {};
|
||||||
|
@ -416,7 +429,7 @@ function TreeGridNode(node, grid, level, insertBefore, startHidden)
|
||||||
if (child.key !== undefined)
|
if (child.key !== undefined)
|
||||||
this.childrenByKey[child.key] = child;
|
this.childrenByKey[child.key] = child;
|
||||||
if (grid.stickyInit && !this.collapsed)
|
if (grid.stickyInit && !this.collapsed)
|
||||||
trs.push(child.tr);
|
trs.push.apply(trs, child.tr);
|
||||||
}
|
}
|
||||||
if (trs.length)
|
if (trs.length)
|
||||||
fixStickyRows(grid.table, trs);
|
fixStickyRows(grid.table, trs);
|
||||||
|
@ -443,7 +456,7 @@ TreeGridNode.prototype._addCollapser = function()
|
||||||
collapser.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
collapser.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
||||||
addListener(collapser, 'click', this._getToggleHandler());
|
addListener(collapser, 'click', this._getToggleHandler());
|
||||||
}
|
}
|
||||||
var c0 = this.tr.cells[0];
|
var c0 = this.tr[0].cells[0];
|
||||||
c0.childNodes.length ? c0.insertBefore(collapser, c0.firstChild) : c0.appendChild(collapser);
|
c0.childNodes.length ? c0.insertBefore(collapser, c0.firstChild) : c0.appendChild(collapser);
|
||||||
if (this.grid._initialPadding === undefined)
|
if (this.grid._initialPadding === undefined)
|
||||||
{
|
{
|
||||||
|
@ -468,33 +481,59 @@ TreeGridNode.prototype._hashEqual = function(a, b)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeGridNode.prototype._renderCell = function(i, cell)
|
TreeGridNode.prototype._renderCell = function(rowIndex, colIndex, cell)
|
||||||
{
|
{
|
||||||
// this may be the first render of a row inside a stickyheaders grid
|
// this may be the first render of a row inside a stickyheaders grid
|
||||||
var ri = this.tr.cells.length && this.tr.cells[0].className.indexOf(' _scri') >= 0 ? (i > 0 ? i+1 : 0) : i;
|
var tr = this.tr[rowIndex];
|
||||||
if (!this.tr.cells[ri])
|
var ri = tr.cells.length && tr.cells[0].className.indexOf(' _scri') >= 0 ? (colIndex > 0 ? colIndex+1 : 0) : colIndex;
|
||||||
this.tr.appendChild(document.createElement('td'));
|
while (!tr.cells[ri])
|
||||||
|
tr.appendChild(document.createElement('td'));
|
||||||
// virtualDOM-like approach: compare old HTML properties
|
// virtualDOM-like approach: compare old HTML properties
|
||||||
if (!this._oldCells[i] || cell && !this._hashEqual(this._oldCells[i], cell))
|
if (!this._oldCells[rowIndex] || !this._oldCells[rowIndex][colIndex] ||
|
||||||
|
cell && !this._hashEqual(this._oldCells[rowIndex][colIndex], cell))
|
||||||
{
|
{
|
||||||
this.grid._setProps(this.tr.cells[ri], cell);
|
this.grid._setProps(tr.cells[ri], cell);
|
||||||
if (i == 0)
|
if (rowIndex == 0 && colIndex == 0)
|
||||||
this._addCollapser();
|
this._addCollapser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeGridNode.prototype.render = function(colidx)
|
TreeGridNode.prototype.render = function(colidx, rowidx)
|
||||||
{
|
{
|
||||||
var cells = this.grid.renderer(this, colidx);
|
var cells = this.grid.renderer(this, colidx, rowidx);
|
||||||
if (colidx !== null && colidx !== undefined && colidx >= 0 && colidx < this.grid.header.length)
|
if (this.tr.length == 1)
|
||||||
|
cells = [ cells ];
|
||||||
|
if (!(colidx !== null && colidx !== undefined && colidx >= 0 && colidx < this.grid.header.length))
|
||||||
|
colidx = undefined;
|
||||||
|
if (!(this.tr.length > 1 && rowidx !== null && rowidx !== undefined && rowidx >= 0 && rowidx < this.tr.length))
|
||||||
|
rowidx = undefined;
|
||||||
|
if (rowidx !== undefined)
|
||||||
{
|
{
|
||||||
this._renderCell(colidx, cells[colidx]);
|
if (colidx !== undefined)
|
||||||
this._oldCells[colidx] = cells[colidx];
|
{
|
||||||
|
this._renderCell(rowidx, colidx, cells[rowidx] && cells[rowidx][colidx]);
|
||||||
|
this._oldCells[rowidx][colidx] = cells[rowidx] && cells[rowidx][colidx];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var j = 0; j < this.grid.header.length; j++)
|
||||||
|
this._renderCell(rowidx, j, cells[rowidx] && cells[rowidx][j]);
|
||||||
|
this._oldCells[rowidx] = cells[rowidx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (colidx !== undefined)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < this.tr.length; i++)
|
||||||
|
{
|
||||||
|
this._renderCell(i, colidx, cells[i] && cells[i][colidx]);
|
||||||
|
this._oldCells[i][colidx] = cells[i] && cells[i][colidx];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.grid.header.length; i++)
|
for (var i = 0; i < this.tr.length; i++)
|
||||||
this._renderCell(i, cells[i]);
|
for (var j = 0; j < this.grid.header.length; j++)
|
||||||
|
this._renderCell(i, j, cells[i] && cells[i][j]);
|
||||||
this._oldCells = cells;
|
this._oldCells = cells;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -519,11 +558,11 @@ TreeGridNode.prototype.toggle = function()
|
||||||
if (this.leaf)
|
if (this.leaf)
|
||||||
return;
|
return;
|
||||||
this.collapsed = !this.collapsed;
|
this.collapsed = !this.collapsed;
|
||||||
this.tr.cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
this.tr[0].cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
||||||
if (this.collapsed)
|
if (this.collapsed)
|
||||||
{
|
{
|
||||||
// collapse all children
|
// collapse all children
|
||||||
var c = this.tr.nextSibling;
|
var c = this.tr[this.tr.length-1].nextSibling;
|
||||||
while (c && c._node.level > this.level)
|
while (c && c._node.level > this.level)
|
||||||
{
|
{
|
||||||
c.style.display = 'none';
|
c.style.display = 'none';
|
||||||
|
@ -538,11 +577,14 @@ TreeGridNode.prototype.toggle = function()
|
||||||
while (st.length)
|
while (st.length)
|
||||||
{
|
{
|
||||||
var e = st.pop();
|
var e = st.pop();
|
||||||
e.tr.style.display = '';
|
for (var i = 0; i < e.tr.length; i++)
|
||||||
|
{
|
||||||
|
e.tr[i].style.display = '';
|
||||||
|
if (this.grid.stickyInit)
|
||||||
|
trs.push(e.tr[i]);
|
||||||
|
}
|
||||||
if (!e.collapsed)
|
if (!e.collapsed)
|
||||||
st = st.concat(e.children);
|
st = st.concat(e.children);
|
||||||
if (this.grid.stickyInit)
|
|
||||||
trs.push(e.tr);
|
|
||||||
}
|
}
|
||||||
if (trs.length)
|
if (trs.length)
|
||||||
initStickyRows(this.grid.table, trs, true);
|
initStickyRows(this.grid.table, trs, true);
|
||||||
|
@ -557,19 +599,20 @@ TreeGridNode.prototype.setChildren = function(isLeaf, newChildren)
|
||||||
this.grid.tbody.innerHTML = '';
|
this.grid.tbody.innerHTML = '';
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
while (this.tr.nextSibling && this.tr.nextSibling._node.level > this.level)
|
var tr = this.tr[this.tr.length-1];
|
||||||
this.grid.tbody.removeChild(this.tr.nextSibling);
|
while (tr.nextSibling && tr.nextSibling._node.level > this.level)
|
||||||
|
this.grid.tbody.removeChild(tr.nextSibling);
|
||||||
if (this.leaf != isLeaf)
|
if (this.leaf != isLeaf)
|
||||||
{
|
{
|
||||||
if (isLeaf)
|
if (isLeaf)
|
||||||
{
|
{
|
||||||
this.tr.cells[0].firstChild.className = 'collapser collapser-inactive';
|
this.tr[0].cells[0].firstChild.className = 'collapser collapser-inactive';
|
||||||
removeListener(this.tr.cells[0].firstChild, 'click', this._getToggleHandler());
|
removeListener(this.tr[0].cells[0].firstChild, 'click', this._getToggleHandler());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.tr.cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
this.tr[0].cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded';
|
||||||
addListener(this.tr.cells[0].firstChild, 'click', this._getToggleHandler());
|
addListener(this.tr[0].cells[0].firstChild, 'click', this._getToggleHandler());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,70 +622,6 @@ TreeGridNode.prototype.setChildren = function(isLeaf, newChildren)
|
||||||
this.addChildren(newChildren);
|
this.addChildren(newChildren);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*// experimental & broken
|
|
||||||
TreeGridNode.prototype._syncChildren = function()
|
|
||||||
{
|
|
||||||
var i, j;
|
|
||||||
for (var i = 0; i < this.children.length; i++)
|
|
||||||
{
|
|
||||||
delete this.children[i]._rownum;
|
|
||||||
}
|
|
||||||
var rows = [];
|
|
||||||
var e = this.tr ? this.tr.nextSibling : this.grid.tbody.firstChild;
|
|
||||||
i = 0;
|
|
||||||
while (e && e._node.level > this.level)
|
|
||||||
{
|
|
||||||
if (e._node.level == this.level+1)
|
|
||||||
{
|
|
||||||
if (rows.length > 0)
|
|
||||||
rows[rows.length-1][2] = i;
|
|
||||||
e._node._rownum = rows.length;
|
|
||||||
rows.push([ e, i, i ]);
|
|
||||||
}
|
|
||||||
e = e.nextSibling;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
if (rows.length > 0)
|
|
||||||
rows[rows.length-1][2] = i;
|
|
||||||
e = this.tr ? this.tr.nextSibling : this.grid.tbody.firstChild;
|
|
||||||
for (var i = 0; i < this.children.length; i++)
|
|
||||||
{
|
|
||||||
if (this.children[i]._rownum !== undefined)
|
|
||||||
{
|
|
||||||
var k = rows[this.children[i]._rownum][0];
|
|
||||||
var n = rows[this.children[i]._rownum][2]-rows[this.children[i]._rownum][1];
|
|
||||||
delete this.children[i]._rownum;
|
|
||||||
var m;
|
|
||||||
while (n > 0)
|
|
||||||
{
|
|
||||||
// Ставим k перед e
|
|
||||||
m = k;
|
|
||||||
k = k.nextSibling;
|
|
||||||
if (!e)
|
|
||||||
this.grid.tbody.appendChild(k);
|
|
||||||
else if (e == k)
|
|
||||||
e = e.nextSibling;
|
|
||||||
else
|
|
||||||
e.parentNode.insertBefore(k, e);
|
|
||||||
n--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.children[i] = new TreeGridNode(this.children[i], this.grid, this.level+1, e, this.collapsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e)
|
|
||||||
{
|
|
||||||
while (e._node.level > this.level)
|
|
||||||
{
|
|
||||||
k = e;
|
|
||||||
e = e.nextSibling;
|
|
||||||
k.parentNode.removeChild(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
TreeGridNode.prototype.removeChild = function(nodeOrIndex)
|
TreeGridNode.prototype.removeChild = function(nodeOrIndex)
|
||||||
{
|
{
|
||||||
if (nodeOrIndex instanceof TreeGridNode)
|
if (nodeOrIndex instanceof TreeGridNode)
|
||||||
|
@ -683,10 +662,10 @@ TreeGridNode.prototype.addChildren = function(nodes, insertBefore)
|
||||||
e = this;
|
e = this;
|
||||||
while (e.children.length)
|
while (e.children.length)
|
||||||
e = e.children[e.children.length-1];
|
e = e.children[e.children.length-1];
|
||||||
e = e.tr ? e.tr.nextSibling : null;
|
e = e.tr ? e.tr[e.tr.length-1].nextSibling : null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
e = this.children[insertBefore].tr;
|
e = this.children[insertBefore].tr[0];
|
||||||
var trs = [];
|
var trs = [];
|
||||||
for (var i = 0; i < nodes.length; i++)
|
for (var i = 0; i < nodes.length; i++)
|
||||||
{
|
{
|
||||||
|
@ -697,7 +676,7 @@ TreeGridNode.prototype.addChildren = function(nodes, insertBefore)
|
||||||
this.childrenByKey[child.key] = child;
|
this.childrenByKey[child.key] = child;
|
||||||
// TODO: fix sticky only once per whole node batch, including children
|
// TODO: fix sticky only once per whole node batch, including children
|
||||||
if (this.grid.stickyInit && !this.collapsed)
|
if (this.grid.stickyInit && !this.collapsed)
|
||||||
trs.push(child.tr);
|
trs.push.apply(trs, child.tr);
|
||||||
}
|
}
|
||||||
for (var i = insertBefore+nodes.length; i < this.children.length; i++)
|
for (var i = insertBefore+nodes.length; i < this.children.length; i++)
|
||||||
this.children[i]._index += nodes.length;
|
this.children[i]._index += nodes.length;
|
||||||
|
|
|
@ -24,11 +24,17 @@ table.stickyheaders td:nth-child(2) { width: 25%; }
|
||||||
var i = {
|
var i = {
|
||||||
data: [ 'Payment from Gazprom to Shell 2', '4', '5' ]
|
data: [ 'Payment from Gazprom to Shell 2', '4', '5' ]
|
||||||
};
|
};
|
||||||
var TG = new TreeGrid({ header: [], items: [], renderer: function(node)
|
var TG = new TreeGrid({ header: [], items: [], bind: { rowSpan: true }, renderer: function(node)
|
||||||
{
|
{
|
||||||
var cells = [];
|
var cells = [];
|
||||||
for (var i = 0; i < node.data.length; i++)
|
for (var i = 0; i < node.data.length; i++)
|
||||||
cells[i] = { innerHTML: node.data[i] };
|
cells[i] = { innerHTML: node.data[i] };
|
||||||
|
if (node.tr.length == 2)
|
||||||
|
{
|
||||||
|
cells = [ cells.slice(0), cells.slice(0) ];
|
||||||
|
cells[0][0].rowSpan = 2;
|
||||||
|
cells[1][0] = { style: 'display: none' };
|
||||||
|
}
|
||||||
return cells;
|
return cells;
|
||||||
} });
|
} });
|
||||||
TG.table.className = 'grid';
|
TG.table.className = 'grid';
|
||||||
|
@ -38,7 +44,8 @@ table.stickyheaders td:nth-child(2) { width: 25%; }
|
||||||
TG.root.setChildren(false, [ {
|
TG.root.setChildren(false, [ {
|
||||||
data: [ 'Payment from Gazprom to Shell', '2', '3' ]
|
data: [ 'Payment from Gazprom to Shell', '2', '3' ]
|
||||||
}, {
|
}, {
|
||||||
data: [ 'Payment from Gazprom to Shell', '2', '31203' ]
|
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', '329x<br>2' ]
|
||||||
}, {
|
}, {
|
||||||
|
|
Loading…
Reference in New Issue