Skip to content

Commit 5443113

Browse files
authored
feat: add columnSort function as an option to ColumnPicker/GridMenu interfaces (#2066)
* feat: add columnSort function as an option to ColumnPicker/GridMenu interfaces
1 parent 308482b commit 5443113

File tree

6 files changed

+151
-5
lines changed

6 files changed

+151
-5
lines changed

docs/grid-functionalities/Column-Picker.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,19 @@ this.gridOptions = {
1919
}
2020
```
2121
#### UI Sample
22-
![image](https://user-images.githubusercontent.com/643976/71301681-6cfc3a00-2370-11ea-9c84-be880f345bcd.png)
22+
![image](https://user-images.githubusercontent.com/643976/71301681-6cfc3a00-2370-11ea-9c84-be880f345bcd.png)
23+
24+
### How to change the "columnSort" option to sort columns
25+
The example demonstrates how to use the new alphabetical sorting feature:
26+
27+
```typescript
28+
columnPicker: {
29+
// enable the "columnSort" option to sort columns by name
30+
columnSort: (item1: Column, item2: Column) => {
31+
const nameA = item1.name?.toString().toLowerCase() || '';
32+
const nameB = item2.name?.toString().toLowerCase() || '';
33+
return nameA.localeCompare(nameB);
34+
},
35+
// ... other grid menu options
36+
}
37+
```

docs/grid-functionalities/Grid-Menu.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,18 @@ this.gridOptions = {
127127
enableGridMenu: false,
128128
};
129129
```
130+
131+
### How to change the "columnSort" option to sort columns
132+
The example demonstrates how to use the new alphabetical sorting feature:
133+
134+
```typescript
135+
gridMenu: {
136+
// enable the "columnSort" option to sort columns by name
137+
columnSort: (item1: Column, item2: Column) => {
138+
const nameA = item1.name?.toString().toLowerCase() || '';
139+
const nameB = item2.name?.toString().toLowerCase() || '';
140+
return nameA.localeCompare(nameB);
141+
},
142+
// ... other grid menu options
143+
}
144+
```

packages/common/src/extensions/__tests__/slickColumnPicker.spec.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,4 +544,104 @@ describe('ColumnPickerControl', () => {
544544
expect(control.getVisibleColumns()).toEqual(columnsMock);
545545
});
546546
});
547+
548+
describe('columnSort functionality', () => {
549+
it('should sort columns alphabetically by name when "columnSort" function is provided', () => {
550+
const handlerSpy = vi.spyOn(control.eventHandler, 'subscribe');
551+
vi.spyOn(gridStub, 'getColumnIndex')
552+
.mockReturnValue(undefined as any)
553+
.mockReturnValue(1);
554+
555+
// Create columns with names that are not in alphabetical order
556+
const unsortedColumnsMock: Column[] = [
557+
{ id: 'field1', field: 'field1', name: 'Zebra Field', width: 100 },
558+
{ id: 'field2', field: 'field2', name: 'Alpha Field', width: 75 },
559+
{ id: 'field3', field: 'field3', name: 'Beta Field', width: 75 },
560+
];
561+
562+
// Mock the shared service to return our custom columns
563+
vi.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(unsortedColumnsMock);
564+
vi.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(unsortedColumnsMock);
565+
vi.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(unsortedColumnsMock);
566+
vi.spyOn(gridStub, 'getColumns').mockReturnValue(unsortedColumnsMock);
567+
568+
// Define the columnSort function to sort alphabetically by name
569+
gridOptionsMock.columnPicker!.columnSort = (col1: Column, col2: Column) => {
570+
const nameA = String(col1.name || '').toLowerCase();
571+
const nameB = String(col2.name || '').toLowerCase();
572+
return nameA.localeCompare(nameB);
573+
};
574+
575+
control.columns = unsortedColumnsMock;
576+
control.init();
577+
578+
gridStub.onHeaderContextMenu.notify({ column: unsortedColumnsMock[1], grid: gridStub }, eventData as any, gridStub);
579+
580+
// Get the column labels from the menu in the order they appear
581+
const liElmList = control.menuElement!.querySelectorAll<HTMLLIElement>('li');
582+
const columnLabels: string[] = [];
583+
584+
// Extract text content from each column item (excluding force fit and sync resize buttons)
585+
for (let i = 0; i < Math.min(liElmList.length, unsortedColumnsMock.length); i++) {
586+
const labelSpan = liElmList[i].querySelector('.checkbox-label');
587+
if (labelSpan && labelSpan.textContent) {
588+
columnLabels.push(labelSpan.textContent.trim());
589+
}
590+
}
591+
592+
expect(handlerSpy).toHaveBeenCalledTimes(4);
593+
expect(control.getAllColumns()).toEqual(unsortedColumnsMock);
594+
expect(control.getVisibleColumns()).toEqual(unsortedColumnsMock);
595+
596+
// Verify that columns are displayed in alphabetical order: Alpha Field, Beta Field, Zebra Field
597+
expect(columnLabels).toEqual(['Alpha Field', 'Beta Field', 'Zebra Field']);
598+
});
599+
600+
it('should maintain the original column order when no "columnSort" function is provided', () => {
601+
const handlerSpy = vi.spyOn(control.eventHandler, 'subscribe');
602+
vi.spyOn(gridStub, 'getColumnIndex')
603+
.mockReturnValue(undefined as any)
604+
.mockReturnValue(1);
605+
606+
// Create columns in a specific order
607+
const originalColumnsMock: Column[] = [
608+
{ id: 'field1', field: 'field1', name: 'Zebra Field', width: 100 },
609+
{ id: 'field2', field: 'field2', name: 'Alpha Field', width: 75 },
610+
{ id: 'field3', field: 'field3', name: 'Beta Field', width: 75 },
611+
];
612+
613+
// Mock the shared service to return our custom columns
614+
vi.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(originalColumnsMock);
615+
vi.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(originalColumnsMock);
616+
vi.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(originalColumnsMock);
617+
vi.spyOn(gridStub, 'getColumns').mockReturnValue(originalColumnsMock);
618+
619+
// Don't set columnSort, so it should remain undefined
620+
gridOptionsMock.columnPicker!.columnSort = undefined;
621+
622+
control.columns = originalColumnsMock;
623+
control.init();
624+
625+
gridStub.onHeaderContextMenu.notify({ column: originalColumnsMock[1], grid: gridStub }, eventData as any, gridStub);
626+
627+
// Get the column labels from the menu in the order they appear
628+
const liElmList = control.menuElement!.querySelectorAll<HTMLLIElement>('li');
629+
const columnLabels: string[] = [];
630+
631+
// Extract text content from each column item (excluding force fit and sync resize buttons)
632+
for (let i = 0; i < Math.min(liElmList.length, originalColumnsMock.length); i++) {
633+
const labelSpan = liElmList[i].querySelector('.checkbox-label');
634+
if (labelSpan && labelSpan.textContent) {
635+
columnLabels.push(labelSpan.textContent.trim());
636+
}
637+
}
638+
639+
expect(handlerSpy).toHaveBeenCalledTimes(4);
640+
expect(control.getAllColumns()).toEqual(originalColumnsMock);
641+
expect(control.getVisibleColumns()).toEqual(originalColumnsMock);
642+
643+
// Verify that columns maintain their original order: Zebra Field, Alpha Field, Beta Field
644+
expect(columnLabels).toEqual(['Zebra Field', 'Alpha Field', 'Beta Field']);
645+
});
646+
});
547647
});

