Building multi level menu using nodejs - node.js

I currently have the following data in my database
The Mongo database stores like this
id parent
1 0
2 0
3 1
4 1
5 2
6 2
7 2
30 3
31 3
70 7
71 7
Now I want the output in a single javascript array like so using nodejs
[
{id:1,sub:[
{id:3, sub:[{id:30},{id:31}]},
{id:4,sub:[]}
]
},
{id:2,sub:[
{id:5,sub: []},
{id:6,sub: []},
{id:7,sub: [{id:70}, {id:71}]}
]
}
]
The purpose of this is basically to output the category in a megamenu.

The following example shows a way to do what you want.
// Example data from the question
var nodes = [
{ id: 1, parent: 0 },
{ id: 2, parent: 0 },
{ id: 3, parent: 1 },
{ id: 4, parent: 1 },
{ id: 5, parent: 2 },
{ id: 6, parent: 2 },
{ id: 7, parent: 2 },
{ id: 30, parent: 3 },
{ id: 31, parent: 3 },
{ id: 70, parent: 7 },
{ id: 71, parent: 7 }
];
// We construct `t`, the array of parents, so that `t[i] === x` means that `x`
// is the parent of `i`
var t = [];
for (var i = 0; i < nodes.length; i++) {
t[nodes[i].id] = nodes[i].parent;
}
// `t` represents the array of parents
// `c` represents the parent whose children should be put in the outputted array
function f(t, c) {
// The output structure
var a = [];
// We loop through all the nodes to fill `a`
for (var i = 0; i < t.length; i++) {
// If the node has the parent `c`
if (t[i] === c) {
// Create an object with the `id` and `sub` properties and push it
// to the `a` array
a.push({
id: i,
// The `sub` property's value is generated recursively
sub: f(t, i)
});
}
}
// Finish by returning the `a` array
return a;
}
// Print the outputted array in a pretty way
// We call the `f` function with the 0 parameter because 0 is the parent of the
// nodes that should be directly put in the returned array
alert(JSON.stringify(f(t, 0)));
On Node.js 0.12.13 running this code instead of the alert at the end of the above snippet:
var util = require('util');
console.log(util.inspect(f(t, 0), {
colors: true,
depth: null
}));
prints the following:
[ { id: 1,
sub:
[ { id: 3, sub: [ { id: 30, sub: [] }, { id: 31, sub: [] } ] },
{ id: 4, sub: [] } ] },
{ id: 2,
sub:
[ { id: 5, sub: [] },
{ id: 6, sub: [] },
{ id: 7, sub: [ { id: 70, sub: [] }, { id: 71, sub: [] } ] } ] } ]
which I think is what you want.
I also read this page where another solution, possibly more efficient, is described.

Related

How to filter children in tree structure in Tabulator?

