4

I have a menu structure as follows:

const menu = [
  {
    title: 'Supervisor Dashboard',
    link: '/dashboard/supervisor-dashboard',
    slug: '/dashboard/supervisor-dashboard'
  },
  {
    title: 'User Dashboard',
    link: '/dashboard/user-dashboard',
    slug: '/dashboard/user-dashboard'
  },
  {
    title: 'Inventory',
    slug: '/inventory',
    children: [
      {
        title: 'Add Inventory',
        link: '/inventory/add-inventory',
        slug: '/inventory/add-inventory'
      },
      {
        title: 'Remove Inventory',
        link: '/inventory/remove-inventory',
        slug: '/inventory/remove-inventory'
      },
    ]
  },
  {
    title: 'Membership',
    slug: '/membership',
    children: [
      {
        title: 'Program A',
        slug: '/membership/program-a',
        children: [
          {
            title: 'View Membership',
            link: '/membership/program-a/view',
            slug: '/membership/program-a/view'
          },
          {
            title: 'Add Membership',
            link: '/membership/program-a/add',
            slug: '/membership/program-a/add'
          },
          {
            title: 'Delete Membership',
            link: '/membership/program-a/delete',
            slug: '/membership/program-a/delete'
          }
        ]
      },
      {
        title: 'Program B',
        slug: '/membership/program-b',
        children: [
          {
            title: 'View Membership',
            link: '/membership/program-b/view',
            slug: '/membership/program-b/view'
          },
          {
            title: 'Add Membership',
            link: '/membership/program-b/add',
            slug: '/membership/program-b/add'
          },
          {
            title: 'Delete Membership',
            link: '/membership/program-b/delete',
            slug: '/membership/program-b/delete'
          }
        ]
      }
    ],
  },
];

I want to filter the menu i.e showing only whatever granted to the user. The user can only view the menu based on allowed slugs as follows:

const allowed_slug = [
  '/dashboard/user-dashboard',
  '/inventory/add-inventory',
  '/membership/program-b/view',
  '/membership/program-b/add'
];

Using .filter I am able to filter the 1st layer of the array. Here's what I achieved so far:

function filterMenu(menus, allowed_slug) {
  const result = menus.filter(function (menu_item) {
    return allowed_slug.filter(function(slug) {
      return menu_item.slug.indexOf(slug) > -1;
    }).length;
  });

  return result;
}

Ideal output should look like this:

o
|-- User Dashboard
|-- Inventory
|   `-- Add Inventory
`-- Membership
    `-- Program B
        |-- View Membership
        `-- Add Membership

The problem is, I cannot filter the nested array i.e. children & children of children. Any help is very much appreciated. :)

6
  • Start by using some() or every() instead of filter().length. Also I'm pretty sure you're looking for menu_item.slug.indexOf(slug) == 0 (not >= 0), or menu_item.slug.startsWith(slug) Commented Mar 19, 2019 at 19:15
  • why is dashboard not indented? Commented Mar 19, 2019 at 19:15
  • @NinaScholz That's just the structure. I want to add some dynamic structure. Commented Mar 19, 2019 at 19:17
  • @Bergi that would still not solve the nested filtering problem. I've tried it for more than a day combining multiple array prototype functions. Commented Mar 19, 2019 at 19:21
  • @NinaScholz actually, it's quite similar to your answer in stackoverflow.com/a/45482594 . Just the structure is more nested. Commented Mar 19, 2019 at 19:22

3 Answers 3

5

You could check if one allowdSlug starts with a slug of the actual object.

