From ec7a3e702dd0f751f65addb5fa9da56b06daa18c Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Fri, 7 Oct 2016 18:11:29 +0300 Subject: [PATCH] Support multi-row nodes, remove experimental & dead syncNodes --- treegrid.js | 179 +++++++++++++++++++++-------------------------- treegridtest.htm | 11 ++- 2 files changed, 88 insertions(+), 102 deletions(-) diff --git a/treegrid.js b/treegrid.js index da222e2..fc87030 100644 --- a/treegrid.js +++ b/treegrid.js @@ -1,7 +1,7 @@ /** * Very simple and fast tree grid/table, compatible with dynamic loading and stickyheaders.js * 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); * * // 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 }, ... ] } * * items: [ node={ @@ -18,6 +19,7 @@ * children: [ node... ], * leaf: true/false, * collapsed: true/false, + * [ rows: ], * data: * }, ... ] * @@ -70,6 +72,9 @@ function TreeGrid(options) { this.renderer = options.renderer; 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.table = document.createElement('table'); this.table.className = 'grid'; @@ -395,13 +400,21 @@ function TreeGridNode(node, grid, level, insertBefore, startHidden) { this.leaf = node.leaf; this.collapsed = node.collapsed || !node.leaf && (!node.children || !node.children.length); - this.tr = document.createElement('tr'); - if (startHidden) - this.tr.style.display = 'none'; - this.tr._node = this; + 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); + } this._oldCells = []; 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.childrenByKey = {}; @@ -416,7 +429,7 @@ function TreeGridNode(node, grid, level, insertBefore, startHidden) if (child.key !== undefined) this.childrenByKey[child.key] = child; if (grid.stickyInit && !this.collapsed) - trs.push(child.tr); + trs.push.apply(trs, child.tr); } if (trs.length) fixStickyRows(grid.table, trs); @@ -443,7 +456,7 @@ TreeGridNode.prototype._addCollapser = function() collapser.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded'; 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); if (this.grid._initialPadding === undefined) { @@ -468,33 +481,59 @@ TreeGridNode.prototype._hashEqual = function(a, b) 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 - var ri = this.tr.cells.length && this.tr.cells[0].className.indexOf(' _scri') >= 0 ? (i > 0 ? i+1 : 0) : i; - if (!this.tr.cells[ri]) - this.tr.appendChild(document.createElement('td')); + var tr = this.tr[rowIndex]; + var ri = tr.cells.length && tr.cells[0].className.indexOf(' _scri') >= 0 ? (colIndex > 0 ? colIndex+1 : 0) : colIndex; + while (!tr.cells[ri]) + tr.appendChild(document.createElement('td')); // 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); - if (i == 0) + this.grid._setProps(tr.cells[ri], cell); + if (rowIndex == 0 && colIndex == 0) this._addCollapser(); } } -TreeGridNode.prototype.render = function(colidx) +TreeGridNode.prototype.render = function(colidx, rowidx) { - var cells = this.grid.renderer(this, colidx); - if (colidx !== null && colidx !== undefined && colidx >= 0 && colidx < this.grid.header.length) + var cells = this.grid.renderer(this, colidx, rowidx); + 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]); - this._oldCells[colidx] = cells[colidx]; + if (colidx !== undefined) + { + 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 { - for (var i = 0; i < this.grid.header.length; i++) - this._renderCell(i, cells[i]); + for (var i = 0; i < this.tr.length; i++) + for (var j = 0; j < this.grid.header.length; j++) + this._renderCell(i, j, cells[i] && cells[i][j]); this._oldCells = cells; } } @@ -519,11 +558,11 @@ TreeGridNode.prototype.toggle = function() if (this.leaf) return; 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) { // collapse all children - var c = this.tr.nextSibling; + var c = this.tr[this.tr.length-1].nextSibling; while (c && c._node.level > this.level) { c.style.display = 'none'; @@ -538,11 +577,14 @@ TreeGridNode.prototype.toggle = function() while (st.length) { 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) st = st.concat(e.children); - if (this.grid.stickyInit) - trs.push(e.tr); } if (trs.length) initStickyRows(this.grid.table, trs, true); @@ -557,19 +599,20 @@ TreeGridNode.prototype.setChildren = function(isLeaf, newChildren) this.grid.tbody.innerHTML = ''; else { - while (this.tr.nextSibling && this.tr.nextSibling._node.level > this.level) - this.grid.tbody.removeChild(this.tr.nextSibling); + var tr = this.tr[this.tr.length-1]; + while (tr.nextSibling && tr.nextSibling._node.level > this.level) + this.grid.tbody.removeChild(tr.nextSibling); if (this.leaf != isLeaf) { if (isLeaf) { - this.tr.cells[0].firstChild.className = 'collapser collapser-inactive'; - removeListener(this.tr.cells[0].firstChild, 'click', this._getToggleHandler()); + this.tr[0].cells[0].firstChild.className = 'collapser collapser-inactive'; + removeListener(this.tr[0].cells[0].firstChild, 'click', this._getToggleHandler()); } else { - this.tr.cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded'; - addListener(this.tr.cells[0].firstChild, 'click', this._getToggleHandler()); + this.tr[0].cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded'; + addListener(this.tr[0].cells[0].firstChild, 'click', this._getToggleHandler()); } } } @@ -579,70 +622,6 @@ TreeGridNode.prototype.setChildren = function(isLeaf, 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) { if (nodeOrIndex instanceof TreeGridNode) @@ -683,10 +662,10 @@ TreeGridNode.prototype.addChildren = function(nodes, insertBefore) e = this; while (e.children.length) 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 - e = this.children[insertBefore].tr; + e = this.children[insertBefore].tr[0]; var trs = []; for (var i = 0; i < nodes.length; i++) { @@ -697,7 +676,7 @@ TreeGridNode.prototype.addChildren = function(nodes, insertBefore) this.childrenByKey[child.key] = child; // TODO: fix sticky only once per whole node batch, including children 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++) this.children[i]._index += nodes.length; diff --git a/treegridtest.htm b/treegridtest.htm index fcbff18..eb9c9be 100644 --- a/treegridtest.htm +++ b/treegridtest.htm @@ -24,11 +24,17 @@ table.stickyheaders td:nth-child(2) { width: 25%; } var i = { 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 = []; for (var i = 0; i < node.data.length; 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; } }); TG.table.className = 'grid'; @@ -38,7 +44,8 @@ table.stickyheaders td:nth-child(2) { width: 25%; } TG.root.setChildren(false, [ { 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
2' ] }, {