707 lines
21 KiB
JavaScript
707 lines
21 KiB
JavaScript
"use strict";
|
|||
|
|
|
||
|
|
exports.__esModule = true;
|
||
|
|
require("core-js/modules/es.array.push.js");
|
||
|
|
var _number = require("../../../helpers/number");
|
||
|
|
var _object = require("../../../helpers/object");
|
||
|
|
var _array = require("../../../helpers/array");
|
||
|
|
/**
|
||
|
|
* Class responsible for making data operations.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
*/
|
||
|
|
class DataManager {
|
||
|
|
constructor(nestedRowsPlugin, hotInstance) {
|
||
|
|
/**
|
||
|
|
* Main Handsontable instance reference.
|
||
|
|
*
|
||
|
|
* @type {object}
|
||
|
|
*/
|
||
|
|
this.hot = hotInstance;
|
||
|
|
/**
|
||
|
|
* Reference to the source data object.
|
||
|
|
*
|
||
|
|
* @type {Handsontable.CellValue[][]|Handsontable.RowObject[]}
|
||
|
|
*/
|
||
|
|
this.data = null;
|
||
|
|
/**
|
||
|
|
* Reference to the NestedRows plugin.
|
||
|
|
*
|
||
|
|
* @type {object}
|
||
|
|
*/
|
||
|
|
this.plugin = nestedRowsPlugin;
|
||
|
|
/**
|
||
|
|
* Map of row object parents.
|
||
|
|
*
|
||
|
|
* @type {WeakMap}
|
||
|
|
*/
|
||
|
|
this.parentReference = new WeakMap();
|
||
|
|
/**
|
||
|
|
* Nested structure cache.
|
||
|
|
*
|
||
|
|
* @type {object}
|
||
|
|
*/
|
||
|
|
this.cache = {
|
||
|
|
levels: [],
|
||
|
|
levelCount: 0,
|
||
|
|
rows: [],
|
||
|
|
nodeInfo: new WeakMap()
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set the data for the manager.
|
||
|
|
*
|
||
|
|
* @param {Handsontable.CellValue[][]|Handsontable.RowObject[]} data Data for the manager.
|
||
|
|
*/
|
||
|
|
setData(data) {
|
||
|
|
this.data = data;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the data cached in the manager.
|
||
|
|
*
|
||
|
|
* @returns {Handsontable.CellValue[][]|Handsontable.RowObject[]}
|
||
|
|
*/
|
||
|
|
getData() {
|
||
|
|
return this.data;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Load the "raw" source data, without NestedRows' modifications.
|
||
|
|
*
|
||
|
|
* @returns {Handsontable.CellValue[][]|Handsontable.RowObject[]}
|
||
|
|
*/
|
||
|
|
getRawSourceData() {
|
||
|
|
let rawSourceData = null;
|
||
|
|
this.plugin.disableCoreAPIModifiers();
|
||
|
|
rawSourceData = this.hot.getSourceData();
|
||
|
|
this.plugin.enableCoreAPIModifiers();
|
||
|
|
return rawSourceData;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update the Data Manager with new data and refresh cache.
|
||
|
|
*
|
||
|
|
* @param {Handsontable.CellValue[][]|Handsontable.RowObject[]} data Data for the manager.
|
||
|
|
*/
|
||
|
|
updateWithData(data) {
|
||
|
|
this.setData(data);
|
||
|
|
this.rewriteCache();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Rewrite the nested structure cache.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
*/
|
||
|
|
rewriteCache() {
|
||
|
|
this.cache = {
|
||
|
|
levels: [],
|
||
|
|
levelCount: 0,
|
||
|
|
rows: [],
|
||
|
|
nodeInfo: new WeakMap()
|
||
|
|
};
|
||
|
|
(0, _number.rangeEach)(0, this.data.length - 1, i => {
|
||
|
|
this.cacheNode(this.data[i], 0, null);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Cache a data node.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {object} node Node to cache.
|
||
|
|
* @param {number} level Level of the node.
|
||
|
|
* @param {object} parent Parent of the node.
|
||
|
|
*/
|
||
|
|
cacheNode(node, level, parent) {
|
||
|
|
if (!this.cache.levels[level]) {
|
||
|
|
this.cache.levels[level] = [];
|
||
|
|
this.cache.levelCount += 1;
|
||
|
|
}
|
||
|
|
this.cache.levels[level].push(node);
|
||
|
|
this.cache.rows.push(node);
|
||
|
|
this.cache.nodeInfo.set(node, {
|
||
|
|
parent,
|
||
|
|
row: this.cache.rows.length - 1,
|
||
|
|
level
|
||
|
|
});
|
||
|
|
if (this.hasChildren(node)) {
|
||
|
|
(0, _array.arrayEach)(node.__children, elem => {
|
||
|
|
this.cacheNode(elem, level + 1, node);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the date for the provided visual row number.
|
||
|
|
*
|
||
|
|
* @param {number} row Row index.
|
||
|
|
* @returns {object}
|
||
|
|
*/
|
||
|
|
getDataObject(row) {
|
||
|
|
return row === null || row === void 0 ? null : this.cache.rows[row];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Read the row tree in search for a specific row index or row object.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {object} parent The initial parent object.
|
||
|
|
* @param {number} readCount Number of read nodes.
|
||
|
|
* @param {number} neededIndex The row index we search for.
|
||
|
|
* @param {object} neededObject The row object we search for.
|
||
|
|
* @returns {number|object}
|
||
|
|
*/
|
||
|
|
readTreeNodes(parent, readCount, neededIndex, neededObject) {
|
||
|
|
let rootLevel = false;
|
||
|
|
let readNodesCount = readCount;
|
||
|
|
if (isNaN(readNodesCount) && readNodesCount.end) {
|
||
|
|
return readNodesCount;
|
||
|
|
}
|
||
|
|
let parentObj = parent;
|
||
|
|
if (!parentObj) {
|
||
|
|
parentObj = {
|
||
|
|
__children: this.data
|
||
|
|
};
|
||
|
|
rootLevel = true;
|
||
|
|
readNodesCount -= 1;
|
||
|
|
}
|
||
|
|
if (neededIndex !== null && neededIndex !== void 0 && readNodesCount === neededIndex) {
|
||
|
|
return {
|
||
|
|
result: parentObj,
|
||
|
|
end: true
|
||
|
|
};
|
||
|
|
}
|
||
|
|
if (neededObject !== null && neededObject !== void 0 && parentObj === neededObject) {
|
||
|
|
return {
|
||
|
|
result: readNodesCount,
|
||
|
|
end: true
|
||
|
|
};
|
||
|
|
}
|
||
|
|
readNodesCount += 1;
|
||
|
|
if (parentObj.__children) {
|
||
|
|
(0, _array.arrayEach)(parentObj.__children, val => {
|
||
|
|
this.parentReference.set(val, rootLevel ? null : parentObj);
|
||
|
|
readNodesCount = this.readTreeNodes(val, readNodesCount, neededIndex, neededObject);
|
||
|
|
if (isNaN(readNodesCount) && readNodesCount.end) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return readNodesCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Mock a parent node.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @returns {*}
|
||
|
|
*/
|
||
|
|
mockParent() {
|
||
|
|
const fakeParent = this.mockNode();
|
||
|
|
fakeParent.__children = this.data;
|
||
|
|
return fakeParent;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Mock a data node.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @returns {{}}
|
||
|
|
*/
|
||
|
|
mockNode() {
|
||
|
|
const fakeNode = {};
|
||
|
|
(0, _object.objectEach)(this.data[0], (val, key) => {
|
||
|
|
fakeNode[key] = null;
|
||
|
|
});
|
||
|
|
return fakeNode;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the row index for the provided row object.
|
||
|
|
*
|
||
|
|
* @param {object} rowObj The row object.
|
||
|
|
* @returns {number} Row index.
|
||
|
|
*/
|
||
|
|
getRowIndex(rowObj) {
|
||
|
|
return rowObj === null || rowObj === void 0 ? null : this.cache.nodeInfo.get(rowObj).row;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the index of the provided row index/row object within its parent.
|
||
|
|
*
|
||
|
|
* @param {number|object} row Row index / row object.
|
||
|
|
* @returns {number}
|
||
|
|
*/
|
||
|
|
getRowIndexWithinParent(row) {
|
||
|
|
let rowObj = null;
|
||
|
|
if (isNaN(row)) {
|
||
|
|
rowObj = row;
|
||
|
|
} else {
|
||
|
|
rowObj = this.getDataObject(row);
|
||
|
|
}
|
||
|
|
const parent = this.getRowParent(row);
|
||
|
|
if (parent === null || parent === void 0) {
|
||
|
|
return this.data.indexOf(rowObj);
|
||
|
|
}
|
||
|
|
return parent.__children.indexOf(rowObj);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Count all rows (including all parents and children).
|
||
|
|
*
|
||
|
|
* @returns {number}
|
||
|
|
*/
|
||
|
|
countAllRows() {
|
||
|
|
const rootNodeMock = {
|
||
|
|
__children: this.data
|
||
|
|
};
|
||
|
|
return this.countChildren(rootNodeMock);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Count children of the provided parent.
|
||
|
|
*
|
||
|
|
* @param {object|number} parent Parent node.
|
||
|
|
* @returns {number} Children count.
|
||
|
|
*/
|
||
|
|
countChildren(parent) {
|
||
|
|
let rowCount = 0;
|
||
|
|
let parentNode = parent;
|
||
|
|
if (!isNaN(parentNode)) {
|
||
|
|
parentNode = this.getDataObject(parentNode);
|
||
|
|
}
|
||
|
|
if (!parentNode || !parentNode.__children) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
(0, _array.arrayEach)(parentNode.__children, elem => {
|
||
|
|
rowCount += 1;
|
||
|
|
if (elem.__children) {
|
||
|
|
rowCount += this.countChildren(elem);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return rowCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the parent of the row at the provided index.
|
||
|
|
*
|
||
|
|
* @param {number|object} row Physical row index.
|
||
|
|
* @returns {object}
|
||
|
|
*/
|
||
|
|
getRowParent(row) {
|
||
|
|
let rowObject;
|
||
|
|
if (isNaN(row)) {
|
||
|
|
rowObject = row;
|
||
|
|
} else {
|
||
|
|
rowObject = this.getDataObject(row);
|
||
|
|
}
|
||
|
|
return this.getRowObjectParent(rowObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the parent of the provided row object.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {object} rowObject The row object (tree node).
|
||
|
|
* @returns {object|null}
|
||
|
|
*/
|
||
|
|
getRowObjectParent(rowObject) {
|
||
|
|
if (!rowObject || typeof rowObject !== 'object') {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return this.cache.nodeInfo.get(rowObject).parent;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the nesting level for the row with the provided row index.
|
||
|
|
*
|
||
|
|
* @param {number} row Row index.
|
||
|
|
* @returns {number|null} Row level or null, when row doesn't exist.
|
||
|
|
*/
|
||
|
|
getRowLevel(row) {
|
||
|
|
let rowObject = null;
|
||
|
|
if (isNaN(row)) {
|
||
|
|
rowObject = row;
|
||
|
|
} else {
|
||
|
|
rowObject = this.getDataObject(row);
|
||
|
|
}
|
||
|
|
return rowObject ? this.getRowObjectLevel(rowObject) : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the nesting level for the row with the provided row index.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {object} rowObject Row object.
|
||
|
|
* @returns {number} Row level.
|
||
|
|
*/
|
||
|
|
getRowObjectLevel(rowObject) {
|
||
|
|
return rowObject === null || rowObject === void 0 ? null : this.cache.nodeInfo.get(rowObject).level;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if the provided row/row element has children.
|
||
|
|
*
|
||
|
|
* @param {number|object} row Row number or row element.
|
||
|
|
* @returns {boolean}
|
||
|
|
*/
|
||
|
|
hasChildren(row) {
|
||
|
|
let rowObj = row;
|
||
|
|
if (!isNaN(rowObj)) {
|
||
|
|
rowObj = this.getDataObject(rowObj);
|
||
|
|
}
|
||
|
|
return !!(rowObj.__children && rowObj.__children.length);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns `true` if the row at the provided index has a parent.
|
||
|
|
*
|
||
|
|
* @param {number} index Row index.
|
||
|
|
* @returns {boolean} `true` if the row at the provided index has a parent, `false` otherwise.
|
||
|
|
*/
|
||
|
|
isChild(index) {
|
||
|
|
return this.getRowParent(index) !== null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get child at a provided index from the parent element.
|
||
|
|
*
|
||
|
|
* @param {object} parent The parent row object.
|
||
|
|
* @param {number} index Index of the child element to be retrieved.
|
||
|
|
* @returns {object|null} The child element or `null` if the child doesn't exist.
|
||
|
|
*/
|
||
|
|
getChild(parent, index) {
|
||
|
|
var _parent$__children;
|
||
|
|
return ((_parent$__children = parent.__children) === null || _parent$__children === void 0 ? void 0 : _parent$__children[index]) || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return `true` of the row at the provided index is located at the topmost level.
|
||
|
|
*
|
||
|
|
* @param {number} index Row index.
|
||
|
|
* @returns {boolean} `true` of the row at the provided index is located at the topmost level, `false` otherwise.
|
||
|
|
*/
|
||
|
|
isRowHighestLevel(index) {
|
||
|
|
return !this.isChild(index);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return `true` if the provided row index / row object represents a parent in the nested structure.
|
||
|
|
*
|
||
|
|
* @param {number|object} row Row index / row object.
|
||
|
|
* @returns {boolean} `true` if the row is a parent, `false` otherwise.
|
||
|
|
*/
|
||
|
|
isParent(row) {
|
||
|
|
var _rowObj$__children;
|
||
|
|
let rowObj = row;
|
||
|
|
if (!isNaN(rowObj)) {
|
||
|
|
rowObj = this.getDataObject(rowObj);
|
||
|
|
}
|
||
|
|
return rowObj && !!rowObj.__children && ((_rowObj$__children = rowObj.__children) === null || _rowObj$__children === void 0 ? void 0 : _rowObj$__children.length) !== 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a child to the provided parent. It's optional to add a row object as the "element".
|
||
|
|
*
|
||
|
|
* @param {object} parent The parent row object.
|
||
|
|
* @param {object} [element] The element to add as a child.
|
||
|
|
*/
|
||
|
|
addChild(parent, element) {
|
||
|
|
let childElement = element;
|
||
|
|
this.hot.runHooks('beforeAddChild', parent, childElement);
|
||
|
|
let parentIndex = null;
|
||
|
|
if (parent) {
|
||
|
|
parentIndex = this.getRowIndex(parent);
|
||
|
|
}
|
||
|
|
this.hot.runHooks('beforeCreateRow', parentIndex + this.countChildren(parent) + 1, 1);
|
||
|
|
let functionalParent = parent;
|
||
|
|
if (!parent) {
|
||
|
|
functionalParent = this.mockParent();
|
||
|
|
}
|
||
|
|
if (!functionalParent.__children) {
|
||
|
|
functionalParent.__children = [];
|
||
|
|
}
|
||
|
|
if (!childElement) {
|
||
|
|
childElement = this.mockNode();
|
||
|
|
}
|
||
|
|
functionalParent.__children.push(childElement);
|
||
|
|
this.rewriteCache();
|
||
|
|
const newRowIndex = this.getRowIndex(childElement);
|
||
|
|
this.hot.rowIndexMapper.insertIndexes(newRowIndex, 1);
|
||
|
|
this.hot.runHooks('afterCreateRow', newRowIndex, 1);
|
||
|
|
this.hot.runHooks('afterAddChild', parent, childElement);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a child node to the provided parent at a specified index.
|
||
|
|
*
|
||
|
|
* @param {object} parent Parent node.
|
||
|
|
* @param {number} index Index to insert the child element at.
|
||
|
|
* @param {object} [element] Element (node) to insert.
|
||
|
|
*/
|
||
|
|
addChildAtIndex(parent, index, element) {
|
||
|
|
let childElement = element;
|
||
|
|
let flattenedIndex;
|
||
|
|
if (!childElement) {
|
||
|
|
childElement = this.mockNode();
|
||
|
|
}
|
||
|
|
this.hot.runHooks('beforeAddChild', parent, childElement, index);
|
||
|
|
if (parent) {
|
||
|
|
const parentIndex = this.getRowIndex(parent);
|
||
|
|
const finalChildIndex = parentIndex + index + 1;
|
||
|
|
this.hot.runHooks('beforeCreateRow', finalChildIndex, 1);
|
||
|
|
parent.__children.splice(index, null, childElement);
|
||
|
|
this.rewriteCache();
|
||
|
|
this.plugin.disableCoreAPIModifiers();
|
||
|
|
this.hot.setSourceDataAtCell(this.getRowIndexWithinParent(parent), '__children', parent.__children, 'NestedRows.addChildAtIndex');
|
||
|
|
this.hot.rowIndexMapper.insertIndexes(finalChildIndex, 1);
|
||
|
|
this.plugin.enableCoreAPIModifiers();
|
||
|
|
this.hot.runHooks('afterCreateRow', finalChildIndex, 1);
|
||
|
|
flattenedIndex = finalChildIndex;
|
||
|
|
} else {
|
||
|
|
this.plugin.disableCoreAPIModifiers();
|
||
|
|
this.hot.alter('insert_row_above', index, 1, 'NestedRows.addChildAtIndex');
|
||
|
|
this.plugin.enableCoreAPIModifiers();
|
||
|
|
flattenedIndex = this.getRowIndex(this.data[index]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Workaround for refreshing cache losing the reference to the mocked row.
|
||
|
|
childElement = this.getDataObject(flattenedIndex);
|
||
|
|
this.hot.runHooks('afterAddChild', parent, childElement, index);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a sibling element at the specified index.
|
||
|
|
*
|
||
|
|
* @param {number} index New element sibling's index.
|
||
|
|
* @param {('above'|'below')} where Direction in which the sibling is to be created.
|
||
|
|
*/
|
||
|
|
addSibling(index) {
|
||
|
|
let where = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'below';
|
||
|
|
const translatedIndex = this.translateTrimmedRow(index);
|
||
|
|
const parent = this.getRowParent(translatedIndex);
|
||
|
|
const indexWithinParent = this.getRowIndexWithinParent(translatedIndex);
|
||
|
|
switch (where) {
|
||
|
|
case 'below':
|
||
|
|
this.addChildAtIndex(parent, indexWithinParent + 1, null);
|
||
|
|
break;
|
||
|
|
case 'above':
|
||
|
|
this.addChildAtIndex(parent, indexWithinParent, null);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Detach the provided element from its parent and add it right after it.
|
||
|
|
*
|
||
|
|
* @param {object|Array} elements Row object or an array of selected coordinates.
|
||
|
|
* @param {boolean} [forceRender=true] If true (default), it triggers render after finished.
|
||
|
|
*/
|
||
|
|
detachFromParent(elements) {
|
||
|
|
let forceRender = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
||
|
|
let element = null;
|
||
|
|
const rowObjects = [];
|
||
|
|
if (Array.isArray(elements)) {
|
||
|
|
(0, _number.rangeEach)(elements[0], elements[2], i => {
|
||
|
|
const translatedIndex = this.translateTrimmedRow(i);
|
||
|
|
rowObjects.push(this.getDataObject(translatedIndex));
|
||
|
|
});
|
||
|
|
(0, _number.rangeEach)(0, rowObjects.length - 2, i => {
|
||
|
|
this.detachFromParent(rowObjects[i], false);
|
||
|
|
});
|
||
|
|
element = rowObjects[rowObjects.length - 1];
|
||
|
|
} else {
|
||
|
|
element = elements;
|
||
|
|
}
|
||
|
|
const childRowIndex = this.getRowIndex(element);
|
||
|
|
const childCount = this.countChildren(element);
|
||
|
|
const indexWithinParent = this.getRowIndexWithinParent(element);
|
||
|
|
const parent = this.getRowParent(element);
|
||
|
|
const grandparent = this.getRowParent(parent);
|
||
|
|
const grandparentRowIndex = this.getRowIndex(grandparent);
|
||
|
|
let movedElementRowIndex = null;
|
||
|
|
this.hot.runHooks('beforeDetachChild', parent, element);
|
||
|
|
if (indexWithinParent !== null && indexWithinParent !== void 0) {
|
||
|
|
const removedRowIndexes = Array.from(new Array(childRowIndex + childCount + 1).keys()).splice(-1 * (childCount + 1));
|
||
|
|
this.hot.runHooks('beforeRemoveRow', childRowIndex, childCount + 1, removedRowIndexes, this.plugin.pluginName);
|
||
|
|
parent.__children.splice(indexWithinParent, 1);
|
||
|
|
this.rewriteCache();
|
||
|
|
this.hot.runHooks('afterRemoveRow', childRowIndex, childCount + 1, removedRowIndexes, this.plugin.pluginName);
|
||
|
|
if (grandparent) {
|
||
|
|
movedElementRowIndex = grandparentRowIndex + this.countChildren(grandparent);
|
||
|
|
const lastGrandparentChild = this.getChild(grandparent, this.countChildren(grandparent) - 1);
|
||
|
|
const lastGrandparentChildIndex = this.getRowIndex(lastGrandparentChild);
|
||
|
|
this.hot.runHooks('beforeCreateRow', lastGrandparentChildIndex + 1, childCount + 1, this.plugin.pluginName);
|
||
|
|
grandparent.__children.push(element);
|
||
|
|
} else {
|
||
|
|
movedElementRowIndex = this.hot.countRows() + 1;
|
||
|
|
this.hot.runHooks('beforeCreateRow', movedElementRowIndex - 2, childCount + 1, this.plugin.pluginName);
|
||
|
|
this.data.push(element);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.rewriteCache();
|
||
|
|
this.hot.runHooks('afterCreateRow', movedElementRowIndex - 2, childCount + 1, this.plugin.pluginName);
|
||
|
|
this.hot.runHooks('afterDetachChild', parent, element, this.getRowIndex(element));
|
||
|
|
if (forceRender) {
|
||
|
|
this.hot.render();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Filter the data by the `logicRows` array.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {number} index Index of the first row to remove.
|
||
|
|
* @param {number} amount Number of elements to remove.
|
||
|
|
* @param {Array} logicRows Array of indexes to remove.
|
||
|
|
*/
|
||
|
|
filterData(index, amount, logicRows) {
|
||
|
|
// TODO: why are the first 2 arguments not used?
|
||
|
|
|
||
|
|
const elementsToRemove = [];
|
||
|
|
(0, _array.arrayEach)(logicRows, elem => {
|
||
|
|
elementsToRemove.push(this.getDataObject(elem));
|
||
|
|
});
|
||
|
|
(0, _array.arrayEach)(elementsToRemove, elem => {
|
||
|
|
const indexWithinParent = this.getRowIndexWithinParent(elem);
|
||
|
|
const tempParent = this.getRowParent(elem);
|
||
|
|
if (tempParent === null) {
|
||
|
|
this.data.splice(indexWithinParent, 1);
|
||
|
|
} else {
|
||
|
|
tempParent.__children.splice(indexWithinParent, 1);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.rewriteCache();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Used to splice the source data. Needed to properly modify the nested structure, which wouldn't work with the
|
||
|
|
* default script.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {number} index Physical index of the element at the splice beginning.
|
||
|
|
* @param {number} amount Number of elements to be removed.
|
||
|
|
* @param {object[]} elements Array of row objects to add.
|
||
|
|
*/
|
||
|
|
spliceData(index, amount, elements) {
|
||
|
|
const previousElement = this.getDataObject(index - 1);
|
||
|
|
let newRowParent = null;
|
||
|
|
let indexWithinParent = index;
|
||
|
|
if (previousElement && previousElement.__children && previousElement.__children.length === 0) {
|
||
|
|
newRowParent = previousElement;
|
||
|
|
indexWithinParent = 0;
|
||
|
|
} else if (index < this.countAllRows()) {
|
||
|
|
newRowParent = this.getRowParent(index);
|
||
|
|
indexWithinParent = this.getRowIndexWithinParent(index);
|
||
|
|
}
|
||
|
|
if (newRowParent) {
|
||
|
|
if (elements) {
|
||
|
|
newRowParent.__children.splice(indexWithinParent, amount, ...elements);
|
||
|
|
} else {
|
||
|
|
newRowParent.__children.splice(indexWithinParent, amount);
|
||
|
|
}
|
||
|
|
} else if (elements) {
|
||
|
|
this.data.splice(indexWithinParent, amount, ...elements);
|
||
|
|
} else {
|
||
|
|
this.data.splice(indexWithinParent, amount);
|
||
|
|
}
|
||
|
|
this.rewriteCache();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update the `__children` key of the upmost parent of the provided row object.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {object} rowElement Row object.
|
||
|
|
*/
|
||
|
|
syncRowWithRawSource(rowElement) {
|
||
|
|
let upmostParent = rowElement;
|
||
|
|
let tempParent = null;
|
||
|
|
do {
|
||
|
|
tempParent = this.getRowParent(tempParent);
|
||
|
|
if (tempParent !== null) {
|
||
|
|
upmostParent = tempParent;
|
||
|
|
}
|
||
|
|
} while (tempParent !== null);
|
||
|
|
this.plugin.disableCoreAPIModifiers();
|
||
|
|
this.hot.setSourceDataAtCell(this.getRowIndex(upmostParent), '__children', upmostParent.__children, 'NestedRows.syncRowWithRawSource');
|
||
|
|
this.plugin.enableCoreAPIModifiers();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* eslint-disable jsdoc/require-param */
|
||
|
|
/**
|
||
|
|
* Move a single row.
|
||
|
|
*
|
||
|
|
* @param {number} fromIndex Index of the row to be moved.
|
||
|
|
* @param {number} toIndex Index of the destination.
|
||
|
|
* @param {boolean} moveToCollapsed `true` if moving a row to a collapsed parent.
|
||
|
|
* @param {boolean} moveToLastChild `true` if moving a row to be a last child of the new parent.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* eslint-enable jsdoc/require-param */
|
||
|
|
moveRow(fromIndex, toIndex, moveToCollapsed, moveToLastChild) {
|
||
|
|
const moveToLastRow = toIndex === this.hot.countRows();
|
||
|
|
const fromParent = this.getRowParent(fromIndex);
|
||
|
|
const indexInFromParent = this.getRowIndexWithinParent(fromIndex);
|
||
|
|
const elemToMove = fromParent.__children.slice(indexInFromParent, indexInFromParent + 1);
|
||
|
|
const movingUp = fromIndex > toIndex;
|
||
|
|
let toParent = moveToLastRow ? this.getRowParent(toIndex - 1) : this.getRowParent(toIndex);
|
||
|
|
if (toParent === null || toParent === void 0) {
|
||
|
|
toParent = this.getRowParent(toIndex - 1);
|
||
|
|
}
|
||
|
|
if (toParent === null || toParent === void 0) {
|
||
|
|
toParent = this.getDataObject(toIndex - 1);
|
||
|
|
}
|
||
|
|
if (!toParent) {
|
||
|
|
toParent = this.getDataObject(toIndex);
|
||
|
|
toParent.__children = [];
|
||
|
|
} else if (!toParent.__children) {
|
||
|
|
toParent.__children = [];
|
||
|
|
}
|
||
|
|
const indexInTargetParent = moveToLastRow || moveToCollapsed || moveToLastChild ? toParent.__children.length : this.getRowIndexWithinParent(toIndex);
|
||
|
|
const sameParent = fromParent === toParent;
|
||
|
|
toParent.__children.splice(indexInTargetParent, 0, elemToMove[0]);
|
||
|
|
fromParent.__children.splice(indexInFromParent + (movingUp && sameParent ? 1 : 0), 1);
|
||
|
|
|
||
|
|
// Sync the changes in the cached data with the actual data stored in HOT.
|
||
|
|
this.syncRowWithRawSource(fromParent);
|
||
|
|
if (!sameParent) {
|
||
|
|
this.syncRowWithRawSource(toParent);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Translate the visual row index to the physical index, taking into consideration the state of collapsed rows.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {number} row Row index.
|
||
|
|
* @returns {number}
|
||
|
|
*/
|
||
|
|
translateTrimmedRow(row) {
|
||
|
|
if (this.plugin.collapsingUI) {
|
||
|
|
return this.plugin.collapsingUI.translateTrimmedRow(row);
|
||
|
|
}
|
||
|
|
return row;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Translate the physical row index to the visual index, taking into consideration the state of collapsed rows.
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {number} row Row index.
|
||
|
|
* @returns {number}
|
||
|
|
*/
|
||
|
|
untranslateTrimmedRow(row) {
|
||
|
|
if (this.plugin.collapsingUI) {
|
||
|
|
return this.plugin.collapsingUI.untranslateTrimmedRow(row);
|
||
|
|
}
|
||
|
|
return row;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
var _default = DataManager;
|
||
|
|
exports.default = _default;
|