How can I create a nested mat-select in Angular? - node.js

I'm new to Angular so if my question is too noobie, please understand.
I have a data structure something like this:
export class Category {
constructor(
public id: string,
public name: string,
public hasChild: boolean,
public parent: string,
public uri: string
) { }
}
export class CategoryTreeItem {
constructor(
public category: Category,
public children: CategoryTreeItem[]
) {}
static createCategoryTree(categories: Category[], parent: string): CategoryTreeItem[] {
const tree: CategoryTreeItem[] = [];
const mainCats = categories.filter(cat => cat.parent === parent);
mainCats.forEach(cat => {
if (cat.hasChild) {
const subCats = this.getCategoryTree(categories, cat.id);
tree.push(new CategoryTreeItem(cat, subCats));
} else {
tree.push(new CategoryTreeItem(cat, null));
}
});
return tree;
}
}
And here is a sample output:
CategoryTreeItem.createCategoryTree(aCategoryArray, null);
// second parameter is null, since I want to get a full tree.
// If I want to start the tree from a specific category, I can give it's ID.
// this will return a data like this:
[
1: CategoryTreeItem {
category: Category {
name: A,
hasChild: true,
parent: null,
uri: a
},
children: [
1: CategoryTreeItem {
category: Category {
name: B,
hasChild: false,
parent: A,
uri: b
}
children: null // null since B.hasChild = false
}
]
},
2: CategoryTreeItem {
category: Category {
name: C,
hasChild: false,
parent: null,
uri: c
},
children: null // null since C.hasChild = false
}
3: CategoryTreeItem {
category: Category {
name: D,
hasChild: true,
parent: null,
uri: d
},
children: [
1: CategoryTreeItem {
category: Category {
name: E,
hasChild: true,
parent: D,
uri: e
}
children: [
// more children since E.hasChild = true
]
},
2: CategoryTreeItem {
category: Category {
name: F,
hasChild: false,
parent: D,
uri: f
}
children: null // null since F.hasChild = false
}
]
},
]
My problem is I have no idea to create a mat-select with this data.
Here is what I tried to do for now:
If a category does have a child category, it should be a <mat-optgroup> and not selectable. In my data structure, products cannot refer to a category which has child categories.
Categories with no child category are going to be <mat-option> placed in it's parent's opt-group.
This way I'm going to have a tree-like-looking mat-select, at least this is what I expect.
Thank you.

Related

Compare two nested objects and return an object with the keys which are into both of object

Let say that I have these two nested objects:
const sourceKeys = {
school: {
name: "Elisa Lemonnier",
students: 250
},
house: {
room : 2,
kids: true,
assets: "elevator",
}
}
const targetKeys = {
school: {
name: "Lucie Faure",
students: 150,
address: "123 Main St.",
phone: "555-555-5555"
},
house: {
room : 4,
kids: false,
assets: "Garden",
shop: "Auchan"
}
}
And I want the targetKeys keep ONLY the keys that are in sourceKeys. So I will get that :
const targetKeysMatchingSourceKeys = {
school: {
name: "Lucie Faure",
students: 150,
},
house: {
room : 4,
kids: false,
assets: "Garden",
}
}
I don't know how to proceed given that is a nested object. So, I will appreciate any help.
thanks you
I have find the solution, here is
const filteredJSON = Object.assign({}, TargetJsonToObject)
// Recursive function to filter the properties of the object
function filterObject(SourceJsonToObject, filteredObj) {
for (const key of Object.keys(filteredObj)) {
// If the key is not present in the source JSON, delete it from filtered JSON
if (!SourceJsonToObject.hasOwnProperty(key)) {
delete filteredObj[key]
} else if (typeof filteredObj[key] === "object") {
// If the key is present in the source JSON and the value is an object, recursively call the function on the nested object
filterObject(SourceJsonToObject[key], filteredObj[key])
}
}
}
filterObject(SourceJsonToObject, TargetJsonToObject)

How to Remove Specific Text / String Out of Array Object Values

