Browse Source

Merge pull request #5376 from abpframework/fix/5374

Fixed TreeAdapter to remove nested nodes properly
pull/5385/head
Bunyamin Coskuner 6 years ago
committed by GitHub
parent
commit
247a2c7f51
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      npm/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts
  2. 16
      npm/ng-packs/packages/core/src/lib/services/routes.service.ts
  3. 24
      npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts
  4. 10
      npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts

11
npm/ng-packs/packages/components/tree/src/lib/utils/nz-tree-adapter.ts

@ -41,17 +41,15 @@ export class TreeAdapter<T extends BaseNode = BaseNode> {
return this.tree; return this.tree;
} }
handleDrop(node: TreeNode<T>) { handleDrop({ key, parentNode }: TreeNode<T>) {
const { key, parentNode } = node;
const index = this.list.findIndex(({ id }) => id === key); const index = this.list.findIndex(({ id }) => id === key);
this.list[index].parentId = parentNode ? parentNode.key : null; this.list[index].parentId = parentNode ? parentNode.key : null;
this.tree = createTreeFromList(this.list); this.tree = createTreeFromList(this.list);
} }
handleRemove(node: TreeNode<T>) { handleRemove({ key }: TreeNode<T>) {
const { key, parentNode } = node; this.tree = createTreeFromList(this.list.filter(item => item.id !== key));
this.list = this.list.filter(({ id, parentId }) => id !== key && parentId !== key); this.list = createListFromTree(this.tree);
this.tree = createTreeFromList(this.list);
} }
} }
@ -66,6 +64,7 @@ function createTreeFromList<T extends BaseNode>(list: T[]): TreeNode<T>[] {
const node = map.get(row.id); const node = map.get(row.id);
if (parentId) { if (parentId) {
const parent = map.get(parentId); const parent = map.get(parentId);
if (!parent) return;
parent.children.push(node); parent.children.push(node);
parent.isLeaf = false; parent.isLeaf = false;
} else { } else {

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

@ -51,9 +51,16 @@ export abstract class AbstractTreeService<T extends object> {
} }
private filterWith(setOrMap: Set<string> | Map<string, T>): T[] { private filterWith(setOrMap: Set<string> | Map<string, T>): T[] {
return this._flat$.value.filter( return this._flat$.value.filter(item => !setOrMap.has(item[this.id]));
item => !setOrMap.has(item[this.id]) && !setOrMap.has(item[this.parentId]), }
);
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[] { private publish(flatItems: T[], visibleItems: T[]): T[] {
@ -104,7 +111,8 @@ export abstract class AbstractTreeService<T extends object> {
const set = new Set<string>(); const set = new Set<string>();
identifiers.forEach(id => set.add(id)); identifiers.forEach(id => set.add(id));
const flatItems = this.filterWith(set); const setToRemove = this.findItemsToRemove(set);
const flatItems = this.filterWith(setToRemove);
const visibleItems = flatItems.filter(item => !this.hide(item)); const visibleItems = flatItems.filter(item => !this.hide(item));
return this.publish(flatItems, visibleItems); return this.publish(flatItems, visibleItems);

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

@ -10,6 +10,7 @@ describe('Routes Service', () => {
{ path: '/foo', name: 'foo' }, { path: '/foo', name: 'foo' },
{ path: '/foo/bar', name: 'bar', parentName: 'foo', invisible: true, order: 2 }, { path: '/foo/bar', name: 'bar', parentName: 'foo', invisible: true, order: 2 },
{ path: '/foo/bar/baz', name: 'baz', parentName: 'bar', order: 1 }, { path: '/foo/bar/baz', name: 'baz', parentName: 'bar', order: 1 },
{ path: '/foo/bar/baz/qux', name: 'qux', parentName: 'baz', order: 1 },
{ path: '/foo/x', name: 'x', parentName: 'foo', order: 1 }, { path: '/foo/x', name: 'x', parentName: 'foo', order: 1 },
]; ];
@ -32,11 +33,12 @@ describe('Routes Service', () => {
const tree = await service.tree$.pipe(take(1)).toPromise(); const tree = await service.tree$.pipe(take(1)).toPromise();
const visible = await service.visible$.pipe(take(1)).toPromise(); const visible = await service.visible$.pipe(take(1)).toPromise();
expect(flat.length).toBe(4); expect(flat.length).toBe(5);
expect(flat[3].name).toBe('foo');
expect(flat[0].name).toBe('baz'); expect(flat[0].name).toBe('baz');
expect(flat[1].name).toBe('x'); expect(flat[1].name).toBe('qux');
expect(flat[2].name).toBe('bar'); expect(flat[2].name).toBe('x');
expect(flat[3].name).toBe('bar');
expect(flat[4].name).toBe('foo');
expect(tree.length).toBe(1); expect(tree.length).toBe(1);
expect(tree[0].name).toBe('foo'); expect(tree[0].name).toBe('foo');
@ -44,6 +46,7 @@ describe('Routes Service', () => {
expect(tree[0].children[0].name).toBe('x'); expect(tree[0].children[0].name).toBe('x');
expect(tree[0].children[1].name).toBe('bar'); expect(tree[0].children[1].name).toBe('bar');
expect(tree[0].children[1].children[0].name).toBe('baz'); expect(tree[0].children[1].children[0].name).toBe('baz');
expect(tree[0].children[1].children[0].children[0].name).toBe('qux');
expect(visible.length).toBe(1); expect(visible.length).toBe(1);
expect(visible[0].name).toBe('foo'); expect(visible[0].name).toBe('foo');
@ -74,7 +77,8 @@ describe('Routes Service', () => {
expect(service.hasChildren('foo')).toBe(true); expect(service.hasChildren('foo')).toBe(true);
expect(service.hasChildren('bar')).toBe(true); expect(service.hasChildren('bar')).toBe(true);
expect(service.hasChildren('baz')).toBe(false); expect(service.hasChildren('baz')).toBe(true);
expect(service.hasChildren('qux')).toBe(false);
}); });
}); });
@ -123,11 +127,12 @@ describe('Routes Service', () => {
const tree = service.tree; const tree = service.tree;
const visible = service.visible; const visible = service.visible;
expect(flat.length).toBe(4); expect(flat.length).toBe(5);
expect(flat[3].name).toBe('foo');
expect(flat[0].name).toBe('baz'); expect(flat[0].name).toBe('baz');
expect(flat[1].name).toBe('x'); expect(flat[1].name).toBe('qux');
expect(flat[2].name).toBe('bar'); expect(flat[2].name).toBe('x');
expect(flat[3].name).toBe('bar');
expect(flat[4].name).toBe('foo');
expect(tree.length).toBe(1); expect(tree.length).toBe(1);
expect(tree[0].name).toBe('foo'); expect(tree[0].name).toBe('foo');
@ -135,6 +140,7 @@ describe('Routes Service', () => {
expect(tree[0].children[0].name).toBe('x'); expect(tree[0].children[0].name).toBe('x');
expect(tree[0].children[1].name).toBe('bar'); expect(tree[0].children[1].name).toBe('bar');
expect(tree[0].children[1].children[0].name).toBe('baz'); expect(tree[0].children[1].children[0].name).toBe('baz');
expect(tree[0].children[1].children[0].children[0].name).toBe('qux');
expect(visible.length).toBe(1); expect(visible.length).toBe(1);
expect(visible[0].name).toBe('foo'); expect(visible[0].name).toBe('foo');

10
npm/ng-packs/packages/core/src/lib/utils/tree-utils.ts

@ -1,7 +1,7 @@
export class BaseTreeNode<T extends object> { export class BaseTreeNode<T extends object> {
children: TreeNode<T>[] = []; children: TreeNode<T>[] = [];
isLeaf = true; isLeaf = true;
parent: TreeNode<T>; parent?: TreeNode<T>;
constructor(props: T) { constructor(props: T) {
Object.assign(this, props); Object.assign(this, props);
@ -14,7 +14,7 @@ export class BaseTreeNode<T extends object> {
export function createTreeFromList<T extends object, R extends unknown>( export function createTreeFromList<T extends object, R extends unknown>(
list: T[], list: T[],
keySelector: (item: T) => number | string | symbol, keySelector: (item: T) => NodeKey,
parentKeySelector: typeof keySelector, parentKeySelector: typeof keySelector,
valueMapper: (item: T) => R, valueMapper: (item: T) => R,
) { ) {
@ -26,6 +26,8 @@ export function createTreeFromList<T extends object, R extends unknown>(
const parentId = parentKeySelector(row); const parentId = parentKeySelector(row);
const node = map.get(id); const node = map.get(id);
if (!node) return;
if (parentId) { if (parentId) {
const parent = map.get(parentId); const parent = map.get(parentId);
if (!parent) return; if (!parent) return;
@ -42,7 +44,7 @@ export function createTreeFromList<T extends object, R extends unknown>(
export function createMapFromList<T extends object, R extends unknown>( export function createMapFromList<T extends object, R extends unknown>(
list: T[], list: T[],
keySelector: (item: T) => number | string | symbol, keySelector: (item: T) => NodeKey,
valueMapper: (item: T) => R, valueMapper: (item: T) => R,
) { ) {
type Key = ReturnType<typeof keySelector>; type Key = ReturnType<typeof keySelector>;
@ -60,6 +62,8 @@ export type TreeNode<T extends object> = {
parent?: TreeNode<T>; parent?: TreeNode<T>;
}; };
type NodeKey = number | string | symbol | undefined | null;
type NodeValue<T extends object, F extends (...args: any) => any> = F extends undefined type NodeValue<T extends object, F extends (...args: any) => any> = F extends undefined
? TreeNode<T> ? TreeNode<T>
: ReturnType<F>; : ReturnType<F>;

Loading…
Cancel
Save