@quantlab/handsontable
Version:
Spreadsheet-like data grid editor that provides copy/paste functionality compatible with Excel/Google Docs
740 lines (615 loc) • 24.6 kB
JavaScript
'use strict';
exports.__esModule = true;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
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; }; }();
var _element = require('./../../../helpers/dom/element');
var _function = require('./../../../helpers/function');
var _coords = require('./cell/coords');
var _coords2 = _interopRequireDefault(_coords);
var _range = require('./cell/range');
var _range2 = _interopRequireDefault(_range);
var _column = require('./filter/column');
var _column2 = _interopRequireDefault(_column);
var _row = require('./filter/row');
var _row2 = _interopRequireDefault(_row);
var _tableRenderer = require('./tableRenderer');
var _tableRenderer2 = _interopRequireDefault(_tableRenderer);
var _base = require('./overlay/_base');
var _base2 = _interopRequireDefault(_base);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
*
*/
var Table = function () {
/**
* @param {Walkontable} wotInstance
* @param {HTMLTableElement} table
*/
function Table(wotInstance, table) {
var _this = this;
_classCallCheck(this, Table);
this.wot = wotInstance;
// legacy support
this.instance = this.wot;
this.TABLE = table;
this.TBODY = null;
this.THEAD = null;
this.COLGROUP = null;
this.tableOffset = 0;
this.holderOffset = 0;
(0, _element.removeTextNodes)(this.TABLE);
this.spreader = this.createSpreader(this.TABLE);
this.hider = this.createHider(this.spreader);
this.holder = this.createHolder(this.hider);
this.wtRootElement = this.holder.parentNode;
this.alignOverlaysWithTrimmingContainer();
this.fixTableDomTree();
this.colgroupChildrenLength = this.COLGROUP.childNodes.length;
this.theadChildrenLength = this.THEAD.firstChild ? this.THEAD.firstChild.childNodes.length : 0;
this.tbodyChildrenLength = this.TBODY.childNodes.length;
this.rowFilter = null;
this.columnFilter = null;
this.correctHeaderWidth = false;
var origRowHeaderWidth = this.wot.wtSettings.settings.rowHeaderWidth;
// Fix for jumping row headers (https://github.com/handsontable/handsontable/issues/3850)
this.wot.wtSettings.settings.rowHeaderWidth = function () {
return _this._modifyRowHeaderWidth(origRowHeaderWidth);
};
}
/**
*
*/
_createClass(Table, [{
key: 'fixTableDomTree',
value: function fixTableDomTree() {
this.TBODY = this.TABLE.querySelector('tbody');
if (!this.TBODY) {
this.TBODY = document.createElement('tbody');
this.TABLE.appendChild(this.TBODY);
}
this.THEAD = this.TABLE.querySelector('thead');
if (!this.THEAD) {
this.THEAD = document.createElement('thead');
this.TABLE.insertBefore(this.THEAD, this.TBODY);
}
this.COLGROUP = this.TABLE.querySelector('colgroup');
if (!this.COLGROUP) {
this.COLGROUP = document.createElement('colgroup');
this.TABLE.insertBefore(this.COLGROUP, this.THEAD);
}
if (this.wot.getSetting('columnHeaders').length && !this.THEAD.childNodes.length) {
this.THEAD.appendChild(document.createElement('TR'));
}
}
/**
* @param table
* @returns {HTMLElement}
*/
}, {
key: 'createSpreader',
value: function createSpreader(table) {
var parent = table.parentNode;
var spreader = void 0;
if (!parent || parent.nodeType !== 1 || !(0, _element.hasClass)(parent, 'wtHolder')) {
spreader = document.createElement('div');
spreader.className = 'wtSpreader';
if (parent) {
// if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it
parent.insertBefore(spreader, table);
}
spreader.appendChild(table);
}
spreader.style.position = 'relative';
return spreader;
}
/**
* @param spreader
* @returns {HTMLElement}
*/
}, {
key: 'createHider',
value: function createHider(spreader) {
var parent = spreader.parentNode;
var hider = void 0;
if (!parent || parent.nodeType !== 1 || !(0, _element.hasClass)(parent, 'wtHolder')) {
hider = document.createElement('div');
hider.className = 'wtHider';
if (parent) {
// if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it
parent.insertBefore(hider, spreader);
}
hider.appendChild(spreader);
}
return hider;
}
/**
*
* @param hider
* @returns {HTMLElement}
*/
}, {
key: 'createHolder',
value: function createHolder(hider) {
var parent = hider.parentNode;
var holder = void 0;
if (!parent || parent.nodeType !== 1 || !(0, _element.hasClass)(parent, 'wtHolder')) {
holder = document.createElement('div');
holder.style.position = 'relative';
holder.className = 'wtHolder';
if (parent) {
// if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it
parent.insertBefore(holder, hider);
}
if (!this.isWorkingOnClone()) {
holder.parentNode.className += 'ht_master handsontable';
}
holder.appendChild(hider);
}
return holder;
}
}, {
key: 'alignOverlaysWithTrimmingContainer',
value: function alignOverlaysWithTrimmingContainer() {
var trimmingElement = (0, _element.getTrimmingContainer)(this.wtRootElement);
if (!this.isWorkingOnClone()) {
this.holder.parentNode.style.position = 'relative';
if (trimmingElement === window) {
var preventOverflow = this.wot.getSetting('preventOverflow');
if (!preventOverflow) {
this.holder.style.overflow = 'visible';
this.wtRootElement.style.overflow = 'visible';
}
} else {
this.holder.style.width = (0, _element.getStyle)(trimmingElement, 'width');
this.holder.style.height = (0, _element.getStyle)(trimmingElement, 'height');
this.holder.style.overflow = '';
}
}
}
}, {
key: 'isWorkingOnClone',
value: function isWorkingOnClone() {
return !!this.wot.cloneSource;
}
/**
* Redraws the table
*
* @param {Boolean} fastDraw If TRUE, will try to avoid full redraw and only update the border positions. If FALSE or UNDEFINED, will perform a full redraw
* @returns {Table}
*/
}, {
key: 'draw',
value: function draw(fastDraw) {
var _wot = this.wot,
wtOverlays = _wot.wtOverlays,
wtViewport = _wot.wtViewport;
var totalRows = this.instance.getSetting('totalRows');
var rowHeaders = this.wot.getSetting('rowHeaders').length;
var columnHeaders = this.wot.getSetting('columnHeaders').length;
var syncScroll = false;
if (!this.isWorkingOnClone()) {
this.holderOffset = (0, _element.offset)(this.holder);
fastDraw = wtViewport.createRenderCalculators(fastDraw);
if (rowHeaders && !this.wot.getSetting('fixedColumnsLeft')) {
var leftScrollPos = wtOverlays.leftOverlay.getScrollPosition();
var previousState = this.correctHeaderWidth;
this.correctHeaderWidth = leftScrollPos > 0;
if (previousState !== this.correctHeaderWidth) {
fastDraw = false;
}
}
}
if (!this.isWorkingOnClone()) {
syncScroll = wtOverlays.prepareOverlays();
}
if (fastDraw) {
if (!this.isWorkingOnClone()) {
// in case we only scrolled without redraw, update visible rows information in oldRowsCalculator
wtViewport.createVisibleCalculators();
}
if (wtOverlays) {
wtOverlays.refresh(true);
}
} else {
if (this.isWorkingOnClone()) {
this.tableOffset = this.wot.cloneSource.wtTable.tableOffset;
} else {
this.tableOffset = (0, _element.offset)(this.TABLE);
}
var startRow = void 0;
if (_base2.default.isOverlayTypeOf(this.wot.cloneOverlay, _base2.default.CLONE_DEBUG) || _base2.default.isOverlayTypeOf(this.wot.cloneOverlay, _base2.default.CLONE_TOP) || _base2.default.isOverlayTypeOf(this.wot.cloneOverlay, _base2.default.CLONE_TOP_LEFT_CORNER)) {
startRow = 0;
} else if (_base2.default.isOverlayTypeOf(this.instance.cloneOverlay, _base2.default.CLONE_BOTTOM) || _base2.default.isOverlayTypeOf(this.instance.cloneOverlay, _base2.default.CLONE_BOTTOM_LEFT_CORNER)) {
startRow = Math.max(totalRows - this.wot.getSetting('fixedRowsBottom'), 0);
} else {
startRow = wtViewport.rowsRenderCalculator.startRow;
}
var startColumn = void 0;
if (_base2.default.isOverlayTypeOf(this.wot.cloneOverlay, _base2.default.CLONE_DEBUG) || _base2.default.isOverlayTypeOf(this.wot.cloneOverlay, _base2.default.CLONE_LEFT) || _base2.default.isOverlayTypeOf(this.wot.cloneOverlay, _base2.default.CLONE_TOP_LEFT_CORNER) || _base2.default.isOverlayTypeOf(this.wot.cloneOverlay, _base2.default.CLONE_BOTTOM_LEFT_CORNER)) {
startColumn = 0;
} else {
startColumn = wtViewport.columnsRenderCalculator.startColumn;
}
this.rowFilter = new _row2.default(startRow, totalRows, columnHeaders);
this.columnFilter = new _column2.default(startColumn, this.wot.getSetting('totalColumns'), rowHeaders);
this.alignOverlaysWithTrimmingContainer();
this._doDraw(); // creates calculator after draw
}
this.refreshSelections(fastDraw);
if (!this.isWorkingOnClone()) {
wtOverlays.topOverlay.resetFixedPosition();
if (wtOverlays.bottomOverlay.clone) {
wtOverlays.bottomOverlay.resetFixedPosition();
}
wtOverlays.leftOverlay.resetFixedPosition();
if (wtOverlays.topLeftCornerOverlay) {
wtOverlays.topLeftCornerOverlay.resetFixedPosition();
}
if (wtOverlays.bottomLeftCornerOverlay && wtOverlays.bottomLeftCornerOverlay.clone) {
wtOverlays.bottomLeftCornerOverlay.resetFixedPosition();
}
}
if (syncScroll) {
wtOverlays.syncScrollWithMaster();
}
this.wot.drawn = true;
return this;
}
}, {
key: '_doDraw',
value: function _doDraw() {
var wtRenderer = new _tableRenderer2.default(this);
wtRenderer.render();
}
}, {
key: 'removeClassFromCells',
value: function removeClassFromCells(className) {
var nodes = this.TABLE.querySelectorAll('.' + className);
for (var i = 0, len = nodes.length; i < len; i++) {
(0, _element.removeClass)(nodes[i], className);
}
}
}, {
key: 'refreshSelections',
value: function refreshSelections(fastDraw) {
if (!this.wot.selections) {
return;
}
var len = this.wot.selections.length;
if (fastDraw) {
for (var i = 0; i < len; i++) {
// there was no rerender, so we need to remove classNames by ourselves
if (this.wot.selections[i].settings.className) {
this.removeClassFromCells(this.wot.selections[i].settings.className);
}
if (this.wot.selections[i].settings.highlightHeaderClassName) {
this.removeClassFromCells(this.wot.selections[i].settings.highlightHeaderClassName);
}
if (this.wot.selections[i].settings.highlightRowClassName) {
this.removeClassFromCells(this.wot.selections[i].settings.highlightRowClassName);
}
if (this.wot.selections[i].settings.highlightColumnClassName) {
this.removeClassFromCells(this.wot.selections[i].settings.highlightColumnClassName);
}
}
}
for (var _i = 0; _i < len; _i++) {
this.wot.selections[_i].draw(this.wot, fastDraw);
}
}
/**
* Get cell element at coords.
*
* @param {CellCoords} coords
* @returns {HTMLElement|Number} HTMLElement on success or Number one of the exit codes on error:
* -1 row before viewport
* -2 row after viewport
*/
}, {
key: 'getCell',
value: function getCell(coords) {
if (this.isRowBeforeRenderedRows(coords.row)) {
// row before rendered rows
return -1;
} else if (this.isRowAfterRenderedRows(coords.row)) {
// row after rendered rows
return -2;
}
var TR = this.TBODY.childNodes[this.rowFilter.sourceToRendered(coords.row)];
if (TR) {
return TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(coords.col)];
}
}
/**
* getColumnHeader
*
* @param {Number} col Column index
* @param {Number} [level=0] Header level (0 = most distant to the table)
* @returns {Object} HTMLElement on success or undefined on error
*/
}, {
key: 'getColumnHeader',
value: function getColumnHeader(col) {
var level = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var TR = this.THEAD.childNodes[level];
if (TR) {
return TR.childNodes[this.columnFilter.sourceColumnToVisibleRowHeadedColumn(col)];
}
}
/**
* getRowHeader
*
* @param {Number} row Row index
* @returns {HTMLElement} HTMLElement on success or Number one of the exit codes on error: `null table doesn't have row headers`
*/
}, {
key: 'getRowHeader',
value: function getRowHeader(row) {
if (this.columnFilter.sourceColumnToVisibleRowHeadedColumn(0) === 0) {
return null;
}
var TR = this.TBODY.childNodes[this.rowFilter.sourceToRendered(row)];
if (TR) {
return TR.childNodes[0];
}
}
/**
* Returns cell coords object for a given TD (or a child element of a TD element).
*
* @param {HTMLTableCellElement} TD A cell DOM element (or a child of one).
* @returns {CellCoords|null} The coordinates of the provided TD element (or the closest TD element) or null, if the provided element is not applicable.
*/
}, {
key: 'getCoords',
value: function getCoords(TD) {
if (TD.nodeName !== 'TD' && TD.nodeName !== 'TH') {
TD = (0, _element.closest)(TD, ['TD', 'TH']);
}
if (TD === null) {
return null;
}
var TR = TD.parentNode;
var CONTAINER = TR.parentNode;
var row = (0, _element.index)(TR);
var col = TD.cellIndex;
if ((0, _element.overlayContainsElement)(_base2.default.CLONE_TOP_LEFT_CORNER, TD) || (0, _element.overlayContainsElement)(_base2.default.CLONE_TOP, TD)) {
if (CONTAINER.nodeName === 'THEAD') {
row -= CONTAINER.childNodes.length;
}
} else if (CONTAINER === this.THEAD) {
row = this.rowFilter.visibleColHeadedRowToSourceRow(row);
} else {
row = this.rowFilter.renderedToSource(row);
}
if ((0, _element.overlayContainsElement)(_base2.default.CLONE_TOP_LEFT_CORNER, TD) || (0, _element.overlayContainsElement)(_base2.default.CLONE_LEFT, TD)) {
col = this.columnFilter.offsettedTH(col);
} else {
col = this.columnFilter.visibleRowHeadedColumnToSourceColumn(col);
}
return new _coords2.default(row, col);
}
}, {
key: 'getTrForRow',
value: function getTrForRow(row) {
return this.TBODY.childNodes[this.rowFilter.sourceToRendered(row)];
}
}, {
key: 'getFirstRenderedRow',
value: function getFirstRenderedRow() {
return this.wot.wtViewport.rowsRenderCalculator.startRow;
}
}, {
key: 'getFirstVisibleRow',
value: function getFirstVisibleRow() {
return this.wot.wtViewport.rowsVisibleCalculator.startRow;
}
}, {
key: 'getFirstRenderedColumn',
value: function getFirstRenderedColumn() {
return this.wot.wtViewport.columnsRenderCalculator.startColumn;
}
/**
* @returns {Number} Returns -1 if no row is visible
*/
}, {
key: 'getFirstVisibleColumn',
value: function getFirstVisibleColumn() {
return this.wot.wtViewport.columnsVisibleCalculator.startColumn;
}
/**
* @returns {Number} Returns -1 if no row is visible
*/
}, {
key: 'getLastRenderedRow',
value: function getLastRenderedRow() {
return this.wot.wtViewport.rowsRenderCalculator.endRow;
}
}, {
key: 'getLastVisibleRow',
value: function getLastVisibleRow() {
return this.wot.wtViewport.rowsVisibleCalculator.endRow;
}
}, {
key: 'getLastRenderedColumn',
value: function getLastRenderedColumn() {
return this.wot.wtViewport.columnsRenderCalculator.endColumn;
}
/**
* @returns {Number} Returns -1 if no column is visible
*/
}, {
key: 'getLastVisibleColumn',
value: function getLastVisibleColumn() {
return this.wot.wtViewport.columnsVisibleCalculator.endColumn;
}
}, {
key: 'isRowBeforeRenderedRows',
value: function isRowBeforeRenderedRows(row) {
return this.rowFilter && this.rowFilter.sourceToRendered(row) < 0 && row >= 0;
}
}, {
key: 'isRowAfterViewport',
value: function isRowAfterViewport(row) {
return this.rowFilter && this.rowFilter.sourceToRendered(row) > this.getLastVisibleRow();
}
}, {
key: 'isRowAfterRenderedRows',
value: function isRowAfterRenderedRows(row) {
return this.rowFilter && this.rowFilter.sourceToRendered(row) > this.getLastRenderedRow();
}
}, {
key: 'isColumnBeforeViewport',
value: function isColumnBeforeViewport(column) {
return this.columnFilter && this.columnFilter.sourceToRendered(column) < 0 && column >= 0;
}
}, {
key: 'isColumnAfterViewport',
value: function isColumnAfterViewport(column) {
return this.columnFilter && this.columnFilter.sourceToRendered(column) > this.getLastVisibleColumn();
}
}, {
key: 'isLastRowFullyVisible',
value: function isLastRowFullyVisible() {
return this.getLastVisibleRow() === this.getLastRenderedRow();
}
}, {
key: 'isLastColumnFullyVisible',
value: function isLastColumnFullyVisible() {
return this.getLastVisibleColumn() === this.getLastRenderedColumn();
}
}, {
key: 'getRenderedColumnsCount',
value: function getRenderedColumnsCount() {
var columnsCount = this.wot.wtViewport.columnsRenderCalculator.count;
var totalColumns = this.wot.getSetting('totalColumns');
if (this.wot.isOverlayName(_base2.default.CLONE_DEBUG)) {
columnsCount = totalColumns;
} else if (this.wot.isOverlayName(_base2.default.CLONE_LEFT) || this.wot.isOverlayName(_base2.default.CLONE_TOP_LEFT_CORNER) || this.wot.isOverlayName(_base2.default.CLONE_BOTTOM_LEFT_CORNER)) {
return Math.min(this.wot.getSetting('fixedColumnsLeft'), totalColumns);
}
return columnsCount;
}
}, {
key: 'getRenderedRowsCount',
value: function getRenderedRowsCount() {
var rowsCount = this.wot.wtViewport.rowsRenderCalculator.count;
var totalRows = this.wot.getSetting('totalRows');
if (this.wot.isOverlayName(_base2.default.CLONE_DEBUG)) {
rowsCount = totalRows;
} else if (this.wot.isOverlayName(_base2.default.CLONE_TOP) || this.wot.isOverlayName(_base2.default.CLONE_TOP_LEFT_CORNER)) {
rowsCount = Math.min(this.wot.getSetting('fixedRowsTop'), totalRows);
} else if (this.wot.isOverlayName(_base2.default.CLONE_BOTTOM) || this.wot.isOverlayName(_base2.default.CLONE_BOTTOM_LEFT_CORNER)) {
rowsCount = Math.min(this.wot.getSetting('fixedRowsBottom'), totalRows);
}
return rowsCount;
}
}, {
key: 'getVisibleRowsCount',
value: function getVisibleRowsCount() {
return this.wot.wtViewport.rowsVisibleCalculator.count;
}
}, {
key: 'allRowsInViewport',
value: function allRowsInViewport() {
return this.wot.getSetting('totalRows') == this.getVisibleRowsCount();
}
/**
* Checks if any of the row's cells content exceeds its initial height, and if so, returns the oversized height
*
* @param {Number} sourceRow
* @returns {Number}
*/
}, {
key: 'getRowHeight',
value: function getRowHeight(sourceRow) {
var height = this.wot.wtSettings.settings.rowHeight(sourceRow);
var oversizedHeight = this.wot.wtViewport.oversizedRows[sourceRow];
if (oversizedHeight !== void 0) {
height = height === void 0 ? oversizedHeight : Math.max(height, oversizedHeight);
}
return height;
}
}, {
key: 'getColumnHeaderHeight',
value: function getColumnHeaderHeight(level) {
var height = this.wot.wtSettings.settings.defaultRowHeight;
var oversizedHeight = this.wot.wtViewport.oversizedColumnHeaders[level];
if (oversizedHeight !== void 0) {
height = height ? Math.max(height, oversizedHeight) : oversizedHeight;
}
return height;
}
}, {
key: 'getVisibleColumnsCount',
value: function getVisibleColumnsCount() {
return this.wot.wtViewport.columnsVisibleCalculator.count;
}
}, {
key: 'allColumnsInViewport',
value: function allColumnsInViewport() {
return this.wot.getSetting('totalColumns') == this.getVisibleColumnsCount();
}
}, {
key: 'getColumnWidth',
value: function getColumnWidth(sourceColumn) {
var width = this.wot.wtSettings.settings.columnWidth;
if (typeof width === 'function') {
width = width(sourceColumn);
} else if ((typeof width === 'undefined' ? 'undefined' : _typeof(width)) === 'object') {
width = width[sourceColumn];
}
return width || this.wot.wtSettings.settings.defaultColumnWidth;
}
}, {
key: 'getStretchedColumnWidth',
value: function getStretchedColumnWidth(sourceColumn) {
var columnWidth = this.getColumnWidth(sourceColumn);
var width = columnWidth == null ? this.instance.wtSettings.settings.defaultColumnWidth : columnWidth;
var calculator = this.wot.wtViewport.columnsRenderCalculator;
if (calculator) {
var stretchedWidth = calculator.getStretchedColumnWidth(sourceColumn, width);
if (stretchedWidth) {
width = stretchedWidth;
}
}
return width;
}
/**
* Modify row header widths provided by user in class contructor.
*
* @private
*/
}, {
key: '_modifyRowHeaderWidth',
value: function _modifyRowHeaderWidth(rowHeaderWidthFactory) {
var widths = (0, _function.isFunction)(rowHeaderWidthFactory) ? rowHeaderWidthFactory() : null;
if (Array.isArray(widths)) {
widths = [].concat(_toConsumableArray(widths));
widths[widths.length - 1] = this._correctRowHeaderWidth(widths[widths.length - 1]);
} else {
widths = this._correctRowHeaderWidth(widths);
}
return widths;
}
/**
* Correct row header width if necessary.
*
* @private
*/
}, {
key: '_correctRowHeaderWidth',
value: function _correctRowHeaderWidth(width) {
if (typeof width !== 'number') {
width = this.wot.getSetting('defaultColumnWidth');
}
if (this.correctHeaderWidth) {
width++;
}
return width;
}
}]);
return Table;
}();
exports.default = Table;