Browse Source

Merge pull request #19440 from mustafapsd/feature/disable-route-filtering

Add disable filtering with id in AbstractTreeService, delete function
pull/19614/head
Masum ULU 2 years ago
committed by GitHub
parent
commit
ea9ebe14af
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 23
      docs/en/UI/Angular/Modifying-the-Menu.md
  2. 136
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  3. 133
      npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts

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

@ -152,6 +152,24 @@ import { APP_ROUTE_PROVIDER } from './route.provider';
export class AppModule {}
```
### Singularize Route Item
- `name` property is must be a unique key. If there are multiple items with the same name, the last one will be displayed in the menu.
- If you want to display multiple items in different parent with the same name, you can call the **setSingularizeStatus(false)** method of the `RoutesService` to disable the singularization.
- **This method should be called before adding the routes.**
- To enable the singularization of the names, you can call the **setSingularizeStatus(true) `(default value: true)`** method of the `RoutesService`.
```typescript
import { RoutesService } from '@abp/ng.core';
import { Component } from '@angular/core';
@Component(/* component metadata */)
export class AppComponent {
constructor(private routes: RoutesService) {
routes.setSingularizeStatus(false);
}
}
```
Here is what every property works as:
- `path` is the absolute path of the navigation element.
@ -226,7 +244,7 @@ After adding the `routes` property as described above, the navigation menu looks
## How to Patch or Remove a Navigation Element
The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children.
The `patch` method of `RoutesService` finds a route by its name and replaces its configuration with the new configuration passed as the second parameter. Similarly, `remove` method finds a route and removes it along with its children. Also you can use `removeByParam` method to delete the routes with given properties.
```js
// this.routes is instance of RoutesService
@ -249,6 +267,9 @@ const newHomeRouteConfig: Partial<ABP.Route> = {
this.routes.add([dashboardRouteConfig]);
this.routes.patch('::Menu:Home', newHomeRouteConfig);
this.routes.remove(['Your navigation']);
// or
this.routes.removeByParam({ name: 'Your navigation' });
```
- Moved the _Home_ navigation under the _Administration_ dropdown based on given `parentName`.

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

@ -26,6 +26,7 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
private _visible$ = new BehaviorSubject<TreeNode<T>[]>([]);
protected othersGroup: string;
protected shouldSingularizeRoutes = true;
get flat(): T[] {
return this._flat$.value;
@ -51,6 +52,29 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
return this._visible$.asObservable();
}
private filterWith(setOrMap: Set<string> | Map<string, T>): T[] {
return this._flat$.value.filter(item => !setOrMap.has(item[this.id]));
}
private findItemsToRemove(set: Set<string>): Set<string> {
return this._flat$.value.reduce((acc, item) => {
if (!acc.has(item[this.parentId])) {
return acc;
}
const childSet = new Set([item[this.id]]);
const children = this.findItemsToRemove(childSet);
return new Set([...acc, ...children]);
}, set);
}
private publish(flatItems: T[]): T[] {
this._flat$.next(flatItems);
this._tree$.next(this.createTree(flatItems));
this._visible$.next(this.createTree(flatItems.filter(item => !this.hide(item))));
return flatItems;
}
protected createTree(items: T[]): TreeNode<T>[] {
return createTreeFromList<T, TreeNode<T>>(
items,
@ -69,57 +93,50 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
return Array.from(map, ([key, items]) => ({ group: key, items }));
}
private filterWith(setOrMap: Set<string> | Map<string, T>): T[] {
return this._flat$.value.filter(item => !setOrMap.has(item[this.id]));
}
private findItemsToRemove(set: Set<string>): Set<string> {
return this._flat$.value.reduce((acc, item) => {
if (!acc.has(item[this.parentId])) return acc;
const childSet = new Set([item[this.id]]);
const children = this.findItemsToRemove(childSet);
return new Set([...acc, ...children]);
}, set);
}
private publish(flatItems: T[], visibleItems: T[]): T[] {
this._flat$.next(flatItems);
this._tree$.next(this.createTree(flatItems));
this._visible$.next(this.createTree(visibleItems));
return flatItems;
}
add(items: T[]): T[] {
const map = new Map<string, T>();
items.forEach(item => map.set(item[this.id], item));
let flatItems: T[] = [];
if (!this.shouldSingularizeRoutes) {
flatItems = [...this.flat, ...items];
}
const flatItems = this.filterWith(map);
map.forEach(pushValueTo(flatItems));
if (this.shouldSingularizeRoutes) {
const map = new Map<string, T>();
items.forEach(item => map.set(item[this.id], item));
flatItems = this.filterWith(map);
map.forEach(pushValueTo(flatItems));
}
flatItems.sort(this.sort);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems, visibleItems);
return this.publish(flatItems);
}
find(predicate: (item: TreeNode<T>) => boolean, tree = this.tree): TreeNode<T> | null {
return tree.reduce<TreeNode<T> | null>(
(acc, node) => (acc ? acc : predicate(node) ? node : this.find(predicate, node.children)),
null,
);
return tree.reduce<TreeNode<T> | null>((acc, node) => {
if (acc) {
return acc;
}
if (predicate(node)) {
return node;
}
return this.find(predicate, node.children);
}, null);
}
patch(identifier: string, props: Partial<T>): T[] | false {
const flatItems = this._flat$.value;
const index = flatItems.findIndex(item => item[this.id] === identifier);
if (index < 0) return false;
if (index < 0) {
return false;
}
flatItems[index] = { ...flatItems[index], ...props };
flatItems.sort(this.sort);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems, visibleItems);
return this.publish(flatItems);
}
refresh(): T[] {
@ -132,23 +149,50 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
const setToRemove = this.findItemsToRemove(set);
const flatItems = this.filterWith(setToRemove);
const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems);
}
removeByParam(params: Partial<T>): T[] | null {
if (!params) {
return null;
}
const keys = Object.keys(params) as Array<keyof Partial<T>>;
if (keys.length === 0) {
return null;
}
const excludedList = this.flat.filter(item => keys.every(key => item[key] === params[key]));
if (!excludedList?.length) {
return null;
}
return this.publish(flatItems, visibleItems);
for (const item of excludedList) {
this.removeByParam({ [this.parentId]: item[this.id] } as Partial<T>);
}
const flatItems = this.flat.filter(item => !excludedList.includes(item));
return this.publish(flatItems);
}
search(params: Partial<T>, tree = this.tree): TreeNode<T> | null {
const searchKeys = Object.keys(params) as Array<keyof Partial<T>>;
return tree.reduce<TreeNode<T> | null>(
(acc, node) =>
acc
? acc
: searchKeys.every(key => node[key] === params[key])
? node
: this.search(params, node.children),
null,
);
return tree.reduce<TreeNode<T> | null>((acc, node) => {
if (acc) {
return acc;
}
if (searchKeys.every(key => node[key] === params[key])) {
return node;
}
return this.search(params, node.children);
}, null);
}
setSingularizeStatus(singularize = true): void {
this.shouldSingularizeRoutes = singularize;
}
}
@ -164,7 +208,7 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
readonly parentId = 'parentName';
readonly hide = (item: T) => item.invisible || !this.isGranted(item);
readonly sort = (a: T, b: T) => {
return this.compareFunc(a,b)
return this.compareFunc(a, b);
};
constructor(protected injector: Injector) {

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

@ -172,6 +172,64 @@ describe('Routes Service', () => {
});
});
describe('#setSingularizeStatus', () => {
it('should allow to duplicate routes when called with false', () => {
service.setSingularizeStatus(false);
service.add(routes);
const flat = service.flat;
expect(flat.length).toBe(routes.length);
});
it('should allow to duplicate routes with the same name when called with false', () => {
service.setSingularizeStatus(false);
service.add([...routes, { path: '/foo/bar/test', name: 'bar', parentName: 'foo', order: 2 }]);
const flat = service.flat;
expect(flat.length).toBe(routes.length + 1);
});
it('should allow to routes with the same name but different parentName when called with false', () => {
service.setSingularizeStatus(false);
service.add([
{ path: '/foo/bar', name: 'bar', parentName: 'foo', order: 2 },
{ path: '/foo/bar', name: 'bar', parentName: 'baz', order: 1 },
]);
const flat = service.flat;
expect(flat.length).toBe(2);
});
it('should not allow to duplicate routes when called with true', () => {
service.setSingularizeStatus(false);
service.add(routes);
service.setSingularizeStatus(true);
service.add(routes);
const flat = service.flat;
expect(flat.length).toBe(5);
});
it('should not allow to duplicate routes with the same name when called with true', () => {
service.setSingularizeStatus(true);
service.add([...routes, { path: '/foo/bar/test', name: 'bar', parentName: 'any', order: 2 }]);
const flat = service.flat;
expect(flat.length).toBe(5);
});
});
describe('#find', () => {
it('should return node found based on query', () => {
service.add(routes);
@ -242,6 +300,81 @@ describe('Routes Service', () => {
});
});
describe('#removeByParam', () => {
it('should remove route based on given route', () => {
service.add(routes);
service.removeByParam({
name: 'bar',
parentName: 'foo',
});
const flat = service.flat;
expect(flat.length).toBe(2);
const notFound = service.find(route => route.name === 'bar');
expect(notFound).toBe(null);
});
it('should remove if more than one route has the same properties', () => {
service.setSingularizeStatus(false);
service.add([
...routes,
{
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
},
]);
service.removeByParam({
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
});
const flat = service.flat;
expect(flat.length).toBe(5);
const notFound = service.search({
path: '/foo/bar',
name: 'bar',
parentName: 'foo',
invisible: true,
order: 2,
breadcrumbText: 'Bar Breadcrumb',
});
expect(notFound).toBe(null);
});
it("shouldn't remove if there is no route with the given properties", () => {
service.add(routes);
const flatLengthBeforeRemove = service.flat.length;
service.removeByParam({
name: 'bar',
parentName: 'baz',
});
const flat = service.flat;
expect(flatLengthBeforeRemove - flat.length).toBe(0);
const notFound = service.find(route => route.name === 'bar');
expect(notFound).not.toBe(null);
});
});
describe('#patch', () => {
it('should patch propeties of routes based on given routeNames', () => {
service['isGranted'] = jest.fn(route => route.requiredPolicy !== 'X');

Loading…
Cancel
Save