Browse Source

feat(admin-ui): Allow overriding built-in nav menu items

Closes #562
Michael Bromley 5 years ago
parent
commit
9d862c6d79

+ 6 - 0
docs/content/docs/plugins/extending-the-admin-ui/adding-navigation-items/_index.md

@@ -61,6 +61,12 @@ Running the server will compile our new shared module into the app, and the resu
 
 {{< figure src="./ui-extensions-navbar.jpg" >}}
 
+## Overriding existing items
+
+It is also possible to override one of the default (built-in) nav menu sections or items. This can be useful for example if you wish to provide a completely different implementation of the product list view. 
+
+This is done by setting the `id` property to that of an existing nav menu section or item.
+
 
 ## Adding new ActionBar buttons
 

+ 228 - 0
packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.spec.ts

@@ -0,0 +1,228 @@
+/* tslint:disable:no-non-null-assertion no-console */
+import { TestBed } from '@angular/core/testing';
+import { take } from 'rxjs/operators';
+
+import { NavMenuSection } from './nav-builder-types';
+import { NavBuilderService } from './nav-builder.service';
+
+describe('NavBuilderService', () => {
+    let service: NavBuilderService;
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({});
+        service = TestBed.inject(NavBuilderService);
+    });
+
+    it('defineNavMenuSections', done => {
+        service.defineNavMenuSections(getBaseNav());
+
+        service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+            expect(result).toEqual(getBaseNav());
+            done();
+        });
+    });
+
+    describe('addNavMenuSection', () => {
+        it('adding new section to end', done => {
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuSection({
+                id: 'reports',
+                label: 'Reports',
+                items: [],
+            });
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                expect(result.map(section => section.id)).toEqual(['catalog', 'sales', 'reports']);
+                done();
+            });
+        });
+
+        it('adding new section before', done => {
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuSection(
+                {
+                    id: 'reports',
+                    label: 'Reports',
+                    items: [],
+                },
+                'sales',
+            );
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                expect(result.map(section => section.id)).toEqual(['catalog', 'reports', 'sales']);
+                done();
+            });
+        });
+
+        it('replacing an existing section', done => {
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuSection({
+                id: 'sales',
+                label: 'Custom Sales',
+                items: [],
+            });
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                expect(result.map(section => section.id)).toEqual(['catalog', 'sales']);
+                expect(result[1].label).toBe('Custom Sales');
+                done();
+            });
+        });
+
+        it('replacing and moving', done => {
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuSection(
+                {
+                    id: 'sales',
+                    label: 'Custom Sales',
+                    items: [],
+                },
+                'catalog',
+            );
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                expect(result.map(section => section.id)).toEqual(['sales', 'catalog']);
+                expect(result[0].label).toBe('Custom Sales');
+                done();
+            });
+        });
+    });
+
+    describe('addNavMenuItem()', () => {
+        it('adding to non-existent section', done => {
+            spyOn(console, 'error');
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuItem(
+                {
+                    id: 'fulfillments',
+                    label: 'Fulfillments',
+                    routerLink: ['/extensions', 'fulfillments'],
+                },
+                'farm-tools',
+            );
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                expect(console.error).toHaveBeenCalledWith(
+                    'Could not add menu item "fulfillments", section "farm-tools" does not exist',
+                );
+                done();
+            });
+        });
+
+        it('adding to end of section', done => {
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuItem(
+                {
+                    id: 'fulfillments',
+                    label: 'Fulfillments',
+                    routerLink: ['/extensions', 'fulfillments'],
+                },
+                'sales',
+            );
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                const salesSection = result.find(r => r.id === 'sales')!;
+
+                expect(salesSection.items.map(item => item.id)).toEqual(['orders', 'fulfillments']);
+                done();
+            });
+        });
+
+        it('adding before existing item', done => {
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuItem(
+                {
+                    id: 'fulfillments',
+                    label: 'Fulfillments',
+                    routerLink: ['/extensions', 'fulfillments'],
+                },
+                'sales',
+                'orders',
+            );
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                const salesSection = result.find(r => r.id === 'sales')!;
+
+                expect(salesSection.items.map(item => item.id)).toEqual(['fulfillments', 'orders']);
+                done();
+            });
+        });
+
+        it('replacing existing item', done => {
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuItem(
+                {
+                    id: 'facets',
+                    label: 'Custom Facets',
+                    routerLink: ['/custom-facets'],
+                },
+                'catalog',
+            );
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                const catalogSection = result.find(r => r.id === 'catalog')!;
+
+                expect(catalogSection.items.map(item => item.id)).toEqual(['products', 'facets']);
+                expect(catalogSection.items[1].label).toBe('Custom Facets');
+                done();
+            });
+        });
+
+        it('replacing existing item and moving', done => {
+            service.defineNavMenuSections(getBaseNav());
+            service.addNavMenuItem(
+                {
+                    id: 'facets',
+                    label: 'Custom Facets',
+                    routerLink: ['/custom-facets'],
+                },
+                'catalog',
+                'products',
+            );
+
+            service.navMenuConfig$.pipe(take(1)).subscribe(result => {
+                const catalogSection = result.find(r => r.id === 'catalog')!;
+
+                expect(catalogSection.items.map(item => item.id)).toEqual(['facets', 'products']);
+                expect(catalogSection.items[0].label).toBe('Custom Facets');
+                done();
+            });
+        });
+    });
+
+    function getBaseNav(): NavMenuSection[] {
+        return [
+            {
+                id: 'catalog',
+                label: 'Catalog',
+                items: [
+                    {
+                        id: 'products',
+                        label: 'Products',
+                        icon: 'library',
+                        routerLink: ['/catalog', 'products'],
+                    },
+                    {
+                        id: 'facets',
+                        label: 'Facets',
+                        icon: 'tag',
+                        routerLink: ['/catalog', 'facets'],
+                    },
+                ],
+            },
+            {
+                id: 'sales',
+                label: 'Sales',
+                requiresPermission: 'ReadOrder',
+                items: [
+                    {
+                        id: 'orders',
+                        label: 'Orders',
+                        routerLink: ['/orders'],
+                        icon: 'shopping-cart',
+                    },
+                ],
+            },
+        ];
+    }
+});

