module.exports = (function(){
// Flag bad practises
'use strict';
// ------------------------------------
// Basic Setup
// ------------------------------------
/**
* @class TreeNode
* @classdesc Represents a node in the tree.
* @constructor
* @param {object} data - that is to be stored in a node
*/
function TreeNode(data){
/**
* Represents the parent node
*
* @property _parentNode
* @type {object}
* @default "null"
*/
this._parentNode = null;
/**
* Represents the child nodes
*
* @property _childNodes
* @type {array}
* @default "[]"
*/
this._childNodes = [];
/**
* Represents the data node has
*
* @property _data
* @type {object}
* @default "null"
*/
this._data = data;
/**
* Depth of the node represents level in hierarchy
*
* @property _depth
* @type {number}
* @default -1
*/
this._depth = -1;
}
// ------------------------------------
// Getters and Setters
// ------------------------------------
/**
* Returns a parent node of current node
*
* @method parentNode
* @memberof TreeNode
* @instance
* @return {TreeNode} - parent of current node
*/
TreeNode.prototype.parentNode = function(){
return this._parentNode;
};
/**
* Returns an array of child nodes
*
* @method childNodes
* @memberof TreeNode
* @instance
* @return {array} - array of child nodes
*/
TreeNode.prototype.childNodes = function(){
return this._childNodes;
};
/**
* Sets or gets the data belonging to this node. Data is what user sets using `insert` and `insertTo` methods.
*
* @method data
* @memberof TreeNode
* @instance
* @param {object | array | string | number | null} data - data which is to be stored
* @return {object | array | string | number | null} - data belonging to this node
*/
TreeNode.prototype.data = function(data){
if(arguments.length > 0){
this._data = data;
} else {
return this._data;
}
};
/**
* Depth of the node. Indicates the level at which node lies in a tree.
*
* @method depth
* @memberof TreeNode
* @instance
* @return {number} - depth of node
*/
TreeNode.prototype.depth = function(){
return this._depth;
};
// ------------------------------------
// Methods
// ------------------------------------
/**
* Indicates whether this node matches the specified criteria. It triggers a callback criteria function that returns something.
*
* @method matchCriteria
* @memberof TreeNode
* @instance
* @param {function} callback - Callback function that specifies some criteria. It receives {@link TreeNode#_data} in parameter and expects different values in different scenarios.
* `matchCriteria` is used by following functions and expects:
* 1. {@link Tree#searchBFS} - {boolean} in return indicating whether given node satisfies criteria.
* 2. {@link Tree#searchDFS} - {boolean} in return indicating whether given node satisfies criteria.
* 3. {@link Tree#export} - {object} in return indicating formatted data object.
*/
TreeNode.prototype.matchCriteria = function(criteria){
return criteria(this._data);
};
/**
* get sibling nodes.
*
* @method siblings
* @memberof TreeNode
* @instance
* @return {array} - array of instances of {@link TreeNode}
*/
TreeNode.prototype.siblings = function(){
var thiss = this;
return !this._parentNode ? [] : this._parentNode._childNodes.filter(function(_child){
return _child !== thiss;
});
};
/**
* Finds distance of node from root node
*
* @method distanceToRoot
* @memberof TreeNode
* @instance
* @return {array} - array of instances of {@link TreeNode}
*/
TreeNode.prototype.distanceToRoot = function(){
// Initialize Distance and Node
var distance = 0,
node = this;
// Loop Over Ancestors
while(node.parentNode()){
distance++;
node = node.parentNode();
}
// Return
return distance;
};
/**
* Gets an array of all ancestor nodes including current node
*
* @method getAncestry
* @memberof TreeNode
* @instance
* @return {Array} - array of ancestor nodes
*/
TreeNode.prototype.getAncestry = function(){
// Initialize empty array and node
var ancestors = [this],
node = this;
// Loop over ancestors and add them in array
while(node.parentNode()){
ancestors.push(node.parentNode());
node = node.parentNode();
}
// Return
return ancestors;
};
/**
* Exports the node data in format specified. It maintains herirachy by adding
* additional "children" property to returned value of `criteria` callback.
*
* @method export
* @memberof TreeNode
* @instance
* @param {TreeNode~criteria} criteria - Callback function that receives data in parameter
* and MUST return a formatted data that has to be exported. A new property "children" is added to object returned
* that maintains the heirarchy of nodes.
* @return {object} - {@link TreeNode}.
* @example
*
* var rootNode = tree.insert({
* key: '#apple',
* value: { name: 'Apple', color: 'Red'}
* });
*
* tree.insert({
* key: '#greenapple',
* value: { name: 'Green Apple', color: 'Green'}
* });
*
* tree.insertToNode(rootNode, {
* key: '#someanotherapple',
* value: { name: 'Some Apple', color: 'Some Color' }
* });
*
* // Export the tree
* var exported = rootNode.export(function(data){
* return { name: data.value.name };
* });
*
* // Result in `exported`
* {
* "name": "Apple",
* "children": [
* {
* "name": "Green Apple",
* "children": []
* },
* {
* "name": "Some Apple",
* "children": []
* }
* ]
*}
*
*/
TreeNode.prototype.export = function(criteria){
// Check if criteria is specified
if(!criteria || typeof criteria !== 'function')
throw new Error('Export criteria not specified');
// Export every node recursively
var exportRecur = function(node){
var exported = node.matchCriteria(criteria);
if(!exported || typeof exported !== 'object'){
throw new Error('Export criteria should always return an object and it cannot be null.');
} else {
exported.children = [];
node._childNodes.forEach(function(_child){
exported.children.push(exportRecur(_child));
});
return exported;
}
};
return exportRecur(this);
};
// ------------------------------------
// Export
// ------------------------------------
return TreeNode;
}());