hyperflow
Version:
A javascript state flow and mutation management toolkit & library for developing universal app.
353 lines (308 loc) • 10.9 kB
JavaScript
/**
* Copyright 2015-present Tuan Le.
*
* Licensed under the MIT License.
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*------------------------------------------------------------------------
*
* @module TreeElement
* @description - An simple implementation of a undirected data tree.
*
* @author Tuan Le (tuan.t.lei@gmail.com)
*
* @flow
*/
; // eslint-disable-line
import CommonElement from './common-element';
/* load undirected node */
import TreeNodeElement from './tree-node-element';
const Hf = CommonElement();
/**
* @description - An undirected tree element prototypes.
*
* TreeElementPrototype
*/
const TreeElementPrototype = Object.create({}).prototype = {
/* ----- Prototype Definitions --------- */
/**
* @description - From pathId, get node at pathId.
*
* @method _getNode
* @param {string|array} pathId - Node pathId.
* @return {object}
* @private
*/
_getNode (pathId) {
const tree = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isString(pathId) || Hf.isArray(pathId))) {
Hf.log(`error`, `TreeElement._getNode - Input node pathId is invalid.`);
}
}
/* convert pathId from array format to string format */
pathId = Hf.isArray(pathId) ? Hf.arrayToString(pathId, `.`) : pathId;
if (Hf.DEVELOPMENT) {
if (!tree.hasNode(pathId)) {
Hf.log(`error`, `TreeElement._getNode - Node with pathId:${pathId} is undefined.`);
}
}
return tree._node[pathId];
},
/**
* @description - Create a new node or existing node at pathId.
*
* @method _createNode
* @param {string|array} pathId - Node pathId.
* @return {object}
* @private
*/
_createNode (pathId) {
const tree = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isString(pathId) || Hf.isArray(pathId))) {
Hf.log(`error`, `TreeElement._createNode - Input node pathId is invalid.`);
}
}
/* convert pathId from array format to string format */
pathId = Hf.isArray(pathId) ? Hf.arrayToString(pathId, `.`) : pathId;
if (Hf.DEVELOPMENT) {
if (tree.hasNode(pathId)) {
Hf.log(`error`, `TreeElement._createNode - Node with pathId:${pathId} is already defined.`);
}
}
/* create a singular node at pathId */
const node = TreeNodeElement(tree, pathId);
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(node)) {
Hf.log(`error`, `TreeElement._createNode - Unable to create a tree node instance.`);
}
}
tree._node[pathId] = node;
return node;
},
/**
* @description - From pathId, delete node at pathId.
*
* @method _destroyNode
* @param {string|array} pathId - Node pathId.
* @return void
* @private
*/
_destroyNode (pathId) {
const tree = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isString(pathId) || Hf.isArray(pathId))) {
Hf.log(`error`, `TreeElement._destroyNode - Input node pathId is invalid.`);
}
}
/* convert pathId from array format to string format */
pathId = Hf.isArray(pathId) ? Hf.arrayToString(pathId, `.`) : pathId;
if (Hf.DEVELOPMENT) {
if (!tree.hasNode(pathId)) {
Hf.log(`error`, `TreeElement._destroyNode - Node with pathId:${pathId} is undefined.`);
}
}
const node = tree._node[pathId];
if (!node.isSingular()) {
let pathIds = [];
node.forEach(`descendants`, (dNode) => {
pathIds.push(dNode.getPathId());
});
pathIds.forEach((_pathId) => {
delete tree._node[_pathId];
// tree._node[_pathId] = undefined;
});
}
delete tree._node[pathId];
// tree._node[pathId] = undefined;
},
/**
* @description - Change Node pathId
*
* @method _changeNodePathId
* @param {string} oldPathId
* @param {string} newPathId
* @return void
* @private
*/
_changeNodePathId (oldPathId, newPathId) {
const tree = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isString(oldPathId) || Hf.isArray(oldPathId)) &&
(!(Hf.isString(newPathId) || Hf.isArray(newPathId)))) {
Hf.log(`error`, `TreeElement._changeNodePathId - Input node pathIds are invalid.`);
}
}
/* convert pathId from array format to string format */
oldPathId = Hf.isArray(oldPathId) ? Hf.arrayToString(oldPathId, `.`) : oldPathId;
newPathId = Hf.isArray(newPathId) ? Hf.arrayToString(newPathId, `.`) : newPathId;
if (Hf.DEVELOPMENT) {
if (tree.hasNode(newPathId)) {
Hf.log(`error`, `TreeElement._changeNodePathId - Node with pathId:${oldPathId} is already defined.`);
} else if (!tree.hasNode(oldPathId)) {
Hf.log(`error`, `TreeElement._changeNodePathId - Node with pathId:${oldPathId} is undefined.`);
}
}
const node = tree._node[oldPathId];
tree._node[newPathId] = node;
delete tree._node[oldPathId];
// tree._node[oldPathId] = undefined;
},
/**
* @description - From pathId, check if there is a node at pathId.
*
* @method hasNode
* @param {string|array} pathId - Node pathId.
* @return {boolean}
*/
hasNode (pathId) {
const tree = this;
if (!(Hf.isString(pathId) || Hf.isArray(pathId))) {
return false;
}
/* convert pathId from array format to string format */
pathId = Hf.isArray(pathId) ? Hf.arrayToString(pathId, `.`) : pathId;
return tree._node.hasOwnProperty(pathId) && Hf.isDefined(tree._node[pathId]);
},
/**
* @description - Get the number of root nodes.
*
* @method getRootCount
* @return {number}
*/
getRootCount () {
const tree = this;
return Object.values(tree._node).filter((node) => Hf.isDefined(node)).reduce((count, node) => {
return node.isRoot() ? count++ : count;
}, 0);
},
/**
* @description - Cut and discard a root node and its descendants.
*
* @method cutRoot
* @param {string|number} rootKey - Root node key.
* @return void
*/
cutRoot (rootKey) {
const tree = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isString(rootKey) || Hf.isInteger(rootKey))) {
Hf.log(`error`, `TreeElement.cutRoot - Input root node key is invalid.`);
}
}
const pathId = rootKey;
if (Hf.DEVELOPMENT) {
if (!tree.hasNode(pathId)) {
Hf.log(`error`, `TreeElement.cutRoot - Root node rootKey:${rootKey} is undefined.`);
} else if (!Hf.isObject(tree._getNode(pathId))) {
Hf.log(`error`, `TreeElement.cutRoot - Unable to cut root node at pathId:${pathId}`);
}
}
tree._destroyNode(pathId);
},
/**
* @description - Sprout out a new root node.
*
* @method sproutRoot
* @param {string|number} rootKey - Root node key.
* @param {*} rootContent - Root node content.
* @return {object}
*/
sproutRoot (rootKey, rootContent = {
cache: undefined,
accessor: undefined
}) {
const tree = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isString(rootKey) || Hf.isInteger(rootKey))) {
Hf.log(`error`, `TreeElement.sproutRoot - Input root node key is invalid.`);
}
}
const pathId = rootKey;
const rootNode = tree._createNode(pathId);
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(rootNode)) {
Hf.log(`error`, `TreeElement.sproutRoot - Unable to sprout root node at pathId:${pathId}`);
}
}
rootNode.setContent(rootContent);
return tree.select(pathId);
},
/**
* @description - Select pathId, get and reveal node at pathId.
*
* @method select
* @param {string|array} pathId - Node pathId.
* @return {object}
*/
select (pathId) {
const tree = this;
if (Hf.DEVELOPMENT) {
if (!(Hf.isString(pathId) || Hf.isArray(pathId))) {
Hf.log(`error`, `TreeElement.select - Input node pathId is invalid.`);
} else if (!tree.hasNode(pathId)) {
Hf.log(`error`, `TreeElement.select - Node with pathId:${pathId} is undefined.`);
} else if (!Hf.isObject(tree._getNode(pathId))) {
Hf.log(`error`, `TreeElement.select - Unable to start from node at pathId:${pathId}.`);
}
}
const node = tree._getNode(pathId);
/* reveal only the public properties and functions */
return Hf.compose(Hf.reveal, Object.freeze)(node);
}
/**
* @description - Return an undirected tree element hierarchy as a string for debugging.
*
* @method DEBUG_LOG
* @return {string}
*/
// DEBUG_LOG () {
// const tree = this;
//
// Hf.forEach(tree._node, (node) => {
// if (node.isRoot() || node.isSingular()) {
// if (node.isSingular()) {
// node.DEBUG_LOG();
// } else {
// node.DEBUG_LOG();
// node.forEach(`descendants`, (dNode) => {
// dNode.DEBUG_LOG();
// });
// }
// }
// });
// }
};
/**
* @description - An undirected data tree element module.
*
* @module TreeElement
* @return {object}
*/
export default function TreeElement () {
const element = Object.create(TreeElementPrototype, {
_node: {
value: {},
writable: false,
configurable: true,
enumerable: false
}
});
if (Hf.DEVELOPMENT) {
if (!Hf.isObject(element)) {
Hf.log(`error`, `TreeElement - Unable to create a this element element instance.`);
}
}
/* reveal only the public properties and functions */
return Hf.compose(Hf.reveal, Object.freeze)(element);
}