+ 29 - 9
packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts

@@ -160,6 +160,8 @@ export class NavBuilderService {
      * move the section before any existing section with the specified id. If
      * omitted (or if the id is not found) the section will be appended to the
      * existing set of sections.
+     *
+     * Providing the `id` of an existing section will replace that section.
      */
     addNavMenuSection(config: NavMenuSection, before?: string) {
         this.addedNavMenuSections.push({ config, before });
@@ -171,6 +173,9 @@ export class NavBuilderService {
      * Providing the `before` argument will move the item before any existing item with the specified id.
      * If omitted (or if the name is not found) the item will be appended to the
      * end of the section.
+     *
+     * Providing the `id` of an existing item in that section will replace
+     * that item.
      */
     addNavMenuItem(config: NavMenuItem, sectionId: string, before?: string) {
         this.addedNavMenuItems.push({ config, sectionId, before });
@@ -208,10 +213,17 @@ export class NavBuilderService {
                     if (!config.requiresPermission) {
                         config.requiresPermission = Permission.Authenticated;
                     }
-                    const index = initialConfig.findIndex(c => c.id === before);
-                    if (-1 < index) {
-                        initialConfig.splice(index, 0, config);
-                    } else {
+                    const existingIndex = initialConfig.findIndex(c => c.id === config.id);
+                    if (-1 < existingIndex) {
+                        initialConfig[existingIndex] = config;
+                    }
+                    const beforeIndex = initialConfig.findIndex(c => c.id === before);
+                    if (-1 < beforeIndex) {
+                        if (-1 < existingIndex) {
+                            initialConfig.splice(existingIndex, 1);
+                        }
+                        initialConfig.splice(beforeIndex, 0, config);
+                    } else if (existingIndex === -1) {
                         initialConfig.push(config);
                     }
                 }
@@ -230,11 +242,19 @@ export class NavBuilderService {
                             `Could not add menu item "${item.config.id}", section "${item.sectionId}" does not exist`,
                         );
                     } else {
-                        const index = section.items.findIndex(i => i.id === item.before);
-                        if (-1 < index) {
-                            section.items.splice(index, 0, item.config);
-                        } else {
-                            section.items.push(item.config);
+                        const { config, sectionId, before } = item;
+                        const existingIndex = section.items.findIndex(i => i.id === config.id);
+                        if (-1 < existingIndex) {
+                            section.items[existingIndex] = config;
+                        }
+                        const beforeIndex = section.items.findIndex(i => i.id === before);
+                        if (-1 < beforeIndex) {
+                            if (-1 < existingIndex) {
+                                section.items.splice(existingIndex, 1);
+                            }
+                            section.items.splice(beforeIndex, 0, config);
+                        } else if (existingIndex === -1) {
+                            section.items.push(config);
                         }
                     }
                 }