var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } import { addClass, empty, getScrollbarWidth, hasClass, innerHeight, outerWidth } from './../../../helpers/dom/element'; import Overlay from './overlay/_base'; var performanceWarningAppeared = false; /** * @class TableRenderer */ var TableRenderer = function () { /** * @param {WalkontableTable} wtTable */ function TableRenderer(wtTable) { _classCallCheck(this, TableRenderer); this.wtTable = wtTable; this.wot = wtTable.instance; // legacy support this.instance = wtTable.instance; this.rowFilter = wtTable.rowFilter; this.columnFilter = wtTable.columnFilter; this.TABLE = wtTable.TABLE; this.THEAD = wtTable.THEAD; this.TBODY = wtTable.TBODY; this.COLGROUP = wtTable.COLGROUP; this.rowHeaders = []; this.rowHeaderCount = 0; this.columnHeaders = []; this.columnHeaderCount = 0; this.fixedRowsTop = 0; this.fixedRowsBottom = 0; } /** * */ _createClass(TableRenderer, [{ key: 'render', value: function render() { if (!this.wtTable.isWorkingOnClone()) { var skipRender = {}; this.wot.getSetting('beforeDraw', true, skipRender); if (skipRender.skipRender === true) { return; } } this.rowHeaders = this.wot.getSetting('rowHeaders'); this.rowHeaderCount = this.rowHeaders.length; this.fixedRowsTop = this.wot.getSetting('fixedRowsTop'); this.fixedRowsBottom = this.wot.getSetting('fixedRowsBottom'); this.columnHeaders = this.wot.getSetting('columnHeaders'); this.columnHeaderCount = this.columnHeaders.length; var columnsToRender = this.wtTable.getRenderedColumnsCount(); var rowsToRender = this.wtTable.getRenderedRowsCount(); var totalColumns = this.wot.getSetting('totalColumns'); var totalRows = this.wot.getSetting('totalRows'); var workspaceWidth = void 0; var adjusted = false; if (Overlay.isOverlayTypeOf(this.wot.cloneOverlay, Overlay.CLONE_BOTTOM) || Overlay.isOverlayTypeOf(this.wot.cloneOverlay, Overlay.CLONE_BOTTOM_LEFT_CORNER)) { // do NOT render headers on the bottom or bottom-left corner overlay this.columnHeaders = []; this.columnHeaderCount = 0; } if (totalColumns >= 0) { // prepare COL and TH elements for rendering this.adjustAvailableNodes(); adjusted = true; // adjust column widths according to user widths settings this.renderColumnHeaders(); // Render table rows this.renderRows(totalRows, rowsToRender, columnsToRender); if (!this.wtTable.isWorkingOnClone()) { workspaceWidth = this.wot.wtViewport.getWorkspaceWidth(); this.wot.wtViewport.containerWidth = null; } this.adjustColumnWidths(columnsToRender); this.markOversizedColumnHeaders(); this.adjustColumnHeaderHeights(); } if (!adjusted) { this.adjustAvailableNodes(); } this.removeRedundantRows(rowsToRender); if (!this.wtTable.isWorkingOnClone() || this.wot.isOverlayName(Overlay.CLONE_BOTTOM)) { this.markOversizedRows(); } if (!this.wtTable.isWorkingOnClone()) { this.wot.wtViewport.createVisibleCalculators(); this.wot.wtOverlays.refresh(false); this.wot.wtOverlays.applyToDOM(); var hiderWidth = outerWidth(this.wtTable.hider); var tableWidth = outerWidth(this.wtTable.TABLE); if (hiderWidth !== 0 && tableWidth !== hiderWidth) { // Recalculate the column widths, if width changes made in the overlays removed the scrollbar, thus changing the viewport width. this.adjustColumnWidths(columnsToRender); } if (workspaceWidth !== this.wot.wtViewport.getWorkspaceWidth()) { // workspace width changed though to shown/hidden vertical scrollbar. Let's reapply stretching this.wot.wtViewport.containerWidth = null; var firstRendered = this.wtTable.getFirstRenderedColumn(); var lastRendered = this.wtTable.getLastRenderedColumn(); var defaultColumnWidth = this.wot.getSetting('defaultColumnWidth'); var rowHeaderWidthSetting = this.wot.getSetting('rowHeaderWidth'); rowHeaderWidthSetting = this.instance.getSetting('onModifyRowHeaderWidth', rowHeaderWidthSetting); if (rowHeaderWidthSetting != null) { for (var i = 0; i < this.rowHeaderCount; i++) { var width = Array.isArray(rowHeaderWidthSetting) ? rowHeaderWidthSetting[i] : rowHeaderWidthSetting; width = width == null ? defaultColumnWidth : width; this.COLGROUP.childNodes[i].style.width = width + 'px'; } } for (var _i = firstRendered; _i < lastRendered; _i++) { var _width = this.wtTable.getStretchedColumnWidth(_i); var renderedIndex = this.columnFilter.sourceToRendered(_i); this.COLGROUP.childNodes[renderedIndex + this.rowHeaderCount].style.width = _width + 'px'; } } this.wot.getSetting('onDraw', true); } else if (this.wot.isOverlayName(Overlay.CLONE_BOTTOM)) { this.wot.cloneSource.wtOverlays.adjustElementsSize(); } } /** * @param {Number} renderedRowsCount */ }, { key: 'removeRedundantRows', value: function removeRedundantRows(renderedRowsCount) { while (this.wtTable.tbodyChildrenLength > renderedRowsCount) { this.TBODY.removeChild(this.TBODY.lastChild); this.wtTable.tbodyChildrenLength--; } } /** * @param {Number} totalRows * @param {Number} rowsToRender * @param {Number} columnsToRender */ }, { key: 'renderRows', value: function renderRows(totalRows, rowsToRender, columnsToRender) { var lastTD = void 0, TR = void 0; var visibleRowIndex = 0; var sourceRowIndex = this.rowFilter.renderedToSource(visibleRowIndex); var isWorkingOnClone = this.wtTable.isWorkingOnClone(); while (sourceRowIndex < totalRows && sourceRowIndex >= 0) { if (!performanceWarningAppeared && visibleRowIndex > 1000) { performanceWarningAppeared = true; console.warn('Performance tip: Handsontable rendered more than 1000 visible rows. Consider limiting the number ' + 'of rendered rows by specifying the table height and/or turning off the "renderAllRows" option.'); } if (rowsToRender !== void 0 && visibleRowIndex === rowsToRender) { // We have as much rows as needed for this clone break; } TR = this.getOrCreateTrForRow(visibleRowIndex, TR); // Render row headers this.renderRowHeaders(sourceRowIndex, TR); // Add and/or remove TDs to TR to match the desired number this.adjustColumns(TR, columnsToRender + this.rowHeaderCount); lastTD = this.renderCells(sourceRowIndex, TR, columnsToRender); if (!isWorkingOnClone || // Necessary to refresh oversized row heights after editing cell in overlays this.wot.isOverlayName(Overlay.CLONE_BOTTOM)) { // Reset the oversized row cache for this row this.resetOversizedRow(sourceRowIndex); } if (TR.firstChild) { // if I have 2 fixed columns with one-line content and the 3rd column has a multiline content, this is // the way to make sure that the overlay will has same row height var height = this.wot.wtTable.getRowHeight(sourceRowIndex); if (height) { // Decrease height. 1 pixel will be "replaced" by 1px border top height--; TR.firstChild.style.height = height + 'px'; } else { TR.firstChild.style.height = ''; } } visibleRowIndex++; sourceRowIndex = this.rowFilter.renderedToSource(visibleRowIndex); } } /** * Reset the oversized row cache for the provided index * * @param {Number} sourceRow Row index */ }, { key: 'resetOversizedRow', value: function resetOversizedRow(sourceRow) { if (this.wot.getSetting('externalRowCalculator')) { return; } if (this.wot.wtViewport.oversizedRows && this.wot.wtViewport.oversizedRows[sourceRow]) { this.wot.wtViewport.oversizedRows[sourceRow] = void 0; } } /** * Check if any of the rendered rows is higher than expected, and if so, cache them */ }, { key: 'markOversizedRows', value: function markOversizedRows() { if (this.wot.getSetting('externalRowCalculator')) { return; } var rowCount = this.instance.wtTable.TBODY.childNodes.length; var expectedTableHeight = rowCount * this.instance.wtSettings.settings.defaultRowHeight; var actualTableHeight = innerHeight(this.instance.wtTable.TBODY) - 1; var previousRowHeight = void 0; var rowInnerHeight = void 0; var sourceRowIndex = void 0; var currentTr = void 0; var rowHeader = void 0; var totalRows = this.instance.getSetting('totalRows'); if (expectedTableHeight === actualTableHeight && !this.instance.getSetting('fixedRowsBottom')) { // If the actual table height equals rowCount * default single row height, no row is oversized -> no need to iterate over them return; } while (rowCount) { rowCount--; sourceRowIndex = this.instance.wtTable.rowFilter.renderedToSource(rowCount); previousRowHeight = this.instance.wtTable.getRowHeight(sourceRowIndex); currentTr = this.instance.wtTable.getTrForRow(sourceRowIndex); rowHeader = currentTr.querySelector('th'); if (rowHeader) { rowInnerHeight = innerHeight(rowHeader); } else { rowInnerHeight = innerHeight(currentTr) - 1; } if (!previousRowHeight && this.instance.wtSettings.settings.defaultRowHeight < rowInnerHeight || previousRowHeight < rowInnerHeight) { this.instance.wtViewport.oversizedRows[sourceRowIndex] = ++rowInnerHeight; } } } /** * Check if any of the rendered columns is higher than expected, and if so, cache them. */ }, { key: 'markOversizedColumnHeaders', value: function markOversizedColumnHeaders() { var overlayName = this.wot.getOverlayName(); if (!this.columnHeaderCount || this.wot.wtViewport.hasOversizedColumnHeadersMarked[overlayName] || this.wtTable.isWorkingOnClone()) { return; } var columnCount = this.wtTable.getRenderedColumnsCount(); for (var i = 0; i < this.columnHeaderCount; i++) { for (var renderedColumnIndex = -1 * this.rowHeaderCount; renderedColumnIndex < columnCount; renderedColumnIndex++) { this.markIfOversizedColumnHeader(renderedColumnIndex); } } this.wot.wtViewport.hasOversizedColumnHeadersMarked[overlayName] = true; } /** * */ }, { key: 'adjustColumnHeaderHeights', value: function adjustColumnHeaderHeights() { var columnHeaders = this.wot.getSetting('columnHeaders'); var children = this.wot.wtTable.THEAD.childNodes; var oversizedColumnHeaders = this.wot.wtViewport.oversizedColumnHeaders; for (var i = 0, len = columnHeaders.length; i < len; i++) { if (oversizedColumnHeaders[i]) { if (!children[i] || children[i].childNodes.length === 0) { return; } children[i].childNodes[0].style.height = oversizedColumnHeaders[i] + 'px'; } } } /** * Check if column header for the specified column is higher than expected, and if so, cache it * * @param {Number} col Index of column */ }, { key: 'markIfOversizedColumnHeader', value: function markIfOversizedColumnHeader(col) { var sourceColIndex = this.wot.wtTable.columnFilter.renderedToSource(col); var level = this.columnHeaderCount; var defaultRowHeight = this.wot.wtSettings.settings.defaultRowHeight; var previousColHeaderHeight = void 0; var currentHeader = void 0; var currentHeaderHeight = void 0; var columnHeaderHeightSetting = this.wot.getSetting('columnHeaderHeight') || []; while (level) { level--; previousColHeaderHeight = this.wot.wtTable.getColumnHeaderHeight(level); currentHeader = this.wot.wtTable.getColumnHeader(sourceColIndex, level); if (!currentHeader) { /* eslint-disable no-continue */ continue; } currentHeaderHeight = innerHeight(currentHeader); if (!previousColHeaderHeight && defaultRowHeight < currentHeaderHeight || previousColHeaderHeight < currentHeaderHeight) { this.wot.wtViewport.oversizedColumnHeaders[level] = currentHeaderHeight; } if (Array.isArray(columnHeaderHeightSetting)) { if (columnHeaderHeightSetting[level] != null) { this.wot.wtViewport.oversizedColumnHeaders[level] = columnHeaderHeightSetting[level]; } } else if (!isNaN(columnHeaderHeightSetting)) { this.wot.wtViewport.oversizedColumnHeaders[level] = columnHeaderHeightSetting; } if (this.wot.wtViewport.oversizedColumnHeaders[level] < (columnHeaderHeightSetting[level] || columnHeaderHeightSetting)) { this.wot.wtViewport.oversizedColumnHeaders[level] = columnHeaderHeightSetting[level] || columnHeaderHeightSetting; } } } /** * @param {Number} sourceRowIndex * @param {HTMLTableRowElement} TR * @param {Number} columnsToRender * @returns {HTMLTableCellElement} */ }, { key: 'renderCells', value: function renderCells(sourceRowIndex, TR, columnsToRender) { var TD = void 0; var sourceColIndex = void 0; for (var visibleColIndex = 0; visibleColIndex < columnsToRender; visibleColIndex++) { sourceColIndex = this.columnFilter.renderedToSource(visibleColIndex); if (visibleColIndex === 0) { TD = TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(sourceColIndex)]; } else { TD = TD.nextSibling; // http://jsperf.com/nextsibling-vs-indexed-childnodes } // If the number of headers has been reduced, we need to replace excess TH with TD if (TD.nodeName == 'TH') { TD = replaceThWithTd(TD, TR); } if (!hasClass(TD, 'hide')) { TD.className = ''; } TD.removeAttribute('style'); this.wot.wtSettings.settings.cellRenderer(sourceRowIndex, sourceColIndex, TD); } return TD; } /** * @param {Number} columnsToRender Number of columns to render. */ }, { key: 'adjustColumnWidths', value: function adjustColumnWidths(columnsToRender) { var scrollbarCompensation = 0; var sourceInstance = this.wot.cloneSource ? this.wot.cloneSource : this.wot; var mainHolder = sourceInstance.wtTable.holder; var defaultColumnWidth = this.wot.getSetting('defaultColumnWidth'); var rowHeaderWidthSetting = this.wot.getSetting('rowHeaderWidth'); if (mainHolder.offsetHeight < mainHolder.scrollHeight) { scrollbarCompensation = getScrollbarWidth(); } this.wot.wtViewport.columnsRenderCalculator.refreshStretching(this.wot.wtViewport.getViewportWidth() - scrollbarCompensation); rowHeaderWidthSetting = this.instance.getSetting('onModifyRowHeaderWidth', rowHeaderWidthSetting); if (rowHeaderWidthSetting != null) { for (var i = 0; i < this.rowHeaderCount; i++) { var width = Array.isArray(rowHeaderWidthSetting) ? rowHeaderWidthSetting[i] : rowHeaderWidthSetting; width = width == null ? defaultColumnWidth : width; this.COLGROUP.childNodes[i].style.width = width + 'px'; } } for (var renderedColIndex = 0; renderedColIndex < columnsToRender; renderedColIndex++) { var _width2 = this.wtTable.getStretchedColumnWidth(this.columnFilter.renderedToSource(renderedColIndex)); this.COLGROUP.childNodes[renderedColIndex + this.rowHeaderCount].style.width = _width2 + 'px'; } } /** * @param {HTMLTableCellElement} TR */ }, { key: 'appendToTbody', value: function appendToTbody(TR) { this.TBODY.appendChild(TR); this.wtTable.tbodyChildrenLength++; } /** * @param {Number} rowIndex * @param {HTMLTableRowElement} currentTr * @returns {HTMLTableCellElement} */ }, { key: 'getOrCreateTrForRow', value: function getOrCreateTrForRow(rowIndex, currentTr) { var TR = void 0; if (rowIndex >= this.wtTable.tbodyChildrenLength) { TR = this.createRow(); this.appendToTbody(TR); } else if (rowIndex === 0) { TR = this.TBODY.firstChild; } else { // http://jsperf.com/nextsibling-vs-indexed-childnodes TR = currentTr.nextSibling; } if (TR.className) { TR.removeAttribute('class'); } return TR; } /** * @returns {HTMLTableCellElement} */ }, { key: 'createRow', value: function createRow() { var TR = document.createElement('TR'); for (var visibleColIndex = 0; visibleColIndex < this.rowHeaderCount; visibleColIndex++) { TR.appendChild(document.createElement('TH')); } return TR; } /** * @param {Number} row * @param {Number} col * @param {HTMLTableCellElement} TH */ }, { key: 'renderRowHeader', value: function renderRowHeader(row, col, TH) { TH.className = ''; TH.removeAttribute('style'); this.rowHeaders[col](row, TH, col); } /** * @param {Number} row * @param {HTMLTableCellElement} TR */ }, { key: 'renderRowHeaders', value: function renderRowHeaders(row, TR) { for (var TH = TR.firstChild, visibleColIndex = 0; visibleColIndex < this.rowHeaderCount; visibleColIndex++) { // If the number of row headers increased we need to create TH or replace an existing TD node with TH if (!TH) { TH = document.createElement('TH'); TR.appendChild(TH); } else if (TH.nodeName == 'TD') { TH = replaceTdWithTh(TH, TR); } this.renderRowHeader(row, visibleColIndex, TH); // http://jsperf.com/nextsibling-vs-indexed-childnodes TH = TH.nextSibling; } } /** * Adjust the number of COL and TH elements to match the number of columns and headers that need to be rendered */ }, { key: 'adjustAvailableNodes', value: function adjustAvailableNodes() { this.adjustColGroups(); this.adjustThead(); } /** * Renders the column headers */ }, { key: 'renderColumnHeaders', value: function renderColumnHeaders() { if (!this.columnHeaderCount) { return; } var columnCount = this.wtTable.getRenderedColumnsCount(); for (var i = 0; i < this.columnHeaderCount; i++) { var TR = this.getTrForColumnHeaders(i); for (var renderedColumnIndex = -1 * this.rowHeaderCount; renderedColumnIndex < columnCount; renderedColumnIndex++) { var sourceCol = this.columnFilter.renderedToSource(renderedColumnIndex); this.renderColumnHeader(i, sourceCol, TR.childNodes[renderedColumnIndex + this.rowHeaderCount]); } } } /** * Adjusts the number of COL elements to match the number of columns that need to be rendered */ }, { key: 'adjustColGroups', value: function adjustColGroups() { var columnCount = this.wtTable.getRenderedColumnsCount(); while (this.wtTable.colgroupChildrenLength < columnCount + this.rowHeaderCount) { this.COLGROUP.appendChild(document.createElement('COL')); this.wtTable.colgroupChildrenLength++; } while (this.wtTable.colgroupChildrenLength > columnCount + this.rowHeaderCount) { this.COLGROUP.removeChild(this.COLGROUP.lastChild); this.wtTable.colgroupChildrenLength--; } if (this.rowHeaderCount) { addClass(this.COLGROUP.childNodes[0], 'rowHeader'); } } /** * Adjusts the number of TH elements in THEAD to match the number of headers and columns that need to be rendered */ }, { key: 'adjustThead', value: function adjustThead() { var columnCount = this.wtTable.getRenderedColumnsCount(); var TR = this.THEAD.firstChild; if (this.columnHeaders.length) { for (var i = 0, len = this.columnHeaders.length; i < len; i++) { TR = this.THEAD.childNodes[i]; if (!TR) { TR = document.createElement('TR'); this.THEAD.appendChild(TR); } this.theadChildrenLength = TR.childNodes.length; while (this.theadChildrenLength < columnCount + this.rowHeaderCount) { TR.appendChild(document.createElement('TH')); this.theadChildrenLength++; } while (this.theadChildrenLength > columnCount + this.rowHeaderCount) { TR.removeChild(TR.lastChild); this.theadChildrenLength--; } } var theadChildrenLength = this.THEAD.childNodes.length; if (theadChildrenLength > this.columnHeaders.length) { for (var _i2 = this.columnHeaders.length; _i2 < theadChildrenLength; _i2++) { this.THEAD.removeChild(this.THEAD.lastChild); } } } else if (TR) { empty(TR); } } /** * @param {Number} index * @returns {HTMLTableCellElement} */ }, { key: 'getTrForColumnHeaders', value: function getTrForColumnHeaders(index) { return this.THEAD.childNodes[index]; } /** * @param {Number} row * @param {Number} col * @param {HTMLTableCellElement} TH * @returns {*} */ }, { key: 'renderColumnHeader', value: function renderColumnHeader(row, col, TH) { TH.className = ''; TH.removeAttribute('style'); return this.columnHeaders[row](col, TH, row); } /** * Add and/or remove the TDs to match the desired number * * @param {HTMLTableCellElement} TR Table row in question * @param {Number} desiredCount The desired number of TDs in the TR */ }, { key: 'adjustColumns', value: function adjustColumns(TR, desiredCount) { var count = TR.childNodes.length; while (count < desiredCount) { var TD = document.createElement('TD'); TR.appendChild(TD); count++; } while (count > desiredCount) { TR.removeChild(TR.lastChild); count--; } } /** * @param {Number} columnsToRender */ }, { key: 'removeRedundantColumns', value: function removeRedundantColumns(columnsToRender) { while (this.wtTable.tbodyChildrenLength > columnsToRender) { this.TBODY.removeChild(this.TBODY.lastChild); this.wtTable.tbodyChildrenLength--; } } }]); return TableRenderer; }(); function replaceTdWithTh(TD, TR) { var TH = document.createElement('TH'); TR.insertBefore(TH, TD); TR.removeChild(TD); return TH; } function replaceThWithTd(TH, TR) { var TD = document.createElement('TD'); TR.insertBefore(TD, TH); TR.removeChild(TH); return TD; } export default TableRenderer;