packages/common/src/extensions/extensionCommonUtils.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,13 @@ export function handleColumnPickerItemClick(this: SlickColumnPicker | SlickGridM
6565
context._areVisibleColumnDifferent = true;
6666
const columnId = event.target.dataset.columnid || '';
6767
const visibleColumns: Column[] = [];
68-
context._columnCheckboxes.forEach((columnCheckbox: HTMLInputElement, idx: number) => {
69-
if (columnCheckbox.checked) {
70-
visibleColumns.push(context.columns[idx]);
68+
69+
// Iterate through columns and add those that have checked checkboxes to visibleColumns
70+
context.columns.forEach((column: Column) => {
71+
const columnId = column.id;
72+
const checkbox = context._columnCheckboxes.find((cb: HTMLInputElement) => cb.dataset.columnid === columnId.toString());
73+
if (checkbox?.checked) {
74+
visibleColumns.push(column);
7175
}
7276
});
7377

@@ -162,7 +166,13 @@ export function populateColumnPicker(this: SlickColumnPicker | SlickGridMenu, ad
162166
const isGridMenu = context instanceof SlickGridMenu;
163167
const menuPrefix = isGridMenu ? 'gridmenu-' : '';
164168

165-
for (const column of context.columns) {
169+
let sortedColumns = context.columns;
170+
if (typeof addonOptions?.columnSort === 'function') {
171+
// create a sorted copy of the columns array based on the "name" property
172+
sortedColumns = [...context.columns].sort(addonOptions.columnSort);
173+
}
174+
175+
for (const column of sortedColumns) {
166176
const columnId = column.id;
167177
const columnLiElm = document.createElement('li');
168178
if ((column.excludeFromColumnPicker && !isGridMenu) || (column.excludeFromGridMenu && isGridMenu)) {

packages/common/src/interfaces/columnPicker.interface.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export interface ColumnPickerOption {
2020
/** Same as "columnTitle", except that it's a translation key which can be used on page load and/or when switching locale */
2121
columnTitleKey?: string;
2222

23+
/** Custom sort function for the columns in the column picker */
24+
columnSort?: (item1: Column, item2: Column) => number;
25+
2326
/** Defaults to "Force fit columns" which is 1 of the last 2 checkbox title shown at the end of the picker list */
2427
forceFitTitle?: string;
2528

packages/common/src/interfaces/gridMenuOption.interface.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export interface GridMenuOption {
3636
/** Same as "columnTitle", except that it's a translation key which can be used on page load and/or when switching locale */
3737
columnTitleKey?: string;
3838

39+
/** Custom sort function for the columns in the grid menu */
40+
columnSort?: (item1: Column, item2: Column) => number;
41+
3942
/** Defaults to "left", which side to align the grid menu dropdown? */
4043
dropSide?: 'left' | 'right';
4144

0 commit comments

Comments
 (0)