Tree Table - Setting children on react.js - javascript

I'm trying to build a Tree Table that is capable of shown/hide their TR childs when clicked (among other things).
I've grab some code from the web to generate easily generate a tree structure. The problem comes trying to set a parent/child relationship with this event. I'm really not used to react so it is hard for me to understand certain things.
This is the data structure:
var data = [
{
id: 1,
label: 'Order 1',
deep: 0,
children: [{
id: 11,
label: 'Cycle November',
deep: 1,
},
{
id: 12,
label: 'Cycle December',
deep: 1,
}]
},
{
id: 2,
label: 'Order 2',
deep: 0,
children: [{
id: 21,
label: 'Cycle January',
deep: 1
}]
},
];
Then i want to click on Parent id 1 and see who their children are.
This is the code that draws the Table:
var TR = React.createClass({
childs: [],
hasParent: function() {
if (this.props.parent) {
return true;
}
else {
return false;
}
},
componentDidMount: function(){
if (this.hasParent()) {
parentKey = this.props.parent.props.keyId.split('-')[1];
thisKey = this.props.keyId.split('-')[0];
if (parentKey == thisKey) {
console.log('I set the child');
this.props.parent.props.childs.push(this);
}
}
},
handleClick: function(event) {
if (this.hasParent()) {
parent = this.props.parent;
console.log('My father is: '+parent.props.keyId);
console.log('Me and my brothers are: ');
parent.props.childs.map(function(child) {
console.log(child.props.keyId);
});
}
else {
console.log('I am: '+this.props.keyId);
console.log('My children are:');
this.props.childs.map(function(child) {
console.log(child.props.keyId);
});
}
},
isShown: function() {
return false;
},
getDefaultProps: function() {
return {
childs: new Array(),
};
},
render: function() {
var iparent = this.props.data.deep*20;
return (
<tr>
<td style={{paddingLeft: iparent+'px'}}>
<span onClick={this.handleClick} >
+ </span>
{this.props.data.label}
</td>
</tr>
);
}
});
var Tree = React.createClass({
render: function() {
var treeItems = [];
parent = null;
var renderTreeItems = function(items, parent) {
if (!items) {
return;
}
for (var i = 0; i < items.length; i++) {
if (items[i].deep == 0) {
var parent_id = items[i].deep;
}
else {
var parent_id = parent.props.data.id;
}
var key = parent_id+'-'+items[i].id;
tr = (
<TR key={key} keyId={key} data={items[i]} parent={parent} >
</TR>
);
treeItems.push(
tr
);
renderTreeItems(items[i].children, tr);
}
};
renderTreeItems(this.props.data);
return (
<table>
{ treeItems }
</table>
);
}
});
And last the HTML to attach the component:
<html>
<body>
<div id="tree"></div>
<script type="text/jsx">
React.render(<Tree data={data}/>, document.body);
</script>
</body>
</html>
The actual problem is that childs (badly written i know...) seems to be shared between all TR instances.
Can someone point what i'm doing wrong and what is the best approach to deal with this?
Simply: i want to hide/show childs clicking on the TR-First TD that "acts" as a parent.
Here is the jsfiddle http://jsfiddle.net/69z2wepo/2731/

