@protobi/exceljs
Version:
Excel Workbook Manager - Temporary fork with pivot table enhancements and bug fixes pending upstream merge
620 lines (599 loc) • 20 kB
JavaScript
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), 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); }
/* eslint-disable max-classes-per-file */
var colCache = require('../utils/col-cache');
var Column = /*#__PURE__*/function () {
// wrapper around column model, allowing access and manipulation
function Column(table, column, index) {
_classCallCheck(this, Column);
this.table = table;
this.column = column;
this.index = index;
}
_createClass(Column, [{
key: "_set",
value: function _set(name, value) {
this.table.cacheState();
this.column[name] = value;
}
/* eslint-disable lines-between-class-members */
}, {
key: "name",
get: function get() {
return this.column.name;
},
set: function set(value) {
this._set('name', value);
}
}, {
key: "filterButton",
get: function get() {
return this.column.filterButton;
},
set: function set(value) {
this.column.filterButton = value;
}
}, {
key: "style",
get: function get() {
return this.column.style;
},
set: function set(value) {
this.column.style = value;
}
}, {
key: "totalsRowLabel",
get: function get() {
return this.column.totalsRowLabel;
},
set: function set(value) {
this._set('totalsRowLabel', value);
}
}, {
key: "totalsRowFunction",
get: function get() {
return this.column.totalsRowFunction;
},
set: function set(value) {
this._set('totalsRowFunction', value);
}
}, {
key: "totalsRowResult",
get: function get() {
return this.column.totalsRowResult;
},
set: function set(value) {
this._set('totalsRowResult', value);
}
}, {
key: "totalsRowFormula",
get: function get() {
return this.column.totalsRowFormula;
},
set: function set(value) {
this._set('totalsRowFormula', value);
}
/* eslint-enable lines-between-class-members */
}]);
return Column;
}();
var Table = /*#__PURE__*/function () {
function Table(worksheet, table) {
_classCallCheck(this, Table);
this.worksheet = worksheet;
if (table) {
this.table = table;
// check things are ok first
this.validate();
this.store();
}
}
_createClass(Table, [{
key: "getFormula",
value: function getFormula(column) {
// get the correct formula to apply to the totals row
switch (column.totalsRowFunction) {
case 'none':
return null;
case 'average':
return "SUBTOTAL(101,".concat(this.table.name, "[").concat(column.name, "])");
case 'countNums':
return "SUBTOTAL(102,".concat(this.table.name, "[").concat(column.name, "])");
case 'count':
return "SUBTOTAL(103,".concat(this.table.name, "[").concat(column.name, "])");
case 'max':
return "SUBTOTAL(104,".concat(this.table.name, "[").concat(column.name, "])");
case 'min':
return "SUBTOTAL(105,".concat(this.table.name, "[").concat(column.name, "])");
case 'stdDev':
return "SUBTOTAL(106,".concat(this.table.name, "[").concat(column.name, "])");
case 'var':
return "SUBTOTAL(107,".concat(this.table.name, "[").concat(column.name, "])");
case 'sum':
return "SUBTOTAL(109,".concat(this.table.name, "[").concat(column.name, "])");
case 'custom':
return column.totalsRowFormula;
default:
throw new Error("Invalid Totals Row Function: ".concat(column.totalsRowFunction));
}
}
}, {
key: "width",
get: function get() {
// width of the table
return this.table.columns.length;
}
}, {
key: "height",
get: function get() {
// height of the table data
return this.table.rows.length;
}
}, {
key: "filterHeight",
get: function get() {
// height of the table data plus optional header row
return this.height + (this.table.headerRow ? 1 : 0);
}
}, {
key: "tableHeight",
get: function get() {
// full height of the table on the sheet
return this.filterHeight + (this.table.totalsRow ? 1 : 0);
}
}, {
key: "validate",
value: function validate() {
var _this = this;
var table = this.table;
// set defaults and check is valid
var assign = function assign(o, name, dflt) {
if (o[name] === undefined) {
o[name] = dflt;
}
};
assign(table, 'headerRow', true);
assign(table, 'totalsRow', false);
assign(table, 'style', {});
assign(table.style, 'theme', 'TableStyleMedium2');
assign(table.style, 'showFirstColumn', false);
assign(table.style, 'showLastColumn', false);
assign(table.style, 'showRowStripes', false);
assign(table.style, 'showColumnStripes', false);
var assert = function assert(test, message) {
if (!test) {
throw new Error(message);
}
};
assert(table.ref, 'Table must have ref');
assert(table.columns, 'Table must have column definitions');
assert(table.rows, 'Table must have row definitions');
table.tl = colCache.decodeAddress(table.ref);
var _table$tl = table.tl,
row = _table$tl.row,
col = _table$tl.col;
assert(row > 0, 'Table must be on valid row');
assert(col > 0, 'Table must be on valid col');
var width = this.width,
tableHeight = this.tableHeight;
// autoFilterRef should be just the header row for Excel tables
if (table.headerRow) {
table.autoFilterRef = colCache.encode(row, col, row, col + width - 1);
}
// tableRef is a range that includes optional headers and totals
table.tableRef = colCache.encode(row, col, row + tableHeight - 1, col + width - 1);
table.columns.forEach(function (column, i) {
assert(column.name, "Column ".concat(i, " must have a name"));
if (i === 0) {
assign(column, 'totalsRowLabel', 'Total');
} else {
assign(column, 'totalsRowFunction', 'none');
column.totalsRowFormula = _this.getFormula(column);
}
});
}
}, {
key: "store",
value: function store() {
var _this2 = this;
// where the table needs to store table data, headers, footers in
// the sheet...
var assignStyle = function assignStyle(cell, style) {
if (style) {
Object.keys(style).forEach(function (key) {
cell.style[key] = style[key];
});
}
};
var worksheet = this.worksheet,
table = this.table;
var _table$tl2 = table.tl,
row = _table$tl2.row,
col = _table$tl2.col;
var count = 0;
if (table.headerRow) {
var r = worksheet.getRow(row + count++);
table.columns.forEach(function (column, j) {
var style = column.style,
name = column.name;
var cell = r.getCell(col + j);
cell.value = name;
assignStyle(cell, style);
});
}
table.rows.forEach(function (data) {
var r = worksheet.getRow(row + count++);
data.forEach(function (value, j) {
var cell = r.getCell(col + j);
cell.value = value;
assignStyle(cell, table.columns[j].style);
});
});
if (table.totalsRow) {
var _r = worksheet.getRow(row + count++);
table.columns.forEach(function (column, j) {
var cell = _r.getCell(col + j);
if (j === 0) {
cell.value = column.totalsRowLabel;
} else {
var formula = _this2.getFormula(column);
if (formula) {
cell.value = {
formula: column.totalsRowFormula,
result: column.totalsRowResult
};
} else {
cell.value = null;
}
}
assignStyle(cell, column.style);
});
}
}
}, {
key: "load",
value: function load(worksheet) {
var _this3 = this;
// where the table will read necessary features from a loaded sheet
var table = this.table;
var _table$tl3 = table.tl,
row = _table$tl3.row,
col = _table$tl3.col;
var count = 0;
if (table.headerRow) {
var r = worksheet.getRow(row + count++);
table.columns.forEach(function (column, j) {
var cell = r.getCell(col + j);
cell.value = column.name;
});
}
table.rows.forEach(function (data) {
var r = worksheet.getRow(row + count++);
data.forEach(function (value, j) {
var cell = r.getCell(col + j);
cell.value = value;
});
});
if (table.totalsRow) {
var _r2 = worksheet.getRow(row + count++);
table.columns.forEach(function (column, j) {
var cell = _r2.getCell(col + j);
if (j === 0) {
cell.value = column.totalsRowLabel;
} else {
var formula = _this3.getFormula(column);
if (formula) {
cell.value = {
formula: column.totalsRowFormula,
result: column.totalsRowResult
};
}
}
});
}
}
}, {
key: "model",
get: function get() {
return this.table;
},
set: function set(value) {
this.table = value;
}
// ================================================================
// TODO: Mutating methods
}, {
key: "cacheState",
value: function cacheState() {
if (!this._cache) {
this._cache = {
ref: this.ref,
width: this.width,
tableHeight: this.tableHeight
};
}
}
}, {
key: "commit",
value: function commit() {
// changes may have been made that might have on-sheet effects
if (!this._cache) {
return;
}
// check things are ok first
this.validate();
var ref = colCache.decodeAddress(this._cache.ref);
if (this.ref !== this._cache.ref) {
// wipe out whole table footprint at previous location
for (var i = 0; i < this._cache.tableHeight; i++) {
var row = this.worksheet.getRow(ref.row + i);
for (var j = 0; j < this._cache.width; j++) {
var cell = row.getCell(ref.col + j);
cell.value = null;
}
}
} else {
// clear out below table if it has shrunk
for (var _i = this.tableHeight; _i < this._cache.tableHeight; _i++) {
var _row = this.worksheet.getRow(ref.row + _i);
for (var _j = 0; _j < this._cache.width; _j++) {
var _cell = _row.getCell(ref.col + _j);
_cell.value = null;
}
}
// clear out to right of table if it has lost columns
for (var _i2 = 0; _i2 < this.tableHeight; _i2++) {
var _row2 = this.worksheet.getRow(ref.row + _i2);
for (var _j2 = this.width; _j2 < this._cache.width; _j2++) {
var _cell2 = _row2.getCell(ref.col + _j2);
_cell2.value = null;
}
}
}
this.store();
}
}, {
key: "addRow",
value: function addRow(values, rowNumber) {
// Add a row of data, either insert at rowNumber or append
this.cacheState();
if (rowNumber === undefined) {
this.table.rows.push(values);
} else {
this.table.rows.splice(rowNumber, 0, values);
}
// Update table reference to reflect new size and re-render entire table
this._updateTableRef();
// Commit changes to worksheet (this will re-render the entire table properly)
this.commit();
}
}, {
key: "removeRows",
value: function removeRows(rowIndex) {
var count = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
// Remove a rows of data
this.cacheState();
this.table.rows.splice(rowIndex, count);
// Update table reference to reflect new size
this._updateTableRef();
// For now, we'll use store() for removeRows as it's more complex to handle
// TODO: Implement targeted row removal
this.store();
}
}, {
key: "_updateTableRef",
value: function _updateTableRef() {
// Update the table's ref property to reflect current table size
if (this.table.ref && this.table.tl) {
var _this$table$tl = this.table.tl,
row = _this$table$tl.row,
col = _this$table$tl.col;
var width = this.width,
tableHeight = this.tableHeight;
// Update the ref to include all current data
this.table.ref = colCache.encode(row, col, row + tableHeight - 1, col + width - 1);
// Update autoFilterRef if table has headers (for filter buttons)
// For Excel tables, autoFilter should just reference the header row
if (this.table.headerRow) {
var newAutoFilterRef = colCache.encode(row, col, row, col + width - 1);
this.table.autoFilterRef = newAutoFilterRef;
}
}
}
}, {
key: "_writeRowToWorksheet",
value: function _writeRowToWorksheet(values, insertIndex) {
var _this4 = this;
// Write a single row to the worksheet at the correct position
var _this$table$tl2 = this.table.tl,
row = _this$table$tl2.row,
col = _this$table$tl2.col;
var targetRowIndex = row;
// Account for header row if it exists
if (this.table.headerRow) {
targetRowIndex += 1;
}
// Add the insert index to get to the right data row
targetRowIndex += insertIndex;
// If there's a totals row, we need to shift it down
if (this.table.totalsRow) {
// First, move the totals row down by clearing and rewriting it
var totalsRowIndex = row + (this.table.headerRow ? 1 : 0) + this.table.rows.length;
var totalsRow = this.worksheet.getRow(totalsRowIndex);
// Clear the old totals row
for (var j = 0; j < this.width; j++) {
totalsRow.getCell(col + j).value = null;
}
// Write totals row at new position
var newTotalsRowIndex = totalsRowIndex + 1;
var newTotalsRow = this.worksheet.getRow(newTotalsRowIndex);
this.table.columns.forEach(function (column, j) {
var cell = newTotalsRow.getCell(col + j);
if (j === 0) {
cell.value = column.totalsRowLabel || 'Total';
} else {
var formula = _this4.getFormula(column);
if (formula) {
cell.value = {
formula: formula
};
}
}
});
}
// Write the new data row
var worksheetRow = this.worksheet.getRow(targetRowIndex);
values.forEach(function (value, j) {
var cell = worksheetRow.getCell(col + j);
cell.value = value;
// Apply column style if it exists
if (_this4.table.columns[j] && _this4.table.columns[j].style) {
Object.keys(_this4.table.columns[j].style).forEach(function (key) {
cell.style[key] = _this4.table.columns[j].style[key];
});
}
});
}
}, {
key: "getColumn",
value: function getColumn(colIndex) {
var column = this.table.columns[colIndex];
return new Column(this, column, colIndex);
}
}, {
key: "addColumn",
value: function addColumn(column, values, colIndex) {
// Add a new column, including column defn and values
// Inserts at colNumber or adds to the right
this.cacheState();
if (colIndex === undefined) {
this.table.columns.push(column);
this.table.rows.forEach(function (row, i) {
row.push(values[i]);
});
} else {
this.table.columns.splice(colIndex, 0, column);
this.table.rows.forEach(function (row, i) {
row.splice(colIndex, 0, values[i]);
});
}
}
}, {
key: "removeColumns",
value: function removeColumns(colIndex) {
var count = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
// Remove a column with data
this.cacheState();
this.table.columns.splice(colIndex, count);
this.table.rows.forEach(function (row) {
row.splice(colIndex, count);
});
}
}, {
key: "_assign",
value: function _assign(target, prop, value) {
this.cacheState();
target[prop] = value;
}
/* eslint-disable lines-between-class-members */
}, {
key: "ref",
get: function get() {
return this.table.ref;
},
set: function set(value) {
this._assign(this.table, 'ref', value);
}
}, {
key: "name",
get: function get() {
return this.table.name;
},
set: function set(value) {
this.table.name = value;
}
}, {
key: "displayName",
get: function get() {
return this.table.displyName || this.table.name;
}
}, {
key: "displayNamename",
set: function set(value) {
this.table.displayName = value;
}
}, {
key: "headerRow",
get: function get() {
return this.table.headerRow;
},
set: function set(value) {
this._assign(this.table, 'headerRow', value);
}
}, {
key: "totalsRow",
get: function get() {
return this.table.totalsRow;
},
set: function set(value) {
this._assign(this.table, 'totalsRow', value);
}
}, {
key: "theme",
get: function get() {
return this.table.style.name;
},
set: function set(value) {
this.table.style.name = value;
}
}, {
key: "showFirstColumn",
get: function get() {
return this.table.style.showFirstColumn;
},
set: function set(value) {
this.table.style.showFirstColumn = value;
}
}, {
key: "showLastColumn",
get: function get() {
return this.table.style.showLastColumn;
},
set: function set(value) {
this.table.style.showLastColumn = value;
}
}, {
key: "showRowStripes",
get: function get() {
return this.table.style.showRowStripes;
},
set: function set(value) {
this.table.style.showRowStripes = value;
}
}, {
key: "showColumnStripes",
get: function get() {
return this.table.style.showColumnStripes;
},
set: function set(value) {
this.table.style.showColumnStripes = value;
}
}, {
key: "autoFilterRef",
get: function get() {
return this.table.autoFilterRef;
},
set: function set(value) {
this._assign(this.table, 'autoFilterRef', value);
}
/* eslint-enable lines-between-class-members */
}]);
return Table;
}();
module.exports = Table;
//# sourceMappingURL=table.js.map