handsontable
Version: 
Handsontable is a JavaScript Spreadsheet Component available for React, Angular and Vue.
587 lines (502 loc) • 18.4 kB
JavaScript
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
import "core-js/modules/es.object.to-string.js";
import "core-js/modules/web.dom-collections.for-each.js";
import "core-js/modules/es.string.trim.js";
import "core-js/modules/es.symbol.js";
import "core-js/modules/es.symbol.description.js";
import "core-js/modules/es.symbol.iterator.js";
import "core-js/modules/es.array.iterator.js";
import "core-js/modules/es.string.iterator.js";
import "core-js/modules/web.dom-collections.iterator.js";
import "core-js/modules/es.array.slice.js";
import "core-js/modules/es.function.name.js";
import "core-js/modules/es.array.from.js";
import "core-js/modules/es.regexp.exec.js";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
import { addClass, outerHeight } from "./../helpers/dom/element.mjs";
import { arrayEach } from "./../helpers/array.mjs";
/**
 * @class GhostTable
 */
var GhostTable = /*#__PURE__*/function () {
  function GhostTable(hotInstance) {
    _classCallCheck(this, GhostTable);
    /**
     * Handsontable instance.
     *
     * @type {Core}
     */
    this.hot = hotInstance;
    /**
     * Container element where every table will be injected.
     *
     * @type {HTMLElement|null}
     */
    this.container = null;
    /**
     * Flag which determine is table was injected to DOM.
     *
     * @type {boolean}
     */
    this.injected = false;
    /**
     * Added rows collection.
     *
     * @type {Array}
     */
    this.rows = [];
    /**
     * Added columns collection.
     *
     * @type {Array}
     */
    this.columns = [];
    /**
     * Samples prepared for calculations.
     *
     * @type {Map}
     * @default {null}
     */
    this.samples = null;
    /**
     * Ghost table settings.
     *
     * @type {object}
     * @default {Object}
     */
    this.settings = {
      useHeaders: true
    };
  }
  /**
   * Add row.
   *
   * @param {number} row Row index.
   * @param {Map} samples Samples Map object.
   */
  _createClass(GhostTable, [{
    key: "addRow",
    value: function 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);
      }
      var rowObject = {
        row: row
      };
      this.rows.push(rowObject);
      this.samples = samples;
      this.table = this.createTable(this.hot.table.className);
      this.table.colGroup.appendChild(this.createColGroupsCol());
      this.table.tr.appendChild(this.createRow(row));
      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.
     */
  }, {
    key: "addColumnHeadersRow",
    value: function addColumnHeadersRow(samples) {
      var colHeader = this.hot.getColHeader(0);
      if (colHeader !== null && colHeader !== void 0) {
        var 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 Column index.
     * @param {Map} samples A map with sampled table values.
     */
  }, {
    key: "addColumn",
    value: function 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);
      }
      var 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);
      }
      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.
     */
  }, {
    key: "getHeights",
    value: function getHeights(callback) {
      if (!this.injected) {
        this.injectTable();
      }
      arrayEach(this.rows, function (row) {
        // -1 <- reduce border-top from table
        callback(row.row, outerHeight(row.table) - 1);
      });
    }
    /**
     * Get calculated widths.
     *
     * @param {Function} callback Callback which will be fired for each calculated column.
     */
  }, {
    key: "getWidths",
    value: function getWidths(callback) {
      if (!this.injected) {
        this.injectTable();
      }
      arrayEach(this.columns, function (column) {
        // The GhostTable class is responsible for calculating the columns' width based on the
        // contents rendered in the cells. In some cases, when the column's width calculated by
        // the browser is a decimal point with a fractional component. For example, 35.32px.
        // The usage of the `.offsetWidth` (or our helper `outerWidth`) is incorrect.
        // The `outerWidth` in the mentioned example (35.32px) would return 35 pixels that
        // would cause the text to not fit in the cell, thus increasing the row height.
        // That's why the `getBoundingClientRect` method is used. The method returns the number
        // that is rounded up to make sure that there will be a space for the cell's content.
        var _column$table$getBoun = column.table.getBoundingClientRect(),
            width = _column$table$getBoun.width;
        callback(column.col, Math.ceil(width));
      });
    }
    /**
     * Set the Ghost Table settings to the provided object.
     *
     * @param {object} settings New Ghost Table Settings.
     */
  }, {
    key: "setSettings",
    value: function setSettings(settings) {
      this.settings = settings;
    }
    /**
     * Set a single setting of the Ghost Table.
     *
     * @param {string} name Setting name.
     * @param {*} value Setting value.
     */
  }, {
    key: "setSetting",
    value: function setSetting(name, value) {
      if (!this.settings) {
        this.settings = {};
      }
      this.settings[name] = value;
    }
    /**
     * Get the Ghost Table settings.
     *
     * @returns {object|null}
     */
  }, {
    key: "getSettings",
    value: function getSettings() {
      return this.settings;
    }
    /**
     * Get a single Ghost Table setting.
     *
     * @param {string} name The setting name to get.
     * @returns {boolean|null}
     */
  }, {
    key: "getSetting",
    value: function getSetting(name) {
      if (this.settings) {
        return this.settings[name];
      }
      return null;
    }
    /**
     * Create colgroup col elements.
     *
     * @returns {DocumentFragment}
     */
  }, {
    key: "createColGroupsCol",
    value: function createColGroupsCol() {
      var _this = this;
      var fragment = this.hot.rootDocument.createDocumentFragment();
      if (this.hot.hasRowHeaders()) {
        fragment.appendChild(this.createColElement(-1));
      }
      this.samples.forEach(function (sample) {
        arrayEach(sample.strings, function (string) {
          fragment.appendChild(_this.createColElement(string.col));
        });
      });
      return fragment;
    }
    /**
     * Create table row element.
     *
     * @param {number} row Row index.
     * @returns {DocumentFragment} Returns created table row elements.
     */
  }, {
    key: "createRow",
    value: function createRow(row) {
      var _this2 = this;
      var rootDocument = this.hot.rootDocument;
      var fragment = rootDocument.createDocumentFragment();
      var th = rootDocument.createElement('th');
      if (this.hot.hasRowHeaders()) {
        this.hot.view.appendRowHeader(row, th);
        fragment.appendChild(th);
      }
      this.samples.forEach(function (sample) {
        arrayEach(sample.strings, function (string) {
          var column = string.col;
          var cellProperties = _this2.hot.getCellMeta(row, column);
          cellProperties.col = column;
          cellProperties.row = row;
          var renderer = _this2.hot.getCellRenderer(cellProperties);
          var 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(_this2.hot, td, row, column, _this2.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.
     */
  }, {
    key: "appendColumnHeadersRow",
    value: function appendColumnHeadersRow() {
      var _this3 = this;
      var rootDocument = this.hot.rootDocument;
      var domFragment = rootDocument.createDocumentFragment();
      var columnHeaders = [];
      if (this.hot.hasRowHeaders()) {
        var th = rootDocument.createElement('th');
        columnHeaders.push([-1, th]);
        domFragment.appendChild(th);
      }
      this.samples.forEach(function (sample) {
        arrayEach(sample.strings, function (string) {
          var column = string.col;
          var th = rootDocument.createElement('th');
          columnHeaders.push([column, th]);
          domFragment.appendChild(th);
        });
      }); // Appending DOM elements for headers
      this.table.tHead.appendChild(domFragment);
      arrayEach(columnHeaders, function (columnHeader) {
        var _columnHeader = _slicedToArray(columnHeader, 2),
            column = _columnHeader[0],
            th = _columnHeader[1]; // Using source method for filling a header with value.
        _this3.hot.view.appendColHeader(column, th);
      });
    }
    /**
     * Create table column elements.
     *
     * @param {number} column Column index.
     * @returns {DocumentFragment} Returns created column table column elements.
     */
  }, {
    key: "createCol",
    value: function createCol(column) {
      var _this4 = this;
      var rootDocument = this.hot.rootDocument;
      var fragment = rootDocument.createDocumentFragment();
      this.samples.forEach(function (sample) {
        arrayEach(sample.strings, function (string) {
          var row = string.row;
          var cellProperties = _this4.hot.getCellMeta(row, column);
          cellProperties.col = column;
          cellProperties.row = row;
          var renderer = _this4.hot.getCellRenderer(cellProperties);
          var td = rootDocument.createElement('td');
          var 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(_this4.hot, td, row, column, _this4.hot.colToProp(column), string.value, cellProperties);
          tr.appendChild(td);
          fragment.appendChild(tr);
        });
      });
      return fragment;
    }
    /**
     * Remove table from document and reset internal state.
     */
  }, {
    key: "clean",
    value: function clean() {
      this.rows.length = 0;
      this.rows[-1] = void 0;
      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.
     */
  }, {
    key: "injectTable",
    value: function injectTable() {
      var 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.
     */
  }, {
    key: "removeTable",
    value: function 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 Column index.
     * @returns {HTMLElement}
     */
  }, {
    key: "createColElement",
    value: function createColElement(column) {
      var col = this.hot.rootDocument.createElement('col');
      col.style.width = "".concat(this.hot.view._wt.wtTable.getStretchedColumnWidth(column), "px");
      return col;
    }
    /**
     * Create table element.
     *
     * @param {string} className The CSS classes to add.
     * @returns {object}
     */
  }, {
    key: "createTable",
    value: function createTable() {
      var className = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
      var rootDocument = this.hot.rootDocument;
      var fragment = rootDocument.createDocumentFragment();
      var table = rootDocument.createElement('table');
      var tHead = rootDocument.createElement('thead');
      var tBody = rootDocument.createElement('tbody');
      var colGroup = rootDocument.createElement('colgroup');
      var tr = rootDocument.createElement('tr');
      var 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);
      addClass(table, className);
      fragment.appendChild(table);
      return {
        fragment: fragment,
        table: table,
        tHead: tHead,
        tBody: tBody,
        colGroup: colGroup,
        tr: tr,
        th: th
      };
    }
    /**
     * Create container for tables.
     *
     * @param {string} className The CSS classes to add.
     * @returns {object}
     */
  }, {
    key: "createContainer",
    value: function createContainer() {
      var className = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
      var rootDocument = this.hot.rootDocument;
      var fragment = rootDocument.createDocumentFragment();
      var container = rootDocument.createElement('div');
      var containerClassName = "htGhostTable htAutoSize ".concat(className.trim());
      addClass(container, containerClassName);
      fragment.appendChild(container);
      return {
        fragment: fragment,
        container: container
      };
    }
    /**
     * Checks if table is raised vertically (checking rows).
     *
     * @returns {boolean}
     */
  }, {
    key: "isVertical",
    value: function isVertical() {
      return !!(this.rows.length && !this.columns.length);
    }
    /**
     * Checks if table is raised horizontally (checking columns).
     *
     * @returns {boolean}
     */
  }, {
    key: "isHorizontal",
    value: function isHorizontal() {
      return !!(this.columns.length && !this.rows.length);
    }
  }]);
  return GhostTable;
}();
export default GhostTable;