I tried callingsetFilter function on my Tabulator tree structure, in order to filter out items. It seems to only filter out top parents. Any idea how to make this work for any level (any children or parents)? http://tabulator.info/docs/4.1/tree doesn't say much about how filtering works.
Function
table.setFilter('id', '=', 214659) is not returning anything...
Tree structure
[
{
"level":0,
"name":"word1",
"id":125582,
"_children":[
{
"level":1,
"name":"word6",
"id":214659
},
{
"level":1,
"name":"word7",
"id":214633
},
{
"level":1,
"name":"word2",
"id":214263,
"_children":[
{
"level":2,
"name":"word8",
"id":131673
},
{
"level":2,
"name":"word9",
"id":125579
},
{
"level":2,
"name":"word10",
"id":125578
},
{
"level":2,
"name":"word4",
"id":172670,
"_children":[
{
"level":3,
"name":"word13",
"id":172669
},
{
"level":3,
"name":"word14",
"id":174777
},
{
"level":3,
"name":"word5",
"id":207661,
"_children":[
{
"level":4,
"name":"word15",
"id":216529
},
{
"level":4,
"name":"word16",
"id":223884,
"_children":[
{
"level":5,
"name":"word17",
"id":223885,
"_children":[
{
"level":6,
"name":"word18",
"id":229186,
"_children":[
{
"level":7,
"name":"word19",
"id":219062
},
{
"level":7,
"name":"word20",
"id":222243
}
]
}
]
}
]
}
]
}
]
},
{
"level":2,
"name":"word3",
"id":214266,
"_children":[
{
"level":3,
"name":"word11",
"id":216675
},
{
"level":3,
"name":"word12",
"id":216671
}
]
}
]
}
]
}
]
After a little searching found out an extension for lodash library called deepdash which has deep level filtering and it works quite well.
You will have 2 new dependencies but I think it will serve your purpose.
Check the documentation on how to install them here
In the snippet here you can see in the log the results. I made a sandbox also here
This is for a list of ids, one or more.
If you need only for one value change the conditional. return _.indexOf(idList, value.id) !== -1; to return id===value.id; where id is your id variable
Also after looking at the documentation from Tabulator, the have only one level filtering, even if you write your own custom filter it wouldn't help, because it expects a bool value to render the row or not. But only for the first level, so if the parent is not what you look for the child will be ignored. The only option for you is to filter the data outside the Tabulator.
const data = [
{
level: 0,
name: "word1",
id: 125582,
_children: [
{
level: 1,
name: "word6",
id: 214659
},
{
level: 1,
name: "word7",
id: 214633
},
{
level: 1,
name: "word2",
id: 214263,
_children: [
{
level: 2,
name: "word8",
id: 131673
},
{
level: 2,
name: "word9",
id: 125579
},
{
level: 2,
name: "word10",
id: 125578
},
{
level: 2,
name: "word4",
id: 172670,
_children: [
{
level: 3,
name: "word13",
id: 172669
},
{
level: 3,
name: "word14",
id: 174777
},
{
level: 3,
name: "word5",
id: 207661,
_children: [
{
level: 4,
name: "word15",
id: 216529
},
{
level: 4,
name: "word16",
id: 223884,
_children: [
{
level: 5,
name: "word17",
id: 223885,
_children: [
{
level: 6,
name: "word18",
id: 229186,
_children: [
{
level: 7,
name: "word19",
id: 219062
},
{
level: 7,
name: "word20",
id: 222243
}
]
}
]
}
]
}
]
}
]
},
{
level: 2,
name: "word3",
id: 214266,
_children: [
{
level: 3,
name: "word11",
id: 216675
},
{
level: 3,
name: "word12",
id: 216671
}
]
}
]
}
]
}
];
const idList = [214659];
const found = _.filterDeep(
data,
function(value) {
return _.indexOf(idList, value.id) !== -1;
},
{ tree: true, childrenPath: '_children' }
);
console.log(found);
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/deepdash/browser/deepdash.min.js"></script>
<script>
deepdash(_);
</script>
Here is a recursive function that will find the parent and/or children matching a condition.
In this example, the parent item will always be displayed if a child item is a match - even if the parent itself is not a match - but you can easily adjust the code to your needs by tuning the test in the for loop.
var filterTree = function (data, filter) {
if (data['_children'] && data['_children'].length > 0) {
for (var i in data['_children']) {
return data[filter.field] == filter.value || filterTree(data['_children'][i], filter);
}
}
return data[filter.field] == filter.value;
};
Call this function as a custom filter callback:
table.setFilter(filterTree, {field:'myfield', type:'=', value:'myvalue'});
Note that this is just example code that focuses on the logic of filtering a tree recursively. The above works only for the '=' comparison.
In a real situation, you will have to implement more code to handle all other operators supported by tabulator, as dynamic operator assignment is not possible in Javascript. You could maybe consider eval() but that's another story.
More info about dynamic operator assignment here:
Are Variable Operators Possible?
Here is an example of implementation handling all tabulator operators:
// Operators
var compare = {
'=': function(a, b) { return a == b },
'<': function(a, b) { return a < b },
'<=': function(a, b) { return a <= b },
'>': function(a, b) { return a > b },
'>=': function(a, b) { return a >= b },
'!=': function(a, b) { return a != b },
'like': function(a, b) { return a.includes(b)}
};
// Filter function
var filterTree = function (data, filter) {
if (data['_children'] && data['_children'].length > 0) {
for (var i in data['_children']) {
return compare[filter.type](data[filter.field], filter.value) || filterTree(data['_children'][i], filter);
}
}
return compare[filter.type](data[filter.field], filter.value);
};
// Set a filter. The operator can now be provided dynamically
table.setFilter(filterTree, {field:'myfield', type: '>=', value:'myvalue'});

Find sum of objects inside array using reduce

