diff --git a/treegrid.css b/treegrid.css index 31d3bb7..6f3e321 100644 --- a/treegrid.css +++ b/treegrid.css @@ -184,8 +184,8 @@ table.grid .celleditor input { position: absolute; left: 0; + right: 0; top: 0; - width: 1px; z-index: 0; } diff --git a/treegrid.js b/treegrid.js index 036694b..79e0083 100644 --- a/treegrid.js +++ b/treegrid.js @@ -6,7 +6,7 @@ * - dynamic loading of child nodes * * License: MPL 2.0+, (c) Vitaliy Filippov 2016+ - * Version: 2017-07-10 + * Version: 2017-07-12 */ /** @@ -180,6 +180,7 @@ function TreeGrid(options) // table body wrapper this.wrapper.className = 'grid-wrapper'; this.tableWrapper = document.createElement('div'); + this.tableWrapper.tabIndex = 1; this.tableWrapper.className = 'grid-body-wrapper'; this.tableSizer = document.createElement('div'); this.tableSizer.className = 'grid-fixed-col-sizer'; @@ -242,9 +243,7 @@ function TreeGridNode(node, grid, parentNode, insertIndex, skipSync) if (node.children) { for (var i = 0; i < node.children.length; i++) - { new TreeGridNode(node.children[i], grid, this, i, true); - } } if (!skipSync) this.grid.syncView(); @@ -403,18 +402,6 @@ TreeGrid.prototype._syncStart = function(nodes) } TreeGrid.prototype._setPlaceholder = function(name, height) -{ - this.placeholders[name][0].firstChild.setAttribute('colspan', this.header.length-(this.fixedColBody ? 1 : 0)); - this.placeholders[name][0].style.height = height+'px'; - this.placeholders[name][0].style.display = height > 0 ? '' : 'none'; - if (this.fixedColBody) - { - this.placeholders[name][1].style.height = height+'px'; - this.placeholders[name][1].style.display = height > 0 ? '' : 'none'; - } -} - -TreeGrid.prototype._addPlaceholder = function(name, height) { if (!this.placeholders[name]) { @@ -427,7 +414,21 @@ TreeGrid.prototype._addPlaceholder = function(name, height) } } if (height !== undefined) - this._setPlaceholder(name, height); + { + this.placeholders[name][0].firstChild.setAttribute('colspan', this.header.length-(this.fixedColBody ? 1 : 0)); + this.placeholders[name][0].style.height = height+'px'; + this.placeholders[name][0].style.display = height > 0 ? '' : 'none'; + if (this.fixedColBody) + { + this.placeholders[name][1].style.height = height+'px'; + this.placeholders[name][1].style.display = height > 0 ? '' : 'none'; + } + } +} + +TreeGrid.prototype._addPlaceholder = function(name, height) +{ + this._setPlaceholder(name, height); this._syncStartNode(this.placeholders[name][0], this.placeholders[name][1]); } @@ -439,6 +440,13 @@ TreeGrid.prototype._finishSync = function() { if (!this.oldRenderedNodes[i]._reuse && this.oldRenderedNodes[i].tr) { + if (this.oldRenderedNodes[i].unrenderHooks) + { + var hooks = this.oldRenderedNodes[i].unrenderHooks; + for (var k in hooks) + hooks[k](); + delete this.oldRenderedNodes[i].unrenderHooks; + } this.oldRenderedNodes[i].tr = null; this.oldRenderedNodes[i].col_tr = null; this.oldRenderedNodes[i]._oldCells = []; @@ -503,13 +511,17 @@ TreeGrid.prototype.syncView = function() { if (!this.table.offsetParent) return; - var offsetHeight = this.wrapper.getBoundingClientRect().height - (this.stickyRow ? this.thead.getBoundingClientRect().height : 0); + var headerHeight = (this.stickyRow ? this.thead.getBoundingClientRect().height : 0); + var offsetHeight = this.tableWrapper.clientHeight - headerHeight; + var scrollTop = this.tableWrapper.scrollTop; var visibleItems = Math.ceil(offsetHeight/this.minItemHeight); var endStart = this.nodeCount - visibleItems; + var targetHeight; if (endStart < 0) endStart = 0; // Always render last items var lastNodes = this._findNodes(this.root, endStart, this.nodeCount-endStart); + this._setPlaceholder('top', 0); this._beginSync(); this._renderNodes(lastNodes); this._syncEnd(lastNodes); @@ -519,18 +531,18 @@ TreeGrid.prototype.syncView = function() var lastFirst = endStart+this._findVisibleNodeOffset(lastNodes, offsetHeight); var avgItemHeight = this.endItemHeight / lastNodes.length; avgItemHeight = (avgItemHeight < this.minItemHeight ? this.minItemHeight : avgItemHeight); + targetHeight = headerHeight + this.nodeCount*avgItemHeight; if (this.stickyColumn) - this.fixedColSizer.style.height = (this.nodeCount*avgItemHeight)+'px'; - this.tableSizer.style.height = (this.nodeCount*avgItemHeight)+'px'; - var tbwh = this.tableWrapper.getBoundingClientRect().height; - var scrollPos = this.nodeCount*avgItemHeight > tbwh ? - this.tableWrapper.scrollTop / (this.nodeCount*avgItemHeight - tbwh) : 0; + this.fixedColSizer.style.height = targetHeight+'px'; + this.tableSizer.style.height = targetHeight+'px'; + var scrollPos = targetHeight > offsetHeight ? scrollTop / (targetHeight - offsetHeight) : 0; if (scrollPos > 1) scrollPos = 1; var firstVisible = scrollPos*lastFirst; var rangeStart = Math.floor(firstVisible); var rangeCount = visibleItems; this._addPlaceholder('top', 0); + this._setPlaceholder('mid', 0); var firstItemHeight; if (rangeStart >= endStart) { @@ -554,7 +566,7 @@ TreeGrid.prototype.syncView = function() this._addPlaceholder('mid', 0); firstItemHeight = getNodeHeight(visibleNodes[0])*(firstVisible-rangeStart); } - this._setPlaceholder('top', this.tableWrapper.scrollTop-firstItemHeight); + this._setPlaceholder('top', scrollTop-firstItemHeight-headerHeight); } else if (this.stickyColumn) { @@ -571,12 +583,13 @@ TreeGrid.prototype.syncView = function() else { this.syncStickyRow(); - for (var i = 0; i < this.tbody.rows.length; i++) - total += (this.tbody.rows[i].getBoundingClientRect().height||0); + if (endStart > 0 && endStart > rangeStart+rangeCount) + for (var i = 0; i < this.tbody.rows.length; i++) + total += (this.tbody.rows[i].getBoundingClientRect().height||0); } if (endStart > 0 && endStart > rangeStart+rangeCount) { - this._setPlaceholder('mid', this.nodeCount*avgItemHeight - total - (this.tableWrapper.scrollTop-firstItemHeight)); + this._setPlaceholder('mid', targetHeight - total - (scrollTop-firstItemHeight)); } } @@ -594,9 +607,11 @@ TreeGrid.prototype.nodeOnHover = function(hover, ev) e = e.previousSibling; subrow++; } + if (!e._node.tr) + return; if (hover) { - if (this.hoveredNode) + if (this.hoveredNode && this.hoveredNode.tr) { this.hoveredNode.tr[this.hoveredRow].className = this.hoveredNode.tr[this.hoveredRow].className.replace(/ hover/g, ''); if (this.stickyColumn) @@ -834,11 +849,13 @@ TreeGrid.prototype.initCellEditing = function() td = td.parentNode; if (td.nodeName == 'TD' && td.parentNode._node && td.className.indexOf('celleditor') < 0) { - var params = self.onStartCellEdit && - self.onStartCellEdit(td.parentNode._node, self._getSubrow(td), self._getCellIndex(td, true), td) || {}; + var node = td.parentNode._node; + var subrow = self._getSubrow(td); + var cellIndex = self._getCellIndex(td, true); + var params = self.onStartCellEdit && self.onStartCellEdit(node, subrow, cellIndex, td) || {}; if (params.abort) return; - self.startCellEditing(td, params); + self.startCellEditing(td, params, node, subrow, cellIndex); } }); addListener(document.body, 'click', function(evt) @@ -851,14 +868,25 @@ TreeGrid.prototype.initCellEditing = function() if (td && /\bcelleditor\b/.exec(td.className)) return; for (var i = self.editedCells.length-1; i >= 0; i--) - self.stopCellEditing(self.editedCells[i], 1); + self.stopCellEditing(self.editedCells[i], true); self.editedCells = []; }); } -TreeGrid.prototype.startCellEditing = function(td, params) +TreeGrid.prototype.startCellEditing = function(td, params, node, subrow, cellIndex) { var self = this; + if (!node) + { + node = td.parentNode._node; + subrow = self._getSubrow(td); + cellIndex = self._getCellIndex(td, true); + } + node.unrenderHooks = node.unrenderHooks||{}; + node.unrenderHooks.editing = function() + { + self.stopCellEditing(td, false, node, subrow, cellIndex); + }; self.editedCells.push(td); if (params.value === undefined) params.value = td.innerHTML; @@ -884,18 +912,25 @@ TreeGrid.prototype.startCellEditing = function(td, params) inp.focus(); } -TreeGrid.prototype.stopCellEditing = function(td, _int) +TreeGrid.prototype.stopCellEditing = function(td, _int, node, subrow, cellIndex) { - var i = this._getCellIndex(td, true); - var subrow = this._getSubrow(td); - var node = td.parentNode._node; - node._oldCells[i] = undefined; + if (td.offsetParent) + { + node = td.parentNode._node; + subrow = this._getSubrow(td); + cellIndex = this._getCellIndex(td, true); + } + delete node.unrenderHooks.editing; td.style.width = td._origWidth; td.style.minWidth = td._origMinWidth; var inp = td.getElementsByTagName('input')[0]; + node._oldCells[cellIndex] = undefined; var params = this.onStopCellEdit && - this.onStopCellEdit(node, subrow, i, inp ? inp.value : null, td) || {}; - node.render(i, subrow, true, 0); + this.onStopCellEdit(node, subrow, cellIndex, inp ? inp.value : null, td) || {}; + if (td.offsetParent) + { + node.render(cellIndex, subrow, true, 0); + } if (!_int) { for (var i = 0; i < this.editedCells.length; i++) @@ -910,6 +945,7 @@ TreeGrid.prototype.stopCellEditing = function(td, _int) } // Simple cell selection +// FIXME add to unrenderHooks? TreeGrid.prototype.initCellSelection = function(useMultiple) { @@ -1252,20 +1288,30 @@ function setHiddenNodes(node) } } -TreeGridNode.prototype.toggle = function() +TreeGridNode.prototype.toggle = function(skipSync) { if (this.leaf) return; this.collapsed = !this.collapsed; - (this.col_tr || this.tr)[0].cells[0].firstChild.className = - this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded'; + if (this.tr) + { + (this.col_tr || this.tr)[0].cells[0].firstChild.className = + this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded'; + } setHiddenNodes(this); - this.grid.syncView(); + if (!skipSync) + this.grid.syncView(); } TreeGridNode.prototype.setChildren = function(isLeaf, newChildren, skipSync) { this.leaf = isLeaf; + if (this.visible) + { + this.visible = false; + setHiddenNodes(this); + this.visible = true; + } this.children = []; this.childrenByKey = {}; this.addChildren(newChildren, null, skipSync); @@ -1297,10 +1343,7 @@ TreeGridNode.prototype.addChildren = function(nodes, insertBefore, skipSync) e = this.children[insertBefore]; for (var i = 0; i < nodes.length; 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; + new TreeGridNode(nodes[i], this.grid, this, insertBefore+i, true); } if (!skipSync) this.grid.syncView();