I am using Node.js 16.17 and Express.
Forgive me if this is answered elsewhere, if a solution exists elsewhere, please point me in that direction.
On my server side, I have an array with objects with properties and their values. I want to be able to remove specific text/string from the property values.
What I Have
I currently have an array with objects (and sometimes arrays and object nested within):
DataArray =
[
{
page: {
results: [
{
id: '1234',
title: 'TextIWantA **(Text) I Dont Want**',
children: {
page: {
results: [
{
id: '5678',
title: 'ChildA TextIWant **(Text I) Dont Want**',
},
{
id: '9101',
title: 'ChildB TextIWant **(Text I) Dont Want**',
children: {
page: {
results: [
{
id: 'abcd',
title: 'GrandchildA TextIWant **(Text I (Dont) Want**',
}
]
}
}
},
],
},
},
},
{
id: '1121',
title: 'TextIWantB **(Text) I Dont Want**',
}
]
}
}
]
I am able to flatten this structure with this function:
function flatten(arr) {
const flattened = []
for (const { children, ...element } of arr) {
flattened.push(element)
if (children) {
flattened.push(...flatten(children.page.results))
}
}
return flattened;
}
const flat = [{ page: { results: flatten(DataArray[0].page.results) } }]
console.log(flat[0].page.results)
The returned data is:
[
{ id: '1234', 'page', title: 'TextIWantA **(Text) I Dont Want**' },
{ id: '5678', 'page', title: 'ChildA TextIWant **(Text I) Dont Want**' },
{ id: '9101', 'page', title: 'ChildB TextIWant **(Text I) Dont Want**' },
{ id: 'abcd', 'page', title: 'GrandchildA TextIWant **(Text I (Dont) Want**' },
{ id: '1121', 'page', title: 'TextIWantB **(Text) I Dont Want**' }
]
I am making an assumption that I have to change my text to a string in order to replace it then parse it again to turn back into an object. I'm happy to learn my assumption is true or incorrect, if incorrect, how to fix to be able to remove text.
So if I try to do a replace using the following, 1) it does not work and 2) it does not differentiate for the different text to remove (perhaps I just run multiple/different replaces/filters?):
const veryFlat = flat;
var veryFlatData = veryFlat.map(function(x){return x.toString().replace(/ **(Text) I Dont Want**/g, '');});
var removedTextData= JSON.parse(veryFlatData);
console.log(removedTextData);
Desired Result
I want to be able to remove all of the variances of Text I Dont Want, so the end result would look like (of now it will be flattened as seen above)
DataArray =
[
{
page: {
results: [
{
id: '1234',
title: 'TextIWantA',
children: {
page: {
results: [
{
id: '5678',
title: 'ChildA TextIWant',
},
{
id: '9101',
title: 'ChildB TextIWant',
children: {
page: {
results: [
{
id: 'abcd',
title: 'GrandchildA TextIWant',
}
]
}
}
},
],
},
},
},
{
id: '1121',
title: 'TextIWantB',
}
]
}
}
]
Each title is unique and I don't seem able to find anything to even say I've tried this or that.
I don't want to use .startswith or .length or .index and would prefer to avoid regex, and the example above using .replace doesn't seem to work.
How do I reach into these property values and rip out the text I don't want?
Thank you for any help you can provide.

mongoose-mpath module is creating the document but not assigned to it's parent

