Skip to content

Commit 2ff0a17

Browse files
authored
feat: table add/remove columns (#333)
* feat: table add/remove columns * fix: some clean up * fix: pr comments and tests * fix: linting and tests * fix: pr comments * fix: sort * fix: json columns are always editable
1 parent 09f2234 commit 2ff0a17

File tree

35 files changed

+713
-122
lines changed

35 files changed

+713
-122
lines changed

projects/components/src/checkbox/checkbox.component.scss

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@
22

33
:host {
44
::ng-deep .mat-checkbox-checked .mat-checkbox-background,
5-
.mat-checkbox-indeterminate .mat-checkbox-background {
5+
.mat-checkbox-indeterminate {
66
background-color: $blue-5;
77
}
88

9+
::ng-deep .mat-checkbox-disabled {
10+
.mat-checkbox-frame {
11+
opacity: 0.4;
12+
}
13+
14+
&.mat-checkbox-checked .mat-checkbox-background,
15+
.mat-checkbox-indeterminate {
16+
opacity: 0.4;
17+
cursor: not-allowed;
18+
}
19+
}
20+
921
::ng-deep .mat-checkbox:not(.mat-checkbox-disabled) .mat-checkbox-ripple .mat-ripple-element {
1022
background-color: $blue-5;
1123
}

projects/components/src/filtering/filter/filter-attribute.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ export interface FilterAttribute {
55
displayName: string;
66
units?: string;
77
type: FilterAttributeType;
8+
onlySupportsAggregation?: boolean;
9+
onlySupportsGrouping?: boolean;
810
}

projects/components/src/modal/modal-container.component.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
}
1818

1919
&.modal-size-medium {
20+
height: 456px;
21+
width: 530px;
22+
}
23+
24+
&.modal-size-large {
2025
height: 720px;
2126
width: 640px;
2227
}

projects/components/src/modal/modal.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export interface ModalConfig<TData = unknown> {
1111

1212
export const enum ModalSize {
1313
Small = 'small',
14-
Medium = 'medium'
14+
Medium = 'medium',
15+
Large = 'large'
1516
}
1617

1718
export const MODAL_DATA = new InjectionToken<unknown>('MODAL_DATA');
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
@import 'mixins';
2+
3+
.edit-modal {
4+
width: 100%;
5+
height: 100%;
6+
7+
display: flex;
8+
flex-direction: column;
9+
justify-content: space-between;
10+
11+
padding-left: 22px;
12+
padding-right: 22px;
13+
padding-bottom: 6px;
14+
15+
.column-items {
16+
height: 100%;
17+
overflow: auto;
18+
19+
.column-item {
20+
height: 36px;
21+
padding: 2px 8px;
22+
cursor: pointer;
23+
24+
display: flex;
25+
align-items: center;
26+
27+
&:hover {
28+
background: $gray-1;
29+
}
30+
}
31+
}
32+
33+
.controls {
34+
display: flex;
35+
border-top: 1px solid $gray-2;
36+
padding-top: 24px;
37+
justify-content: space-between;
38+
align-items: center;
39+
}
40+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
2+
import { ButtonRole } from '../../button/button';
3+
import { ModalRef, MODAL_DATA } from '../../modal/modal';
4+
import { TableColumnConfigExtended } from '../table.service';
5+
6+
@Component({
7+
selector: 'ht-edit-columns-modal',
8+
styleUrls: ['./table-edit-columns-modal.component.scss'],
9+
changeDetection: ChangeDetectionStrategy.OnPush,
10+
template: `
11+
<div class="edit-modal">
12+
<div class="column-items">
13+
<ng-container *ngFor="let column of this.editColumns">
14+
<div class="column-item">
15+
<ht-checkbox
16+
[label]="column.title"
17+
[htTooltip]="column.titleTooltip"
18+
[checked]="column.visible"
19+
[disabled]="!column.editable"
20+
(checkedChange)="column.visible = $event"
21+
></ht-checkbox>
22+
</div>
23+
</ng-container>
24+
</div>
25+
26+
<div class="controls">
27+
<ht-button
28+
label="Cancel"
29+
class="cancel-button"
30+
role="${ButtonRole.Tertiary}"
31+
(click)="this.onCancel()"
32+
></ht-button>
33+
<ht-button
34+
label="Apply"
35+
class="action-button"
36+
role="${ButtonRole.Additive}"
37+
(click)="this.onApply()"
38+
></ht-button>
39+
</div>
40+
</div>
41+
`
42+
})
43+
export class TableEditColumnsModalComponent {
44+
public readonly editColumns: TableColumnConfigExtended[];
45+
46+
public constructor(
47+
private readonly modalRef: ModalRef<TableColumnConfigExtended[]>,
48+
@Inject(MODAL_DATA) public readonly modalData: TableColumnConfigExtended[]
49+
) {
50+
this.editColumns = this.modalData
51+
.filter(column => (column.attribute?.type as string) !== '$$state')
52+
.sort((a, b) => (a.visible === b.visible ? 0 : a.visible ? -1 : 1));
53+
}
54+
55+
public onApply(): void {
56+
this.modalRef.close(this.editColumns); // $$state columns filtered out, but they are recreated by table
57+
}
58+
59+
public onCancel(): void {
60+
this.modalRef.close();
61+
}
62+
}

projects/components/src/table/header/table-header-cell-renderer.component.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IconSize } from '../../icon/icon-size';
99
import { ModalSize } from '../../modal/modal';
1010
import { ModalService } from '../../modal/modal.service';
1111
import { TableCellAlignmentType } from '../cells/types/table-cell-alignment-type';
12+
import { TableEditColumnsModalComponent } from '../columns/table-edit-columns-modal.component';
1213
import { TableCdkColumnUtil } from '../data/table-cdk-column-util';
1314
import { TableSortDirection } from '../table-api';
1415
import { TableColumnConfigExtended } from '../table.service';
@@ -24,11 +25,11 @@ import { TableColumnConfigExtended } from '../table.service';
2425
[htTooltip]="this.columnConfig.titleTooltip || this.columnConfig.title"
2526
class="table-header-cell-renderer"
2627
>
27-
<ng-container *ngIf="this.columnConfig?.filterable && this.leftAlignFilterButton">
28+
<ng-container *ngIf="this.leftAlignFilterButton">
2829
<ng-container *ngTemplateOutlet="optionsButton"></ng-container>
2930
</ng-container>
3031
<div class="title" [ngClass]="this.classes" (click)="this.onSortChange()">{{ this.columnConfig.title }}</div>
31-
<ng-container *ngIf="this.columnConfig?.filterable && !this.leftAlignFilterButton">
32+
<ng-container *ngIf="!this.leftAlignFilterButton">
3233
<ng-container *ngTemplateOutlet="optionsButton"></ng-container>
3334
</ng-container>
3435
@@ -51,6 +52,8 @@ import { TableColumnConfigExtended } from '../table.service';
5152
Sort Descending
5253
<ht-icon class="popover-item-icon" icon="${IconType.ArrowDown}" size="${IconSize.Small}"></ht-icon>
5354
</div>
55+
<div class="popover-item-divider" *ngIf="this.editable"></div>
56+
<div class="popover-item" (click)="this.onEditColumns()" *ngIf="this.editable">Edit Columns</div>
5457
</div>
5558
</ht-popover-content>
5659
</ht-popover>
@@ -62,9 +65,15 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges {
6265
public readonly SORT_ASC: TableSortDirection = TableSortDirection.Ascending;
6366
public readonly SORT_DESC: TableSortDirection = TableSortDirection.Descending;
6467

68+
@Input()
69+
public editable?: boolean = false;
70+
6571
@Input()
6672
public metadata?: FilterAttribute[];
6773

74+
@Input()
75+
public availableColumns?: TableColumnConfigExtended[] = [];
76+
6877
@Input()
6978
public columnConfig?: TableColumnConfigExtended;
7079

@@ -77,6 +86,9 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges {
7786
@Output()
7887
public readonly sortChange: EventEmitter<TableSortDirection | undefined> = new EventEmitter();
7988

89+
@Output()
90+
public readonly columnsChange: EventEmitter<TableColumnConfigExtended[]> = new EventEmitter();
91+
8092
public alignment?: TableCellAlignmentType;
8193
public leftAlignFilterButton: boolean = false;
8294
public classes: string[] = [];
@@ -139,17 +151,31 @@ export class TableHeaderCellRendererComponent implements OnInit, OnChanges {
139151
this.isFilterable &&
140152
this.modalService.createModal<InFilterModalData>({
141153
content: InFilterModalComponent,
142-
size: ModalSize.Small,
154+
size: ModalSize.Medium,
143155
showControls: true,
144156
title: 'Filter Column',
145157
data: {
146-
metadata: this.metadata!,
158+
metadata: this.metadata || [],
147159
attribute: this.columnConfig?.attribute!,
148160
values: this.columnConfig?.filterValues ?? []
149161
}
150162
});
151163
}
152164

165+
public onEditColumns(): void {
166+
this.modalService
167+
.createModal<TableColumnConfigExtended[], TableColumnConfigExtended[]>({
168+
content: TableEditColumnsModalComponent,
169+
size: ModalSize.Medium,
170+
showControls: true,
171+
title: 'Edit Columns',
172+
data: this.availableColumns ?? []
173+
})
174+
.closed$.subscribe(columnConfigs => {
175+
this.columnsChange.emit(columnConfigs);
176+
});
177+
}
178+
153179
private getNextSortDirection(sortDirection?: TableSortDirection): TableSortDirection | undefined {
154180
// Order: undefined -> Ascending -> Descending -> undefined
155181
switch (sortDirection) {

projects/components/src/table/table-api.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ export interface TableColumnConfig {
1111
title?: string;
1212
titleTooltip?: string;
1313
sort?: TableSortDirection;
14-
visible?: boolean;
15-
sortable?: boolean;
16-
filterable?: boolean;
14+
visible?: boolean; // Is the column shown
15+
editable?: boolean; // Can the column be added/removed
16+
sortable?: boolean; // Can the column be sorted
17+
filterable?: boolean; // Can the column be filtered
1718
alignment?: TableCellAlignmentType;
1819
width?: number | string;
1920
onClick?(row: TableRow, column: TableColumnConfig): void;

projects/components/src/table/table.component.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,17 @@ describe('Table component', () => {
573573
spectator.dispatchMouseEvent('cdk-table', 'mousemove', 1, 0);
574574
spectator.dispatchMouseEvent('cdk-table', 'mouseup');
575575

576-
expect(spectator.component.columnConfigsSubject.value[0].width).toBe('101px');
577-
expect(spectator.component.columnConfigsSubject.value[1].width).toBe('199px');
576+
runFakeRxjs(({ expectObservable }) => {
577+
expectObservable(spectator.component.columnConfigs$).toBe('x', {
578+
x: [
579+
expect.objectContaining({
580+
width: '101px'
581+
}),
582+
expect.objectContaining({
583+
width: '199px'
584+
})
585+
]
586+
});
587+
});
578588
}));
579589
});

0 commit comments

Comments
 (0)