I approached the problem from a new perspective using a parent_key on every TR and querying against all the array in search of those objects that have that parent_key, changing it's properties. Now I have the start of a fully functional table tree.
//Won't work below IE9, but totally safe otherwise
!function() {
function _dynamicSortMultiple(attr) {
/*
* save the arguments object as it will be overwritten
* note that arguments object is an array-like object
* consisting of the names of the properties to sort by
*/
var props = arguments;
return function (obj1, obj2) {
var i = 0, result = 0, numberOfProperties = props.length;
/* try getting a different result from 0 (equal)
* as long as we have extra properties to compare
*/
while(result === 0 && i < numberOfProperties) {
result = _dynamicSort(props[i])(obj1, obj2);
i++;
}
return result;
};
}
function _dynamicSort(property) {
var sortOrder = 1;
if(property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
return function (a,b) {
var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
return result * sortOrder;
}
}
Object.defineProperty(Array.prototype, "sortBy", {
enumerable: false,
writable: true,
value: function() {
return this.sort(_dynamicSortMultiple.apply(null, arguments));
}
});
}();
var TD = React.createClass({
onHandleClick: function(event) {
this.props.onHandleClick(this.props.data.key);
},
render: function() {
var iparent = this.props.data.deep*20;
var icon = "fa-chevron-down";
return (
<td style={{paddingLeft: iparent+'px'}}>
<span onClick={this.onHandleClick} className={"fa "+icon}>
</span>
{this.props.data.label}
</td>
);
}
})
var TR = React.createClass({
onHandleClick: function(TD) {
this.props.onHandleClick(TD);
},
render: function() {
if (this.props.data.isShown) {
return (
<tr>
<TD data={this.props.data} onHandleClick={this.onHandleClick}/>
</tr>
);
}
else {
return (
<tr>
</tr>
);
}
}
});
var List = React.createClass({
render: function() {
var self = this;
var trs = this.props.data.map(function(row) {
var key = row.deep+'-'+row.id;
return (<TR key={key} keyId={key} parentKey={row.parent_key} data={row} onHandleClick={self.props.onHandleClick} />);
});
return (
<table>
{trs}
</table>
);
}
});
var Tree = React.createClass({
handleClick: function(key) {
newState = this.state.data.slice();
for (var i = newState.length - 1; i >= 0; i--) {
var item = newState[i];
if (item.parent_key == key) {
item.isShown = (item.isShown)?false:true;
newState[i] = item;
}
};
this.setState({data: newState});
},
loadFromServer: function() {
var data = [
{
id: 1,
label: 'Order 1',
deep: 0,
key: '0-1',
parent_key: '0',
},
{
id: 11,
label: 'Cycle November',
deep: 1,
key: '0-1-11',
parent_key: '0-1',
},
{
id: 12,
label: 'Cycle December',
deep: 1,
key: '0-1-12',
parent_key: '0-1',
},
{
id: 2,
label: 'Order 2',
deep: 0,
key: '0-2',
parent_key: '0'
},
{
id: 21,
label: 'Cycle January',
deep: 1,
key: '0-2-21',
parent_key: '0-2',
}
];
newState = [];
data.map(function(item) {
item.isShown = (item.deep != 0)?false:true;
newState.push(item);
});
newState.sortBy("key");
this.setState({data:newState});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadFromServer();
},
render: function() {
return (
<div className="Box">
<table className="table table-striped table-hover dataTables-orders" >
<thead>
</thead>
<List data={this.state.data} onHandleClick={this.handleClick} />
</table>
</div>
);
}
});
React.render(<Tree data={data}/>, document.getElementById('tree'));

Related

Filter the data array by onChange an input value

In the code below, I am trying to run onChange={this.handleChange} with react js.I would like to obtain the items by filtering them based on what is written on Input,I tried the following :
<input value={this.state.name} onChange={this.handleChange}/>
handleChange= evt =>
this.setState(
{
name: evt.target.value.toLowerCase()
},
() => {
.
.
.
}
)
Firstly there is an input and the its function that return the value of the input.
const data=[
{ "info": [{ "name": "ali" }, { "name": "amir" }, { "name": "maya" }] },
{ "info": [{ "name": "eli" }, { "name": "mary" }] },
{ "info": [{ "name": "ali" }] },
{
"info": [{ "name": "emila" }, { "name": "alex" }, { "name": "sosan" }]
}
]
data = data .filter(item => {
if (this.renderName(item).some((r) => {
r.includes(name)
}
)) return item;
})
renderName(element){
let elementAdd = []
for (let i = 1; i < element.info.length; i++) {
elementAdd.push(element.info[i].name.toLowerCase())
}
return elementAdd
}
And I want to filter the data array based on input value, but it does not work!
Edit:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{ id: 1, info: [{ name: "ali" }, { name: "amir" }, { name: "maya" }] },
{ id: 2, info: [{ name: "eli" }, { name: "mary" }] },
{ id: 3, info: [{ name: "mary" }] },
{
id: 4,
info: [{ name: "emila" }, { name: "alex" }, { name: "sosan" }],
},
],
name: "",
};
}
reorganiseLibrary = () => {
const { name } = this.state;
let library = data;
if (name !== "") {
library = library.filter((item) => {
if (
this.renderName(item).some((r) => {
name.includes(r);
})
)
return item;
});
}
};
renderName(element) {
let elementAdd = [];
for (let i = 1; i < element.info.length; i++) {
elementAdd.push(element.info[i].name.toLowerCase());
}
return elementAdd;
}
handleChange = (evt) =>
this.setState(
{
name: evt.target.value.toLowerCase(),
},
() => {
this.reorganiseLibrary();
}
);
renderLibrary = () => {
const { library } = this.state;
if (!library || (library && library.length === 0)) {
return "";
}
return library.map((item) => <div className="item">{item.id}</div>);
};
render() {
return (
<div>
<input value={this.state.name} onChange={this.handleChange} />
{this.renderLibrary()}
</div>
);
}
}
ReactDOM.render(<App></App>, document.getElementById("app"));
There are many issues in your code and I will only discuss the critical points.
reorganiseLibrary method
data not extracted from props
handleChange method
wrong use of setState. No second parameter as far as I know.
renderName method
you only get name property but you expect an object in renderLibrary method
Here is a solution that I can think of.
state = {
data: [],
name: "",
library: [] // use this to show latest filtered data
}
function onChange(event) {
const { data} = this.state;
this.setState(
{
name: event.target.value.toLowerCase()
});
let filteredResult = [];
for(var index = 0; index < data.length; index++) {
var filteredValue = data[index].info.filter(item => item.name.includes(event.target.value));
if(filteredValue.length != 0)
filteredResult.push(filteredValue);
}
if(filteredResult.length != 0) // remove this if you want to reset the display in your UI
setState({library : filteredResult});
}
renderLibrary = () => {
const { library } = this.state;
if (library.length > 0)) {
return library.foreach(item => (<div className="item">{item.id}</div>)); // modify the onChange filter if you want the outer object
};

