From 468b3e839c0f4b204be51ff1c8d3c109632952f7 Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Wed, 9 Mar 2022 18:38:28 -0800 Subject: [PATCH 1/4] feat: adding checkbox to header for multi selection --- .../src/checkbox/checkbox.component.scss | 4 ++ .../src/checkbox/checkbox.component.ts | 10 ++- .../src/table/data/table-cdk-data-source.ts | 4 ++ .../table-header-cell-renderer.component.scss | 50 ++++++--------- .../table-header-cell-renderer.component.ts | 62 ++++++++++++++++--- .../components/src/table/table.component.scss | 4 +- .../components/src/table/table.component.ts | 20 ++++++ 7 files changed, 112 insertions(+), 42 deletions(-) diff --git a/projects/components/src/checkbox/checkbox.component.scss b/projects/components/src/checkbox/checkbox.component.scss index aa0b639be..d19b23419 100644 --- a/projects/components/src/checkbox/checkbox.component.scss +++ b/projects/components/src/checkbox/checkbox.component.scss @@ -4,6 +4,10 @@ ::ng-deep .mat-checkbox-checked .mat-checkbox-background, .mat-checkbox-indeterminate { background-color: $blue-4; + + .mat-checkbox-mixedmark { + background-color: white; + } } ::ng-deep .mat-checkbox-disabled { diff --git a/projects/components/src/checkbox/checkbox.component.ts b/projects/components/src/checkbox/checkbox.component.ts index ad6dbcac4..2520bb85c 100644 --- a/projects/components/src/checkbox/checkbox.component.ts +++ b/projects/components/src/checkbox/checkbox.component.ts @@ -11,6 +11,7 @@ import { MatCheckboxChange } from '@angular/material/checkbox'; labelPosition="after" [checked]="this.isChecked" [disabled]="this.isDisabled" + [(indeterminate)]="this.indeterminate" (change)="this.onCheckboxChange($event)" class="ht-checkbox" [ngClass]="{ disabled: this.isDisabled }" @@ -32,13 +33,16 @@ export class CheckboxComponent implements ControlValueAccessor { @Input() public set checked(checked: boolean | undefined) { - this.isChecked = checked ?? false; + this.isChecked = checked; } - public get checked(): boolean { + public get checked(): boolean | undefined { return this.isChecked; } + @Input() + public indeterminate?: boolean; + @Input() public set disabled(disabled: boolean | undefined) { this.isDisabled = disabled ?? false; @@ -51,7 +55,7 @@ export class CheckboxComponent implements ControlValueAccessor { @Output() public readonly checkedChange: EventEmitter = new EventEmitter(); - public isChecked: boolean = false; + public isChecked?: boolean = false; public isDisabled: boolean = false; private onTouched?: () => void; diff --git a/projects/components/src/table/data/table-cdk-data-source.ts b/projects/components/src/table/data/table-cdk-data-source.ts index cd82abbf4..3163fb4af 100644 --- a/projects/components/src/table/data/table-cdk-data-source.ts +++ b/projects/components/src/table/data/table-cdk-data-source.ts @@ -154,6 +154,10 @@ export class TableCdkDataSource implements DataSource { this.rowsChange$.next(TableCdkRowUtil.mergeRowStates(this.cachedRows, unselectedRows)); } + public getAllRows(): StatefulTableRow[] { + return this.cachedRows; + } + /**************************** * Change Detection ****************************/ diff --git a/projects/components/src/table/header/table-header-cell-renderer.component.scss b/projects/components/src/table/header/table-header-cell-renderer.component.scss index a0e4656dd..d3b1c6b8c 100644 --- a/projects/components/src/table/header/table-header-cell-renderer.component.scss +++ b/projects/components/src/table/header/table-header-cell-renderer.component.scss @@ -1,13 +1,11 @@ @import 'mixins'; -:host { - width: 100%; // Sorry... -} - .table-header-cell-renderer { @include ellipsis-overflow(); - @include overline($gray-5); display: flex; + align-items: center; + width: 100%; + height: 32px; &.sortable { cursor: pointer; @@ -17,18 +15,6 @@ color: $gray-9; } - &.left { - text-align: left; - } - - &.center { - text-align: center; - } - - &.right { - text-align: right; - } - .options-button { display: none; color: $gray-7; @@ -46,29 +32,33 @@ } .title { + @include overline($gray-5); + min-width: 0; - width: 100%; + flex: 1 1 auto; + display: flex; - &.asc, - &.desc { - color: $gray-9; + &.left { + justify-content: flex-start; } - &:after { - display: inline-block; + &.center { + justify-content: center; } - &.desc:after { - content: '▼'; - font-size: 9px; + &.right { + justify-content: flex-end; } - &.asc:after { - content: '▼'; - font-size: 10px; - transform: scale(1, -1) translateY(1.5px); // That's right! Half pixels! + .sort-icon { + margin-left: 4px; + color: $gray-9; } } + + .state-checkbox { + margin-left: 12px; + } } .popover-content { diff --git a/projects/components/src/table/header/table-header-cell-renderer.component.ts b/projects/components/src/table/header/table-header-cell-renderer.component.ts index 90db5ec34..9104867d9 100644 --- a/projects/components/src/table/header/table-header-cell-renderer.component.ts +++ b/projects/components/src/table/header/table-header-cell-renderer.component.ts @@ -31,18 +31,42 @@ import { TableColumnConfigExtended } from '../table.service'; template: `
- - - -
{{ this.columnConfig.title }}
- - + + + + +
+ {{ this.columnConfig.title }} + + + +
+ + + +
+ + + + + +
@@ -104,12 +128,18 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges { @Input() public sort?: TableSortDirection; + @Input() + public indeterminateRowsSelected?: boolean; + @Output() public readonly sortChange: EventEmitter = new EventEmitter(); @Output() public readonly columnsChange: EventEmitter = new EventEmitter(); + @Output() + public readonly allRowsSelectionChange: EventEmitter = new EventEmitter(); + public alignment?: TableCellAlignmentType; public leftAlignFilterButton: boolean = false; public classes: string[] = []; @@ -117,6 +147,9 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges { public isFilterable: boolean = false; public isEditableAvailableColumns: boolean = false; public isShowOptionButton: boolean = false; + public isStateColumn: boolean = false; + public isMultipleSelectionStateColumn: boolean = false; + private allRowsSelected: boolean = false; @ViewChild('htmlTooltip') public htmlTooltipTemplate?: TemplateRef; @@ -137,6 +170,8 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges { this.isEditableAvailableColumns = this.areAnyAvailableColumnsEditable(); this.isShowOptionButton = this.isFilterable || this.isEditableAvailableColumns || this.columnConfig?.sortable === true; + this.isStateColumn = this.columnConfig?.id === '$$selected' || this.columnConfig?.id === '$$expanded'; + this.isMultipleSelectionStateColumn = this.columnConfig?.id === '$$selected'; } } @@ -155,6 +190,19 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges { this.classes = this.buildClasses(); } + public onToggleAllSelectedChange(allSelected: boolean): void { + this.allRowsSelected = allSelected; + this.allRowsSelectionChange.emit(allSelected); + } + + public getHeaderCheckboxTooltip(): string { + return this.indeterminateRowsSelected + ? 'Some rows are selected' + : this.allRowsSelected + ? 'All rows in the current page are selected' + : 'Select all rows in the current page'; + } + private buildClasses(): string[] { return [ ...(this.alignment !== undefined ? [this.alignment.toLowerCase()] : []), diff --git a/projects/components/src/table/table.component.scss b/projects/components/src/table/table.component.scss index 90de96246..509778166 100644 --- a/projects/components/src/table/table.component.scss +++ b/projects/components/src/table/table.component.scss @@ -71,11 +71,11 @@ $header-height: 32px; .header-cell-renderer { width: 100%; - padding: 10px 12px 10px 7px; + // padding: 10px 12px 10px 7px; } .header-cell-renderer:first-child { - padding-left: 12px; + // padding-left: 12px; } .header-column-resize-handle { diff --git a/projects/components/src/table/table.component.ts b/projects/components/src/table/table.component.ts index 77c528365..8b01b6948 100644 --- a/projects/components/src/table/table.component.ts +++ b/projects/components/src/table/table.component.ts @@ -94,8 +94,10 @@ import { TableColumnConfigExtended, TableService } from './table.service'; [availableColumns]="this.columnConfigs$ | async" [index]="index" [sort]="columnDef.sort" + [indeterminateRowsSelected]="this.indeterminateRowsSelected" (sortChange)="this.onSortChange($event, columnDef)" (columnsChange)="this.onColumnsEdit($event)" + (allRowsSelectionChange)="this.onHeaderAllRowsSelectionChange($event)" > @@ -380,6 +382,7 @@ export class TableComponent private resizeHeaderOffsetLeft: number = 0; private resizeStartX: number = 0; private resizeColumns?: ResizeColumns; + public indeterminateRowsSelected?: boolean; public constructor( private readonly elementRef: ElementRef, @@ -564,6 +567,22 @@ export class TableComponent this.columnConfigsChange.emit(columnConfigs); } + public onHeaderAllRowsSelectionChange(allRowsSelected: boolean): void { + if (this.hasMultiSelect()) { + if (allRowsSelected) { + this.dataSource?.selectAllRows(); + this.selections = this.dataSource?.getAllRows(); + } else { + this.dataSource?.unselectAllRows(); + this.selections = []; + } + + this.selectionsChange.emit(this.selections); + this.indeterminateRowsSelected = false; + this.changeDetector.markForCheck(); + } + } + public onDataCellClick(row: StatefulTableRow): void { // NOTE: Cell Renderers generally handle their own clicks. We should only perform table actions here. // Propagate the cell click to the row @@ -654,6 +673,7 @@ export class TableComponent this.selections = [toggledRow]; } this.selectionsChange.emit(this.selections); + this.indeterminateRowsSelected = this.selections?.length !== this.dataSource?.getAllRows().length; this.changeDetector.markForCheck(); } From 6b7af238d166cef7fbcdf969dd96286ee8fa4faf Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Wed, 9 Mar 2022 19:04:20 -0800 Subject: [PATCH 2/4] refactor: fix tests --- .../table/header/table-header-cell-renderer.component.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/components/src/table/header/table-header-cell-renderer.component.test.ts b/projects/components/src/table/header/table-header-cell-renderer.component.test.ts index 9396fc096..900a9d775 100644 --- a/projects/components/src/table/header/table-header-cell-renderer.component.test.ts +++ b/projects/components/src/table/header/table-header-cell-renderer.component.test.ts @@ -35,7 +35,7 @@ describe('Table Header Cell Renderer', () => { } }); - expect(spectator.query('.table-header-cell-renderer')).toHaveClass('sortable'); + expect(spectator.query('.table-header-cell-renderer > .title')).toHaveClass('sortable'); }); test('should not have sortable class, if column cannot be sorted', () => { @@ -53,7 +53,7 @@ describe('Table Header Cell Renderer', () => { } }); - expect(spectator.query('.table-header-cell-renderer')).not.toHaveClass('sortable'); + expect(spectator.query('.table-header-cell-renderer > .title')).not.toHaveClass('sortable'); }); test('should sort column when header title is clicked', fakeAsync(() => { From ba79f28aa230d445a70c93b6f9f720db05de08b9 Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Wed, 9 Mar 2022 19:14:38 -0800 Subject: [PATCH 3/4] refactor: revert checkbox change --- projects/components/src/checkbox/checkbox.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/components/src/checkbox/checkbox.component.ts b/projects/components/src/checkbox/checkbox.component.ts index 2520bb85c..6d45196bc 100644 --- a/projects/components/src/checkbox/checkbox.component.ts +++ b/projects/components/src/checkbox/checkbox.component.ts @@ -33,7 +33,7 @@ export class CheckboxComponent implements ControlValueAccessor { @Input() public set checked(checked: boolean | undefined) { - this.isChecked = checked; + this.isChecked = checked ?? false; } public get checked(): boolean | undefined { @@ -55,7 +55,7 @@ export class CheckboxComponent implements ControlValueAccessor { @Output() public readonly checkedChange: EventEmitter = new EventEmitter(); - public isChecked?: boolean = false; + public isChecked: boolean = false; public isDisabled: boolean = false; private onTouched?: () => void; From f5793ceafee54d29a6ed6c0d9696acda813067f9 Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Thu, 10 Mar 2022 12:16:40 -0800 Subject: [PATCH 4/4] refactor: addressing review comments --- .../src/table/header/table-header-cell-renderer.component.ts | 2 +- projects/components/src/table/table.component.scss | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/projects/components/src/table/header/table-header-cell-renderer.component.ts b/projects/components/src/table/header/table-header-cell-renderer.component.ts index 9104867d9..3a321a311 100644 --- a/projects/components/src/table/header/table-header-cell-renderer.component.ts +++ b/projects/components/src/table/header/table-header-cell-renderer.component.ts @@ -200,7 +200,7 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges { ? 'Some rows are selected' : this.allRowsSelected ? 'All rows in the current page are selected' - : 'Select all rows in the current page'; + : 'None of the rows in the current page are selected'; } private buildClasses(): string[] { diff --git a/projects/components/src/table/table.component.scss b/projects/components/src/table/table.component.scss index 509778166..de3fb8ce6 100644 --- a/projects/components/src/table/table.component.scss +++ b/projects/components/src/table/table.component.scss @@ -71,11 +71,6 @@ $header-height: 32px; .header-cell-renderer { width: 100%; - // padding: 10px 12px 10px 7px; - } - - .header-cell-renderer:first-child { - // padding-left: 12px; } .header-column-resize-handle {