Skip to content

Commit 9ecc56c

Browse files
authored
feat: adding a custom control template option for table (#1189)
* feat: adding a custom control template option for table * refactor: adding interaction controls * refactor: updating tests
1 parent fa155e6 commit 9ecc56c

10 files changed

+138
-84
lines changed

projects/components/src/table/controls/table-controls.component.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import {
66
IterableDiffer,
77
IterableDiffers,
88
OnChanges,
9-
Output
9+
Output,
10+
TemplateRef
1011
} from '@angular/core';
1112
import { IconType } from '@hypertrace/assets-library';
1213
import { TypedSimpleChanges } from '@hypertrace/common';
1314
import { IconSize } from '../../icon/icon-size';
1415
import { MultiSelectJustify } from '../../multi-select/multi-select-justify';
1516
import { MultiSelectSearchMode, TriggerLabelDisplayMode } from '../../multi-select/multi-select.component';
1617
import { ToggleItem } from '../../toggle-group/toggle-item';
18+
import { StatefulTableRow } from '../table-api';
1719
import {
1820
TableCheckboxChange,
1921
TableCheckboxControl,
@@ -86,6 +88,13 @@ import {
8688
[activeItem]="this.activeViewItem"
8789
(activeItemChange)="this.onViewChange($event)"
8890
></ht-toggle-group>
91+
92+
<!-- Custom Control -->
93+
<ng-container *ngIf="this.customControlContent">
94+
<ng-container
95+
*ngTemplateOutlet="this.customControlContent; context: { selectedRows: this.selectedRows }"
96+
></ng-container>
97+
</ng-container>
8998
</div>
9099
</div>
91100
`
@@ -114,6 +123,12 @@ export class TableControlsComponent implements OnChanges {
114123
@Input()
115124
public activeViewItem?: ToggleItem;
116125

126+
@Input()
127+
public selectedRows?: StatefulTableRow[] = [];
128+
129+
@Input()
130+
public customControlContent?: TemplateRef<{ selectedRows?: StatefulTableRow[] }>;
131+
117132
@Output()
118133
public readonly searchChange: EventEmitter<string> = new EventEmitter<string>();
119134

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

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ describe('Table component', () => {
6060
queryParamMap: EMPTY
6161
}),
6262
mockProvider(DomElementMeasurerService, {
63-
measureHtmlElement: (): ClientRect => ({
63+
measureHtmlElement: (): Partial<ClientRect> => ({
6464
top: 0,
6565
left: 0,
6666
bottom: 20,
@@ -397,37 +397,6 @@ describe('Table component', () => {
397397
});
398398
}));
399399

400-
test('should trigger toggle row selection for multi row select config', () => {
401-
const columns = buildColumns();
402-
const spectator = createHost(
403-
`<ht-table [columnConfigs]="columnConfigs" [data]="data" [selectionMode]="selectionMode"
404-
[mode]="mode" (selectionsChange)="selectionsChange($event)"></ht-table>`,
405-
{
406-
hostProps: {
407-
columnConfigs: columns,
408-
data: buildData(),
409-
selectionMode: TableSelectionMode.Multiple,
410-
mode: TableMode.Flat
411-
}
412-
}
413-
);
414-
415-
const row: StatefulTableRow = {
416-
$$state: {
417-
parent: undefined,
418-
expanded: false,
419-
selected: false,
420-
root: false,
421-
leaf: true,
422-
depth: 1
423-
}
424-
};
425-
426-
const spyToggleRowSelection = spyOn(spectator.component, 'toggleRowSelected');
427-
spectator.component.onDataCellClick(row);
428-
expect(spyToggleRowSelection).toHaveBeenCalledWith(row);
429-
});
430-
431400
test('should emit selections on toggle select', () => {
432401
const mockSelectionsChange = jest.fn();
433402
const columns = buildColumns();

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ export class TableComponent
272272
@Input()
273273
public pageSize?: number = 50;
274274

275+
@Output()
276+
public readonly rowClicked: EventEmitter<StatefulTableRow> = new EventEmitter<StatefulTableRow>();
277+
275278
@Output()
276279
public readonly selectionsChange: EventEmitter<StatefulTableRow[]> = new EventEmitter<StatefulTableRow[]>();
277280

@@ -548,9 +551,7 @@ export class TableComponent
548551
}
549552

550553
public onDataRowClick(row: StatefulTableRow): void {
551-
if (this.hasSelectableRows()) {
552-
this.toggleRowSelected(row);
553-
}
554+
this.rowClicked.emit(row);
554555
}
555556

556557
public onDataRowMouseEnter(row: StatefulTableRow): void {
@@ -695,10 +696,6 @@ export class TableComponent
695696
return this.isDetailType() && row.$$state.expanded;
696697
}
697698

698-
public hasSelectableRows(): boolean {
699-
return this.hasSingleSelect() || this.hasMultiSelect();
700-
}
701-
702699
public hasSingleSelect(): boolean {
703700
return this.selectionMode === TableSelectionMode.Single;
704701
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { TableWidgetRowSelectionModel } from './table-widget-row-selection.model';
1+
import { TableWidgetRowInteractionModel } from './table-widget-row-interaction.model';
22

3-
describe('TableWidgetRowSelectionModel applies to current row depth', () => {
3+
describe('TableWidgetRowInteractionModel applies to current row depth', () => {
44
test('works for default properties', () => {
5-
const model = new TableWidgetRowSelectionModel();
5+
const model = new TableWidgetRowInteractionModel();
66
model.handler = {
77
execute: jest.fn()
88
};
@@ -13,7 +13,7 @@ describe('TableWidgetRowSelectionModel applies to current row depth', () => {
1313
});
1414

1515
test('works for higher row depth', () => {
16-
const model = new TableWidgetRowSelectionModel();
16+
const model = new TableWidgetRowInteractionModel();
1717
model.handler = {
1818
execute: jest.fn()
1919
};
@@ -26,7 +26,7 @@ describe('TableWidgetRowSelectionModel applies to current row depth', () => {
2626
});
2727

2828
test('works correctly when applyToChildRows is false', () => {
29-
const model = new TableWidgetRowSelectionModel();
29+
const model = new TableWidgetRowInteractionModel();
3030
model.handler = {
3131
execute: jest.fn()
3232
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { BOOLEAN_PROPERTY, Model, ModelProperty, ModelPropertyType, NUMBER_PROPE
22
import { InteractionHandler } from '../../../interaction/interaction-handler';
33

44
@Model({
5-
type: 'table-widget-row-selection',
5+
type: 'table-widget-row-interaction',
66
displayName: 'Row Selection'
77
})
8-
export class TableWidgetRowSelectionModel {
8+
export class TableWidgetRowInteractionModel {
99
@ModelProperty({
1010
key: 'handler',
1111
displayName: 'Selection Handler',

projects/observability/src/shared/dashboard/widgets/table/table-widget-base.model.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from '@hypertrace/hyperdash';
1818
import { ModelInject, MODEL_API } from '@hypertrace/hyperdash-angular';
1919
import { Observable } from 'rxjs';
20-
import { TableWidgetRowSelectionModel } from './selections/table-widget-row-selection.model';
20+
import { TableWidgetRowInteractionModel } from './selections/table-widget-row-interaction.model';
2121
import { SpecificationBackedTableColumnDef } from './table-widget-column.model';
2222
import { TableWidgetControlCheckboxOptionModel } from './table-widget-control-checkbox-option.model';
2323
import { TableWidgetControlSelectOptionModel } from './table-widget-control-select-option.model';
@@ -121,16 +121,11 @@ export abstract class TableWidgetBaseModel extends BaseModel {
121121
return this.api.getData<TableDataSource<TableRow>>();
122122
}
123123

124-
public getRowSelectionHandlers(_row: TableRow): TableWidgetRowSelectionModel[] {
124+
public getRowSelectionHandlers(_row: TableRow): TableWidgetRowInteractionModel[] {
125125
// No-op here, but can be overridden
126126
return [];
127127
}
128128

129-
public getSelectionMode(): TableSelectionMode {
130-
// No-op here, but can be overridden
131-
return TableSelectionMode.Single;
132-
}
133-
134129
public setView(_view: string): void {
135130
// No-op here, but can be overridden
136131
return;
@@ -141,6 +136,11 @@ export abstract class TableWidgetBaseModel extends BaseModel {
141136
return [];
142137
}
143138

139+
public abstract getRowClickHandlers(): TableWidgetRowInteractionModel[];
140+
public abstract getSelectionMode(): TableSelectionMode;
141+
public abstract isCustomControlPresent(): boolean;
142+
public abstract getCustomControlWidgetModel(selectedRows?: TableRow[]): object | undefined;
143+
144144
public getSearchAttribute(): string | undefined {
145145
return this.searchAttribute;
146146
}

projects/observability/src/shared/dashboard/widgets/table/table-widget-renderer.component.ts

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
TableRow,
2323
TableSelectChange,
2424
TableSelectControl,
25-
TableSelectionMode,
2625
TableStyle,
2726
ToggleItem,
2827
toInFilter
@@ -36,6 +35,7 @@ import { filter, map, pairwise, share, startWith, switchMap, take, tap, withLate
3635
import { AttributeMetadata, toFilterAttributeType } from '../../../graphql/model/metadata/attribute-metadata';
3736
import { MetadataService } from '../../../services/metadata/metadata.service';
3837
import { InteractionHandler } from '../../interaction/interaction-handler';
38+
import { TableWidgetRowInteractionModel } from './selections/table-widget-row-interaction.model';
3939
import { TableWidgetBaseModel } from './table-widget-base.model';
4040
import { SpecificationBackedTableColumnDef } from './table-widget-column.model';
4141
import { TableWidgetViewToggleModel } from './table-widget-view-toggle.model';
@@ -61,6 +61,8 @@ import { TableWidgetModel } from './table-widget.model';
6161
[searchPlaceholder]="this.api.model.getSearchPlaceholder()"
6262
[selectControls]="this.selectControls$ | async"
6363
[checkboxControls]="this.checkboxControls$ | async"
64+
[selectedRows]="this.selectedRows"
65+
[customControlContent]="(this.isCustomControlPresent | htMemoize) ? customControlDetail : undefined"
6466
[viewItems]="this.viewItems"
6567
(searchChange)="this.onSearchChange($event)"
6668
(selectChange)="this.onSelectChange($event)"
@@ -83,6 +85,7 @@ import { TableWidgetModel } from './table-widget.model';
8385
[resizable]="this.api.model.isResizable()"
8486
[detailContent]="childDetail"
8587
[syncWithUrl]="this.syncWithUrl"
88+
(rowClicked)="this.onRowClicked($event)"
8689
(selectionsChange)="this.onRowSelection($event)"
8790
(columnConfigsChange)="this.onColumnsChange($event)"
8891
>
@@ -93,6 +96,10 @@ import { TableWidgetModel } from './table-widget.model';
9396
<ng-template #childDetail let-row="row">
9497
<ng-container [hdaDashboardModel]="this.getChildModel | htMemoize: row"></ng-container>
9598
</ng-template>
99+
100+
<ng-template #customControlDetail let-selectedRows="selectedRows">
101+
<ng-container [hdaDashboardModel]="this.getCustomControlWidgetModel | htMemoize: selectedRows"></ng-container>
102+
</ng-template>
96103
`
97104
})
98105
export class TableWidgetRendererComponent
@@ -107,6 +114,8 @@ export class TableWidgetRendererComponent
107114
public columnConfigs$!: Observable<TableColumnConfig[]>;
108115
public combinedFilters$!: Observable<TableFilter[]>;
109116

117+
public selectedRows?: StatefulTableRow[] = [];
118+
110119
private readonly toggleFilterSubject: Subject<TableFilter[]> = new BehaviorSubject<TableFilter[]>([]);
111120
private readonly searchFilterSubject: Subject<TableFilter[]> = new BehaviorSubject<TableFilter[]>([]);
112121
private readonly selectFilterSubject: BehaviorSubject<TableFilter[]> = new BehaviorSubject<TableFilter[]>([]);
@@ -116,8 +125,6 @@ export class TableWidgetRendererComponent
116125
>({});
117126
public queryProperties$: Observable<Dictionary<unknown>> = this.queryPropertiesSubject.asObservable();
118127

119-
private selectedRowInteractionHandler?: InteractionHandler;
120-
121128
public constructor(
122129
@Inject(RENDERER_API) api: RendererApi<TableWidgetModel>,
123130
changeDetectorRef: ChangeDetectorRef,
@@ -152,6 +159,11 @@ export class TableWidgetRendererComponent
152159

153160
public getChildModel = (row: TableRow): object | undefined => this.model.getChildModel(row);
154161

162+
public isCustomControlPresent = (): boolean => this.model.isCustomControlPresent();
163+
164+
public getCustomControlWidgetModel = (selectedRows?: TableRow[]): object | undefined =>
165+
this.model.getCustomControlWidgetModel(selectedRows);
166+
155167
protected fetchData(): Observable<TableDataSource<TableRow> | undefined> {
156168
return this.model.getData().pipe(
157169
startWith(undefined),
@@ -368,28 +380,30 @@ export class TableWidgetRendererComponent
368380
}
369381
}
370382

383+
public onRowClicked(row: StatefulTableRow): void {
384+
this.getRowClickInteractionHandler(row)?.execute(row);
385+
}
386+
371387
public onRowSelection(selections: StatefulTableRow[]): void {
372-
if (this.api.model.getSelectionMode() === TableSelectionMode.Single) {
373-
/**
374-
* Execute selection handler for single selection mode only
375-
*/
376-
let selectedRow;
377-
if (selections.length > 0) {
378-
selectedRow = selections[0];
379-
this.selectedRowInteractionHandler = this.getInteractionHandler(selectedRow);
380-
}
381-
382-
this.selectedRowInteractionHandler?.execute(selectedRow);
383-
}
388+
this.selectedRows = selections;
389+
/**
390+
* Todo: Stich this with selection handlers
391+
*/
392+
}
393+
394+
private getRowClickInteractionHandler(selectedRow: StatefulTableRow): InteractionHandler | undefined {
395+
return this.getInteractionHandler(selectedRow, this.api.model.getRowClickHandlers());
384396
}
385397

386-
private getInteractionHandler(selectedRow: StatefulTableRow): InteractionHandler | undefined {
387-
const matchedSelectionHandlers = this.api.model
388-
.getRowSelectionHandlers(selectedRow)
389-
?.filter(selectionModel => selectionModel.appliesToCurrentRowDepth(selectedRow.$$state.depth))
398+
private getInteractionHandler(
399+
row: StatefulTableRow,
400+
rowHandlers: TableWidgetRowInteractionModel[] = []
401+
): InteractionHandler | undefined {
402+
const matchedHandlers = rowHandlers
403+
.filter(interactionModel => interactionModel.appliesToCurrentRowDepth(row.$$state.depth))
390404
.sort((model1, model2) => model2.rowDepth - model1.rowDepth);
391405

392-
return !isEmpty(matchedSelectionHandlers) ? matchedSelectionHandlers[0].handler : undefined;
406+
return !isEmpty(matchedHandlers) ? matchedHandlers[0].handler : undefined;
393407
}
394408

395409
private pickPersistColumnProperties(column: TableColumnConfig): Pick<TableColumnConfig, 'id' | 'visible'> {

projects/observability/src/shared/dashboard/widgets/table/table-widget-view-toggle.model.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { TableDataSource, TableRow } from '@hypertrace/components';
1+
import { TableDataSource, TableRow, TableSelectionMode } from '@hypertrace/components';
22
import { ArrayPropertyTypeInstance } from '@hypertrace/dashboards';
33
import { ARRAY_PROPERTY, Model, ModelApi, ModelOnInit, ModelProperty, ModelPropertyType } from '@hypertrace/hyperdash';
44
import { ModelInject, MODEL_API } from '@hypertrace/hyperdash-angular';
55
import { NEVER, Observable } from 'rxjs';
6-
import { TableWidgetRowSelectionModel } from './selections/table-widget-row-selection.model';
6+
import { TableWidgetRowInteractionModel } from './selections/table-widget-row-interaction.model';
77
import { SpecificationBackedTableColumnDef } from './table-widget-column.model';
88
import { TableWidgetControlCheckboxOptionModel } from './table-widget-control-checkbox-option.model';
99
import { TableWidgetControlSelectOptionModel } from './table-widget-control-select-option.model';
@@ -73,7 +73,23 @@ export class TableWidgetViewToggleModel extends TableWidgetModel implements Mode
7373
return this.delegateModel && this.delegateModel?.getChildModel(row);
7474
}
7575

76-
public getRowSelectionHandlers(row: TableRow): TableWidgetRowSelectionModel[] {
76+
public getSelectionMode(): TableSelectionMode {
77+
return this.delegateModel?.getSelectionMode() ?? TableSelectionMode.Single;
78+
}
79+
80+
public isCustomControlPresent(): boolean {
81+
return this.delegateModel?.isCustomControlPresent() ?? false;
82+
}
83+
84+
public getCustomControlWidgetModel(selectedRows?: TableRow[]): object | undefined {
85+
return this.delegateModel && this.delegateModel?.getCustomControlWidgetModel(selectedRows);
86+
}
87+
88+
public getRowClickHandlers(): TableWidgetRowInteractionModel[] {
89+
return this.delegateModel?.getRowClickHandlers() ?? [];
90+
}
91+
92+
public getRowSelectionHandlers(row: TableRow): TableWidgetRowInteractionModel[] {
7793
return this.delegateModel && this.delegateModel?.getRowSelectionHandlers(row).length > 0
7894
? this.delegateModel?.getRowSelectionHandlers(row)
7995
: [];

0 commit comments

Comments
 (0)