From ede717a8f1d3cb727162a8333500a39a8c7992b5 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Sun, 5 Jun 2016 01:17:51 +0300 Subject: [PATCH] Use renderer and fast rectangle selection --- treegrid.css | 10 ++++ treegrid.js | 144 ++++++++++++++++++++++++++++++++--------------- treegridtest.htm | 12 +++- 3 files changed, 118 insertions(+), 48 deletions(-) diff --git a/treegrid.css b/treegrid.css index 042d68d..afc48a8 100644 --- a/treegrid.css +++ b/treegrid.css @@ -95,6 +95,16 @@ table.grid .collapser-expanded display: none; } +.disable-text-select +{ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + /* cell editor */ table.grid .celleditor { diff --git a/treegrid.js b/treegrid.js index 904923f..c3b35a0 100644 --- a/treegrid.js +++ b/treegrid.js @@ -1,17 +1,19 @@ /** * Very simple and fast tree grid/table, compatible with dynamic loading and jQuery fixedHeaderTable * License: MPL 2.0+, (c) Vitaliy Filippov 2016+ - * Version: 2016-05-18 + * Version: 2016-06-05 */ /** * USAGE: * - * var TG = new TreeGrid(items, header); + * var TG = new TreeGrid({ items: items, header: header, renderer: renderer }); * document.body.appendChild(TG.table); * + * // renderer is a function that generates cell HTML properties for a node + * renderer = function(node) { return [ { innerHTML, style, className, title }, ... ] } + * * items: [ node={ - * cells: [ 'html' or { innerHTML, style, className, title }, ... ], * children: [ node... ], * leaf: true/false, * collapsed: true/false, @@ -36,6 +38,9 @@ * // expand/collapse a node * node.toggle(); * + * // change node.data and re-render node + * node.render(); + * * // use simple cell editing * TG.initCellEditing(); * @@ -60,8 +65,9 @@ * TG.onStopCellEdit = function (node, colIndex, value, td) { return { html: } } * */ -function TreeGrid(items, header) +function TreeGrid(options) { + this.renderer = options.renderer; this.bindProps = { 'style': 1, 'className': 1, 'title': 1, 'innerHTML': 1 }; this.levelIndent = 20; this.table = document.createElement('table'); @@ -71,8 +77,8 @@ function TreeGrid(items, header) this.table.appendChild(this.thead); this.table.appendChild(this.tbody); this.thead.appendChild(document.createElement('tr')); - this.setHeader(header); - new TreeGridNode({ children: items }, this); + this.setHeader(options.header); + new TreeGridNode({ children: options.items }, this); } TreeGrid.prototype.setHeader = function(newHeader) @@ -113,7 +119,7 @@ TreeGrid.prototype._setProps = function(el, props) // Simple cell editing -TreeGrid.prototype.getNodeIndex = function(n) +TreeGrid.prototype._getCellIndex = function(n) { var i = 0; while ((n = n.previousSibling)) @@ -133,7 +139,7 @@ TreeGrid.prototype.initCellEditing = function() td = td.parentNode; if (td.nodeName == 'TD' && td.parentNode._node && td.previousSibling && td.className != 'celleditor') { - var params = self.onStartCellEdit && self.onStartCellEdit(td.parentNode._node, self.getNodeIndex(td), td) || {}; + var params = self.onStartCellEdit && self.onStartCellEdit(td.parentNode._node, self._getCellIndex(td), td) || {}; if (params.abort) return; self.editedCells.push(td); @@ -166,14 +172,11 @@ TreeGrid.prototype.initCellEditing = function() TreeGrid.prototype.stopCellEditing = function(td, _int) { - var i = this.getNodeIndex(td); - var params = this.onStopCellEdit && this.onStopCellEdit(td.parentNode._node, i, td.firstChild.value, td) || {}; - td.innerHTML = params.html === undefined || params.html === null ? td.firstChild.value : params.html; - if (typeof td.parentNode._node.cells[i] == 'object') - td.parentNode._node.cells[i].innerHTML = td.innerHTML; - else - td.parentNode._node.cells[i] = td.innerHTML; - td.className = ''; + var i = this._getCellIndex(td); + var node = td.parentNode._node; + node._oldCells[i] = undefined; + var params = this.onStopCellEdit && this.onStopCellEdit(node, i, td.firstChild.value, td) || {}; + node.render(i); if (!_int) { for (var i = 0; i < this.editedCells.length; i++) @@ -194,19 +197,20 @@ TreeGrid.prototype.initCellSelection = function(restrictToNode) var startDrag, dragDiv; var self = this; + self.table.className += ' disable-text-select'; self.selectCell = function(cell) { if (!cell.parentNode._node || cell.className.indexOf(' selected') >= 0) return; - var i = self.getNodeIndex(cell); + var i = self._getCellIndex(cell); if (!self.onCellSelect || self.onCellSelect(cell.parentNode._node, i, cell)) cell.className += ' selected'; }; self.deselectCell = function(cell) { - self.onCellDeselect && self.onCellDeselect(cell.parentNode._node, self.getNodeIndex(cell), cell); + self.onCellDeselect && self.onCellDeselect(cell.parentNode._node, self._getCellIndex(cell), cell); cell.className = cell.className.replace(' selected', ''); }; @@ -288,7 +292,43 @@ TreeGrid.prototype.initCellSelection = function(restrictToNode) } startDrag = null; if (x2 > x1+10 && y2 > y1+10) - rectangleSelect(self.table.getElementsByTagName('td'), x1, y1, x2, y2, self.selectCell); + { + var bodyPos = getOffset(self.tbody); + if (x1 < bodyPos.left+self.tbody.offsetWidth && + y1 < bodyPos.top+self.tbody.offsetHeight && + x2 > bodyPos.left && y2 > bodyPos.top) + { + if (x1 <= bodyPos.left) + x1 = bodyPos.left+1; + if (y1 <= bodyPos.top) + y1 = bodyPos.top+1; + if (x2 >= bodyPos.left+self.tbody.offsetWidth) + x2 = bodyPos.left+self.tbody.offsetWidth-1; + if (y2 >= bodyPos.top+self.tbody.offsetHeight) + y2 = bodyPos.top+self.tbody.offsetHeight-1; + var e1 = document.elementFromPoint(x1, y1); + var e2 = document.elementFromPoint(x2, y2); + while (e1 && e1.nodeName != 'TD') + e1 = e1.parentNode; + while (e2 && e2.nodeName != 'TD') + e2 = e2.parentNode; + if (e1 && e2) + { + var col1 = self._getCellIndex(e1); + var col2 = self._getCellIndex(e2); + var row = e1.parentNode; + do + { + if (row.style.display != 'none') + for (var i = col1; i <= col2; i++) + self.selectCell(row.cells[i]); + if (row == e2.parentNode) + break; + row = row.nextSibling; + } while (row); + } + } + } else { var t = evt.target || evt.srcElement; @@ -303,18 +343,6 @@ TreeGrid.prototype.initCellSelection = function(restrictToNode) } } }); - - function rectangleSelect(elements, x1, y1, x2, y2, cb) - { - for (var i = 0; i < elements.length; i++) - { - var offset = getOffset(elements[i]); - var w = elements[i].offsetWidth; - var h = elements[i].offsetHeight; - if (offset.left+w >= x1 && offset.top+h >= y1 && offset.left <= x2 && offset.top <= y2) - cb(elements[i]); - } - } } // Tree grid node class @@ -332,19 +360,12 @@ function TreeGridNode(node, grid, level, insertBefore, startHidden) { this.leaf = node.leaf; this.collapsed = node.collapsed || !node.leaf && (!node.children || !node.children.length); - this.cells = node.cells || []; this.tr = document.createElement('tr'); if (startHidden) this.tr.style.display = 'none'; this.tr._node = this; - for (var i = 0; i < this.grid.header.length; i++) - { - var td = document.createElement('td'); - if (this.cells[i]) - grid._setProps(td, this.cells[i]); - this.tr.appendChild(td); - } - this.addCollapser(); + this._oldCells = []; + this.render(); insertBefore ? grid.tbody.insertBefore(this.tr, insertBefore) : grid.tbody.appendChild(this.tr); } this.children = []; @@ -353,7 +374,7 @@ function TreeGridNode(node, grid, level, insertBefore, startHidden) this.children.push(new TreeGridNode(node.children[i], grid, this.level+1, insertBefore, this.collapsed)); } -TreeGridNode.prototype.addCollapser = function() +TreeGridNode.prototype._addCollapser = function() { var collapser = document.createElement('div'); if (this.leaf) @@ -377,11 +398,44 @@ TreeGridNode.prototype.addCollapser = function() c0.style.paddingLeft = (this.grid._initialPadding + this.grid.levelIndent * this.level + 20) + 'px'; } -TreeGridNode.prototype.refreshCells = function() +TreeGridNode.prototype._hashEqual = function(a, b) { - for (var i = 0; i < this.grid.header.length; i++) - this.grid._setProps(this.tr.cells[i], this.cells[i]); - this.addCollapser(); + for (var i in a) + if (b[i] !== a[i]) + return false; + for (var i in b) + if (b[i] !== a[i]) + return false; + return true; +} + +TreeGridNode.prototype._renderCell = function(i, cell) +{ + if (!this.tr.cells[i]) + 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); + if (i == 0) + this._addCollapser(); + } +} + +TreeGridNode.prototype.render = function(colidx) +{ + var cells = this.grid.renderer(this, colidx); + if (colidx !== null && colidx !== undefined && colidx >= 0 && colidx < this.grid.header.length) + { + this._renderCell(colidx, cells[colidx]); + this._oldCells[colidx] = cells[colidx]; + } + else + { + for (var i = 0; i < this.grid.header.length; i++) + this._renderCell(i, cells[i]); + this._oldCells = cells; + } } TreeGridNode.prototype._getToggleHandler = function() diff --git a/treegridtest.htm b/treegridtest.htm index 3648318..e211f2d 100644 --- a/treegridtest.htm +++ b/treegridtest.htm @@ -17,16 +17,22 @@ onDomReady(function() { var i = { - cells: [ 'Payment from Gazprom to Shell 2', '4', '5' ] + data: [ 'Payment from Gazprom to Shell 2', '4', '5' ] }; - var TG = new TreeGrid([], []); + var TG = new TreeGrid({ header: [], items: [], renderer: function(node) + { + var cells = []; + for (var i = 0; i < node.data.length; i++) + cells[i] = { innerHTML: node.data[i] }; + return cells; + } }); TG.table.className = 'grid'; document.getElementById('innerdiv').appendChild(TG.table); $(TG.table).fixedHeaderTable({}); setTimeout(function() { TG.setHeader([ '', '15 фев', '16 фев', '17 фев', '18 фев', '19 фев', '20 фев', '21 фев' ]); TG.root.setChildren(false, [ { - cells: [ 'Payment from Gazprom to Shell', '2', '3' ] + data: [ 'Payment from Gazprom to Shell', '2', '3' ] } ]); $(TG.table).fixedHeaderTable({}); }, 1000);