From 363358aaa10945cd5a66d268f105aeaddd25330d Mon Sep 17 00:00:00 2001 From: Developer 02 Date: Wed, 4 Sep 2024 14:58:48 +0200 Subject: [PATCH] feat: Implementieren und integrieren von wiederverwendbaren Angular Material-Tabellenkomponenten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Erstellen der `DDTable`-Komponente für dynamische Angular Material-Tabellen mit Unterstützung für erweiterbare Zeilen, Filterung, Sortierung und Pagination. - Ersetzen der bestehenden Tabellenimplementierung in `EnvelopeTableComponent` durch die `DDTable`-Komponente für verbesserte Funktionalität und Wartbarkeit. - Aktualisieren der `EnvelopeTableComponent`, um `DDTable` für die Anzeige von Umschlagdaten zu verwenden, einschließlich Filter- und Paginierungsoptionen. - Implementieren von `ClearableInputComponent` für eine verbesserte Filtererfahrung. - Anpassen des Datenabrufs und -handlings, um die Integration von `DDTable` zu berücksichtigen. - Verbessern der `EnvelopeTableComponent` mit ordnungsgemäßen Animationstriggern für erweiterbare Zeilen. --- .../dd-table/dd-table.component.html | 59 ++++++++++ .../dd-table/dd-table.component.scss | 52 +++++++++ .../dd-table/dd-table.component.spec.ts | 23 ++++ .../components/dd-table/dd-table.component.ts | 103 ++++++++++++++++++ .../envelope-table.component.html | 43 +------- .../envelope-table.component.scss | 49 +++++++++ .../envelope-table.component.ts | 64 +++-------- 7 files changed, 304 insertions(+), 89 deletions(-) create mode 100644 EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.html create mode 100644 EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.scss create mode 100644 EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.spec.ts create mode 100644 EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.ts diff --git a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.html b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.html new file mode 100644 index 00000000..6bdca454 --- /dev/null +++ b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.html @@ -0,0 +1,59 @@ +@if(isFilterable) { + + {{filter.label}} + + +} + + + @for (column of __columnsToDisplay; track column) { + + @if(isSortable) { + + } + @else { + + } + + + } + + + @if(isExpandable) { + + + + + + + + } + @if(isExpandable) { + + + + + } + @else { + + + } +
{{schema[column].header}} {{schema[column].header}} {{schema[column].field(element)}}   + + +
+ @if(__expandedElement === element){ + + } +
+
+@if(paginatorSizeOptions && paginatorSizeOptions.length > 0) { + +} \ No newline at end of file diff --git a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.scss b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.scss new file mode 100644 index 00000000..155617b3 --- /dev/null +++ b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.scss @@ -0,0 +1,52 @@ +table { + width: 100%; +} + +tr.example-detail-row { + height: 0; +} + +tr.example-element-row:not(.example-expanded-row):hover { + background: whitesmoke; +} + +tr.example-element-row:not(.example-expanded-row):active { + background: #efefef; +} + +.example-element-row td { + border-bottom-width: 0; +} + +.example-element-detail { + overflow: hidden; + display: flex; +} + +.example-element-diagram { + min-width: 80px; + border: 2px solid black; + padding: 8px; + font-weight: lighter; + margin: 8px 0; + height: 104px; +} + +.example-element-symbol { + font-weight: bold; + font-size: 40px; + line-height: normal; +} + +.example-element-description { + padding: 16px; +} + +.example-element-description-attribution { + opacity: 0.5; +} + +.mat-mdc-form-field { + font-size: 14px; + width: 100%; +} \ No newline at end of file diff --git a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.spec.ts b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.spec.ts new file mode 100644 index 00000000..0c704ba9 --- /dev/null +++ b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DDTable } from './dd-table.component'; + +describe('TableExpandableRowsExampleComponent', () => { + let component: DDTable; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DDTable] + }) + .compileComponents(); + + fixture = TestBed.createComponent(DDTable); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.ts b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.ts new file mode 100644 index 00000000..7e2fa4fc --- /dev/null +++ b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/dd-table/dd-table.component.ts @@ -0,0 +1,103 @@ +import { AfterViewInit, Component, Input, ViewChild, inject } from '@angular/core'; +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { MatTable, MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { ConfigurationService } from '../../services/configuration.service'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSort, MatSortModule } from '@angular/material/sort'; +import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; + +/** + * @title Table with expandable rows + */ +@Component({ + selector: 'dd-table', + styleUrl: 'dd-table.component.scss', + templateUrl: 'dd-table.component.html', + animations: [ + trigger('detailExpand', [ + state('collapsed,void', style({ height: '0px', minHeight: '0' })), + state('expanded', style({ height: '*' })), + transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), + ]), + ], + standalone: true, + imports: [ + MatTableModule, + MatButtonModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + MatTableModule, + MatSort, + MatSortModule, + MatPaginator, + MatPaginatorModule + ], +}) +export class DDTable implements AfterViewInit { + public readonly dataSource: any = new MatTableDataSource(); + @Input() public set columnsToDisplay(value: string[]) { + this.__columnsToDisplay = value; + this.__columnsToDisplayWithExpand = [...value, 'expand']; + } + @Input() public set data(value: any[]) { + this.dataSource.data = value; + } + @Input() schema: Record any; }> = {} + + @Input() paginatorSizeOptions?: number[]; + + @Input() filter: { label: string, placeholder: string } = { label: '', placeholder: '' } + + @Input() isFilterable: boolean = false; + + @Input() isExpandable: boolean = false; + + @Input() isSortable: boolean = false; + + @Input() onToggleExpandedRow: (element: any, event: Event) => void = (element: any, event: Event) => { } + + public get data(): any[] { + return this.dataSource.data; + } + __columnsToDisplay: string[] = []; + __columnsToDisplayWithExpand: string[] = []; + __expandedElement!: any; + + config: ConfigurationService = inject(ConfigurationService); + + @ViewChild(MatSort) sort!: MatSort; + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatTable) table!: MatTable; + + ngAfterViewInit(): void { + if (this.isSortable) + this.dataSource.sort = this.sort; + if (this.paginatorSizeOptions && this.paginatorSizeOptions.length > 0) + this.dataSource.paginator = this.paginator; + } + + applyFilter(event: Event) { + const filterValue = (event.target as HTMLInputElement).value; + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + update() { + this.table.renderRows(); + } + + toggleExpandedRow(element: any, event: Event): void { + // first determine the new expanded element, thus it would be possible to use up-to-date + const newExpandedElement = this.__expandedElement === element ? null : element; + + // before update the expanded element call the call-back method to show up-to-date component + this.onToggleExpandedRow(newExpandedElement, event); + + // assign expanded element + this.__expandedElement = newExpandedElement; + event.stopPropagation(); + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.html b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.html index f3e30cc5..331a5270 100644 --- a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.html +++ b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.html @@ -1,39 +1,4 @@ - - Filter - - - - - - @for (colId of displayedColumns; track colId) { - - - - - } - - - - - - - - - - - - - -
{{schema[colId].header}} {{schema[colId].field(element)}}   - -
- \ No newline at end of file + + \ No newline at end of file diff --git a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.scss b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.scss index 25f0e5a8..1272cd53 100644 --- a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.scss +++ b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.scss @@ -7,4 +7,53 @@ table { font-size: 14px; width: 100%; margin-top: 10px; +} + +/* For expanding table */ +table { + width: 100%; +} + +tr.example-detail-row { + height: 0; +} + +tr.example-element-row:not(.example-expanded-row):hover { + background: whitesmoke; +} + +tr.example-element-row:not(.example-expanded-row):active { + background: #efefef; +} + +.example-element-row td { + border-bottom-width: 0; +} + +.example-element-detail { + overflow: hidden; + display: flex; +} + +.example-element-diagram { + min-width: 80px; + border: 2px solid black; + padding: 8px; + font-weight: lighter; + margin: 8px 0; + height: 104px; +} + +.example-element-symbol { + font-weight: bold; + font-size: 40px; + line-height: normal; +} + +.example-element-description { + padding: 16px; +} + +.example-element-description-attribution { + opacity: 0.5; } \ No newline at end of file diff --git a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.ts b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.ts index 43a8d6bf..f32a0455 100644 --- a/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.ts +++ b/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/src/app/components/envelope-table/envelope-table.component.ts @@ -1,20 +1,14 @@ -import { AfterViewInit, Component, Input, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, Input, ViewChild, inject, viewChild } from '@angular/core'; import { EnvelopeReceiverService } from '../../services/envelope-receiver.service'; -import { MatTable, MatTableModule, MatTableDataSource } from '@angular/material/table'; -import { CommonModule } from '@angular/common' -import { MatIconModule } from '@angular/material/icon'; -import { MatButtonModule } from '@angular/material/button'; import { animate, state, style, transition, trigger } from '@angular/animations'; -import { MatInputModule } from '@angular/material/input'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator'; -import {MatSort, MatSortModule} from '@angular/material/sort'; import { ConfigurationService } from '../../services/configuration.service'; +import { DDTable } from "../dd-table/dd-table.component"; +import { ClearableInputComponent } from '../clearable-input/clearable-input.component' @Component({ selector: 'app-envelope-table', standalone: true, - imports: [MatTableModule, CommonModule, MatTableModule, MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatTableModule, MatPaginator, MatPaginatorModule, MatSort, MatSortModule], + imports: [DDTable, ClearableInputComponent], templateUrl: './envelope-table.component.html', animations: [ trigger('detailExpand', [ @@ -25,13 +19,13 @@ import { ConfigurationService } from '../../services/configuration.service'; ], styleUrl: './envelope-table.component.scss' }) -export class EnvelopeTableComponent implements AfterViewInit { +export class EnvelopeTableComponent implements AfterViewInit { @Input() options?: { min_status?: number; max_status?: number; ignore_status?: number[] } - @Input() displayedColumns: string[] = ['title', 'status', 'type', 'privateMessage', 'addedWhen']; + displayedColumns: string[] = ['title', 'status', 'type', 'privateMessage', 'addedWhen']; - @Input() schema: Record any; }> = { + schema: Record any; }> = { 'title': { header: 'Title', field: (element: any) => element.envelope.title @@ -51,50 +45,20 @@ export class EnvelopeTableComponent implements AfterViewInit { 'addedWhen': { header: 'Added When', field: (element: any) => element.addedWhen - }, + } } - columnsToDisplayWithExpand = [...this.displayedColumns, 'expand']; - - expandedElement: any | null; + data: any[] = []; - @ViewChild(MatTable) table!: MatTable; + @ViewChild(ClearableInputComponent) input!: ClearableInputComponent - @ViewChild(MatPaginator) paginator!: MatPaginator; + onToggleExpandedRow(element: any, event: Event) { } - @ViewChild(MatSort) sort!: MatSort; + private erService: EnvelopeReceiverService = inject(EnvelopeReceiverService); - dataSource = new MatTableDataSource(); - - constructor(private erService: EnvelopeReceiverService, private config: ConfigurationService) { - this.dataSource.filterPredicate = (data: any, filter: string): boolean => { - const dataStr = Object.values(data as { [key: string]: any }).reduce((currentTerm, key) => { - return currentTerm + (key ? key.toString().toLowerCase() : ''); - }, '').trim().toLowerCase(); - return dataStr.indexOf(filter) !== -1; - }; - } + private config: ConfigurationService = inject(ConfigurationService); async ngAfterViewInit() { - this.dataSource.paginator = this.paginator; - this.dataSource.data = await this.erService.getEnvelopeReceiverAsync(this.options); - this.dataSource.sort = this.sort; - } - - public updateTable() { - this.table.renderRows(); - } - - isExpandedRow(index: number, row: any): boolean { - return (row?.envelopeId === this.expandedElement?.envelopeId) && (row?.receiverId === this.expandedElement?.receiverId); - } - - applyFilter(event: Event) { - const filterValue = (event.target as HTMLInputElement).value; - this.dataSource.filter = filterValue.trim().toLowerCase(); - - if (this.dataSource.paginator) { - this.dataSource.paginator.firstPage(); - } + this.data = await this.erService.getEnvelopeReceiverAsync(this.options); } } \ No newline at end of file