var menus = [{ title: 'Supervisor Dashboard', link: '/dashboard/supervisor-dashboard', slug: '/dashboard/supervisor-dashboard' }, { title: 'User Dashboard', link: '/dashboard/user-dashboard', slug: '/dashboard/user-dashboard' }, { title: 'Inventory', slug: '/inventory', children: [{ title: 'Add Inventory', link: '/inventory/add-inventory', slug: '/inventory/add-inventory' }, { title: 'Remove Inventory', link: '/inventory/remove-inventory', slug: '/inventory/remove-inventory' }] }, { title: 'Membership', slug: '/membership', children: [{ title: 'Program A', slug: '/membership/program-a', children: [{ title: 'View Membership', link: '/membership/program-a/view', slug: '/membership/program-a/view' }, { title: 'Add Membership', link: '/membership/program-a/add', slug: '/membership/program-a/add' }, { title: 'Delete Membership', link: '/membership/program-a/delete', slug: '/membership/program-a/delete' }] }, { title: 'Program B', slug: '/membership/program-b', children: [{ title: 'View Membership', link: '/membership/program-b/view', slug: '/membership/program-b/view' }, { title: 'Add Membership', link: '/membership/program-b/add', slug: '/membership/program-b/add' }, { title: 'Delete Membership', link: '/membership/program-b/delete', slug: '/membership/program-b/delete' }] }] }], allowed_slug = ['/dashboard/user-dashboard', '/inventory/add-inventory', '/membership/program-b/view', '/membership/program-b/add'],
    filter = menus => menus
        .filter(({ slug }) => allowed_slug.some(s => s.startsWith(slug)))
        .map(({ title, slug, children = [] }) => {
            children = filter(children);
            return Object.assign({ title, slug }, children.length && { children })
        }),
    result = filter(menus);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the answer. It works but not completely. Just like @JonasWilms answer. It creates an empty children array in every menus, which breaks my styling.
Clever use of Object.assign ;)
4

Try with this function to see if it fits your needs. It basically creates a new array using a recursive reduce function checking that each element matches the criteria of the slugs array:

const menu = [ { title: 'Supervisor Dashboard', link: '/dashboard/supervisor-dashboard', slug: '/dashboard/supervisor-dashboard' }, { title: 'User Dashboard', link: '/dashboard/user-dashboard', slug: '/dashboard/user-dashboard' }, { title: 'Inventory', slug: '/inventory', children: [ { title: 'Add Inventory', link: '/inventory/add-inventory', slug: '/inventory/add-inventory' }, { title: 'Remove Inventory', link: '/inventory/remove-inventory', slug: '/inventory/remove-inventory' }, ] }, { title: 'Membership', slug: '/membership', children: [ { title: 'Program A', slug: '/membership/program-a', children: [ { title: 'View Membership', link: '/membership/program-a/view', slug: '/membership/program-a/view' }, { title: 'Add Membership', link: '/membership/program-a/add', slug: '/membership/program-a/add' }, { title: 'Delete Membership', link: '/membership/program-a/delete', slug: '/membership/program-a/delete' } ] }, { title: 'Program B', slug: '/membership/program-b', children: [ { title: 'View Membership', link: '/membership/program-b/view', slug: '/membership/program-b/view' }, { title: 'Add Membership', link: '/membership/program-b/add', slug: '/membership/program-b/add' }, { title: 'Delete Membership', link: '/membership/program-b/delete', slug: '/membership/program-b/delete' } ] } ], }, ]; const allowed_slug = [ '/dashboard/user-dashboard', '/inventory/add-inventory', '/membership/program-b/view', '/membership/program-b/add' ];

const filterMenu = (menu, allowed) =>
    menu.reduce((a, {title, link, slug, children = []}) =>
        (children = filterMenu(children, allowed), (children.length && (a = [...a, {title, slug, children}])) || (allowed.includes(slug) && (a = [...a, {title, link, slug}])), a), []);

console.log(filterMenu(menu, allowed_slug));

A more readable version of the function:

const filterMenu = (menu, allowed) =>
    menu.reduce((array, {title, link, slug, children = []}) => {
        children = filterMenu(children, allowed);
        if (children.length) {
            array.push({title, slug, children});
        } else if (allowed.includes(slug)) {
            array.push({title, link, slug});
        }
        return array;
    }, []);

Comments

3

Recursion is your friend here:

 // smae signature as your function
 const filterMenu = (menus, allowed) => menus
    // first of all, copy & filter recursively
   .map(({ title, slug, link, children }) => ({ title, slug, link, children: children && filterMenu(children, allowed) }))
   // then remove all that don't have allowed children and are not allowed themself
   .filter(it => it.children && it.children.length || allowed.includes(it.slug));

1 Comment

Just remove an extra parenthesis in the filterMenu function. This filterMenu(children), allowed) should be filterMenu(children, allowed).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.