I am trying to find the total from objects inside an array, which each object has a price and quantity,
i can find the total when the array has exactly two objects, but for more than two it produces NaN.
arr = [ { quantity: 1, price: 30 },
{ quantity: 1, price: 40 },
{ quantity: 2, price: 10 },
{ quantity: 1, price: 10 } ]
const reducer = (accumulator, currentValue) => {
var a = accumulator.quantity * accumulator.price;
var b = currentValue.quantity * currentValue.price;
return a + b;
}
console.log(arr.reduce(reducer)); // sum if array contains 2 objects, NaN otherwise.
let arr = [
{ quantity: 1, price: 30 },
{ quantity: 1, price: 40 },
{ quantity: 2, price: 10 },
{ quantity: 1, price: 10 }
]
let reducer = (acc, cur) => {
return acc + (Number(cur.quantity) * Number(cur.price));
};
console.log(arr.reduce(reducer, 0));
// 100
Your reducer function seems to be wrong. Accumulator no longer has any parameters to it, since well, it accumulates - its an integer.
Also, set a initial value for your accumulator to start accumulating from, as shown in the reduce function, second parameter input
arr = [ { quantity: 1, price: 30 },
{ quantity: 1, price: 40 },
{ quantity: 2, price: 10 },
{ quantity: 1, price: 10 } ]
const reducer = (accumulator, currentValue) {
return accumulator + (currentValue.quantity * accumulator.price);
}
console.log(arr.reduce(reducer, 0 ));
you can simply say
arr = [ { quantity: 1, price: 30 },
{ quantity: 1, price: 40 },
{ quantity: 2, price: 10 },
{ quantity: 1, price: 10 } ]
const total = arr.reduce((total,item)=>{
total += item.quantity * item.price;
return total
},0)

How to simultaneously constrain one key with a range and constrain another key with an exact match?