const {category, parent} = newCategory;
const newCat = new Category({
_id: new mongoose.Types.ObjectId(),
name: category,
parent: parent
}).save((err, res) => {
if(err) { console.log(err)}
else{
console.log("res in create category:", res);
// io.to(user.room).emit('create-category-to-list', {res});
}
})
In log
res in create category: {
codewords: [],
children: [],
_id: 604916cf866a154e284b2e29,
name: 'nameofasdasdasdcat',
parent: 60490c9ce00e8b7a38cf4752,
path: '60490c9ce00e8b7a38cf4752#604916cf866a154e284b2e29',
_v:0
}
Here the document is created but not assigned to its parent why this is happening.
Here you can see parent children array is empty
root: {
children: [],
_id: 60490c9ce00e8b7a38cf4752,
name: 'root',
path: '60490c9ce00e8b7a38cf4752',
_v:0
}
Here i am using mongoose pulgin(mongoose-mpath) for create a tree form data.
For mongoose-mpath https://www.npmjs.com/package/mongoose-mpath
In getChildrenTree function need to pass options as lean:false
const parent = await Folder.findOne({ _id: user.rootId });
const tree = await parent.getChildrenTree(
{
populate: 'codewords',
options: { lean: false } //you need to pass options as lean: false
});
console.log({ tree });
In log
{
tree: [
{
codewords: [],
children: [],
_id: 604c6c5bc8a2e03ee43c4c7b,
name: 'Category-name-2',
parent: 604c690b87924705a401f9ce,
path: '604c690b87924705a401f9ce#604c6c5bc8a2e03ee43c4c7b',
__v: 0
},
{
codewords: [],
children: [],
_id: 604c6ce2c8a2e03ee43c4c7d,
name: 'Category-name-2',
parent: 604c690b87924705a401f9ce,
path: '604c690b87924705a401f9ce#604c6ce2c8a2e03ee43c4c7d',
__v: 0
},
{
codewords: [],
children: [Array],
_id: 604c6d21c8a2e03ee43c4c7e,
name: 'Category-name-3',
parent: 604c690b87924705a401f9ce,
path: '604c690b87924705a401f9ce#604c6d21c8a2e03ee43c4c7e',
__v: 0
}
]
}
for your better understanding you can check mongoose-mpath github issue section ==>
https://github.com/vikpe/mongoose-mpath/issues/10

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'});

All nested rows does not inserted in a table

I want tree structure in my Categories table.
So I tried this:
Categories model looks like:
import * as Sequelize from 'sequelize';
export default class CategoriesModel extends Sequelize.Model {
static init(sequelize, DataTypes) {
return super.init({
name: {
type: DataTypes.STRING,
},
}, {
modelName: 'categories',
sequelize,
});
}
}
association in my CategoriesModel class looks like:
static associate({ Categories }) {
this.nestedCategories = this.hasMany(Categories, {
as: 'nestedCategories',
foreignKey: 'parentId',
});
}
and when i tried to insert rows, for example:
sequelize.sync({ force: true }).then(() => {
models.Categories.create({
name: 'parent',
nestedCategories: [
{
name: 'child 1',
},
{
name: 'child 2',
nestedCategories: [
{
name: 'child 3',
}
],
},
],
}, {
include: [models.Categories.nestedCategories]
}).then(cat => {
console.log(cat.toJSON());
})
});
result is:
{
id: 1,
name: 'parent',
nestedCategories:
[ { id: 2,
name: 'child 1',
parentId: 1,
updatedAt: 2019-02-05T08:39:45.655Z,
createdAt: 2019-02-05T08:39:45.655Z },
{ id: 3,
name: 'child 2',
parentId: 1,
updatedAt: 2019-02-05T08:39:45.657Z,
createdAt: 2019-02-05T08:39:45.657Z } ],
updatedAt: 2019-02-05T08:39:45.624Z,
createdAt: 2019-02-05T08:39:45.624Z,
parentId: null
}
child 3 does not inserted in a table.
I dont understand what i'm doing wrong...
You need to specify one include per nested category in order to make your request parsed correctly.
Here I made a function to do this recursively based on model you want to create:
function buildIncludeRecursive(model, includeTemplate) {
const include = Object.assign({}, includeTemplate);
let currInclude = include;
let currModel = model;
while(currModel[includeTemplate.as]){
currInclude.include = [Object.assign({}, includeTemplate)];
currInclude = currInclude.includes[0];
currModel = currModel[includeTemplate.as];
}
return include;
}
const model = {
name: 'parent',
nestedCategories: [
{ name: 'child 1' },
{
name: 'child 2',
nestedCategories: [
{ name: 'child 3' }
]
},
],
};
const includeTemplate = {
model: models.Categories,
as: 'nestedCategories'
};
model.categories.create(model, {
include: buildIncludeRecursive(model, includeTemplate)
});
Here's what your include is gonna be in this case:
{
model: models.Categories,
as: 'nestedCategories',
include: [
{
model: models.Categories,
as: 'nestedCategories'
}
]
}

Resources