@nodeject/ui-components
Version:
UI library for non-trivial components
102 lines (101 loc) • 4.7 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var defaultConfig = {
id: 'id',
parentId: 'parentId',
dataField: 'data',
childrenField: 'children',
throwChildrenFieldIfEmpty: false,
throwIfOrphans: false,
};
var getItemNestedProperty = function (item, nestedProperty) {
return nestedProperty.split('.').reduce(function (o, i) { return o[i]; }, item);
};
/**
* Unflattens an array to a tree with runtime O(n)
*/
export function arrayToTree(items, config) {
var _a;
if (config === void 0) { config = {}; }
var conf = __assign(__assign({}, defaultConfig), config);
// the resulting unflattened tree
var rootItems = [];
// stores all already processed items with their ids as key so we can easily look them up
var lookup = {};
// stores all item ids that have not been added to the resulting unflattened tree yet
// this is an opt-in property, since it has a slight runtime overhead
var orphanIds = config.throwIfOrphans
? new Set()
: null;
// idea of this loop:
// whenever an item has a parent, but the parent is not yet in the lookup object, we store a preliminary parent
// in the lookup object and fill it with the data of the parent later
// if an item has no parentId, add it as a root element to rootItems
if (items) {
var _loop_1 = function (item) {
var _a, _b, _c;
var itemId = getItemNestedProperty(item, conf.id);
var parentId = getItemNestedProperty(item, conf.parentId);
var isLeaf = ((_a = items.filter(function (n) { return itemId === getItemNestedProperty(n, conf.parentId); })) === null || _a === void 0 ? void 0 : _a.length) === 0;
// look whether item already exists in the lookup table
if (!Object.prototype.hasOwnProperty.call(lookup, itemId)) {
// item is not yet there, so add a preliminary item (its data will be added later)
lookup[itemId] = (_a = {}, _a[conf.childrenField] = [], _a);
}
// if we track orphans, delete this item from the orphan set if it is in it
if (orphanIds) {
orphanIds.delete(itemId);
}
// add the current item's data to the item in the lookup table
if (conf.dataField) {
lookup[itemId][conf.dataField] = item;
}
else {
if (conf.throwChildrenFieldIfEmpty && isLeaf) {
lookup[itemId] = __assign({}, item);
}
else {
lookup[itemId] = __assign(__assign({}, item), (_b = {}, _b[conf.childrenField] = lookup[itemId][conf.childrenField], _b));
}
}
var TreeItem = lookup[itemId];
if (parentId === null || parentId === undefined || parentId === '') {
// is a root item
rootItems.push(TreeItem);
}
else {
// has a parent
// look whether the parent already exists in the lookup table
if (!Object.prototype.hasOwnProperty.call(lookup, parentId)) {
// parent is not yet there, so add a preliminary parent (its data will be added later)
lookup[parentId] = (_c = {}, _c[conf.childrenField] = [], _c);
// if we track orphans, add the generated parent to the orphan list
if (orphanIds) {
orphanIds.add(parentId);
}
}
// add the current item to the parent
lookup[parentId][conf.childrenField].push(TreeItem);
}
};
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
var item = items_1[_i];
_loop_1(item);
}
}
if (orphanIds && orphanIds.size) {
throw new Error("The items array contains orphans that point to the following parentIds: " +
("[" + Array.from(orphanIds) + "]. These parentIds do not exist in the items array. Hint: prevent orphans to result ") +
"in an error by passing the following option: { throwIfOrphans: false }");
}
return rootItems;
}