I created a test Node.js script that uses Nano to generate some example data documents, create two views, and run two test queries. Each data document has two keys: "a" and "b". I'd like my query to result in all of the documents where "a" is between 1 and 3 and "b" is equal to 2. I tested a view/query pattern that I found online which uses a startkey array and an endkey array. However, it does not behave as expected when I constrain "a" before constraining "b", but it does appear to behave as expected when I constrain "b" before constraining "a".
Why does the b_then_a view appear to work but the a_then_b view does not? Is this approach incorrect? The script and its output are below.
var nano = require("nano")("http://HCOADAMM:HcoAdammRSM#localhost:5984");
let jasonDB = nano.db.use("jason");
const DESIGN_DOCUMENT_NAME = "findtest";
var testData = [
{ a: 1, b: 1 },
{ a: 1, b: 2 },
{ a: 1, b: 3 },
{ a: 1, b: 4 },
{ a: 2, b: 1 },
{ a: 2, b: 2 },
{ a: 2, b: 3 },
{ a: 2, b: 4 },
{ a: 3, b: 1 },
{ a: 3, b: 2 },
{ a: 3, b: 3 },
{ a: 3, b: 4 },
{ a: 4, b: 1 },
{ a: 4, b: 2 },
{ a: 4, b: 3 },
{ a: 4, b: 4 }
];
var shuffleArray = function(arrayIn) {
var arrayInLength = arrayIn.length;
var arrayOut = [];
while(arrayInLength)
arrayOut.push(arrayIn.splice(
parseInt(Math.random() * (arrayInLength--)), 1
)[0]);
return arrayOut;
}
var createTestRecords = function() {
var recordsShuffled = shuffleArray(testData);
recordsShuffled.forEach(function(record) {
jasonDB.insert(
record,
function(err, body) {
if(err)
console.log(err);
else
console.log("updated user doc " + JSON.stringify(body));
}
);
});
}
var createDesignDocument = function() {
jasonDB.get("_design/" + DESIGN_DOCUMENT_NAME, {}, function(err, body, headers) {
if(!err || err.error === "not_found") {
var dbObject = new Object();
dbObject._id = "_design/" + DESIGN_DOCUMENT_NAME;
if(!err) {
dbObject._rev = body._rev;
}
dbObject.language = "javascript";
dbObject.views = {
a_then_b: {
map: function(doc) {
emit([doc.a, doc.b]);
}
},
b_then_a: {
map: function(doc) {
emit([doc.b, doc.a]);
}
},
};
jasonDB.insert(dbObject, function(err, body, header) {
if(err) {
console.log("insert error:");
console.log(err);
} else {
console.log("created " + "jason/_design/" + DESIGN_DOCUMENT_NAME);
}
})
} else {
console.log("get error:");
console.log(err);
}
});
}
var queryTest = function() {
jasonDB.view(
DESIGN_DOCUMENT_NAME,
"a_then_b",
{ startkey: [1, 2], endkey: [3, 2] },
function(err, body) {
if(err) {
console.log(err);
} else {
console.log("a_then_b")
body.rows.forEach(function(el) {
console.log(el);
});
console.log("body.rows.length = " + body.rows.length);
console.log("");
}
}
);
jasonDB.view(
DESIGN_DOCUMENT_NAME,
"b_then_a",
{ startkey: [2, 1], endkey: [2, 3] },
function(err, body) {
if(err) {
console.log(err);
} else {
console.log("b_then_a")
body.rows.forEach(function(el) {
console.log(el);
});
console.log("body.rows.length = " + body.rows.length);
}
}
);
}
//createTestRecords();
//createDesignDocument();
setTimeout(function() {
queryTest();
}, 1000);
output:
a_then_b
{ id: '812f16b3826569ec94eb35d087030d64',
key: [ 1, 2 ],
value: null }
{ id: '812f16b3826569ec94eb35d087030709',
key: [ 1, 3 ],
value: null }
{ id: '812f16b3826569ec94eb35d08702a846',
key: [ 1, 4 ],
value: null }
{ id: '812f16b3826569ec94eb35d087032077',
key: [ 2, 1 ],
value: null }
{ id: '812f16b3826569ec94eb35d08702fd89',
key: [ 2, 2 ],
value: null }
{ id: '812f16b3826569ec94eb35d08702caee',
key: [ 2, 3 ],
value: null }
{ id: '812f16b3826569ec94eb35d08702c32a',
key: [ 2, 4 ],
value: null }
{ id: '812f16b3826569ec94eb35d08702b358',
key: [ 3, 1 ],
value: null }
{ id: '812f16b3826569ec94eb35d087031386',
key: [ 3, 2 ],
value: null }
body.rows.length = 9
b_then_a
{ id: '812f16b3826569ec94eb35d087030d64',
key: [ 2, 1 ],
value: null }
{ id: '812f16b3826569ec94eb35d08702fd89',
key: [ 2, 2 ],
value: null }
{ id: '812f16b3826569ec94eb35d087031386',
key: [ 2, 3 ],
value: null }
body.rows.length = 3
You can only do this if the second key is the first in the index. So you need to revere the keys in your index, such that b is indexed first, and a, second. This will allow you to search for on a range of [2,1] through [2,3].

Traverse Tree in Nodejs with Promises

