Context: I want to be able to look through my nested arrays of objects and depending on the array key that property belonged to then prepend the string.
Issue: I was able to do it before I changed my data structure to include more objects within the parent array. Probably not the most efficient way to do it but it worked (appreciate any pointers on tidying this up).
method to append:
for (let key in temps) {
let test = temps[key].display;
if (key === "room1") {
temps[key].display = "Our friend: " + test;
}
if (key === "room2") {
temps[key].display = "Our friend: " + test;
}
if (key === "room3") {
temps[key].display = "Unknown:" + test;
}
}
So I am appending the value of display depending on the parent key they came from "room1, room2 or room3".
Original data structure:
let temps = {
room1: { id: 1, display: "shahid" },
room2: { id: 2, display: "akram" },
room3: { id: 3, display: "zia" }
};
New data structure:
let temps = {
room1: [{ id: 1, display: "shahid" }, { id: 11, display: "Zen" }],
room2: [{ id: 2, display: "akram" }, { id: 12, display: "Julia" }],
room3: [{ id: 3, display: "zia" }, { id: 13, display: "Octane" }]
};
So how do I get the method to work with my new data structure... better still, whats a better way of doing this if any please?
As your new structure has an extra (array) layer, you need an extra level of looping:
let temps = {
room1: [{ id: 1, display: "shahid" }, { id: 11, display: "Zen" }],
room2: [{ id: 2, display: "akram" }, { id: 12, display: "Julia" }],
room3: [{ id: 3, display: "zia" }, { id: 13, display: "Octane" }]
};
for (let key in temps) {
for (let item of temps[key]) {
let test = item.display;
if (key === "room1") {
item.display = "Our friend: " + test;
}
if (key === "room2") {
item.display = "Our friend: " + test;
}
if (key === "room3") {
item.display = "Unknown:" + test;
}
}
}
console.log(temps);
Remarks
There are a few things you could improve. For instance, it is a pity that you overwrite the original display name, which really is a user name. The way it gets displayed should better be a separate property. Imagine that such a user-object would move to another room, and then updating that property...
If your rooms are really called room1, room2, ...etc, then using those as object keys is not really called for. Then you are better off with an array, where the index determines the room.
I would also suggest using more descriptive variable names. temps or test are not very descriptive of what they really represent. rooms and name would probably better describe what they are.
For instance:
let rooms = [
[{ id: 1, name: "shahid" }, { id: 11, name: "Zen" }],
[{ id: 2, name: "akram" }, { id: 12, name: "Julia" }],
[{ id: 3, name: "zia" }, { id: 13, name: "Octane" }]
];
for (let [roomId, room] of rooms.entries()) {
for (let item of room) {
item.display = (roomId === 2 ? "Unknown: " : "Our friend: ") + item.name;
}
}
console.log(rooms);
Or in an object oriented way, where you can define methods to move users in and out of a room, and where the display feature can determine the string dynamically on-the-fly (as a getter):
class User {
constructor(id, name) {
this.id = id;
this.name = name;
this.room = null;
}
exitRoom() {
if (this.room) this.room.removeUser(this);
}
enterRoom(room) {
room.addUser(room);
}
get display() {
return (this.room?.hasFriends ? "Our friend: " : "Unknown: ") + this.name;
}
}
class Room {
constructor(name, hasFriends=false) {
this.name = name;
this.users = [];
this.hasFriends = hasFriends;
}
addUser(user) {
if (user.room) user.room.removeUser(user);
user.room = this;
this.users.push(user);
return this;
}
removeUser(user) {
if (user.room != this) return;
this.users.splice(this.users.indexOf(user), 1);
user.room = null;
}
}
let rooms = [
new Room("room1", true)
.addUser(new User(1, "shahid"))
.addUser(new User(11, "Zen")),
new Room("room2", true)
.addUser(new User(2, "akram"))
.addUser(new User(12, "Julia")),
new Room("room3", false)
.addUser(new User(3, "zia"))
.addUser(new User(13, "Octane")),
];
for (let room of rooms) {
console.log(`Room: ${room.name}`);
for (let user of room.users) {
console.log(` ${user.display}`);
}
}
let temps = {
room1: [{ id: 1, display: "shahid" }, { id: 11, display: "Zen" }],
room2: [{ id: 2, display: "akram" }, { id: 12, display: "Julia" }],
room3: [{ id: 3, display: "zia" }, { id: 13, display: "Octane" }]
};
temps = Object.keys(temps).map(function (key) {
return { [key]: temps[key] };
});
for (let i of temps) {
for (let key in i) {
if (key === "room1") {
i[key].forEach(e=>e.display = "Our friend: "+e.display);
}
if (key === "room2") {
i[key].forEach(e=>e.display = "Our friend: "+e.display );
}
if (key === "room3") {
i[key].forEach(e=>e.display = "Unknown: "+e.display );
}
}
}
console.log(temps)
Related
i am working on react-flow, and my task is to transform the following data => `
`const configObj = {
name: "Dataset",
nodeChild: {
type: "schema",
nodeConfiguration: {
sid1: {
name: "Schema 1",
nodeChild: {
type: "dashboard",
nodeConfiguration: {
did1: {
name: "Dashboard 1"
}
}
}
},
sid2: {
name: "Schema 2",
nodeChild: {
type: "dashboard",
nodeConfiguration: {
did2: {
name: "Dashboard s1",
},
did3: {
name: "Dashboard 3"
}
}
}
}
}
}
}` to this ->
const elements = [
{
id: '1',
type: 'input', // input node
data: { label: 'Input Node' },
position: { x: 250, y: 25 },
},
// default node
{
id: '2',
// you can also pass a React component as a label
data: { label: <div>Default Node</div> },
position: { x: 100, y: 125 },
},
{
id: '3',
type: 'output', // output node
data: { label: 'Output Node' },
position: { x: 250, y: 250 },
},
// animated edge
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e2-3', source: '2', target: '3' },
];
`
not exactly but according to data1 so i prepare a code for it and it is working well in node environment but the moment i try it on react it shows some errorenter image description here here is my code
const configObj = {
name: "Dataset",
onClick: true,
nodeChild: {
type: "schema",
nodeConfiguration: {
sid1: {
name: "Schema 1",
nodeChild: {
type: "dashboard",
nodeConfiguration: {
did1: {
name: "Dashboard 1"
}
}
}
},
sid2: {
name: "Schema 2",
nodeChild: {
type: "dashboard",
nodeConfiguration: {
did2: {
name: "Dashboard s1",
nodeChild: {
type: "ritik",
nodeConfiguration: {
ri1: {
name: "Ritik",
}
}
}
},
did3: {
name: "Dashboard 3"
}
}
}
}
}
},
}
let count =1;
let dataConfig = []
const recursion = (obj, level,type) => {
let objData = {}
for(let j in obj){
if(j !== 'nodeChild' && j !== 'nodeParent'){
if(j === 'name'){
objData= {
...objData,
label: obj[j]
}
}else {
objData= {
...objData,
[j]: obj[j]
}
}
}
}
let idd = count
dataConfig = [...dataConfig, {id: count, data: objData, type, level, parentID}]
count++;
if('nodeChild' in obj){
const {nodeConfiguration, type} = obj.nodeChild
for(let val in nodeConfiguration){
recursion(nodeConfiguration[val], level+1, type, parentID = idd)
}
}
if('nodeParent' in obj){
const {nodeConfiguration, type} = obj.nodeParent
for(let val in nodeConfiguration){
recursion(nodeConfiguration[val], level-1, type)
}
}
}
recursion(configObj, level=0, type='root', parentID=1)
let edges = []
for(let i=1; i<dataConfig.length; i++){
let e = {
id: `e${dataConfig[i].id}-${dataConfig[i].parentID}`,
source: `${dataConfig[i].parentID}`, target: `${dataConfig[i].id}`, animated: true
}
edges = [
...edges,
e
]
}
let finalDataSet = []
let x=650, y=25;
let flag = false;
for(let i in dataConfig){
let element = {}
for(let key in dataConfig[i]){
if(key !== 'parentID'){
if(key === 'type'){
let k = dataConfig[i][key]
if(k === 'schema' || k === 'root'){
element = {
...element,
[key]: 'input'
}
}else {
element = {
...element,
[key]: 'output'
}
}
}else {
element = {
...element,
[key]: dataConfig[i][key]
}
}
}
}
element = {
...element,
position: { x, y }
}
// console.log(i)
finalDataSet = [
...finalDataSet,
element
]
y += 75;
if(!flag){
x = 25;
}
x = flag ? x+155 : x
flag = true
}
for(let i =0; i<edges.length; i++){
finalDataSet = [
...finalDataSet,
edges[i]
]
}
const DataSET = finalDataSet
export default DataSET
this code is perfectly working on local nodejs but the same code pops errors on react.js can any one help me on this
It's the recursion(configObj, level=0, type='root', parentID=1) calls that are causing trouble. You think that level=0 is saying to pass 0 to the level parameter but javascript doesn't recognize that syntax. It thinks that level is some variable you forgot to define. Hence the is not defined error.
To fix the issue, just do something like recursion(configObj, 0, 'root', 1) instead.
I would like to add a function in which users can go to the next searched result. Thanks, #ggorlen for helping with the recursive search.
I have a recursive search function that gives the first value and makes them selected = true and if it is in nested array make showTree=true.
How I can add a function in which if the user clicks the next search record then the selected: true will set to the next result and remove the previous one?
and based on the new results showTree will change.
How to add a variable which gets updated based on the number of time search is called...
previous record option so user can go back to the previous result
const expandPath = (nodes, targetLabel) => {
for (const node of nodes) {
if (node.label.includes(targetLabel)) {
return (node.selected = true);
} else if (expandPath(node.item, targetLabel)) {
return (node.showTree = true);
}
}
};
// Output
expandPath(testData, 'ch');
//// add variable for count example: 1 of 25
console.log(testData);
//if user click on nextrecord after search
//nextrecord(){
//logic to remove the selected true from current and add for next
//update showtree
//update recordNumber of totalValue example: 2 of 25
//}
//child3 should get selected true and remove child1 selected true and showtree
//same add showTree= true based on selected value
//if user click on previous record after search
//previousrecord(){
//logic to remove the selected true from current and add for previous
//update showtree
//update recordNumber of totalValue example: 1 of 25
//}
console.log(testData);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script>
// Test Data
const testData = [
{
id: 1,
label: 'parent1',
item: [
{
id: 21,
label: 'child1',
item: [
{
id: 211,
label: 'child31',
item: [
{
id: 2111,
label: 'child2211',
item: [{ id: 21111, label: 'child22111' }]
}
]
},
{ id: 222, label: 'child32' }
]
},
{
id: 22,
label: 'child2',
item: [
{
id: 221,
label: 'child421',
item: [{ id: 2211, label: 'child2211' }]
},
{ id: 222, label: 'child222' }
]
}
]
},
{
id: 2,
label: 'parent2',
item: [
{
id: 21,
label: 'child2',
item: [
{
id: 511,
label: 'child51',
item: [
{
id: 5111,
label: 'child5211',
item: [{ id: 51111, label: 'child52111' }]
}
]
},
{ id: 522, label: 'child352' }
]
}
]
}
];
</script>
You can use refer following code
const testData = [
{
id: 1,
label: 'parent1',
item: [
{
id: 21,
label: 'child1',
item: [
{
id: 211,
label: 'child31',
item: [
{
id: 2111,
label: 'child2211',
item: [{ id: 21111, label: 'child22111' }]
}
]
},
{ id: 222, label: 'child32' }
]
},
{
id: 22,
label: 'child2',
item: [
{
id: 221,
label: 'child421',
item: [{ id: 2211, label: 'child2211' }]
},
{ id: 222, label: 'child222' }
]
}
]
},
{
id: 2,
label: 'parent2',
item: [
{
id: 21,
label: 'child2',
item: [
{
id: 511,
label: 'child51',
item: [
{
id: 5111,
label: 'child5211',
item: [{ id: 51111, label: 'child52111' }]
}
]
},
{ id: 522, label: 'child352' }
]
}
]
}
];
// flatten down tree to array and add parent pointer
const flatten = (data) => {
let flattenData = [data]
if (data.item) {
for (const item of data.item) {
item.parent = data;
flattenData = flattenData.concat(flatten(item));
}
}
return flattenData;
}
let flattenData = [];
// flatten down the test data
for (const data of testData) {
flattenData = flattenData.concat(flatten(data));
}
// to update showTree flag
const toggle = (item, expand = true) => {
const parent = item.parent;
if (parent) {
parent.showTree = expand;
if (parent.parent) {
return toggle(parent, expand);
}
return parent;
}
return item;
}
/**
*
* #param {targetLabel} query
* #returns function navigate with param forward flag
*/
const seach = (query) => {
let index = -1;
const items = flattenData.filter(x => x.label.includes(query));
return (forward = true) => {
if (index > -1) {
items[index].selected = false;
toggle(items[index], false);
}
index = index + (forward ? 1 : -1);
let item = null;
if (index > -1 && index < items.length) {
items[index].selected = true;
item = toggle(items[index], true);
}
return {
item,
index,
length: items.length
}
}
}
const navigate = seach('child5211');
// next result
let result = navigate();
// previous result
result = navigate(false);
// result will look like this
/**
* {
* item: root of current item with showTree and selected falgs or null if out of bound,
* index: current match,
* length: total match
* }
*
*/
Tackling one thing at a time here, you can pretty quickly get the desired 'next' functionality you want by converting you're recursive search to a generator function:
function* expandPath(nodes, targetLabel) {
for (const node of nodes) {
if (node.label.includes(targetLabel)) {
yield (node.selected = true);
} else if (expandPath(node.item, targetLabel)) {
yield (node.showTree = true);
}
}
};
const gen = expandPath(mynodes, "thisTargetLabel");
gen.next()
gen.next() //<-- the next one
It's a little hard to know without more of your context how to answer the other questions, but what it really seems like you need here is state, and (es6) class is a good way to make this:
class Searcher {
constructor(mynodes, mylabel){
this.count=0;
this.nodes=mynodes;
this.label=mylabel;
this.generateMatches(this.nodes);
this.selectNode(this.matches[0]); // select the first node
}
generateMatches(nodes){
this.matches=[];
for (const node of nodes) {
if (node.label.includes(this.label)) {
this.matches.push(node);
} else {
this.generateMatches(nodes.node)
}
}
}
updateTreeById(id, node){
this.nodes.forEach(n=>n.showTree = false);
for (const node of this.nodes) {
if (node.id === id) {
//noop but we are here
} else if(this.updateTreeById(id, this.nodes.node)) {
node.showTree = true;
}
}
}
selectNode(i){
const index = i % this.matches.length;
this.currNodeId = this.matches[index].id;
this.matches[index].selected = true // we are wrapping around
this.count = i; // setting your current count
this.updateTreeById(this.matches[index].id)
// update logic, reset trees
}
nextNode(){
this.selectNode(this.count + 1)
}
prevNode(){
this.selectNode(this.count - 1)
}
}
Traverse through a JSON object which has nested arrays objects inside it .
The label value is provided which is the identifier with which need to return the associated level metrics value . If the label is found in the 2nd level find the metrics at the second level and it should be returned
I couldn't get the logic on how to traverse through an object and return the specific value
function getMetrics(arr, label) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].label === label) {
return arr[i].metricsValue;
} else if (arr[i].children) {
return getMetrics(arr[i].children, label);
}
}
return "Not found";
}
const selectedMetrics = getMetrics(dataObj.series, '1');
Consider the JSON object with children specifies the sub level of the current level .
const dataObj = {
series: [
{
label: "A",
metricsValue: "ma",
children: [
{
label: "A-B",
value: 6,
metricsValue: "ma-mb"
},
{
label: "A-B-C",
metricsValue: "ma-mb-mc",
children: [
{
label : "A-B-C-D",
value: 6,
metricsValue: "ma-mb-mc-md"
}
]
}
]
},
{
label: "1",
metricsValue: "m1",
}
]
};
Expected Result :
When the input is "1", it should return
selectedMetrics= "m1"
Input : "A-B-C-D"
selectedMetrics= "ma-mb-mc-md"
You can perform a Depth first search (DFS) or Breadth first search (BFS) to find metricValues at any level.
Here I'm using DFS to find the required value. This works for data with any nested levels.
const dataObj = { series: [ { label: "A", metricsValue: "ma", children: [ { label: "A-B", value: 6, metricsValue: "ma-mb" }, { label: "A-B-C", metricsValue: "ma-mb-mc", children: [ { label: "A-B-C-D", value: 6, metricsValue: "ma-mb-mc-md" } ] } ] }, { label: "1", metricsValue: "m1"} ] };
function getMetrics(arr, label) {
var result;
for (let i = 0; i < arr.length; i++) {
if (arr[i].label === label) {
return arr[i].metricsValue;
} else if (arr[i].children) {
result = getMetrics(arr[i].children, label);
if (result) {
return result;
}
}
}
return null;
}
console.log("selectedMetrics for 'A' = " + getMetrics(dataObj.series, 'A'));
console.log("selectedMetrics for 'A-B' = " + getMetrics(dataObj.series, 'A-B'));
console.log("selectedMetrics for 'A-B-C' = " + getMetrics(dataObj.series, 'A-B-C'));
console.log("selectedMetrics for 'A-B-C-D' = " + getMetrics(dataObj.series, 'A-B-C-D'));
console.log("selectedMetrics for '1' = " + getMetrics(dataObj.series, '1'));
Your'e passing in the value, so use it instead of the string & you're not accessing the children nodes.
for(var i=0; i< arr.length;i++){
const x = arr[i];
if (x.children.label === value) {
console.log(x.metricValue)
}else{
x.forEach(element => {
if (element.children.label === value) {
console.log(element.metricValue)
}else{
element.forEach(secondEl =>{
if (secondEl.children.label === value) {
console.log(secondEl.metricValue)
}
})
}
});
}
}
You can create a more elegant way of iterating around the children nodes but that may help you out
So for example I have object like this:
{
data: [
{
id: 13,
name: "id13"
},
{
id: 21,
name: "id21"
}
],
included: [
{
id: "13",
badge: true
},
{
id: "21",
badge: false
}
]
}
And now I need to loop over included and push included to data where id is equal.
So after transformation it would have badge in data, for example like this:
{
data: [
{
id: "13",
name: "id13",
included: {
id: "13",
badge: true
},
},
{
id: "21",
name: "id21",
included: {
id: "21",
badge: false
}
}
]
}
of course I tried on my own and I've created this code:
for(let i=0; i<includedLength; i++) {
console.log(a.included[i].id);
for(n=0; n<dataLength; n++) {
console.log(a.data[n]);
if(a.icluded[i].id === a.data[i].id) {
console.log('We have match!!!');
}
}
}
but it doesn't work I have an error in console
Uncaught TypeError: Cannot read property '0' of undefined
This is demo of my code.
All solutions here have gone in the same path as you had, which is not efficient. So I am posting my solution, which is more efficient than the other solutions so far. Read the code comments to understand the optimizations done.
// Convert data array into a map (This is a O(n) operation)
// This will give O(1) performance when adding items.
let dataMap = a.data.reduce((map, item) => {
map[item.id] = item;
return map;
}, {});
// Now we map items from included array into the dataMap object
// This operation is O(n). In other solutions, this step is O(n^2)
a.included.forEach(item => {
dataMap[item.id].included = item;
});
// Now we map through the original data array (to maintain the original order)
// This is also O(n)
let finalResult = {
data: a.data.map(({id}) => {
return dataMap[id];
})
};
console.log(JSON.stringify(finalResult))
Here is my solution, this will provide the required output!
It constains the same standard for loops.
Some points I would like to highlight are,
The id in included property is string, so you can use the + operator to convert it to number.
The Object.assign() method is used so that we create a new copy of the corresponding object. Read more here
var data = {
data: [{
id: 13,
name: "id13"
},
{
id: 21,
name: "id21"
}
],
included: [{
id: "13",
badge: true
},
{
id: "21",
badge: false
}
]
}
var output = {
data: data.data
};
for (var q of data.included) {
for (var j of output.data) {
if (+q.id === j.id) {
j['included'] = Object.assign({}, j);;
}
}
}
console.log(output);
.as-console {
height: 100%;
}
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}
It seems like a waste of space to push the whole "included" element into the first array when a match is found (you really need that extra id element in there?) - so this just makes output like
[{id: 1, name: 'name', badge: true},{...}]
If no matching badge element is found, it sets badge to false.
var notJSON = {
data: [
{
id: 13,
name: "id13"
},
{
id: 21,
name: "id21"
}
],
included: [
{
id: "13",
badge: true
},
{
id: "21",
badge: false
}
]
};
var badged = notJSON.data.map(function (el, i) {
el.badge = notJSON.included.find(function (inc) {
return inc.id == el.id;
}).badge || false;
return el;
});
console.log(badged);
Its not a JSON, its an Object. A valid json consists of both its key and value as string. What you are trying to do is manipulate an object. The following code should help in getting the desired output.
const obj ={
data: [
{
id: 13,
name: "id13"
},
{
id: 21,
name: "id21"
}
],
included: [
{
id: "13",
badge: true
},
{
id: "21",
badge: false
}
]
}
for (var i=0; i<obj.data.length;i++){
for(var j=0; j< obj.included.length;j++){
if(obj.data[i].id == obj.included[j].id){
obj.data[i].included={
id: obj.included[j].id,
badge: obj.included[j].badge
}
}
}
}
delete obj.included
console.log(obj)
What I am doing her is:
Checking if id of obj.data is equal to that of obj.included
If they are equal add a new key called "included" in obj[data]
When the loop is over delete the "included" key from obj as its not required anymore.
var obj = {
data: [
{
id: 13,
name: "id13"
},
{
id: 21,
name: "id21"
}
],
included: [
{
id: "13",
badge: true
},
{
id: "21",
badge: false
}
]
};
obj.included.forEach((item) => {
obj.data.forEach(item1 => {
if(item.id == item1.id){
item1.included = item;
}
});
});
delete obj.included;
I want to store the "node indentation string" for each object, something like this:
foo
┣bar
┃┗baz
┃ ┗qux
┃ ┣quux
┃ ┗corge
┣fizz
┗buzz
Given data for each object:
objects = [
{'id':1,'parent_id':null, 'name':'foo'}
{'id':2,'parent_id':1, 'name':'bar'}
];
Note that I don't want to print anything, I just want to work out the indent as an array of characters for each object:
{'id':6,'parent_id':4, 'name':'corge', 'indent':['┃',' ',' ','┗']}
So far I can only indent them with spaces but no 'pipes' and I am stumped at coming up with a solution. Any help?
I am using JS with Angular if it helps.
EDIT: As requested the code I have so far. I didn't post this at first because I felt that it's a wrong foundation/approach to build on. How it works is pretty trivial: for each object, count it's ancestors and add " "'s accordingly.
// go through all our objects and set their indent strings
setIndents = function()
{
for (var x in objects) {
var o = objects[x];
o.nodes = [];
// push space character for amount of ancestors
numParents = countParents(o, 0);
for (var i = 0; i < numParents; i++)
o.nodes.push(" ");
}
};
// recursively counts how many ancestors until we hit the root
countParents = function(current, count)
{
if (current.parent_id !== null) {
for (var x in objects) {
if (objects[x].id == current.parent_id) {
current = objects[x]; //set as new current
count++;
break;
}
}
return countParents(current, count);
} else {
return count;
}
};
As #JBCP pointed out (see comments) there is a serious flaw in my original code that would break the whole thing if the initial order was anything but perfect.
So here's an updated version, the order of elements can now be random (it still plays a role in such that it indirectly defines the children order, but the tree-structure will be correct).
I also split the functions so that they can be better configured. For example treeIndent now expects a node branch produced by treeify. (Note: the shuffle function is just there to test the order independence)
'use strict';
/**
* #see https://bost.ocks.org/mike/shuffle/
*
* #param array
* #returns {*}
*/
function shuffle(array) {
var m = array.length, t, i;
// While there remain elements to shuffle…
while (m) {
// Pick a remaining element…
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
function treeify(flat) {
var map = { __root__: { children: [] }};
flat.forEach(function (node) {
var
parentId = node.parent_id || '__root__',
id = node.id;
// init parent
if (!map.hasOwnProperty(parentId)) {
map[parentId] = { element: null, children: [] };
}
// init self
if (!map.hasOwnProperty(id)) {
map[id] = { element: null, children: [] };
}
map[id].element = node;
map[parentId].children.push(map[id]);
});
return map.__root__.children;
}
function treeIndent(branch, cfg, decorator, indent)
{
indent = indent || [];
branch.forEach(function (node, i) {
decorator(node.element, indent.concat(
i === branch.length - 1 ? cfg.isLastChild : cfg.hasNextSibling
));
treeIndent(node.children, cfg, decorator, indent.concat(
i === branch.length - 1 ? cfg.ancestorIsLastChild : cfg.ancestorHasNextSibling
));
});
}
var input = [
{ id: 1, parent_id: null, name: 'root' },
{ id: 2, parent_id: 1, name: 'bar' },
{ id: 5, parent_id: 2, name: 'baz' },
{ id: 6, parent_id: 5, name: 'qux' },
{ id: 7, parent_id: 6, name: 'quux' },
{ id: 8, parent_id: 6, name: 'corge' },
{ id: 9, parent_id: 2, name: 'but' },
{ id: 3, parent_id: 1, name: 'fizz' },
{ id: 4, parent_id: 1, name: 'buzz' }
];
var log = document.getElementById('log');
treeIndent(treeify(shuffle(input)), {
hasNextSibling: '├',
isLastChild: '└',
ancestorHasNextSibling: '│',
ancestorIsLastChild: ' '
}, function (element, indent) {
log.innerHTML += indent.join(' ') + ' ' + element.name + "\n";
});
<pre id="log"></pre>
Old answer (broken!):
try the following:
function makeTree(flat) {
var map = { __root__: { children: [] }};
flat.forEach(function (node) {
var
parentId = node.parent_id || '__root__',
id = node.id;
// init parent
if (!map.hasOwnProperty(parentId)) {
map[parentId] = { children: [] };
}
// init self
if (!map.hasOwnProperty(id)) {
map[id] = { children: [] };
}
map[id].element = node;
map[parentId].children.push(map[id]);
});
return map.__root__.children;
}
function injectTreeIndent(input) {
var
levelMap = [],
indicators = {
hasNextSibling: '┣',
isLastChild: '┗',
ancestorHasNextSibling: '┃',
ancestorIsLastChild: ' '
}
;
// apply `indent`
(function traverse(branch, depth) {
branch.forEach(function (node, idx) {
node.element.indent = levelMap.map(function (ancestor) {
return ancestor === indicators.hasNextSibling ? indicators.ancestorHasNextSibling : indicators.ancestorIsLastChild;
});
// if (depth > 0) { // uncomment this, if root elements should have no indentation
node.element.indent.push(
levelMap[depth] = branch.length - 1 > idx ? indicators.hasNextSibling : indicators.isLastChild
);
// }
traverse(node.children, depth + 1);
levelMap.pop();
});
}(makeTree(input), 0));
}
var input = [
{ id: 1, parent_id: null, name: 'foo' },
{ id: 2, parent_id: 1, name: 'bar' },
{ id: 5, parent_id: 2, name: 'baz' },
{ id: 6, parent_id: 5, name: 'qux' },
{ id: 7, parent_id: 6, name: 'quux' },
{ id: 8, parent_id: 6, name: 'corge' },
{ id: 3, parent_id: 1, name: 'fizz' },
{ id: 4, parent_id: 1, name: 'buzz' }
];
injectTreeIndent(input);
makeTree is used to optain a nested structure derived from the given flat data.
injectTreeIndent then traverses that nested structure to inject the required indent informatoin.
demo: http://jsfiddle.net/6R7wf/1/
demo with root elements having no indenation: http://jsfiddle.net/zMY7v/
After for (var i = 0; i < numParents; i++) o.nodes.push(" ");, try
if (o.nodes.length === 1)
o.nodes[0] = "┣";
else if (o.nodes.length > 1) {
o.nodes[0] = "┃";
o.nodes[o.nodes.length - 1] = "┗";
}