UNPKG

@rxap/data-source

Version:

Provides a set of classes and decorators for creating and managing data sources in Angular applications, including base classes, static data sources, observable data sources, and method data sources. It also includes a component for displaying data source

305 lines (300 loc) 13.2 kB
import { SelectionModel } from '@angular/cdk/collections'; import * as i0 from '@angular/core'; import { InjectionToken, isDevMode, Injectable, Inject, Optional } from '@angular/core'; import { RXAP_DATA_SOURCE_METADATA } from '@rxap/data-source'; import { AbstractTableDataSource, RXAP_TABLE_DATA_SOURCE_PAGINATOR, RXAP_TABLE_DATA_SOURCE_SORT, RXAP_TABLE_DATA_SOURCE_FILTER, RXAP_TABLE_DATA_SOURCE_PARAMETERS } from '@rxap/data-source/table'; import { Node } from '@rxap/data-structure-tree'; import '@rxap/rxjs'; import { equals, joinPath } from '@rxap/utilities'; import * as i1 from 'rxjs'; import { BehaviorSubject, Subject, combineLatest, of, share } from 'rxjs'; import { debounceTime, map, distinctUntilChanged, skip, tap, switchMap, startWith } from 'rxjs/operators'; const RXAP_TREE_TABLE_DATA_SOURCE_ROOT_METHOD = new InjectionToken('rxap/tree/data-source/root-method'); const RXAP_TREE_TABLE_DATA_SOURCE_CHILDREN_METHOD = new InjectionToken('rxap/tree/data-source/children-method'); class TreeTableDataSource extends AbstractTableDataSource { constructor(rootMethod, childrenMethod, paginator = null, sort = null, filter = null, parameters = null, metadata = rootMethod.metadata) { super(paginator, sort, filter, parameters, metadata ?? rootMethod.metadata ?? { id: 'tree-table-data-source' }); this.rootMethod = rootMethod; this.childrenMethod = childrenMethod; this.tree$ = new BehaviorSubject([]); this._data$ = new Subject(); this._expandedLocalStorageSubscription = null; this.tree$ .pipe(debounceTime(100), map((tree) => tree .map((child) => this.flatTree(child)) .reduce((acc, nodes) => [...acc, ...nodes], [])), map((flatTree) => flatTree.map((node) => ({ ...node.item, __node: node, })))) .subscribe(this._data$); this.initExpanded(); } init() { if (this._initialised) { return; } super.init(); this.getTreeRoot(); // TODO : workaround the handle parameters change if (this.parameters) { this._subscription = this.parameters .pipe(distinctUntilChanged((a, b) => equals(a, b)), skip(1), tap(() => this.refresh())) .subscribe(); } } async getTreeRoot(event = {}) { this.loading$.next(true); this.hasError$.disable(); const root = await this.getRoot(event).catch((error) => { this.hasError$.enable(); throw new Error(`Failed to load root nodes: ${error.message}`); }); let rootNode = []; if (Array.isArray(root)) { for (const node of root) { rootNode.push(await this._toNode(node)); } } else { rootNode = [await this._toNode(root)]; if (!rootNode[0].expanded) { await rootNode[0].expand(); } } this.loading$.next(false); this.tree$.next(rootNode); } async getChildren(node, event) { return this.childrenMethod.call({ node, event }); } async getRoot(event) { return this.rootMethod.call(event); } collapseNode(node) { this.expanded.deselect(node.id); this.tree$.next(this.tree$.value); return Promise.resolve(); } async _toNode(row, depth = 0, onExpand = this.expandNode.bind(this), onCollapse = this.collapseNode.bind(this)) { const node = await this.toNode(row, depth, onExpand, onCollapse); if (this.expanded.isSelected(node.id) && !node.expanded) { await node .expand() .then(() => console.debug(`Restore expand for node '${node.id}' SUCCESSFULLY`)) .catch(() => console.debug(`Restore expand for node '${node.id}' FAILED`)); } for (const child of node.children) { if (this.expanded.isSelected(child.id) && !child.expanded) { await child .expand() .then(() => console.debug(`Restore expand for child node '${child.id}' SUCCESSFULLY`)) .catch(() => console.debug(`Restore expand for child node '${child.id}' FAILED`)); } } return node; } async expandNode(node) { if (node.item.hasChildren && !node.item.children?.length) { // TODO : get treeTableEvent from paginator, sort and filter const children = await this.getChildren(node, {}).catch((error) => { this.hasError$.enable(); throw new Error(`Failed to load children nodes for '${node.id}': ${error.message}`); }); node.item.children = children; const addChildren = []; for (const child of children) { const addChild = await this._toNode(child, node.depth + 1, node.onExpand, node.onCollapse); addChildren.push(addChild); } node.addChildren(addChildren); } this.expanded.select(node.id); this.tree$.next(this.tree$.value); } toNode(row, depth = 0, onExpand = this.expandNode.bind(this), onCollapse = this.collapseNode.bind(this)) { return Node.ToNode(null, row, depth, onExpand, onCollapse); } ngOnDestroy() { super.ngOnDestroy(); this._expandedLocalStorageSubscription?.unsubscribe(); this._subscription?.unsubscribe(); } flatTree(tree) { function flat(acc, list) { return [...acc, ...list]; } function flatTree(node) { if (!Array.isArray(node.children)) { if (isDevMode()) { console.log(node); } throw new Error('Node has not defined children'); } if (node.expanded) { return [ node, ...node.children.map((child) => flatTree(child)).reduce(flat, []), ]; } else { return [node]; } } return flatTree(tree); } retry() { return this.getTreeRoot(); } refresh() { return this.getTreeRoot(); } isMatchingFilter(item, filterList) { for (const filter of filterList) { const value = item[filter.column]; if (value === filter.filter) { return true; } if (typeof value === 'string' && typeof filter.filter === 'string') { return value.toLowerCase().includes(filter.filter.toLowerCase()); } } return false; } hasMatchingNodesByFilter(nodeList, filterList) { if (!filterList.length) { return true; } return nodeList.some((node) => { if (this.isMatchingFilter(node.item, filterList)) { return true; } if (!node.hasChildren) { // If item has no children and doesn't pass the filter, remove it return false; } return this.hasMatchingNodesByFilter(node.children ?? [], filterList); }); } applyFilterBy(data, filter) { const filterList = typeof filter === 'string' ? [] : Object.entries(filter).map(([column, filter]) => ({ column, filter })); if (!filterList.length || filterList.every((filter) => !filter.filter && filter.filter !== false && filter.filter !== 0)) { return data.slice(); } return data.filter(item => { if (this.isMatchingFilter(item, filterList)) { return true; } const hasMatch = this.hasMatchingNodesByFilter(item.__node.children, filterList); if (hasMatch) { item.__node.expand(); } return hasMatch; }); } _connect(viewer) { this.init(); return this._data$.pipe(tap((data) => this.updateTotalLength(data.length)), distinctUntilChanged((a, b) => { if (a.length !== b.length) { return false; } if (!equals(a.map(item => item.__node.id), b.map(item => item.__node.id))) { return false; } return equals(a.map(item => item.__node.item).map(item => ({ ...item, children: null })), b.map(item => item.__node.item).map(item => ({ ...item, children: null }))); }), switchMap((data) => { return combineLatest([ this.paginator?.page?.pipe(startWith({ pageIndex: this.paginator.pageIndex, pageSize: this.paginator.pageSize, length: this.paginator.length, })) ?? of(null), this.sort?.sortChange?.pipe(startWith({ active: this.sort?.active, direction: this.sort?.direction, })) ?? of(null), this.filter?.change.pipe(startWith({})) ?? of(null), ]).pipe(map(([page, sort, filter]) => ({ page, sort, filter, })), debounceTime(500), distinctUntilChanged((a, b) => equals(a, b)), map((event) => { const { page, sort, filter } = event; let filteredData = data; if (filter) { filteredData = this.applyFilterBy(filteredData, filter); } let sortData = filteredData; if (sort) { sortData = this.applySortBy(sortData, sort.active, sort.direction); } if (page) { return this.applyPagination(sortData, page.pageSize, page.pageIndex); } return sortData; })); }), share()); } initExpanded() { const key = joinPath('rxap/table-system/tree-table', this.id, 'expanded'); let expanded = []; if (localStorage.getItem(key)) { try { expanded = JSON.parse(localStorage.getItem(key)); } catch (e) { console.debug('parse expanded tree table data source nodes failed'); } } this.expanded = new SelectionModel(true, expanded); this._expandedLocalStorageSubscription = this.expanded.changed .pipe(tap(() => localStorage.setItem(key, JSON.stringify(this.expanded.selected)))) .subscribe(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: TreeTableDataSource, deps: [{ token: RXAP_TREE_TABLE_DATA_SOURCE_ROOT_METHOD }, { token: RXAP_TREE_TABLE_DATA_SOURCE_CHILDREN_METHOD }, { token: RXAP_TABLE_DATA_SOURCE_PAGINATOR, optional: true }, { token: RXAP_TABLE_DATA_SOURCE_SORT, optional: true }, { token: RXAP_TABLE_DATA_SOURCE_FILTER, optional: true }, { token: RXAP_TABLE_DATA_SOURCE_PARAMETERS, optional: true }, { token: RXAP_DATA_SOURCE_METADATA, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: TreeTableDataSource }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.16", ngImport: i0, type: TreeTableDataSource, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [RXAP_TREE_TABLE_DATA_SOURCE_ROOT_METHOD] }] }, { type: undefined, decorators: [{ type: Inject, args: [RXAP_TREE_TABLE_DATA_SOURCE_CHILDREN_METHOD] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_TABLE_DATA_SOURCE_PAGINATOR] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_TABLE_DATA_SOURCE_SORT] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_TABLE_DATA_SOURCE_FILTER] }] }, { type: i1.Observable, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_TABLE_DATA_SOURCE_PARAMETERS] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_DATA_SOURCE_METADATA] }] }] }); // region // endregion /** * Generated bundle index. Do not edit. */ export { RXAP_TREE_TABLE_DATA_SOURCE_CHILDREN_METHOD, RXAP_TREE_TABLE_DATA_SOURCE_ROOT_METHOD, TreeTableDataSource }; //# sourceMappingURL=rxap-data-source-table-tree.mjs.map