Skip to content

Commit e0a82b1

Browse files
authored
feat(components/ag-grid): auto-populate column configuration for data manager (#4153)
[AB#3642107](https://dev.azure.com/blackbaud/f565481a-7bc9-4083-95d5-4f953da6d499/_workitems/edit/3642107)
1 parent 112f2cd commit e0a82b1

File tree

3 files changed

+132
-6
lines changed

3 files changed

+132
-6
lines changed

libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-data-manager-adapter.directive.spec.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,3 +779,94 @@ it('should refresh the grid when a view is reactivated', async () => {
779779

780780
expect(agGrid!.api.refreshCells).toHaveBeenCalled();
781781
});
782+
783+
describe('Read columnOptions from grid API', () => {
784+
it("when column picker enabled, should get columnOptions when they're not provided in viewConfig", async () => {
785+
TestBed.configureTestingModule({
786+
imports: [SkyAgGridFixtureModule],
787+
providers: [SkyDataManagerService, provideSkyMediaQueryTesting()],
788+
});
789+
790+
const fixture = TestBed.createComponent(
791+
SkyAgGridDataManagerFixtureComponent,
792+
);
793+
const dataManagerService = TestBed.inject(SkyDataManagerService);
794+
795+
// Verify viewConfig initially has no columnOptions
796+
const viewConfig = fixture.componentInstance.viewConfig;
797+
expect(viewConfig.columnOptions).toBeUndefined();
798+
viewConfig.columnPickerEnabled = true;
799+
800+
fixture.detectChanges();
801+
await fixture.whenStable();
802+
803+
// After grid ready, columnOptions should be populated from grid API
804+
const updatedViewConfig = dataManagerService.getViewById(viewConfig.id);
805+
expect(updatedViewConfig).toBeDefined();
806+
expect(updatedViewConfig!.columnOptions).toBeDefined();
807+
expect(updatedViewConfig!.columnOptions!.length).toBeGreaterThan(0);
808+
809+
// Verify the columnOptions were read from the grid API
810+
// The method #readColumnOptionsFromGrid marks the following as alwaysDisplayed:
811+
// - Pinned columns
812+
// - Columns with lockVisible set
813+
// - Columns without a headerName
814+
// It still includes all columns from the grid in the returned columnOptions
815+
const columnOptions = updatedViewConfig!.columnOptions!;
816+
817+
// Verify we have the expected columns (name, target, noHeader at minimum)
818+
expect(columnOptions.length).toBeGreaterThan(0);
819+
820+
// Check that each column option has the required properties
821+
columnOptions.forEach((option) => {
822+
expect(option.id).toBeDefined();
823+
expect(option.label).toBeDefined();
824+
// alwaysDisplayed can be undefined when lockVisible is not set on the column
825+
expect(
826+
option.alwaysDisplayed === undefined ||
827+
typeof option.alwaysDisplayed === 'boolean',
828+
).toBe(true);
829+
});
830+
831+
// Verify specific columns that should be present
832+
const nameColumn = columnOptions.find((opt) => opt.id === 'name');
833+
expect(nameColumn).toBeDefined();
834+
expect(nameColumn!.label).toBe('First Name');
835+
expect(nameColumn!.alwaysDisplayed).toBeTrue();
836+
837+
const targetColumn = columnOptions.find((opt) => opt.id === 'target');
838+
expect(targetColumn).toBeDefined();
839+
expect(targetColumn!.label).toBe('Goal');
840+
841+
// The noHeader column has no headerName, so label becomes an empty string
842+
const noHeaderColumn = columnOptions.find((opt) => opt.id === 'noHeader');
843+
expect(noHeaderColumn).toBeDefined();
844+
expect(noHeaderColumn!.label).toBe('');
845+
expect(noHeaderColumn!.alwaysDisplayed).toBeTrue();
846+
});
847+
848+
it('when column picker not enabled, should not get columnOptions', async () => {
849+
TestBed.configureTestingModule({
850+
imports: [SkyAgGridFixtureModule],
851+
providers: [SkyDataManagerService, provideSkyMediaQueryTesting()],
852+
});
853+
854+
const fixture = TestBed.createComponent(
855+
SkyAgGridDataManagerFixtureComponent,
856+
);
857+
const dataManagerService = TestBed.inject(SkyDataManagerService);
858+
859+
// Verify viewConfig initially has no columnOptions
860+
const viewConfig = fixture.componentInstance.viewConfig;
861+
expect(viewConfig.columnOptions).toBeUndefined();
862+
expect(viewConfig.columnPickerEnabled).toBeUndefined();
863+
864+
fixture.detectChanges();
865+
await fixture.whenStable();
866+
867+
// After grid ready, columnOptions should remain undefined when column picker is not enabled
868+
const updatedViewConfig = dataManagerService.getViewById(viewConfig.id);
869+
expect(updatedViewConfig).toBeDefined();
870+
expect(updatedViewConfig!.columnOptions).toBeUndefined();
871+
});
872+
});

libs/components/ag-grid/src/lib/modules/ag-grid/ag-grid-data-manager-adapter.directive.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { toSignal } from '@angular/core/rxjs-interop';
1515
import { SkyBreakpoint, SkyMediaQueryService } from '@skyux/core';
1616
import {
17+
SkyDataManagerColumnPickerOption,
1718
SkyDataManagerService,
1819
SkyDataManagerState,
1920
SkyDataViewColumnWidths,
@@ -29,7 +30,15 @@ import {
2930
IColumnLimit,
3031
RowSelectedEvent,
3132
} from 'ag-grid-community';
32-
import { Subject, filter, fromEvent, of, switchMap, takeUntil } from 'rxjs';
33+
import {
34+
Subject,
35+
filter,
36+
fromEvent,
37+
map,
38+
of,
39+
switchMap,
40+
takeUntil,
41+
} from 'rxjs';
3342

3443
import { SkyAgGridWrapperComponent } from './ag-grid-wrapper.component';
3544

@@ -38,7 +47,7 @@ function toColumnWidthName(breakpoint: SkyBreakpoint): 'xs' | 'sm' {
3847
}
3948

4049
/**
41-
* @internal
50+
* Connects `SkyAgGridWrapperComponent` with a `SkyDataViewComponent` to control the grid using a `SkyDataManagerService` instance.
4251
*/
4352
@Directive({ selector: '[skyAgGridDataManagerAdapter]' })
4453
export class SkyAgGridDataManagerAdapterDirective implements OnDestroy {
@@ -88,7 +97,7 @@ export class SkyAgGridDataManagerAdapterDirective implements OnDestroy {
8897
});
8998

9099
readonly #breakpoint = toSignal(
91-
inject(SkyMediaQueryService).breakpointChange,
100+
inject(SkyMediaQueryService).breakpointChange.pipe(map(toColumnWidthName)),
92101
);
93102

94103
constructor() {
@@ -206,10 +215,18 @@ export class SkyAgGridDataManagerAdapterDirective implements OnDestroy {
206215
.pipe(
207216
takeUntil(this.#ngUnsubscribe),
208217
switchMap(() => {
209-
const viewConfig = this.#viewConfig();
218+
let viewConfig = this.#viewConfig();
210219
if (viewConfig) {
211-
viewConfig.onSelectAllClick = (): void => agGrid.api.selectAll();
212-
viewConfig.onClearAllClick = (): void => agGrid.api.deselectAll();
220+
viewConfig = {
221+
...viewConfig,
222+
onSelectAllClick: (): void => agGrid.api.selectAll(),
223+
onClearAllClick: (): void => agGrid.api.deselectAll(),
224+
};
225+
if (viewConfig.columnPickerEnabled && !viewConfig.columnOptions) {
226+
viewConfig.columnOptions = this.#readColumnOptionsFromGrid(
227+
agGrid.api,
228+
);
229+
}
213230
this.#dataManagerSvc.updateViewConfig(viewConfig);
214231

215232
this.#applyColumnWidths();
@@ -496,4 +513,20 @@ export class SkyAgGridDataManagerAdapterDirective implements OnDestroy {
496513
}
497514
return gridColumnLimits;
498515
}
516+
517+
#readColumnOptionsFromGrid(api: GridApi): SkyDataManagerColumnPickerOption[] {
518+
// Technically `api.getColumns()` can return null but it's not testable.
519+
/* istanbul ignore next */
520+
const columns = api.getColumns() ?? [];
521+
return columns.map((col) => {
522+
const colDef = col.getColDef();
523+
return {
524+
id: col.getColId(),
525+
initialHide: colDef.initialHide,
526+
label: `${colDef.headerName ?? ''}`,
527+
alwaysDisplayed:
528+
colDef.lockVisible || !colDef.headerName || col.isPinned(),
529+
};
530+
});
531+
}
499532
}

libs/components/ag-grid/src/lib/modules/ag-grid/fixtures/ag-grid-data-manager.component.fixture.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ export class SkyAgGridDataManagerFixtureComponent implements OnInit {
5353
maxWidth: 50,
5454
sortable: false,
5555
type: SkyCellType.RowSelector,
56+
pinned: 'left',
5657
},
5758
{
5859
field: 'name',
5960
headerName: 'First Name',
61+
lockVisible: true,
6062
},
6163
{
6264
field: 'target',

0 commit comments

Comments
 (0)