src/lib/table-row-actions/abstract-table-row-action.ts
ConfirmDirective
Properties |
Methods |
|
Accessors |
constructor(renderer: Renderer2, overlay: Overlay, elementRef: ElementRef, actionMethodList: Array<TableRowActionMethod<Data>> | TableRowActionMethod<Data>, cdr: ChangeDetectorRef, vcr: ViewContainerRef, tableDataSourceDirective: TableDataSourceDirective, snackBar: MatSnackBar, matButton: MatIconButton | null, matTooltip: MatTooltip | null, injector: Injector)
|
||||||||||||||||||||||||||||||||||||
Parameters :
|
Public Async execute |
execute()
|
Returns :
Promise<void>
|
Protected Abstract getElementList |
getElementList()
|
Returns :
Data[]
|
Public onClick | ||||||
onClick($event: Event)
|
||||||
Decorators :
@HostListener('click', ['$event'])
|
||||||
Parameters :
Returns :
any
|
Public onConfirmed |
onConfirmed()
|
Decorators :
@HostListener('confirmed')
|
Returns :
Promise<void>
|
Protected setButtonDisabled |
setButtonDisabled()
|
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
Returns :
void
|
Protected setButtonEnabled |
setButtonEnabled()
|
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
Returns :
void
|
Public Readonly isHeader |
Type : boolean
|
Default value : false
|
Protected options |
Type : TableActionMethodOptions | null
|
Default value : null
|
Public Abstract type |
Type : string
|
hasConfirmDirective | ||||||
sethasConfirmDirective(value: any)
|
||||||
Parameters :
Returns :
void
|
import { Overlay } from '@angular/cdk/overlay';
import {
ChangeDetectorRef,
ContentChild,
ElementRef,
HostListener,
Inject,
Injectable,
INJECTOR,
Injector,
Input,
isDevMode,
OnInit,
Optional,
Renderer2,
ViewContainerRef,
} from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { ThemePalette } from '@angular/material/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTooltip } from '@angular/material/tooltip';
import { ConfirmDirective } from '@rxap/components';
import {
coerceArray,
coerceBoolean,
dasherize,
} from '@rxap/utilities';
import { TableDataSourceDirective } from '../table-data-source.directive';
import { TableActionMethodOptions } from './decorators';
import { TableRowActionExecutingDirective } from './table-row-action-executing.directive';
import { TableRowActionStatus } from './table-row-action-status';
import {
GetTableRowActionMetadata,
IsTableRowActionTypeMethod,
IsTableRowActionTypeSwitchMethod,
} from './table-row-action.method';
import { RXAP_TABLE_ROW_ACTION_METHOD } from './tokens';
import {
TableRowActionMethod,
TableRowActionTypeMethod,
TableRowActionTypeSwitchMethod,
} from './types';
@Injectable()
export abstract class AbstractTableRowAction<Data extends Record<string, any>> extends ConfirmDirective
implements OnInit {
public abstract type: string;
@Input()
public errorMessage?: string;
@Input()
public successMessage?: string;
/**
* true - after the action is executed the table datasource is refreshed
*/
@Input()
public refresh?: boolean;
@Input()
public color?: ThemePalette;
public readonly isHeader: boolean = false;
protected options: TableActionMethodOptions | null = null;
@ContentChild(TableRowActionExecutingDirective)
private readonly executingDirective?: TableRowActionExecutingDirective;
private _currentStatus: TableRowActionStatus = TableRowActionStatus.DONE;
private readonly actionMethodList: Array<TableRowActionMethod<Data>>;
private _actionDisabled = false;
constructor(
@Inject(Renderer2)
protected readonly renderer: Renderer2,
@Inject(Overlay)
overlay: Overlay,
@Inject(ElementRef)
elementRef: ElementRef,
@Inject(RXAP_TABLE_ROW_ACTION_METHOD)
actionMethodList:
| Array<TableRowActionMethod<Data>>
| TableRowActionMethod<Data>,
@Inject(ChangeDetectorRef)
protected readonly cdr: ChangeDetectorRef,
@Inject(ViewContainerRef)
protected readonly vcr: ViewContainerRef,
@Inject(TableDataSourceDirective)
protected readonly tableDataSourceDirective: TableDataSourceDirective,
@Inject(MatSnackBar)
protected readonly snackBar: MatSnackBar,
@Optional()
@Inject(MatIconButton)
protected matButton: MatIconButton | null,
@Optional()
@Inject(MatTooltip)
protected matTooltip: MatTooltip | null,
@Inject(INJECTOR)
protected readonly injector: Injector,
) {
super(overlay, elementRef);
this.actionMethodList = coerceArray(actionMethodList);
}
private _hasConfirmDirective = false;
// eslint-disable-next-line @angular-eslint/no-input-rename
@Input('rxapConfirm')
public set hasConfirmDirective(value: any) {
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) }`);
}
@HostListener('confirmed')
public override onConfirmed() {
return this.execute();
}
@HostListener('click', [ '$event' ])
public override onClick($event: 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();
}
public async execute(): Promise<void> {
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: any) {
console.error(`Failed to execute row action: ${ e.message }`);
this.setStatus(TableRowActionStatus.ERROR);
}
}
protected abstract getElementList(): Data[];
/**
* 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
*/
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
*/
protected setButtonEnabled() {
this._actionDisabled = false;
}
/**
* find all method instance in the actionMethodList member that
* do not have a @TableActionMethod decorators
* @private
*/
private findUntypedActionMethod(): Array<TableRowActionTypeSwitchMethod<Data>> {
return this.actionMethodList.filter(IsTableRowActionTypeSwitchMethod);
}
/**
* find all method instance in the actionMethodList member that
* do have a @TableActionMethod decorators with the current type
* @private
*/
private findTypedActionMethod(): Array<TableRowActionTypeMethod<Data>> {
return this.actionMethodList.filter(IsTableRowActionTypeMethod(this.type));
}
private setStatus(status: TableRowActionStatus) {
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();
}
private getTableActionOptions(): TableActionMethodOptions | null {
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];
}
}