UNPKG

@rxap/material-table-system

Version:

This package provides a set of Angular directives, components, and services to enhance and customize Angular Material tables. It includes features such as row selection, column filtering, expandable rows, table actions, and more. The goal is to simplify c

1,107 lines (1,089 loc) 127 kB
import * as i0 from '@angular/core'; import { InjectionToken, Injectable, Optional, Inject, Component, ChangeDetectionStrategy, isDevMode, Directive, Input, Renderer2, ElementRef, ChangeDetectorRef, ViewContainerRef, INJECTOR, ContentChild, HostListener, Pipe, NgModule, forwardRef, ContentChildren, HostBinding, TemplateRef } from '@angular/core'; import { RXAP_WINDOW_REF } from '@rxap/window-system'; import { map, tap, startWith, debounceTime, distinctUntilChanged, filter } from 'rxjs/operators'; import { hasIdentifierProperty, getIdentifierPropertyValue, clone, DeleteEmptyProperties, equals, coerceArray, coerceBoolean, dasherize } from '@rxap/utilities'; import { Subject, ReplaySubject, Subscription, isObservable, BehaviorSubject, EMPTY } from 'rxjs'; import { AsyncPipe, CommonModule, NgFor, NgClass, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault, DatePipe } from '@angular/common'; import * as i4 from '@angular/material/button'; import { MatButtonModule, MatIconButton, MatButton, MatMiniFabButton } from '@angular/material/button'; import * as i1$1 from '@angular/cdk/overlay'; import { Overlay } from '@angular/cdk/overlay'; import * as i3$1 from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar'; import * as i5 from '@angular/material/tooltip'; import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; import { ConfirmDirective, ConfirmModule, CopyToClipboardComponent } from '@rxap/components'; import * as i1 from '@angular/cdk/table'; import { CdkTable } from '@angular/cdk/table'; import * as i3 from '@angular/material/sort'; import { MatSort } from '@angular/material/sort'; import { pipeDataSource } from '@rxap/data-source'; import * as i2 from '@rxap/data-source/table'; import { RXAP_TABLE_METHOD, DynamicTableDataSource } from '@rxap/data-source/table'; export { RXAP_TABLE_METHOD } from '@rxap/data-source/table'; import { ToggleSubject } from '@rxap/rxjs'; import { setMetadata, getMetadata, hasMetadata } from '@rxap/reflect-metadata'; import * as i2$2 from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import * as i1$4 from '@angular/forms'; import { ControlContainer, NgModel } from '@angular/forms'; import { FormDirective } from '@rxap/forms'; import * as i1$6 from '@angular/material/paginator'; import * as i1$3 from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { StopPropagationDirective, BackgroundSizeOptions, BackgroundRepeatOptions, BackgroundPositionOptions, BackgroundImageDirective } from '@rxap/directives'; import * as i4$1 from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox'; import * as i2$1 from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu'; import * as i1$2 from '@angular/router'; import * as i1$5 from '@angular/cdk/portal'; import { TemplatePortal, PortalModule } from '@angular/cdk/portal'; import { trigger, state, style, transition, animate, sequence } from '@angular/animations'; import { IconDirective } from '@rxap/material-directives/icon'; import { MatOption } from '@angular/material/core'; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * Class to be used to power selecting one or more options from a list. */ class SelectionModel { /** Selected values. */ get selected() { if (!this._selected) { this._selected = Array.from(this._selection.values()); } return this._selected; } constructor(_multiple = false, initiallySelectedValues, _emitChanges = true, compareWith) { this._multiple = _multiple; this._emitChanges = _emitChanges; this.compareWith = compareWith; /** Currently-selected values. */ this._selection = new Set(); /** Keeps track of the deselected options that haven't been emitted by the change event. */ this._deselectedToEmit = []; /** Keeps track of the selected options that haven't been emitted by the change event. */ this._selectedToEmit = []; /** Cache for the array value of the selected items. */ this._selected = null; /** Event emitted when the value has changed. */ this.changed = new Subject(); if (initiallySelectedValues && initiallySelectedValues.length) { if (_multiple) { initiallySelectedValues.forEach(value => this._markSelected(value)); } else { this._markSelected(initiallySelectedValues[0]); } // Clear the array in order to avoid firing the change event for preselected values. this._selectedToEmit.length = 0; } } /** * Selects a value or an array of values. * @param values The values to select * @return Whether the selection changed as a result of this call * @breaking-change 16.0.0 make return type boolean */ select(...values) { this._verifyValueAssignment(values); values.forEach(value => this._markSelected(value)); const changed = this._hasQueuedChanges(); this._emitChangeEvent(); return changed; } /** * Deselects a value or an array of values. * @param values The values to deselect * @return Whether the selection changed as a result of this call * @breaking-change 16.0.0 make return type boolean */ deselect(...values) { this._verifyValueAssignment(values); values.forEach(value => this._unmarkSelected(value)); const changed = this._hasQueuedChanges(); this._emitChangeEvent(); return changed; } /** * Sets the selected values * @param values The new selected values * @return Whether the selection changed as a result of this call * @breaking-change 16.0.0 make return type boolean */ setSelection(...values) { this._verifyValueAssignment(values); const oldValues = this.selected; const newSelectedSet = new Set(values); values.forEach(value => this._markSelected(value)); oldValues .filter(value => !newSelectedSet.has(value)) .forEach(value => this._unmarkSelected(value)); const changed = this._hasQueuedChanges(); this._emitChangeEvent(); return changed; } /** * Toggles a value between selected and deselected. * @param value The value to toggle * @return Whether the selection changed as a result of this call * @breaking-change 16.0.0 make return type boolean */ toggle(value) { return this.isSelected(value) ? this.deselect(value) : this.select(value); } /** * Clears all of the selected values. * @param flushEvent Whether to flush the changes in an event. * If false, the changes to the selection will be flushed along with the next event. * @return Whether the selection changed as a result of this call * @breaking-change 16.0.0 make return type boolean */ clear(flushEvent = true) { this._unmarkAll(); const changed = this._hasQueuedChanges(); if (flushEvent) { this._emitChangeEvent(); } return changed; } /** * Determines whether a value is selected. */ isSelected(value) { if (this.compareWith) { for (const otherValue of this._selection) { if (this.compareWith(otherValue, value)) { return true; } } return false; } return this._selection.has(value); } /** * Determines whether the model does not have a value. */ isEmpty() { return this._selection.size === 0; } /** * Determines whether the model has a value. */ hasValue() { return !this.isEmpty(); } /** * Sorts the selected values based on a predicate function. */ sort(predicate) { if (this._multiple && this.selected) { this._selected.sort(predicate); } } /** * Gets whether multiple values can be selected. */ isMultipleSelection() { return this._multiple; } /** Emits a change event and clears the records of selected and deselected values. */ _emitChangeEvent() { // Clear the selected values so they can be re-cached. this._selected = null; if (this._selectedToEmit.length || this._deselectedToEmit.length) { this.changed.next({ source: this, added: this._selectedToEmit, removed: this._deselectedToEmit, }); this._deselectedToEmit = []; this._selectedToEmit = []; } } /** Selects a value. */ _markSelected(value) { if (!this.isSelected(value)) { if (!this._multiple) { this._unmarkAll(); } if (!this.isSelected(value)) { this._selection.add(value); } if (this._emitChanges) { this._selectedToEmit.push(value); } } } /** Deselects a value. */ _unmarkSelected(value) { if (this.isSelected(value)) { this._selection.delete(value); if (this._emitChanges) { this._deselectedToEmit.push(value); } } } /** Clears out the selected values. */ _unmarkAll() { if (!this.isEmpty()) { this._selection.forEach(value => this._unmarkSelected(value)); } } /** * Verifies the value assignment and throws an error if the specified value array is * including multiple values while the selection model is not supporting multiple values. */ _verifyValueAssignment(values) { if (values.length > 1 && !this._multiple && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw getMultipleValuesInSingleSelectionError(); } } /** Whether there are queued up change to be emitted. */ _hasQueuedChanges() { return !!(this._deselectedToEmit.length || this._selectedToEmit.length); } } /** * Returns an error that reports that multiple values are passed into a selection model * with a single value. * @docs-private */ function getMultipleValuesInSingleSelectionError() { return Error('Cannot pass multiple values into SelectionModel with single-value mode.'); } const RXAP_MATERIAL_TABLE_SYSTEM_SELECT_ROW_OPTIONS = new InjectionToken('rxap-material/table-system/select-row/options'); class SelectRowService { get selectedRows() { return this.selectionModel.selected; } constructor(options = null) { this.selectionModel = new SelectionModel(true); this.selectionModel = new SelectionModel(options?.multiple, options?.selected, options?.emitChanges, options?.compareWith ?? this.compareWith); this.selectedRows$ = this.selectionModel.changed.pipe(map(() => this.selectionModel.selected)); } clear() { this.selectionModel.clear(); } compareWith(a, b) { if (a === b) { return true; } if (hasIdentifierProperty(a) && hasIdentifierProperty(b)) { return getIdentifierPropertyValue(a) === getIdentifierPropertyValue(b); } return false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: SelectRowService, deps: [{ token: RXAP_MATERIAL_TABLE_SYSTEM_SELECT_ROW_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: SelectRowService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: SelectRowService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_MATERIAL_TABLE_SYSTEM_SELECT_ROW_OPTIONS] }] }] }); class TableSelectControlsComponent { constructor(selectRows, windowRef) { this.selectRows = selectRows; this.windowRef = windowRef; this.hasNotSelected$ = this.selectRows.selectedRows$.pipe(map((selected) => selected.length === 0)); } cancel() { this.windowRef.close(); } select() { this.windowRef.close(this.selectRows.selectedRows); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableSelectControlsComponent, deps: [{ token: SelectRowService }, { token: RXAP_WINDOW_REF }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.3", type: TableSelectControlsComponent, isStandalone: true, selector: "rxap-table-select-controls", ngImport: i0, template: "<div class=\"flex flex-row gap-4\">\n <button (click)=\"cancel()\" class=\"grow-0\" mat-stroked-button type=\"button\">\n <ng-container i18n>Cancel</ng-container>\n </button>\n <button (click)=\"select()\" [disabled]=\"hasNotSelected$ | async\" class=\"grow-0\" mat-raised-button type=\"button\">\n <ng-container i18n>Select</ng-container>\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableSelectControlsComponent, decorators: [{ type: Component, args: [{ selector: 'rxap-table-select-controls', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ MatButtonModule, AsyncPipe, ], template: "<div class=\"flex flex-row gap-4\">\n <button (click)=\"cancel()\" class=\"grow-0\" mat-stroked-button type=\"button\">\n <ng-container i18n>Cancel</ng-container>\n </button>\n <button (click)=\"select()\" [disabled]=\"hasNotSelected$ | async\" class=\"grow-0\" mat-raised-button type=\"button\">\n <ng-container i18n>Select</ng-container>\n </button>\n</div>\n" }] }], ctorParameters: () => [{ type: SelectRowService }, { type: undefined, decorators: [{ type: Inject, args: [RXAP_WINDOW_REF] }] }] }); class TableFilterService { constructor() { this.change = new ReplaySubject(1); this.current = {}; this.reset$ = new Subject(); /** * a flag to indicate whether any value was already send to the change subject * true - a value was send * false - no value was send * @private */ this._init = false; this._subscription = this.change.subscribe(current => this.current = current); } reset() { this.reset$.next(); } setMap(map) { const current = this.current; const copy = clone(this.current); const next = DeleteEmptyProperties(Object.assign(current, map)); if (!this._init || !equals(copy, next)) { this._init = true; this.change.next(next); } } set(key, value) { const current = this.current; current[key] = value; this.change.next(current); } remove(key) { const current = this.current; // eslint-disable-next-line no-prototype-builtins if (current.hasOwnProperty(key)) { delete current[key]; } this.change.next(current); } ngOnDestroy() { this._subscription.unsubscribe(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableFilterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableFilterService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableFilterService, decorators: [{ type: Injectable }], ctorParameters: () => [] }); /** * @deprecated use TABLE_METHOD instead */ const TABLE_REMOTE_METHOD = RXAP_TABLE_METHOD; const TABLE_REMOTE_METHOD_ADAPTER_FACTORY = new InjectionToken('table-remote-method-adapter-factory'); const RXAP_TABLE_FILTER = new InjectionToken('rxap/material-table-system/table-filter'); const TABLE_DATA_SOURCE = new InjectionToken('table-data-source'); class TableDataSourceDirective { /** * @deprecated use dataSource instead */ // eslint-disable-next-line @angular-eslint/no-input-rename set setDataSource(dataSource) { if (typeof dataSource !== 'string') { this.dataSource = dataSource; } } get lastRefreshed() { return this.dataSource?.lastRefreshed ?? null; } /** * @deprecated use method instead */ get remoteMethod() { return this.method; } /** * @deprecated use sourceMethod instead */ get sourceRemoteMethod() { return this.sourceMethod; } constructor(matTable, cdr, sourceMethod, sourceDataSource, adapterFactory, matSort, tableFilter, _tableFilter) { this.matTable = matTable; this.cdr = cdr; this.sourceMethod = sourceMethod; this.sourceDataSource = sourceDataSource; this.matSort = matSort; this.tableFilter = tableFilter; this._tableFilter = _tableFilter; this.loading$ = new ToggleSubject(true); this.hasError$ = new ToggleSubject(); this.error$ = new Subject(); this._subscription = new Subscription(); this.adapterFactory = null; this.matTable.trackBy = this.trackBy; this.adapterFactory = adapterFactory; this.retry = this.retry.bind(this); this.refresh = this.refresh.bind(this); this.reset = this.reset.bind(this); } trackBy(index, item) { return item['uuid'] ?? index; } ngOnInit() { const tableFilter = this._tableFilter ?? this.tableFilter ?? undefined; if (!this.dataSource) { if (this.sourceDataSource) { this.dataSource = this.sourceDataSource; } else if (this.sourceMethod) { if (this.adapterFactory) { this.method = this.adapterFactory(this.sourceMethod, this.paginator, this.matSort, tableFilter, this.parameters); } else { this.method = this.sourceMethod; } this.dataSource = new DynamicTableDataSource(this.method, this.paginator, this.matSort, tableFilter, this.parameters, this.method.metadata ?? { id: this.id }); } if (!this.dataSource) { throw new Error('The TABLE_DATA_SOURCE and RXAP_TABLE_METHOD token are not defined!'); } } this.dataSource.paginator = this.paginator; this.dataSource.sort = this.matSort ?? undefined; this.dataSource.filter = tableFilter; this.dataSource.parameters = this.parameters; if (this.dataSource instanceof DynamicTableDataSource) { this.dataSource.setPaginator(this.paginator, this.id); this.dataSource.setSort(this.matSort, this.id); this.dataSource.setFilter(tableFilter, this.id); this.dataSource.setParameters(this.parameters, this.id); } this._subscription.add(this.dataSource.loading$.pipe(tap(loading => this.loading$.next(!!loading))).subscribe()); this._subscription.add(this.dataSource.hasError$.pipe(tap(hasError => this.hasError$.next(!!hasError))).subscribe()); this._subscription.add(this.dataSource.error$.pipe(tap(error => this.error$.next(error))).subscribe()); // create the id property for the mat table component. // the instance of the mat table component is used as viewer object // with the set of the id property it is possible to use the same data source // instance for multiple table component simultaneously // on connect the data source can then use the correct paginator/matSort/tableFilter/parameters instance // to create the TableEvent objects Reflect.set(this.matTable, 'id', this.id); this.matTable.dataSource = pipeDataSource(this.dataSource, tap(rowList => { if (rowList.some((element) => !element.__metadata__)) { if (isDevMode()) { console.debug('Ensure to use the NormalizeTableRow function to normalize the table row!'); } rowList.forEach((element) => { element.__metadata__ ??= {}; element.__metadata__.loading$ ??= new ToggleSubject(); }); } })); } ngOnDestroy() { this._subscription?.unsubscribe(); } refresh() { this.dataSource?.refresh(); } retry() { this.dataSource?.retry(); } reset() { if (this.tableFilter) { this.tableFilter.reset(); } else { this.dataSource?.reset(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableDataSourceDirective, deps: [{ token: CdkTable }, { token: i0.ChangeDetectorRef }, { token: RXAP_TABLE_METHOD, optional: true }, { token: TABLE_DATA_SOURCE, optional: true }, { token: TABLE_REMOTE_METHOD_ADAPTER_FACTORY, optional: true }, { token: MatSort, optional: true }, { token: TableFilterService, optional: true }, { token: RXAP_TABLE_FILTER, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.3", type: TableDataSourceDirective, isStandalone: true, selector: "table[mat-table][rxapTableDataSource],mat-table[rxapTableDataSource]", inputs: { setDataSource: ["rxapTableDataSource", "setDataSource"], paginator: "paginator", id: "id", parameters: "parameters", dataSource: "dataSource" }, exportAs: ["rxapTableDataSource"], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableDataSourceDirective, decorators: [{ type: Directive, args: [{ selector: 'table[mat-table][rxapTableDataSource],mat-table[rxapTableDataSource]', exportAs: 'rxapTableDataSource', standalone: true, }] }], ctorParameters: () => [{ type: i1.CdkTable, decorators: [{ type: Inject, args: [CdkTable] }] }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_TABLE_METHOD] }] }, { type: i2.AbstractTableDataSource, decorators: [{ type: Optional }, { type: Inject, args: [TABLE_DATA_SOURCE] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [TABLE_REMOTE_METHOD_ADAPTER_FACTORY] }] }, { type: i3.MatSort, decorators: [{ type: Optional }, { type: Inject, args: [MatSort] }] }, { type: TableFilterService, decorators: [{ type: Optional }, { type: Inject, args: [TableFilterService] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_TABLE_FILTER] }] }], propDecorators: { setDataSource: [{ type: Input, args: ['rxapTableDataSource'] }], paginator: [{ type: Input }], id: [{ type: Input, args: [{ required: true }] }], parameters: [{ type: Input }], dataSource: [{ type: Input }] } }); class TableRowActionExecutingDirective { constructor(templateRef, vcr) { this.templateRef = templateRef; this.vcr = vcr; } show() { this.vcr.createEmbeddedView(this.templateRef); } hide() { this.vcr.clear(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowActionExecutingDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.3", type: TableRowActionExecutingDirective, isStandalone: true, selector: "[rxapTableRowActionExecuting]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowActionExecutingDirective, decorators: [{ type: Directive, args: [{ selector: '[rxapTableRowActionExecuting]', standalone: true, }] }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }] }); var TableRowActionStatus; (function (TableRowActionStatus) { TableRowActionStatus["EXECUTING"] = "executing"; TableRowActionStatus["DONE"] = "done"; TableRowActionStatus["ERROR"] = "error"; TableRowActionStatus["SUCCESS"] = "success"; })(TableRowActionStatus || (TableRowActionStatus = {})); /** * @deprecated use RXAP_TABLE_ACTION_METHOD_METADATA instead */ const RXAP_TABLE_ACTION_METHOD_TYPE_METADATA = 'rxap-table-action-method-type-metadata'; const RXAP_TABLE_ACTION_METHOD_METADATA = 'rxap-table-action-method-metadata'; /** * @deprecated use RXAP_TABLE_ACTION_METHOD_METADATA instead */ const RXAP_TABLE_ACTION_METHOD_CHECK_FUNCTION_METADATA = 'rxap-table-action-method-check-function-metadata'; function TableActionMethod(typeOrOptions, checkFunction) { let type; let options; if (typeof typeOrOptions === 'string') { type = typeOrOptions; options = { type, checkFunction, }; } else { options = typeOrOptions; type = options.type; checkFunction = options.checkFunction; } return function (target) { setMetadata(RXAP_TABLE_ACTION_METHOD_METADATA, options, target); if (type) { setMetadata(RXAP_TABLE_ACTION_METHOD_TYPE_METADATA, type, target); } if (checkFunction) { setMetadata(RXAP_TABLE_ACTION_METHOD_CHECK_FUNCTION_METADATA, checkFunction, target); } }; } function IsTableRowActionTypeSwitchMethod(method) { return getMetadata(RXAP_TABLE_ACTION_METHOD_METADATA, method.constructor)?.type === undefined; } function IsTableRowActionTypeMethod(type) { return (method) => { return (getMetadata(RXAP_TABLE_ACTION_METHOD_METADATA, method.constructor)?.type === type); }; } function HasTableRowActionCheckFunction(method) { return getMetadata(RXAP_TABLE_ACTION_METHOD_METADATA, method.constructor)?.checkFunction !== undefined; } function GetTableRowActionCheckFunction(method) { const checkFunction = getMetadata(RXAP_TABLE_ACTION_METHOD_METADATA, method.constructor)?.checkFunction; if (!checkFunction) { throw new Error(`Extracted check function from '${method.constructor.name}' is empty`); } return checkFunction; } function HasTableRowActionMetadata(method) { return hasMetadata(RXAP_TABLE_ACTION_METHOD_METADATA, method.constructor); } function GetTableRowActionMetadata(method) { const metadata = getMetadata(RXAP_TABLE_ACTION_METHOD_METADATA, method.constructor); if (!metadata) { throw new Error(`Extracted metadata from '${method.constructor.name}' is empty`); } return metadata; } const RXAP_TABLE_ROW_ACTION_METHOD = new InjectionToken('rxap-table-row-action-method'); class AbstractTableRowAction extends ConfirmDirective { constructor(renderer, overlay, elementRef, actionMethodList, cdr, vcr, tableDataSourceDirective, snackBar, matButton, matTooltip, injector) { super(overlay, elementRef); this.renderer = renderer; this.cdr = cdr; this.vcr = vcr; this.tableDataSourceDirective = tableDataSourceDirective; this.snackBar = snackBar; this.matButton = matButton; this.matTooltip = matTooltip; this.injector = injector; this.isHeader = false; this.options = null; this._currentStatus = TableRowActionStatus.DONE; this._actionDisabled = false; this._hasConfirmDirective = false; this.actionMethodList = coerceArray(actionMethodList); } // eslint-disable-next-line @angular-eslint/no-input-rename set hasConfirmDirective(value) { this._hasConfirmDirective = coerceBoolean(value); } // eslint-disable-next-line @angular-eslint/contextual-lifecycle ngOnInit() { this.options = this.getTableActionOptions(); if (this.options) { this.refresh ??= this.options.refresh ?? false; this.errorMessage ??= this.options.errorMessage ?? undefined; this.successMessage ??= this.options.successMessage ?? undefined; if (this.matTooltip && this.options.tooltip) { this.matTooltip.message = this.options.tooltip; } this.color ??= this.options.color ?? undefined; } if (this.matButton) { if (this.color) { this.matButton.color = this.color; } } if (this.isHeader) { this.renderer.addClass(this.elementRef.nativeElement, 'rxap-table-row-header-action'); } else { this.renderer.addClass(this.elementRef.nativeElement, 'rxap-table-row-action'); } this.renderer.addClass(this.elementRef.nativeElement, `rxap-action-${dasherize(this.type)}`); } onConfirmed() { return this.execute(); } onClick($event) { $event.stopPropagation(); if (!this._hasConfirmDirective && !this.options?.confirm) { return this.execute(); } else if (this.options?.confirm) { this.openConfirmOverly(); } else { if (isDevMode()) { console.debug('skip remote method call. Wait for confirmation.'); } } return Promise.resolve(); } async execute() { if (this._actionDisabled) { return Promise.resolve(); } this.setStatus(TableRowActionStatus.EXECUTING); try { await Promise.all(this.getElementList().map((element) => { return Promise.all([ Promise.all(this.findUntypedActionMethod().map((am) => { return am.call({ element, type: this.type, }); })), Promise.all(this.findTypedActionMethod().map((am) => { return am.call(element); })), ]); })); this.setStatus(TableRowActionStatus.SUCCESS); } catch (e) { console.error(`Failed to execute row action: ${e.message}`); this.setStatus(TableRowActionStatus.ERROR); } } /** * Disables the action. If the button is pressed the action is NOT executed * * Hint: the button is set to disabled = true to prevent any conflict with * extern button enable features linke : rxapHasEnablePermission * @protected */ setButtonDisabled() { this._actionDisabled = true; } /** * Enables the action. If the button is pressed the action is executed * * TODO : find a way to communicate the disabled state between the features * Hint: the button is set to disabled = false to prevent any conflict with * extern button enable features linke : rxapHasEnablePermission * @protected */ setButtonEnabled() { this._actionDisabled = false; } /** * find all method instance in the actionMethodList member that * do not have a @TableActionMethod decorators * @private */ findUntypedActionMethod() { return this.actionMethodList.filter(IsTableRowActionTypeSwitchMethod); } /** * find all method instance in the actionMethodList member that * do have a @TableActionMethod decorators with the current type * @private */ findTypedActionMethod() { return this.actionMethodList.filter(IsTableRowActionTypeMethod(this.type)); } setStatus(status) { if (this._currentStatus === status) { return; } this._currentStatus = status; switch (status) { case TableRowActionStatus.EXECUTING: this.setButtonDisabled(); this.executingDirective?.show(); break; case TableRowActionStatus.SUCCESS: if (this.refresh) { this.tableDataSourceDirective.refresh(); } if (this.successMessage) { this.snackBar.open(this.successMessage, 'ok', { duration: 2560 }); } this.setStatus(TableRowActionStatus.DONE); break; case TableRowActionStatus.ERROR: this.setStatus(TableRowActionStatus.DONE); if (this.errorMessage) { this.snackBar.open(this.errorMessage, 'ok', { duration: 5120 }); } break; case TableRowActionStatus.DONE: this.setButtonEnabled(); this.executingDirective?.hide(); break; } this.cdr.detectChanges(); } getTableActionOptions() { const metadataList = this.actionMethodList.map(actionMethod => GetTableRowActionMetadata(actionMethod)); if (metadataList.length === 0) { return null; } // TODO : handle multiple metadata or not exist metadata return metadataList.filter(metadata => metadata.type === this.type) .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))[0]; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AbstractTableRowAction, deps: [{ token: Renderer2 }, { token: Overlay }, { token: ElementRef }, { token: RXAP_TABLE_ROW_ACTION_METHOD }, { token: ChangeDetectorRef }, { token: ViewContainerRef }, { token: TableDataSourceDirective }, { token: MatSnackBar }, { token: MatIconButton, optional: true }, { token: MatTooltip, optional: true }, { token: INJECTOR }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AbstractTableRowAction }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: AbstractTableRowAction, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i0.Renderer2, decorators: [{ type: Inject, args: [Renderer2] }] }, { type: i1$1.Overlay, decorators: [{ type: Inject, args: [Overlay] }] }, { type: i0.ElementRef, decorators: [{ type: Inject, args: [ElementRef] }] }, { type: undefined, decorators: [{ type: Inject, args: [RXAP_TABLE_ROW_ACTION_METHOD] }] }, { type: i0.ChangeDetectorRef, decorators: [{ type: Inject, args: [ChangeDetectorRef] }] }, { type: i0.ViewContainerRef, decorators: [{ type: Inject, args: [ViewContainerRef] }] }, { type: TableDataSourceDirective, decorators: [{ type: Inject, args: [TableDataSourceDirective] }] }, { type: i3$1.MatSnackBar, decorators: [{ type: Inject, args: [MatSnackBar] }] }, { type: i4.MatIconButton, decorators: [{ type: Optional }, { type: Inject, args: [MatIconButton] }] }, { type: i5.MatTooltip, decorators: [{ type: Optional }, { type: Inject, args: [MatTooltip] }] }, { type: i0.Injector, decorators: [{ type: Inject, args: [INJECTOR] }] }], propDecorators: { errorMessage: [{ type: Input }], successMessage: [{ type: Input }], refresh: [{ type: Input }], color: [{ type: Input }], executingDirective: [{ type: ContentChild, args: [TableRowActionExecutingDirective] }], hasConfirmDirective: [{ type: Input, args: ['rxapConfirm'] }], onConfirmed: [{ type: HostListener, args: ['confirmed'] }], onClick: [{ type: HostListener, args: ['click', ['$event']] }] } }); class RowActionCheckPipe { constructor(tableRowActionMethodList) { this.actionMethodList = coerceArray(tableRowActionMethodList); } transform(value, type) { if (!type) { throw new Error(`The provided type is empty '${type}'`); } const actionMethodList = this.actionMethodList.filter(IsTableRowActionTypeMethod(type)); if (actionMethodList.length > 1) { throw new Error(`Multiple (${actionMethodList.length}) action method with the same type '${type}' found`); } if (actionMethodList.length === 0) { throw new Error(`Could not find a action method with the type '${type}'`); } const actionMethod = actionMethodList[0]; if (HasTableRowActionCheckFunction(actionMethod)) { const checkFunction = GetTableRowActionCheckFunction(actionMethod); const input = coerceArray(value); return input.length !== 0 && input.every(checkFunction); } return true; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: RowActionCheckPipe, deps: [{ token: RXAP_TABLE_ROW_ACTION_METHOD, optional: true }], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.3", ngImport: i0, type: RowActionCheckPipe, isStandalone: true, name: "rxapRowActionCheck" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: RowActionCheckPipe, decorators: [{ type: Pipe, args: [{ name: 'rxapRowActionCheck', standalone: true, }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [RXAP_TABLE_ROW_ACTION_METHOD] }] }] }); class TableRowActionDirective extends AbstractTableRowAction { getElementList() { return [this.element]; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowActionDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.3", type: TableRowActionDirective, isStandalone: true, selector: "button[rxapTableRowAction]", inputs: { errorMessage: "errorMessage", successMessage: "successMessage", refresh: "refresh", color: "color", type: ["rxapTableRowAction", "type"], element: "element" }, usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowActionDirective, decorators: [{ type: Directive, args: [{ selector: 'button[rxapTableRowAction]', standalone: true, // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property inputs: ['errorMessage', 'successMessage', 'refresh', 'color'], }] }], propDecorators: { type: [{ type: Input, args: [{ required: true, alias: 'rxapTableRowAction', }] }], element: [{ type: Input, args: [{ required: true }] }] } }); class TableRowHeaderActionDirective extends AbstractTableRowAction { constructor(renderer, overlay, elementRef, actionMethod, cdr, vcr, tableDataSourceDirective, snackBar, matButton, matTooltip, injector, selectRowService) { super(renderer, overlay, elementRef, actionMethod, cdr, vcr, tableDataSourceDirective, snackBar, matButton, matTooltip, injector); this.selectRowService = selectRowService; this.isHeader = true; } ngOnDestroy() { this._subscription?.unsubscribe(); } ngOnInit() { if (this.selectRowService) { this._subscription = this.selectRowService.selectedRows$ .pipe(startWith(this.selectRowService.selectedRows), map((rows) => rows.length !== 0), tap((hasSelected) => { if (hasSelected) { this.setButtonEnabled(); } else { this.setButtonDisabled(); } this.cdr.detectChanges(); })) .subscribe(); } else { this.setButtonDisabled(); } } getElementList() { return this.selectRowService?.selectedRows ?? []; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowHeaderActionDirective, deps: [{ token: Renderer2 }, { token: Overlay }, { token: ElementRef }, { token: RXAP_TABLE_ROW_ACTION_METHOD }, { token: ChangeDetectorRef }, { token: ViewContainerRef }, { token: TableDataSourceDirective }, { token: MatSnackBar }, { token: MatButton, optional: true }, { token: MatTooltip, optional: true }, { token: INJECTOR }, { token: SelectRowService, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.3", type: TableRowHeaderActionDirective, isStandalone: true, selector: "button[rxapTableRowHeaderAction]", inputs: { errorMessage: "errorMessage", successMessage: "successMessage", refresh: "refresh", color: "color", type: ["rxapTableRowHeaderAction", "type"] }, usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowHeaderActionDirective, decorators: [{ type: Directive, args: [{ selector: 'button[rxapTableRowHeaderAction]', standalone: true, // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property inputs: ['errorMessage', 'successMessage', 'refresh', 'color'], }] }], ctorParameters: () => [{ type: i0.Renderer2, decorators: [{ type: Inject, args: [Renderer2] }] }, { type: i1$1.Overlay, decorators: [{ type: Inject, args: [Overlay] }] }, { type: i0.ElementRef, decorators: [{ type: Inject, args: [ElementRef] }] }, { type: undefined, decorators: [{ type: Inject, args: [RXAP_TABLE_ROW_ACTION_METHOD] }] }, { type: i0.ChangeDetectorRef, decorators: [{ type: Inject, args: [ChangeDetectorRef] }] }, { type: i0.ViewContainerRef, decorators: [{ type: Inject, args: [ViewContainerRef] }] }, { type: TableDataSourceDirective, decorators: [{ type: Inject, args: [TableDataSourceDirective] }] }, { type: i3$1.MatSnackBar, decorators: [{ type: Inject, args: [MatSnackBar] }] }, { type: i4.MatButton, decorators: [{ type: Optional }, { type: Inject, args: [MatButton] }] }, { type: i5.MatTooltip, decorators: [{ type: Optional }, { type: Inject, args: [MatTooltip] }] }, { type: i0.Injector, decorators: [{ type: Inject, args: [INJECTOR] }] }, { type: SelectRowService, decorators: [{ type: Optional }, { type: Inject, args: [SelectRowService] }] }], propDecorators: { type: [{ type: Input, args: [{ required: true, alias: 'rxapTableRowHeaderAction', }] }] } }); class TableRowActionsModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowActionsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.3", ngImport: i0, type: TableRowActionsModule, imports: [TableRowActionDirective, TableRowActionExecutingDirective, TableRowHeaderActionDirective, RowActionCheckPipe, ConfirmModule], exports: [TableRowActionDirective, TableRowHeaderActionDirective, TableRowActionExecutingDirective, RowActionCheckPipe, MatTooltipModule, MatButtonModule, ConfirmModule, MatIconModule, MatProgressBarModule, CommonModule] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowActionsModule, imports: [ConfirmModule, MatTooltipModule, MatButtonModule, ConfirmModule, MatIconModule, MatProgressBarModule, CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: TableRowActionsModule, decorators: [{ type: NgModule, args: [{ imports: [ TableRowActionDirective, TableRowActionExecutingDirective, TableRowHeaderActionDirective, RowActionCheckPipe, ConfirmModule, ], exports: [ TableRowActionDirective, TableRowHeaderActionDirective, TableRowActionExecutingDirective, RowActionCheckPipe, MatTooltipModule, MatButtonModule, ConfirmModule, MatIconModule, MatProgressBarModule, CommonModule, ], }] }] }); const RXAP_TABLE_HEADER_BUTTON_METHOD_METADATA = 'rxap-table-header-button-method-metadata'; function TableHeaderButtonMethod(options = {}) { return function (target) { setMetadata(RXAP_TABLE_HEADER_BUTTON_METHOD_METADATA, options, target); }; } function GetTableHeaderButtonMetadata(method) { const metadata = getMetadata(RXAP_TABLE_HEADER_BUTTON_METHOD_METADATA, method.constructor); if (!metadata) { throw new Error(`Extracted metadata from '${method.constructor.name}' is empty`); } return metadata; } var TableHeaderButtonActionStatus; (function (TableHeaderButtonActionStatus) { TableHeaderButtonActionStatus["EXECUTING"] = "executing"; TableHeaderButtonActionStatus["DONE"] = "done"; TableHeaderButtonActionStatus["ERROR"] = "error"; TableHeaderButtonActionStatus["SUCCESS"] = "success"; })(TableHeaderButtonActionStatus || (TableHeaderButtonActi