I have searched on here for bottom-up search examples and I understand how they are done, but I have a specific need here that I have been unable to solve.
We have a menu system that I need to create a filter for.
So for example if the menu is
- Testing
- Test
- Something
- Something Else
- Test
If I filter for "Test" I want 4 nodes back and everything else pruned out. The tricky part is that it can't remove the "Something" node in this case due to it having matching children that need to be accessed.
My code works, but only for the top level items, due to the filter pass during the first recursion step removing anything that may have matching children but the parent doesn't match.
private recursiveFilter(menuItems: MenuItem[], label: string): MenuItem[] {
if (label === '') {
this.menuItems = this.originalMenuItems;
return this.menuItems;
} else if (!menuItems) {
return [];
}
return menuItems
.filter((el) => el.label?.toLowerCase().includes(label.toLowerCase()))
.map((el) => {
if (!(el.items || !Array.isArray(el.items))) {
return el;
} else {
const menuChildren = el.items as MenuItem[];
if (menuChildren) {
el.items = this.recursiveFilter(menuChildren, label);
}
return el;
}
});
}
Example of a MenuItem with nested children:
{
"label": "Messages",
"expanded": false,
"items": [
{
"label": "Dashboard",
"expanded": false,
"routerLink": "/messages",
"visible": true
},
{
"label": "Voicemail",
"expanded": false,
"items": [
{
"label": "Inbox",
"icon": "pi pi-fw pi-folder",
"routerLink": "/mailbox/inbox"
},
{
"label": "Archived",
"icon": "pi pi-fw pi-folder",
"routerLink": "/mailbox/archived"
},
{
"label": "Trash",
"icon": "pi pi-fw pi-folder",
"routerLink": "/mailbox/trash"
}
]
},
{
"label": "Text",
"expanded": false,
"items": [
{
"label": "Messages",
"routerLink": "/sms",
"routerLinkActiveOptions": {
"exact": true
}
},
{
"label": "Send",
"routerLink": "/sms/send",
"routerLinkActiveOptions": {
"exact": true
}
},
{
"label": "Contacts",
"routerLink": "/sms/contacts"
}
]
}
]
}
CodePudding user response:
Do the filtering after the recursive call has identified which children to keep. If, after filtering, there still exist any child elements or the label matches, keep that parent item.
Something along the lines of:
private recursiveFilter(menuItems: MenuItem[], label: string, labelLower = label.toLowerCase()) {
return menuItems.filter((item) => {
if (item.items) item.items = recursiveFilter(item.items, label, labelLower);
return item.label?.toLowerCase().includes(labelLower) || item.items?.length;
});
}
Side-effects inside a .filter is a bit smelly, but I think it's the clearest way to approach this problem.
Live demo:
const recursiveFilter = (menuItems, label) => (
menuItems.filter((item) => {
if (item.items) item.items = recursiveFilter(item.items, label);
return item.label?.toLowerCase().includes(label) || item.items?.length;
})
);
const topItem={label:"Messages",expanded:!1,items:[{label:"Dashboard",expanded:!1,routerLink:"/messages",visible:!0},{label:"Voicemail",expanded:!1,items:[{label:"Inbox",icon:"pi pi-fw pi-folder",routerLink:"/mailbox/inbox"},{label:"Archived",icon:"pi pi-fw pi-folder",routerLink:"/mailbox/archived"},{label:"Trash",icon:"pi pi-fw pi-folder",routerLink:"/mailbox/trash"}]},{label:"Text",expanded:!1,items:[{label:"Messages",routerLink:"/sms",routerLinkActiveOptions:{exact:!0}},{label:"Send",routerLink:"/sms/send",routerLinkActiveOptions:{exact:!0}},{label:"Contacts",routerLink:"/sms/contacts"}]}]};
topItem.items = recursiveFilter(topItem.items, 'send');
console.log(topItem);
