From 90970014bf676f4abdb795fc316945896174bf8a Mon Sep 17 00:00:00 2001 From: Bastian Jakobi <55296998+bastianjakobi@users.noreply.github.com> Date: Fri, 20 Sep 2024 08:49:10 +0200 Subject: [PATCH] feat: allow hiding of selectAll checkbox & fix initial selection of disabled table rows (#479) * refactor: dynamically hide selectAll & add selectionEnabledField input to parents of ocx-data-table * feat: allow initial specification of selected + disabled row * fix: fix linter errors * refactor: split selection change handler into multiple methods --------- Co-authored-by: SchettlerKoehler <62466958+SchettlerKoehler@users.noreply.github.com> --- .../lib/angular-accelerator-primeng.module.ts | 3 + .../data-table/data-table.component.html | 18 ++- .../data-table.component.stories.ts | 15 +- .../data-table/data-table.component.ts | 129 ++++++++++++------ .../data-view/data-view.component.html | 2 + .../data-view/data-view.component.ts | 2 + .../interactive-data-view.component.html | 2 + .../interactive-data-view.component.ts | 2 + 8 files changed, 128 insertions(+), 45 deletions(-) diff --git a/libs/angular-accelerator/src/lib/angular-accelerator-primeng.module.ts b/libs/angular-accelerator/src/lib/angular-accelerator-primeng.module.ts index bb93c724..e2f5cedf 100644 --- a/libs/angular-accelerator/src/lib/angular-accelerator-primeng.module.ts +++ b/libs/angular-accelerator/src/lib/angular-accelerator-primeng.module.ts @@ -13,10 +13,12 @@ import { BreadcrumbModule } from 'primeng/breadcrumb' import { SkeletonModule } from 'primeng/skeleton' import { MessageModule } from 'primeng/message' import { SharedModule } from 'primeng/api' +import { CheckboxModule } from 'primeng/checkbox' @NgModule({ imports: [ BreadcrumbModule, + CheckboxModule, DropdownModule, ButtonModule, DialogModule, @@ -33,6 +35,7 @@ import { SharedModule } from 'primeng/api' ], exports: [ BreadcrumbModule, + CheckboxModule, DropdownModule, ButtonModule, DialogModule, diff --git a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.html b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.html index 339ac101..13e42ca1 100644 --- a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.html +++ b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.html @@ -124,7 +124,7 @@ - + @@ -194,10 +194,18 @@ - + + + + diff --git a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.stories.ts b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.stories.ts index 7bbd8805..a4eca429 100644 --- a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.stories.ts +++ b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.stories.ts @@ -14,6 +14,8 @@ import { HAS_PERMISSION_CHECKER, IfPermissionDirective } from '../../directives/ import { ColumnType } from '../../model/column-type.model' import { MenuModule } from 'primeng/menu' import { DynamicLocaleId } from '../../utils/dynamic-locale-id' +import { CheckboxModule } from 'primeng/checkbox' +import { FormsModule } from '@angular/forms' type DataTableInputTypes = Pick @@ -36,7 +38,7 @@ const DataTableComponentSBConfig: Meta = { }), moduleMetadata({ declarations: [DataTableComponent, IfPermissionDirective], - imports: [TableModule, ButtonModule, MultiSelectModule, StorybookTranslateModule, MockAuthModule, MenuModule], + imports: [TableModule, ButtonModule, MultiSelectModule, StorybookTranslateModule, MockAuthModule, MenuModule, CheckboxModule, FormsModule], }), ], } @@ -52,6 +54,7 @@ const dataTableActionsArgTypes = { const dataTableSelectionArgTypes = { selectionChanged: { action: 'selectionChanged' }, + componentStateChanged: { action: 'componentStateChanged' }, } const defaultComponentArgs: DataTableInputTypes = { @@ -142,6 +145,16 @@ export const WithRowSelectionAndDefaultSelection = { }, } +export const WithRowSelectionAndDisabledDefaultSelection = { + argTypes: dataTableSelectionArgTypes, + render: Template, + args: { + ...defaultComponentArgs, + selectedRows: [1], + selectionEnabledField: 'available' + }, +} + const extendedComponentArgs: DataTableInputTypes = { columns: [ { diff --git a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.ts b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.ts index eb1e12df..3fdad604 100644 --- a/libs/angular-accelerator/src/lib/components/data-table/data-table.component.ts +++ b/libs/angular-accelerator/src/lib/components/data-table/data-table.component.ts @@ -20,7 +20,17 @@ import { isValidDate } from '@onecx/accelerator' import { UserService } from '@onecx/angular-integration-interface' import { MenuItem, PrimeTemplate, SelectItem } from 'primeng/api' import { Menu } from 'primeng/menu' -import { BehaviorSubject, Observable, combineLatest, debounceTime, first, map, mergeMap, of } from 'rxjs' +import { + BehaviorSubject, + Observable, + combineLatest, + debounceTime, + first, + map, + mergeMap, + of, + withLatestFrom, +} from 'rxjs' import { ColumnType } from '../../model/column-type.model' import { DataAction } from '../../model/data-action' import { DataSortDirection } from '../../model/data-sort-direction' @@ -64,7 +74,7 @@ export interface DataTableComponentState { }) export class DataTableComponent extends DataSortBase implements OnInit, AfterContentInit { TemplateType = TemplateType - + checked = true _rows$ = new BehaviorSubject([]) @Input() get rows(): Row[] { @@ -74,14 +84,19 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon !this._rows$.getValue().length ?? this.resetPage() this._rows$.next(value) } - _selection$ = new BehaviorSubject([]) + _selectionIds$ = new BehaviorSubject<(string | number)[]>([]) @Input() - get selectedRows(): Row[] { - return this._selection$.getValue() - } - set selectedRows(value: Row[]) { - this._selection$.next(value) + set selectedRows(value: Row[] | string[] | number[]) { + this._selectionIds$.next( + value.map((row) => { + if (typeof row === 'object') { + return row.id + } + return row + }) + ) } + _filters$ = new BehaviorSubject([]) @Input() get filters(): Filter[] { @@ -109,7 +124,7 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon } columnTemplates$: Observable | null>> | undefined _columns$ = new BehaviorSubject([]) - @Input() + @Input() get columns(): DataTableColumn[] { return this._columns$.getValue() } @@ -117,7 +132,7 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon this._columns$.next(value) const obs = value.map((c) => this.getTemplate(c, TemplateType.CELL)) this.columnTemplates$ = combineLatest(obs).pipe( - map(values => Object.fromEntries(value.map((c, i) => [c.id, values[i]]))) + map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]]))) ) } @Input() clientSideFiltering = true @@ -155,6 +170,7 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon @Input() editActionVisibleField: string | undefined @Input() editActionEnabledField: string | undefined @Input() selectionEnabledField: string | undefined + @Input() allowSelectAll = true @Input() paginator = true @Input() page = 0 @Input() @@ -187,13 +203,13 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon return this.numberCellTemplate || this.numberCellChildTemplate } - /** + /** * @deprecated Will be removed and instead to change the template of a specific column * use the new approach instead by following the naming convention column id + IdCell * e.g. for a column with the id 'status' use pTemplate="statusIdCell" */ @Input() customCellTemplate: TemplateRef | undefined - /** + /** * @deprecated Will be removed and instead to change the template of a specific column * use the new approach instead by following the naming convention column id + IdCell * e.g. for a column with the id 'status' use pTemplate="statusIdCell" @@ -448,20 +464,21 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon } emitComponentStateChanged(state: DataTableComponentState = {}) { - combineLatest([this.displayedPageSize$, this._selection$]).pipe(first()).subscribe(([pageSize, selectedRows]) => { - this.componentStateChanged.emit({ - filters: this.filters, - sorting: { - sortColumn: this.sortColumn, - sortDirection: this.sortDirection - }, - pageSize, - activePage: this.page, - selectedRows, - ...state - }) - }) - + this.displayedPageSize$ + .pipe(withLatestFrom(this._selectionIds$, this._rows$), first()) + .subscribe(([pageSize, selectedIds, rows]) => { + this.componentStateChanged.emit({ + filters: this.filters, + sorting: { + sortColumn: this.sortColumn, + sortDirection: this.sortDirection, + }, + pageSize, + activePage: this.page, + selectedRows: rows.filter((row) => selectedIds.includes(row.id)), + ...state, + }) + }) } ngAfterContentInit() { @@ -523,8 +540,8 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon this.emitComponentStateChanged({ sorting: { sortColumn: sortColumn, - sortDirection: newSortDirection - } + sortDirection: newSortDirection, + }, }) } @@ -564,7 +581,7 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon } this.filtered.emit(filters) this.emitComponentStateChanged({ - filters + filters, }) this.resetPage() } @@ -593,21 +610,55 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon } mapSelectionToRows() { - this.selectedRows$ = combineLatest([this._selection$, this._rows$]).pipe( - map(([selectedRows, rows]) => { - return selectedRows.map((row) => { - return rows.find((r) => r.id === row.id) + this.selectedRows$ = combineLatest([this._selectionIds$, this._rows$]).pipe( + map(([selectedRowIds, rows]) => { + return selectedRowIds.map((rowId) => { + return rows.find((r) => r.id === rowId) }) }) ) } - onSelectionChange(event: Row[]) { - this.selectedRows = event; - this.selectionChanged.emit(event) - this.emitComponentStateChanged({ - selectedRows: event + onSelectionChange(selection: Row[]) { + let newSelectionIds = selection.map((row) => row.id) + const rows = this._rows$.getValue() + + if (this.selectionEnabledField) { + const disabledRowIds = rows.filter((r) => !this.fieldIsTruthy(r, this.selectionEnabledField)).map((row) => row.id) + if (disabledRowIds.length > 0) { + newSelectionIds = this.mergeWithDisabledKeys(newSelectionIds, disabledRowIds) + } + } + + this._selectionIds$.next(newSelectionIds) + this.selectionChanged.emit(this._rows$.getValue().filter((row) => newSelectionIds.includes(row.id))) + this.emitComponentStateChanged() + } + + mergeWithDisabledKeys(newSelectionIds: (string | number)[], disabledRowIds: (string | number)[]) { + const previousSelectionIds = this._selectionIds$.getValue() + const previouslySelectedAndDisabled = previousSelectionIds.filter((id) => disabledRowIds.includes(id)) + const disabledAndPreviouslyDeselected = disabledRowIds.filter((id) => !previousSelectionIds.includes(id)) + const updatedSelection = [...newSelectionIds] + + previouslySelectedAndDisabled.forEach((id) => { + if (!updatedSelection.includes(id)) { + updatedSelection.push(id) + } + }) + + disabledAndPreviouslyDeselected.forEach((id) => { + const index = updatedSelection.indexOf(id) + if (index > -1) { + updatedSelection.splice(index, 1) + } }) + + return updatedSelection + } + + isSelected(row: Row) { + return this._selectionIds$.getValue().includes(row.id) } onPageChange(event: any) { @@ -618,7 +669,7 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon this.pageSizeChanged.emit(event.rows) this.emitComponentStateChanged({ activePage: page, - pageSize: event.rows + pageSize: event.rows, }) } diff --git a/libs/angular-accelerator/src/lib/components/data-view/data-view.component.html b/libs/angular-accelerator/src/lib/components/data-view/data-view.component.html index bea326f1..61868d5d 100644 --- a/libs/angular-accelerator/src/lib/components/data-view/data-view.component.html +++ b/libs/angular-accelerator/src/lib/components/data-view/data-view.component.html @@ -182,6 +182,8 @@ [currentPageShowingKey]="currentPageShowingKey" [currentPageShowingWithTotalOnServerKey]="currentPageShowingWithTotalOnServerKey" [parentTemplates]="templatesForChildren$ | async" + [allowSelectAll]="tableAllowSelectAll" + [selectionEnabledField]="tableSelectionEnabledField" > diff --git a/libs/angular-accelerator/src/lib/components/data-view/data-view.component.ts b/libs/angular-accelerator/src/lib/components/data-view/data-view.component.ts index 8bbb07d1..da45415e 100644 --- a/libs/angular-accelerator/src/lib/components/data-view/data-view.component.ts +++ b/libs/angular-accelerator/src/lib/components/data-view/data-view.component.ts @@ -63,6 +63,8 @@ export class DataViewComponent implements DoCheck, OnInit, AfterContentInit { @Input() viewActionEnabledField: string | undefined @Input() editActionVisibleField: string | undefined @Input() editActionEnabledField: string | undefined + @Input() tableSelectionEnabledField: string | undefined + @Input() tableAllowSelectAll = true @Input() data: RowListGridData[] = [] @Input() name = 'Data table' @Input() titleLineId: string | undefined diff --git a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.html b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.html index 1432dd9d..d8377188 100644 --- a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.html +++ b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.html @@ -113,6 +113,8 @@ [currentPageShowingWithTotalOnServerKey]="currentPageShowingWithTotalOnServerKey" (componentStateChanged)="dataViewComponentState$.next($event)" [parentTemplates]="templates$ | async" + [tableAllowSelectAll]="tableAllowSelectAll" + [tableSelectionEnabledField]="tableSelectionEnabledField" > diff --git a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.ts b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.ts index bd882acd..ee4b06e2 100644 --- a/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.ts +++ b/libs/angular-accelerator/src/lib/components/interactive-data-view/interactive-data-view.component.ts @@ -67,6 +67,8 @@ export class InteractiveDataViewComponent implements OnInit, AfterContentInit { @Input() viewActionEnabledField: string | undefined @Input() editActionVisibleField: string | undefined @Input() editActionEnabledField: string | undefined + @Input() tableSelectionEnabledField: string | undefined + @Input() tableAllowSelectAll = true @Input() name = 'Data' @Input() titleLineId: string | undefined @Input() subtitleLineIds: string[] = []