Browse Source

grouped menu feature refactored

document updated
test updated
pull/15938/head
masumulu28 3 years ago
parent
commit
309eeada03
  1. 23
      docs/en/UI/Angular/Modifying-the-Menu.md
  2. 2
      npm/ng-packs/packages/core/src/lib/core.module.ts
  3. 13
      npm/ng-packs/packages/core/src/lib/models/common.ts
  4. 40
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  5. 32
      npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts
  6. 3
      npm/ng-packs/packages/core/src/lib/tokens/others-group.token.ts

23
docs/en/UI/Angular/Modifying-the-Menu.md

@ -96,19 +96,12 @@ import { ABP } from '@abp/ng.core';
type GroupType = ABP.Group<string>; type GroupType = ABP.Group<string>;
function configureRoutes(routes: RoutesService) { function configureRoutes(routes: RoutesService) {
const myGroup: GroupType = { key:'groupKey', text:'GroupName' };
return () => { return () => {
routes.add([ routes.add([
{ {
path: '/your-path', //etc..
name: 'Your navigation', group: 'ModuleName::GroupName'
requiredPolicy: 'permission key here',
order: 101,
iconClass: 'fas fa-question-circle',
layout: eLayoutType.application,
group: myGroup
}, },
{ {
path: '/your-path/child', path: '/your-path/child',
@ -138,11 +131,11 @@ export class AppComponent {
``` ```
...and then in app.module.ts... ...and then in app.module.ts...
- `groupedVisible` method will return `Others` group for ungrouped items, we can define `key` and `text` via `OTHERS_GROUP` injection token for this group - `groupedVisible` method will return `Others` group for ungrouped items, Default key is `AbpUi::OthersGroup` we can change this `key` via `OTHERS_GROUP` injection token
```js ```js
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { ABP, OTHERS_GROUP } from '@abp/ng.core'; import { OTHERS_GROUP } from '@abp/ng.core';
import { APP_ROUTE_PROVIDER } from './route.provider'; import { APP_ROUTE_PROVIDER } from './route.provider';
@NgModule({ @NgModule({
@ -150,7 +143,7 @@ import { APP_ROUTE_PROVIDER } from './route.provider';
APP_ROUTE_PROVIDER, APP_ROUTE_PROVIDER,
{ {
provide: OTHERS_GROUP, provide: OTHERS_GROUP,
useValue: { key: 1, text: 'MyOthersGroup' } as ABP.Group<number>, useValue: 'ModuleName::MyOthersGroupKey',
}, },
], ],
// imports, declarations, and bootstrap // imports, declarations, and bootstrap
@ -168,9 +161,7 @@ Here is what every property works as:
- `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label. - `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label.
- `layout` defines in which layout the route will be loaded. (default: `eLayoutType.empty`) - `layout` defines in which layout the route will be loaded. (default: `eLayoutType.empty`)
- `invisible` makes the item invisible in the menu. (default: `false`) - `invisible` makes the item invisible in the menu. (default: `false`)
- `group` is an optional property that is used to group together related routes in an application. It's an object and it have 2 property - `group` is an optional property that is used to group together related routes in an application. (type: `string`, default: `AbpUi::OthersGroup`)
- `key` is a generic type property that we use for gather items in same group. (default type: `string`)
- `text` is the display name on menu
### Via `routes` Property in `AppRoutingModule` ### Via `routes` Property in `AppRoutingModule`

2
npm/ng-packs/packages/core/src/lib/core.module.ts

@ -179,7 +179,7 @@ export class CoreModule {
}, },
{ {
provide: OTHERS_GROUP, provide: OTHERS_GROUP,
useValue: options.othersGroup || { key: 'others', text: '::Others' } as ABP.Group<string>, useValue: options.othersGroup || 'AbpUi::OthersGroup',
}, },
IncludeLocalizationResourcesProvider, IncludeLocalizationResourcesProvider,
], ],

13
npm/ng-packs/packages/core/src/lib/models/common.ts

@ -13,7 +13,7 @@ export namespace ABP {
sendNullsAsQueryParam?: boolean; sendNullsAsQueryParam?: boolean;
tenantKey?: string; tenantKey?: string;
localizations?: Localization[]; localizations?: Localization[];
othersGroup?: Group<any>; othersGroup?: string;
} }
export interface Child { export interface Child {
@ -68,20 +68,15 @@ export namespace ABP {
invisible?: boolean; invisible?: boolean;
} }
export interface Group<TKey = string> {
key: TKey;
text: string;
}
export interface Route extends Nav { export interface Route extends Nav {
path?: string; path?: string;
layout?: eLayoutType; layout?: eLayoutType;
iconClass?: string; iconClass?: string;
group?: Group<any>; group?: string;
} }
export interface RouteGroup<TKey = string> { export interface RouteGroup {
group: Group<TKey>; group: string;
items: TreeNode<Route>[]; items: TreeNode<Route>[];
} }

40
npm/ng-packs/packages/core/src/lib/services/routes.service.ts

@ -1,4 +1,4 @@
import { Injectable, Injector, Inject, Optional, OnDestroy } from '@angular/core'; import { Injectable, Injector, Inject, OnDestroy} from '@angular/core';
import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { ABP } from '../models/common'; import { ABP } from '../models/common';
import { OTHERS_GROUP } from '../tokens'; import { OTHERS_GROUP } from '../tokens';
@ -182,34 +182,20 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class RoutesService extends AbstractNavTreeService<ABP.Route> { export class RoutesService extends AbstractNavTreeService<ABP.Route> {
constructor( constructor(injector: Injector, @Inject(OTHERS_GROUP) private readonly othersGroup: string) {
injector: Injector,
@Optional() @Inject(OTHERS_GROUP) private readonly othersGroup: ABP.Group<any>,
) {
super(injector); super(injector);
} }
get groupedVisible(): ABP.RouteGroup[] | undefined { get groupedVisible(): ABP.RouteGroup[] | undefined {
const groupTree = this.visible.filter(node => node.group); const hasGroup = this.visible.some(node => !!node.group);
if (groupTree.length < 1) return; if (!hasGroup) return;
const map = new Map<ABP.Group, TreeNode<ABP.Route>[]>(groupTree?.map(node => [node.group, []])); const groups = this.visible.reduce((acc, node) => {
const otherGroup = this.othersGroup; const groupName = node.group ?? this.othersGroup;
map.set(otherGroup, []); acc[groupName] ||= [];
acc[groupName].push(node);
for (const node of this.visible) { return acc;
const { path, children, group } = node; }, {} as Record<string, TreeNode<ABP.Route>[]>);
if (!group && (children?.length > 0 || path)) { return Object.entries(groups).map(([group, items]) => ({ group, items }));
map.get(otherGroup)?.push(node);
} else if (group) {
map.get(group)?.push(node);
}
}
return Array.from(map.entries()).map<ABP.RouteGroup>(([group, nodes]) => ({
group,
items: nodes,
}));
} }
} }

32
npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts

@ -1,29 +1,26 @@
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { ABP } from '../models';
import { RoutesService } from '../services/routes.service'; import { RoutesService } from '../services/routes.service';
import { DummyInjector } from './utils/common.utils'; import { DummyInjector } from './utils/common.utils';
import { mockPermissionService } from './utils/permission-service.spec.utils'; import { mockPermissionService } from './utils/permission-service.spec.utils';
const updateStream$ = new Subject<void>(); const updateStream$ = new Subject<void>();
type GroupType = ABP.Group<string>;
export const mockRoutesService = (injectorPayload = {} as { [key: string]: any }) => { export const mockRoutesService = (injectorPayload = {} as { [key: string]: any }) => {
const injector = new DummyInjector({ const injector = new DummyInjector({
PermissionService: mockPermissionService(), PermissionService: mockPermissionService(),
ConfigStateService: { createOnUpdateStream: () => updateStream$ }, ConfigStateService: { createOnUpdateStream: () => updateStream$ },
...injectorPayload, ...injectorPayload,
}); });
const othersGroupToken: ABP.Group<number> = { key: 1, text: 'Others' }; return new RoutesService(injector, 'OthersGroup');
return new RoutesService(injector, othersGroupToken);
}; };
describe('Routes Service', () => { describe('Routes Service', () => {
let service: RoutesService; let service: RoutesService;
const fooGroup: GroupType = { key: 'foo', text: 'FooGroup' }; const fooGroup = 'FooGroup';
const barGroup: GroupType = { key: 'bar', text: 'BarGroup' }; const barGroup = 'BarGroup';
const othersGroup = 'OthersGroup';
const routes = [ const routes = [
{ path: '/foo', name: 'foo' }, { path: '/foo', name: 'foo' },
@ -75,18 +72,6 @@ describe('Routes Service', () => {
}); });
}); });
describe('#addGroup', () => {
it('should have routes with and without group', async () => {
service.add(groupedRoutes);
const grouped = service.visible.filter(f => f.group);
const unGrouped = service.visible.filter(f => !f.group);
expect(grouped.length).toBe(3);
expect(unGrouped.length).toBe(1);
});
});
describe('#groupedVisible', () => { describe('#groupedVisible', () => {
it('should have groups and items', async () => { it('should have groups and items', async () => {
service.add(groupedRoutes); service.add(groupedRoutes);
@ -95,18 +80,15 @@ describe('Routes Service', () => {
expect(tree.length).toBe(3); expect(tree.length).toBe(3);
expect(tree[0].group.key).toBe('foo'); expect(tree[0].group).toBe('FooGroup');
expect(tree[0].group.text).toBe('FooGroup');
expect(tree[0].items[0].name).toBe('foo'); expect(tree[0].items[0].name).toBe('foo');
expect(tree[0].items[0].children[0].name).toBe('y'); expect(tree[0].items[0].children[0].name).toBe('y');
expect(tree[1].group.key).toBe('bar'); expect(tree[1].group).toBe('BarGroup');
expect(tree[1].group.text).toBe('BarGroup');
expect(tree[1].items[0].name).toBe('bar'); expect(tree[1].items[0].name).toBe('bar');
expect(tree[1].items[1].name).toBe('baz'); expect(tree[1].items[1].name).toBe('baz');
expect(tree[2].group.key).toBe(1); expect(tree[2].group).toBe(othersGroup);
expect(tree[2].group.text).toBe('Others');
expect(tree[2].items[0].name).toBe('z'); expect(tree[2].items[0].name).toBe('z');
}); });
}); });

3
npm/ng-packs/packages/core/src/lib/tokens/others-group.token.ts

@ -1,4 +1,3 @@
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { ABP } from '../models';
export const OTHERS_GROUP = new InjectionToken<ABP.Group>('OTHERS_GROUP'); export const OTHERS_GROUP = new InjectionToken<string>('OTHERS_GROUP');

Loading…
Cancel
Save