Given a simple user tree:
users:
id | name | parent_id
1 | Bob | NULL
2 | Jan | 1
3 | Mat | 2
4 | Irene | 2
5 | Ellie | 2
6 | Laura | 5
7 | Uma | 6
I am trying to walk it with this code:
addChildren: function(db, treeUsers, parentId) {
const main = this;
var sql = Sql.select().from('users')
.where('parent_id = ?', parentId)
.toParam();
return db.execute(sql.text, sql.values)
.then(function([rs,meta]) {
rs.map(function(obj) {
treeUsers[obj.id] = obj;
});
return treeUsers;
});
},
getTreeUsers: function(db, depth) {
const main = this;
const rootNodeId = 1;
var treeUsers = {};
const getChildren = function(parentId) {
return main.addChildren(db, treeUsers, parentId).then(function(children) {
treeUsers = Object.assign(treeUsers, children);
--depth;
if(depth < 1) {
return Promise.resolve(treeUsers);
}
return Promise.all(Object.keys(children).map(function(id) {
return getChildren(id);
}));
};
return getChildren(rootNodeId);
});
The desired output would like this:
{
'2': { id: 2, name: 'Jan' , parent_id: 2 },
'3': { id: 3, name: 'Mat' , parent_id: 2 },
'4': { id: 4, name: 'Irene', parent_id: 2 },
'5': { id: 5, name: 'Ellie', parent_id: 2 },
'6': { id: 6, name: 'Laura', parent_id: 5 },
'7': { id: 7, name: 'Uma' , parent_id: 6 }
}
Right now I'm getting an more like this:
[[ {
'2': { id: 2, name: 'Jan' , parent_id: 2 },
'3': { id: 3, name: 'Mat' , parent_id: 2 },
'4': { id: 4, name: 'Irene', parent_id: 2 },
'5': { id: 5, name: 'Ellie', parent_id: 2 },
'6': { id: 6, name: 'Laura', parent_id: 5 },
'7': { id: 7, name: 'Uma' , parent_id: 6 }
} ],
[ {
'2': { id: 2, name: 'Jan' , parent_id: 2 },
'3': { id: 3, name: 'Mat' , parent_id: 2 },
'4': { id: 4, name: 'Irene', parent_id: 2 },
'5': { id: 5, name: 'Ellie', parent_id: 2 },
'6': { id: 6, name: 'Laura', parent_id: 5 },
'7': { id: 7, name: 'Uma' , parent_id: 6 }
} ],
[ {
'2': { id: 2, name: 'Jan' , parent_id: 2 },
'3': { id: 3, name: 'Mat' , parent_id: 2 },
'4': { id: 4, name: 'Irene', parent_id: 2 },
'5': { id: 5, name: 'Ellie', parent_id: 2 },
'6': { id: 6, name: 'Laura', parent_id: 5 },
'7': { id: 7, name: 'Uma' , parent_id: 6 }
} ] ... ]
I'm positive that the problem is the Promise.all(getChildren(..)) call, but I'm not sure how to refactor so that I get back exactly the one final result set.
Some issues:
You mutate the treeUsers object in both methods, which means that the object you resolve with, will still be mutated later. Note how you only have one instance of that object for one call of getTreeUsers. Note that the assignment in:
treeUsers = Object.assign(treeUsers, children);
does not do anything, as Object.assign already mutates the first argument anyway.
Promise.all resolves to an array value, where each element contains the treeUsers object. As it was mutated by all separate calls, all these array entries point to the same object reference, explaining the repetitions you see
depth is a variable common to each call to getChildren. It would be safer to pass it as argument to be sure that siblings are expanded with the same value for depth
I would just avoid passing the treeUsers object around, as it should really be just the resolved value. Let each promise resolve to the children it can "see" and consolidate the results after each Promise.all promise resolves.
Here is how that could look:
addChildren: function(db, treeUsers, parentId) {
const main = this,
sql = Sql.select().from('users')
.where('parent_id = ?', parentId)
.toParam();
return db.execute(sql.text, sql.values).then(function([rs,meta]) {
// Create a new(!) object and return it
return Object.assign(...rs.map(function(obj) {
return { [obj.id]: obj };
}));
});
},
getTreeUsers: function(db, depth) {
const main = this,
rootNodeId = 1;
const getChildren = function(parentId, depth) { // pass depth
return main.addChildren(db, parentId).then(function(children) {
if (depth <= 1) {
return children; // only the children. No need for Promise.resolve
}
return Promise.all(Object.keys(children).map(function(id) {
return getChildren(id, depth - 1); // pass depth
})).then(arr => Object.assign(children, ...arr)); // consolidate
});
}
return getChildren(rootNodeId, depth);
}
Yes, Promise.all always returns an array of all the result values. When your calls all return promises that fulfill with the same value, treeUsers, you get an array of those objects. As you always want to make getChildren return always just that object, you can simplify to
function getChildren(parentId) {
return main.addChildren(db, treeUsers, parentId).then(function(children) {
Object.assign(treeUsers, children);
// when `children` is empty, the recursion terminates here
return Promise.all(Object.keys(children).map(getChildren)).then(() =>
treeUsers
);
});
}
(Of course this does not produce a tree structure)

convert user input to JavaScript object

I'd like to convert the user input into an object
ex:if user enter in the input box :
{ id: 11, pId: 1, name: "xyz" },
{ id: 111, pId: 11, name: "abc" }
convert to 2 objects
this is my example:
$("#generate").click(function () {
if ($("#jsonData").val() == "") {
zNodes = [
{ id: 1, pId: 0, name: "LEVEL 1", expand: false },
{ id: 11, pId: 1, name: "ClientSide" }
];
}
if ($("#jsonData").val() !== "") {
//i want from user to enter the data
var a = $("#jsonData").val();
var b = {a};
zNodes = [b];
}

Resources