Javascript array of headers to a tree

I'm working with a list of HTML headers (h2,h3,h4,h5,h6).
The picture describes the idea:
[
{
text: 'Some header',
rank: 2, // stays for <h2>
},
{
text: 'Some another header',
rank: 3, // stays for <h3>
},
{
text: 'A header with the same rank',
rank: 3, // stays for <h3>
},
{
text: 'One more subsection header',
rank: 4, // stays for <h4>
}
]
And I'm trying to turn it into a tree:
[
{
text: 'Some header',
children: [
{
text: 'Some another header',
},
{
text: 'A header with the same rank',
children: [
{
text: 'One more subsection header',
}
]
}
]
}
]
Here's my current code:
function list_to_tree(list) {
// We go from the end to the beggining
list = list.reverse();
let node, nextNode, roots = [], i;
for (i = 0; i < list.length; i += 1) {
node = list[i];
nextNode = list[i+1];
// If the next one's rank is greater, the current into the next as a child
if (nextNode !== undefined && node.rank > nextNode.rank) {
list[i+1].children.push(node);
} else {
// Else it's a root
roots.push(node);
}
}
return roots;
};
But it works only for the first h3, but the second h3 will go as a root. Any idea on how to achieve the goal? Thank you.
You could use the level property rank for indicating the nested position in a helper array.
Then iterate the data and build children arrays, if necessary.
function getTree(array) {
var levels = [{}];
array.forEach(function (o) {
levels.length = o.rank;
levels[o.rank - 1].children = levels[o.rank - 1].children || [];
levels[o.rank - 1].children.push(o);
levels[o.rank] = o;
});
return levels[0].children;
}
var data = [{ text: 'Main Heading', rank: 1 }, { text: 'Sub Heading', rank: 2 }, { text: 'Sub Sub Heading', rank: 3 }, { text: 'Sub Heading', rank: 2 }, { text: 'Sub Sub Heading', rank: 3 }, { text: 'Sub Sub Heading', rank: 3 }];
console.log(getTree(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This solution will work irrespective of starting rank and also of the order in which the list is given... and it uses the exact data you provided...
var jsonHeaders =
[
{
text: 'Some header',
rank: 2, // stays for <h2>
},
{
text: 'Some another header',
rank: 3, // stays for <h3>
},
{
text: 'A header with the same rank',
rank: 3, // stays for <h3>
},
{
text: 'One more subsection header',
rank: 4, // stays for <h4>
}
];
function list_to_tree(list)
{
var jsonTree = [{}];
list = list.reverse();
for (i = 0, l = list.length; i < l; i++)
{
node = list[i];
var json = {};
json.text = node.text;
json.rank = node.rank;
if(jsonTree[0].rank == undefined)
{
jsonTree[0] = json;
}
else
if(jsonTree[0].rank == json.rank)
{
jsonTree.push(json);
}
else
if(jsonTree[0].rank < json.rank)
{
jsonTree[0] = ranker(jsonTree[0], json);
}
else
if(jsonTree[0].rank > json.rank)
{
var jsonTemp = jsonTree[0];
jsonTree[0] = json;
json = jsonTemp;
jsonTree[0] = ranker(jsonTree[0], json);
}
}
return jsonTree;
}
function ranker(jsonTree, json)
{
if(jsonTree.children == undefined)
{
jsonTree.children = [];
jsonTree.children.push(json);
}
else
if(jsonTree.children[0].rank == json.rank)
{
jsonTree.children.push(json);
}
else
if(jsonTree.children[0].rank < json.rank)
{
jsonTree.children[0] = ranker(jsonTree.children[0], json);
}
else
if(jsonTree.children[0].rank > json.rank)
{
var jsonTemp = jsonTree;
jsonTree = json;
json = jsonTemp;
jsonTree.children[0] = ranker(jsonTree.children[0], json);
}
return jsonTree;
}
var jsonTree = list_to_tree(jsonHeaders);
console.log('jsonArrayTree = ', jsonTree);
Here's the working function in case someone needs it:
function list_to_tree(list) {
list = list.reverse();
let node, currentRank = list[0].rank, i, roots = [];
for (i = 0; i < list.length; i += 1) {
node = list[i];
if (node.rank > currentRank) {
for (let n = i; n < list.length; n += 1) {
if (list[n].rank < node.rank) {
list[n].children.unshift(node);
break;
}
}
} else {
currentRank = node.rank;
roots.push(node);
}
}
return roots.reverse();
};

how to add child Node in orgChart2

How to addChild node in lib orgChart2
https://github.com/rchockxm/js-orgChart-2
my code
Ascedance.FamilyTree = (function() {
function FamilyTree() {
var params = {
.....
}
this.pChart = new OrgChartV2(chartParams);
this.pChart.render();
$('.add-root-child').click(this.addChild);
}
FamilyTree.prototype.addChild = function() {
var node, nodeChildParams;
nodeChildParams = {
options: {
targetName: "orgchart",
subTargetName: "orgnode",
clsName: "org-node"
},
customParams: {
caption: "Frank",
description: "Demo Child Nodes"
}
};
node = new OrgNodeV2(nodeChildParams);
return this.pChart.nodes.add.nodes(node);
};
rendering the original wood holds fine
I have by clicking on the button to add another node (method addChild)
You have to create the root node.
// Add by click node.
function addNodesByClick(pData, id) {
if (typeof pData === "object" && pData !== null) {
var isFind = false;
if (typeof pData.node === "object" && pData.node !== null) {
if (pData.node.idt1 == id) {
isFind = true;
}
if (isFind == true) {
var nodeNewChildParams = {
options: {
targetName: "orgchart",
subTargetName: "orgnode",
clsName: "org-node"
},
customParams: {
caption: lpszDemoData,
description: "New Child Nodes"
}
};
var node = new OrgNodeV2(nodeNewChildParams);
pData.addNodes(node);
}
}
if (isFind == false) {
if (typeof pData.nodes === "object" && pData.nodes !== null) {
for (var i = 0; i < pData.nodes.length; i ++) {
addNodesByClick(pData.nodes[i], id);
}
}
}
}
}
(function() {
// Create params for chart.
var chartParams = {
options: {
top: 12,
left: 12,
line: {
size: 2,
color: "#3388dd"
},
node: {
width: 64,
height: 64,
maxWidth: 128,
maxHeight: 128,
template: "<div id=\"{id}\"><p class=\"node-caption\">{caption}</p><span class=\"node-description\">{description}</span><br /><label>Click to Add</label></div>"
}
},
event: {
node: {
onProcess: function(node, nodes) {
console.log("node.onProcess");
},
onClick: function() {
console.log("node.onClick");
addNodesByClick(pOrgNodes, this.id);
document.getElementById("orgchart").innerHTML = "";
// Re-Create OrgChartV2.
var pChart = new OrgChartV2(chartParams);
// Re-Init.
pChart.render();
},
onMouseMove: function() {
console.log("node.onMouseMove");
},
onMouseOver: function() {
console.log("node.onMouseOver");
},
onMouseOut: function() {
console.log("node.onMouseOut");
}
},
onCreate: function() {
console.log("onCreate");
},
onError: null,
onFinish: function() {
console.log("onFinish");
}
},
nodes: pOrgNodes
};
// Create OrgChartV2.
var pChart = new OrgChartV2(chartParams);
// Init.
pChart.render();
})();

ng-repeat filter negate string from object - angularjs

I want to put this filter: filter:'!Category'
on to this element like so:
<div ng-repeat="(prop, ignoredValue) in wines[0] | filter:'!Category'" ng-init="filter[prop]={}">
But it does not filter out the property "Category". However if I put a similar filter to exclude "Wine" on the following ng-repeat element, it works fine:
<span class="quarter" ng-repeat="opt in getOptionsFor(prop) | filter:'!Wine'">
I don't understand why I can't filter out property values here. I am very new to angularjs and I'm going out of my mind trying to figure this out. I want to exclude specific property names from the object. either "name" or "category"
I linked the fiddle below. Any help much appreciated! Thanks!
jsfiddle <- link to the fiddle
<div ng-controller="myCtrl">
<div ng-repeat="(prop, ignoredValue) in wines[0]" ng-init="filter[prop]={}">
<b>{{prop | capitalizeFirst}}:</b><br />
<span class="quarter" ng-repeat="opt in getOptionsFor(prop)">
<b><input type="checkbox" ng-model="filter[prop][opt]" /> {{opt}}</b>
</span>
<hr />
</div>
<div ng-repeat="w in filtered=(wines | filter:filterByProperties)">
{{w.name}} ({{w.category}})
</div>
<hr />
Number of results: {{filtered.length}}
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.wines = [
{ name: "Wine A", category: "red" },
{ name: "Wine B", category: "red" },
{ name: "wine C", category: "white" },
{ name: "Wine D", category: "red" },
{ name: "Wine E", category: "red" },
{ name: "wine F", category: "white" },
{ name: "wine G", category: "champagne"},
{ name: "wine H", category: "champagne" }
];
$scope.filter = {};
$scope.getOptionsFor = function (propName) {
return ($scope.wines || []).map(function (w) {
return w[propName];
}).filter(function (w, idx, arr) {
return arr.indexOf(w) === idx;
});
};
$scope.filterByProperties = function (wine) {
// Use this snippet for matching with AND
var matchesAND = true;
for (var prop in $scope.filter) {
if (noSubFilter($scope.filter[prop])) continue;
if (!$scope.filter[prop][wine[prop]]) {
matchesAND = false;
break;
}
}
return matchesAND;
/**/
/*
// Use this snippet for matching with OR
var matchesOR = true;
for (var prop in $scope.filter) {
if (noSubFilter($scope.filter[prop])) continue;
if (!$scope.filter[prop][wine[prop]]) {
matchesOR = false;
} else {
matchesOR = true;
break;
}
}
return matchesOR;
/**/
};
function noSubFilter(subFilterObj) {
for (var key in subFilterObj) {
if (subFilterObj[key]) return false;
}
return true;
}
});
app.filter('capitalizeFirst', function () {
return function (str) {
str = str || '';
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
};
});
You could write a simple filter to extract keys first:
app.filter('keys', function () {
return function (object) {
return Object.keys(object || {}).filter(function (key) {
return key !== '$$hashKey'; // this is from ng-repeat
});
};
});
and use it like this:
<div ng-repeat="prop in wines[0] | keys | filter:'!Category'"
Example JSFiddle: http://jsfiddle.net/1qhba9fs/1/
Hope this helps.

How to find a node in a tree with JavaScript

I have and object literal that is essentially a tree that does not have a fixed number of levels. How can I go about searching the tree for a particualy node and then return that node when found in an effcient manner in javascript?
Essentially I have a tree like this and would like to find the node with the title 'randomNode_1'
var data = [
{
title: 'topNode',
children: [
{
title: 'node1',
children: [
{
title: 'randomNode_1'
},
{
title: 'node2',
children: [
{
title: 'randomNode_2',
children:[
{
title: 'node2',
children: [
{
title: 'randomNode_3',
}]
}
]
}]
}]
}
]
}];
Basing this answer off of #Ravindra's answer, but with true recursion.
function searchTree(element, matchingTitle){
if(element.title == matchingTitle){
return element;
}else if (element.children != null){
var i;
var result = null;
for(i=0; result == null && i < element.children.length; i++){
result = searchTree(element.children[i], matchingTitle);
}
return result;
}
return null;
}
Then you could call it:
var element = data[0];
var result = searchTree(element, 'randomNode_1');
Here's an iterative solution:
var stack = [], node, ii;
stack.push(root);
while (stack.length > 0) {
node = stack.pop();
if (node.title == 'randomNode_1') {
// Found it!
return node;
} else if (node.children && node.children.length) {
for (ii = 0; ii < node.children.length; ii += 1) {
stack.push(node.children[ii]);
}
}
}
// Didn't find it. Return null.
return null;
Here's an iterative function using the Stack approach, inspired by FishBasketGordo's answer but taking advantage of some ES2015 syntax to shorten things.
Since this question has already been viewed a lot of times, I've decided to update my answer to also provide a function with arguments that makes it more flexible:
function search (tree, value, key = 'id', reverse = false) {
const stack = [ tree[0] ]
while (stack.length) {
const node = stack[reverse ? 'pop' : 'shift']()
if (node[key] === value) return node
node.children && stack.push(...node.children)
}
return null
}
This way, it's now possible to pass the data tree itself, the desired value to search and also the property key which can have the desired value:
search(data, 'randomNode_2', 'title')
Finally, my original answer used Array.pop which lead to matching the last item in case of multiple matches. In fact, something that could be really confusing. Inspired by Superole comment, I've made it use Array.shift now, so the first in first out behavior is the default.
If you really want the old last in first out behavior, I've provided an additional arg reverse:
search(data, 'randomNode_2', 'title', true)
My answer is inspired from FishBasketGordo's iterativ answer. It's a little bit more complex but also much more flexible and you can have more than just one root node.
/**searchs through all arrays of the tree if the for a value from a property
* #param aTree : the tree array
* #param fCompair : This function will receive each node. It's upon you to define which
condition is necessary for the match. It must return true if the condition is matched. Example:
function(oNode){ if(oNode["Name"] === "AA") return true; }
* #param bGreedy? : us true to do not stop after the first match, default is false
* #return an array with references to the nodes for which fCompair was true; In case no node was found an empty array
* will be returned
*/
var _searchTree = function(aTree, fCompair, bGreedy){
var aInnerTree = []; // will contain the inner children
var oNode; // always the current node
var aReturnNodes = []; // the nodes array which will returned
// 1. loop through all root nodes so we don't touch the tree structure
for(keysTree in aTree) {
aInnerTree.push(aTree[keysTree]);
}
while(aInnerTree.length > 0) {
oNode = aInnerTree.pop();
// check current node
if( fCompair(oNode) ){
aReturnNodes.push(oNode);
if(!bGreedy){
return aReturnNodes;
}
} else { // if (node.children && node.children.length) {
// find other objects, 1. check all properties of the node if they are arrays
for(keysNode in oNode){
// true if the property is an array
if(oNode[keysNode] instanceof Array){
// 2. push all array object to aInnerTree to search in those later
for (var i = 0; i < oNode[keysNode].length; i++) {
aInnerTree.push(oNode[keysNode][i]);
}
}
}
}
}
return aReturnNodes; // someone was greedy
}
Finally you can use the function like this:
var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, false);
console.log("Node with title found: ");
console.log(foundNodes[0]);
And if you want to find all nodes with this title you can simply switch the bGreedy parameter:
var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, true);
console.log("NodeS with title found: ");
console.log(foundNodes);
FIND A NODE IN A TREE :
let say we have a tree like
let tree = [{
id: 1,
name: 'parent',
children: [
{
id: 2,
name: 'child_1'
},
{
id: 3,
name: 'child_2',
children: [
{
id: '4',
name: 'child_2_1',
children: []
},
{
id: '5',
name: 'child_2_2',
children: []
}
]
}
]
}];
function findNodeById(tree, id) {
let result = null
if (tree.id === id) {
return tree;
}
if (Array.isArray(tree.children) && tree.children.length > 0) {
tree.children.some((node) => {
result = findNodeById(node, id);
return result;
});
}
return result;}
You have to use recursion.
var currChild = data[0];
function searchTree(currChild, searchString){
if(currChild.title == searchString){
return currChild;
}else if (currChild.children != null){
for(i=0; i < currChild.children.length; i ++){
if (currChild.children[i].title ==searchString){
return currChild.children[i];
}else{
searchTree(currChild.children[i], searchString);
}
}
return null;
}
return null;
}
ES6+:
const deepSearch = (data, value, key = 'title', sub = 'children', tempObj = {}) => {
if (value && data) {
data.find((node) => {
if (node[key] == value) {
tempObj.found = node;
return node;
}
return deepSearch(node[sub], value, key, sub, tempObj);
});
if (tempObj.found) {
return tempObj.found;
}
}
return false;
};
const result = deepSearch(data, 'randomNode_1', 'title', 'children');
This function is universal and does search recursively.
It does not matter, if input tree is object(single root), or array of objects (many root objects). You can configure prop name that holds children array in tree objects.
// Searches items tree for object with specified prop with value
//
// #param {object} tree nodes tree with children items in nodesProp[] table, with one (object) or many (array of objects) roots
// #param {string} propNodes name of prop that holds child nodes array
// #param {string} prop name of searched node's prop
// #param {mixed} value value of searched node's prop
// #returns {object/null} returns first object that match supplied arguments (prop: value) or null if no matching object was found
function searchTree(tree, nodesProp, prop, value) {
var i, f = null; // iterator, found node
if (Array.isArray(tree)) { // if entry object is array objects, check each object
for (i = 0; i < tree.length; i++) {
f = searchTree(tree[i], nodesProp, prop, value);
if (f) { // if found matching object, return it.
return f;
}
}
} else if (typeof tree === 'object') { // standard tree node (one root)
if (tree[prop] !== undefined && tree[prop] === value) {
return tree; // found matching node
}
}
if (tree[nodesProp] !== undefined && tree[nodesProp].length > 0) { // if this is not maching node, search nodes, children (if prop exist and it is not empty)
return searchTree(tree[nodesProp], nodesProp, prop, value);
} else {
return null; // node does not match and it neither have children
}
}
I tested it localy and it works ok, but it somehow won't run on jsfiddle or jsbin...(recurency issues on those sites ??)
run code :
var data = [{
title: 'topNode',
children: [{
title: 'node1',
children: [{
title: 'randomNode_1'
}, {
title: 'node2',
children: [{
title: 'randomNode_2',
children: [{
title: 'node2',
children: [{
title: 'randomNode_3',
}]
}]
}]
}]
}]
}];
var r = searchTree(data, 'children', 'title', 'randomNode_1');
//var r = searchTree(data, 'children', 'title', 'node2'); // check it too
console.log(r);
It works in http://www.pythontutor.com/live.html#mode=edit (paste the code)
no BS version:
const find = (root, title) =>
root.title === title ?
root :
root.children?.reduce((result, n) => result || find(n, title), undefined)
This is basic recursion problem.
window.parser = function(searchParam, data) {
if(data.title != searchParam) {
returnData = window.parser(searchParam, children)
} else {
returnData = data;
}
return returnData;
}
here is a more complex option - it finds the first item in a tree-like node with providing (node, nodeChildrenKey, key/value pairs & optional additional key/value pairs)
const findInTree = (node, childrenKey, key, value, additionalKey?, additionalValue?) => {
let found = null;
if (additionalKey && additionalValue) {
found = node[childrenKey].find(x => x[key] === value && x[additionalKey] === additionalValue);
} else {
found = node[childrenKey].find(x => x[key] === value);
}
if (typeof(found) === 'undefined') {
for (const item of node[childrenKey]) {
if (typeof(found) === 'undefined' && item[childrenKey] && item[childrenKey].length > 0) {
found = findInTree(item, childrenKey, key, value, additionalKey, additionalValue);
}
}
}
return found;
};
export { findInTree };
Hope it helps someone.
A flexible recursive solution that will work for any tree
// predicate: (item) => boolean
// getChildren: (item) => treeNode[]
searchTree(predicate, getChildren, treeNode) {
function search(treeNode) {
if (!treeNode) {
return undefined;
}
for (let treeItem of treeNode) {
if (predicate(treeItem)) {
return treeItem;
}
const foundItem = search(getChildren(treeItem));
if (foundItem) {
return foundItem;
}
}
}
return search(treeNode);
}
find all parents of the element in the tree
let objects = [{
id: 'A',
name: 'ObjA',
children: [
{
id: 'A1',
name: 'ObjA1'
},
{
id: 'A2',
name: 'objA2',
children: [
{
id: 'A2-1',
name: 'objA2-1'
},
{
id: 'A2-2',
name: 'objA2-2'
}
]
}
]
},
{
id: 'B',
name: 'ObjB',
children: [
{
id: 'B1',
name: 'ObjB1'
}
]
}
];
let docs = [
{
object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{
object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD2',
name: 'Typde Doc2'
}
},
{
object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
},
{
object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx2',
name: 'Typde Doc x1'
}
},
{
object: {
id: 'A2',
name: 'docA2'
},
typedoc: {
id: 'TDx2',
name: 'Type de Doc x2'
}
},
{
object: {
id: 'A2-1',
name: 'docA2-1'
},
typedoc: {
id: 'TDx2-1',
name: 'Type de Docx2-1'
},
},
{
object: {
id: 'A2-2',
name: 'docA2-2'
},
typedoc: {
id: 'TDx2-2',
name: 'Type de Docx2-2'
},
},
{
object: {
id: 'B',
name: 'docB'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{
object: {
id: 'B1',
name: 'docB1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
}
];
function buildAllParents(doc, objects) {
for (let o = 0; o < objects.length; o++) {
let allParents = [];
let getAllParents = (o, eleFinded) => {
if (o.id === doc.object.id) {
doc.allParents = allParents;
eleFinded = true;
return { doc, eleFinded };
}
if (o.children) {
allParents.push(o.id);
for (let c = 0; c < o.children.length; c++) {
let { eleFinded, doc } = getAllParents(o.children[c], eleFinded);
if (eleFinded) {
return { eleFinded, doc };
} else {
continue;
}
}
}
return { eleFinded };
};
if (objects[o].id === doc.object.id) {
doc.allParents = [objects[o].id];
return doc;
} else if (objects[o].children) {
allParents.push(objects[o].id);
for (let c = 0; c < objects[o].children.length; c++) {
let eleFinded = null;`enter code here`
let res = getAllParents(objects[o].children[c], eleFinded);
if (res.eleFinded) {
return res.doc;
} else {
continue;
}
}
}
}
}
docs = docs.map(d => buildAllParents(d, objects`enter code here`))
This is an iterative breadth first search. It returns the first node that contains a child of a given name (nodeName) and a given value (nodeValue).
getParentNode(nodeName, nodeValue, rootNode) {
const queue= [ rootNode ]
while (queue.length) {
const node = queue.shift()
if (node[nodeName] === nodeValue) {
return node
} else if (node instanceof Object) {
const children = Object.values(node)
if (children.length) {
queue.push(...children)
}
}
}
return null
}
It would be used like this to solve the original question:
getParentNode('title', 'randomNode_1', data[0])
Enhancement of the code based on "Erick Petrucelli"
Remove the 'reverse' option
Add multi-root support
Add an option to control the visibility of 'children'
Typescript ready
Unit test ready
function searchTree(
tree: Record<string, any>[],
value: unknown,
key = 'value',
withChildren = false,
) {
let result = null;
if (!Array.isArray(tree)) return result;
for (let index = 0; index < tree.length; index += 1) {
const stack = [tree[index]];
while (stack.length) {
const node = stack.shift()!;
if (node[key] === value) {
result = node;
break;
}
if (node.children) {
stack.push(...node.children);
}
}
if (result) break;
}
if (withChildren !== true) {
delete result?.children;
}
return result;
}
And the tests can be found at: https://gist.github.com/aspirantzhang/a369aba7f84f26d57818ddef7d108682
Wrote another one based on my needs
condition is injected.
path of found branch is available
current path could be used in condition statement
could be used to map the tree items to another object
// if predicate returns true, the search is stopped
function traverse2(tree, predicate, path = "") {
if (predicate(tree, path)) return true;
for (const branch of tree.children ?? [])
if (traverse(branch, predicate, `${path ? path + "/" : ""}${branch.name}`))
return true;
}
example
let tree = {
name: "schools",
children: [
{
name: "farzanegan",
children: [
{
name: "classes",
children: [
{ name: "level1", children: [{ name: "A" }, { name: "B" }] },
{ name: "level2", children: [{ name: "C" }, { name: "D" }] },
],
},
],
},
{ name: "dastgheib", children: [{ name: "E" }, { name: "F" }] },
],
};
traverse(tree, (branch, path) => {
console.log("searching ", path);
if (branch.name === "C") {
console.log("found ", branch);
return true;
}
});
output
searching
searching farzanegan
searching farzanegan/classes
searching farzanegan/classes/level1
searching farzanegan/classes/level1/A
searching farzanegan/classes/level1/B
searching farzanegan/classes/level2
searching farzanegan/classes/level2/C
found { name: 'C' }
In 2022 use TypeScript and ES5
Just use basic recreation and built-in array method to loop over the array. Don't use Array.find() because this it will return the wrong node. Use Array.some() instead which allow you to break the loop.
interface iTree {
id: string;
children?: iTree[];
}
function findTreeNode(tree: iTree, id: string) {
let result: iTree | null = null;
if (tree.id === id) {
result = tree;
} else if (tree.children) {
tree.children.some((node) => {
result = findTreeNode(node, id);
return result; // break loop
});
}
return result;
}
const flattenTree = (data: any) => {
return _.reduce(
data,
(acc: any, item: any) => {
acc.push(item);
if (item.children) {
acc = acc.concat(flattenTree(item.children));
delete item.children;
}
return acc;
},
[]
);
};
An Approach to convert the nested tree into an object with depth 0.
We can convert the object in an object like this and can perform search more easily.
The following is working at my end:
function searchTree(data, value) {
if(data.title == value) {
return data;
}
if(data.children && data.children.length > 0) {
for(var i=0; i < data.children.length; i++) {
var node = traverseChildren(data.children[i], value);
if(node != null) {
return node;
}
}
}
return null;
}

Categories

Resources