handsontable
Version:
Handsontable is a JavaScript Data Grid available for React, Angular and Vue.
491 lines (465 loc) • 14.7 kB
JavaScript
"use strict";
exports.__esModule = true;
require("core-js/modules/es.error.cause.js");
require("core-js/modules/es.array.push.js");
require("core-js/modules/esnext.iterator.constructor.js");
require("core-js/modules/esnext.iterator.for-each.js");
var _element = require("./../helpers/dom/element");
var _array = require("./../helpers/array");
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
/**
* @class GhostTable
*/
class GhostTable {
constructor(hotInstance) {
/**
* Handsontable instance.
*
* @type {Core}
*/
_defineProperty(this, "hot", null);
/**
* Container element where every table will be injected.
*
* @type {HTMLElement|null}
*/
_defineProperty(this, "container", null);
/**
* Flag which determine is table was injected to DOM.
*
* @type {boolean}
*/
_defineProperty(this, "injected", false);
/**
* Added rows collection.
*
* @type {Array}
*/
_defineProperty(this, "rows", []);
/**
* Added columns collection.
*
* @type {Array}
*/
_defineProperty(this, "columns", []);
/**
* Samples prepared for calculations.
*
* @type {Map}
* @default {null}
*/
_defineProperty(this, "samples", null);
/**
* Ghost table settings.
*
* @type {object}
* @default {Object}
*/
_defineProperty(this, "settings", {
useHeaders: true
});
this.hot = hotInstance;
}
/**
* Add row.
*
* @param {number} row Visual row index.
* @param {Map} samples Samples Map object.
*/
addRow(row, samples) {
if (this.columns.length) {
throw new Error('Doesn\'t support multi-dimensional table');
}
if (!this.rows.length) {
this.container = this.createContainer(this.hot.rootElement.className);
}
const rowObject = {
row
};
this.rows.push(rowObject);
this.samples = samples;
this.table = this.createTable(this.hot.table.className);
this.table.colGroup.appendChild(this.createColGroupsCol(row));
this.table.tr.appendChild(this.createRow(row));
if (row === 0) {
(0, _element.addClass)(this.table.table, 'htGhostTableFirstRow');
}
this.container.container.appendChild(this.table.fragment);
rowObject.table = this.table.table;
}
/**
* Add a row consisting of the column headers.
*
* @param {Map} samples A map with sampled table values.
*/
addColumnHeadersRow(samples) {
const colHeader = this.hot.getColHeader(0);
if (colHeader !== null && colHeader !== undefined) {
const rowObject = {
row: -1
};
this.rows.push(rowObject);
this.container = this.createContainer(this.hot.rootElement.className);
this.samples = samples;
this.table = this.createTable(this.hot.table.className);
this.table.colGroup.appendChild(this.createColGroupsCol());
this.appendColumnHeadersRow();
this.container.container.appendChild(this.table.fragment);
rowObject.table = this.table.table;
}
}
/**
* Add column.
*
* @param {number} column Visual column index.
* @param {Map} samples A map with sampled table values.
*/
addColumn(column, samples) {
if (this.rows.length) {
throw new Error('Doesn\'t support multi-dimensional table');
}
if (!this.columns.length) {
this.container = this.createContainer(this.hot.rootElement.className);
}
const columnObject = {
col: column
};
this.columns.push(columnObject);
this.samples = samples;
this.table = this.createTable(this.hot.table.className);
if (this.getSetting('useHeaders') && this.hot.getColHeader(column) !== null) {
// Please keep in mind that the renderable column index equal to the visual columns index for the GhostTable.
// We render all columns.
this.hot.view.appendColHeader(column, this.table.th, undefined, -1);
}
this.table.tBody.appendChild(this.createCol(column));
this.container.container.appendChild(this.table.fragment);
columnObject.table = this.table.table;
}
/**
* Get calculated heights.
*
* @param {Function} callback Callback which will be fired for each calculated row.
*/
getHeights(callback) {
if (!this.injected) {
this.injectTable();
}
(0, _array.arrayEach)(this.rows, row => {
// In cases when the cell's content produces the height with a decimal point, the height
// needs to be rounded up to make sure that there will be a space for the cell's content.
// The `.offsetHeight` always returns the rounded number (floored), so it's not suitable for this case.
const {
height
} = row.table.getBoundingClientRect();
callback(row.row, Math.ceil(height));
});
}
/**
* Get calculated widths.
*
* @param {Function} callback Callback which will be fired for each calculated column.
*/
getWidths(callback) {
if (!this.injected) {
this.injectTable();
}
(0, _array.arrayEach)(this.columns, column => {
// In cases when the cell's content produces the width with a decimal point, the width
// needs to be rounded up to make sure that there will be a space for the cell's content.
// The `.offsetWidth` always returns the rounded number (floored), so it's not suitable for this case.
const {
width
} = column.table.getBoundingClientRect();
callback(column.col, Math.ceil(width));
});
}
/**
* Set the Ghost Table settings to the provided object.
*
* @param {object} settings New Ghost Table Settings.
*/
setSettings(settings) {
this.settings = settings;
}
/**
* Set a single setting of the Ghost Table.
*
* @param {string} name Setting name.
* @param {*} value Setting value.
*/
setSetting(name, value) {
if (!this.settings) {
this.settings = {};
}
this.settings[name] = value;
}
/**
* Get the Ghost Table settings.
*
* @returns {object|null}
*/
getSettings() {
return this.settings;
}
/**
* Get a single Ghost Table setting.
*
* @param {string} name The setting name to get.
* @returns {boolean|null}
*/
getSetting(name) {
if (this.settings) {
return this.settings[name];
}
return null;
}
/**
* Create colgroup col elements.
*
* @param {number} row Visual row index.
* @returns {DocumentFragment}
*/
createColGroupsCol(row) {
const fragment = this.hot.rootDocument.createDocumentFragment();
if (this.hot.hasRowHeaders()) {
fragment.appendChild(this.createColElement(-1, -1));
}
this.samples.forEach(sample => {
(0, _array.arrayEach)(sample.strings, string => {
fragment.appendChild(this.createColElement(string.col, row));
});
});
return fragment;
}
/**
* Create table row element.
*
* @param {number} row Visual row index.
* @returns {DocumentFragment} Returns created table row elements.
*/
createRow(row) {
const {
rootDocument
} = this.hot;
const fragment = rootDocument.createDocumentFragment();
const th = rootDocument.createElement('th');
if (this.hot.hasRowHeaders()) {
this.hot.view.appendRowHeader(row, th);
fragment.appendChild(th);
}
this.samples.forEach(sample => {
(0, _array.arrayEach)(sample.strings, string => {
const column = string.col;
const cellProperties = this.hot.getCellMeta(row, column);
const renderer = this.hot.getCellRenderer(cellProperties);
const td = rootDocument.createElement('td');
// Indicate that this element is created and supported by GhostTable. It can be useful to
// exclude rendering performance costly logic or exclude logic which doesn't work within a hidden table.
td.setAttribute('ghost-table', 1);
renderer(this.hot, td, row, column, this.hot.colToProp(column), string.value, cellProperties);
fragment.appendChild(td);
});
});
return fragment;
}
/**
* Creates DOM elements for headers and appends them to the THEAD element of the table.
*/
appendColumnHeadersRow() {
const {
rootDocument
} = this.hot;
const domFragment = rootDocument.createDocumentFragment();
const columnHeaders = [];
if (this.hot.hasRowHeaders()) {
const th = rootDocument.createElement('th');
columnHeaders.push([-1, th]);
domFragment.appendChild(th);
}
this.samples.forEach(sample => {
(0, _array.arrayEach)(sample.strings, string => {
const column = string.col;
const th = rootDocument.createElement('th');
columnHeaders.push([column, th]);
domFragment.appendChild(th);
});
});
// Appending DOM elements for headers
this.table.tHead.appendChild(domFragment);
(0, _array.arrayEach)(columnHeaders, columnHeader => {
const [column, th] = columnHeader;
// Using source method for filling a header with value.
this.hot.view.appendColHeader(column, th);
});
}
/**
* Create table column elements.
*
* @param {number} column Visual column index.
* @returns {DocumentFragment} Returns created column table column elements.
*/
createCol(column) {
const {
rootDocument
} = this.hot;
const fragment = rootDocument.createDocumentFragment();
this.samples.forEach(sample => {
(0, _array.arrayEach)(sample.strings, string => {
const row = string.row;
const cellProperties = this.hot.getCellMeta(row, column);
const renderer = this.hot.getCellRenderer(cellProperties);
const td = rootDocument.createElement('td');
const tr = rootDocument.createElement('tr');
// Indicate that this element is created and supported by GhostTable. It can be useful to
// exclude rendering performance costly logic or exclude logic which doesn't work within a hidden table.
td.setAttribute('ghost-table', 1);
renderer(this.hot, td, row, column, this.hot.colToProp(column), string.value, cellProperties);
tr.appendChild(td);
fragment.appendChild(tr);
});
});
return fragment;
}
/**
* Remove table from document and reset internal state.
*/
clean() {
this.rows.length = 0;
this.rows[-1] = undefined;
this.columns.length = 0;
if (this.samples) {
this.samples.clear();
}
this.samples = null;
this.removeTable();
}
/**
* Inject generated table into document.
*
* @param {HTMLElement} [parent=null] The element to which the ghost table is injected.
*/
injectTable() {
let parent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
if (!this.injected) {
(parent || this.hot.rootElement).appendChild(this.container.fragment);
this.injected = true;
}
}
/**
* Remove table from document.
*/
removeTable() {
if (this.injected && this.container.container.parentNode) {
this.container.container.parentNode.removeChild(this.container.container);
this.container = null;
this.injected = false;
}
}
/**
* Create col element.
*
* @param {number} column Visual column index.
* @param {number} row Visual row index.
* @returns {HTMLElement}
*/
createColElement(column, row) {
const col = this.hot.rootDocument.createElement('col');
let colspan = 0;
if (row >= 0 && column >= 0) {
colspan = this.hot.getCellMeta(row, column).colspan;
}
let width = this.hot.getColWidth(column);
if (colspan > 1) {
for (let nextColumn = column + 1; nextColumn < column + colspan; nextColumn++) {
width += this.hot.getColWidth(nextColumn);
}
}
col.style.width = `${width}px`;
return col;
}
/**
* Create table element.
*
* @param {string} className The CSS classes to add.
* @returns {object}
*/
createTable() {
let className = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
const {
rootDocument
} = this.hot;
const fragment = rootDocument.createDocumentFragment();
const table = rootDocument.createElement('table');
const tHead = rootDocument.createElement('thead');
const tBody = rootDocument.createElement('tbody');
const colGroup = rootDocument.createElement('colgroup');
const tr = rootDocument.createElement('tr');
const th = rootDocument.createElement('th');
if (this.isVertical()) {
table.appendChild(colGroup);
}
if (this.isHorizontal()) {
tr.appendChild(th);
tHead.appendChild(tr);
table.style.tableLayout = 'auto';
table.style.width = 'auto';
}
table.appendChild(tHead);
if (this.isVertical()) {
tBody.appendChild(tr);
}
table.appendChild(tBody);
(0, _element.addClass)(table, className);
fragment.appendChild(table);
return {
fragment,
table,
tHead,
tBody,
colGroup,
tr,
th
};
}
/**
* Create container for tables.
*
* @param {string} className The CSS classes to add.
* @returns {object}
*/
createContainer() {
let className = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
const {
rootDocument
} = this.hot;
const fragment = rootDocument.createDocumentFragment();
const container = rootDocument.createElement('div');
const containerClassName = `htGhostTable htAutoSize ${className.trim()}`;
(0, _element.addClass)(container, containerClassName);
fragment.appendChild(container);
return {
fragment,
container
};
}
/**
* Checks if table is raised vertically (checking rows).
*
* @returns {boolean}
*/
isVertical() {
return !!(this.rows.length && !this.columns.length);
}
/**
* Checks if table is raised horizontally (checking columns).
*
* @returns {boolean}
*/
isHorizontal() {
return !!(this.columns.length && !this.rows.length);
}
}
var _default = exports.default = GhostTable;