@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
JavaScript
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