Skip to content

Commit

Permalink
feat(sorting): header menu clear sort, reset sorting when nothing left (
Browse files Browse the repository at this point in the history
#374)

* feat(sorting): header menu clear sort, reset sorting when nothing left
- when there is no more column to sort, we could resort by default sort id which would display dataset the way it was when it was first loaded
- ref SO question: https://stackoverflow.com/questions/62489108/angular-slickgrid-column-wise-remove-sort-option-not-working-properly
- also found that Clear Column Sort on a Local Grid was not trigger a "sortChanged" event while it should so that the Grid State is also notified
  • Loading branch information
ghiscoding committed Jun 25, 2020
1 parent 27b8c21 commit 3f09823
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 95 deletions.
4 changes: 0 additions & 4 deletions src/aurelia-slickgrid/custom-elements/aurelia-slickgrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export class AureliaSlickgridCustomElement {
private _fixedWidth: number | null;
private _hideHeaderRowAfterPageLoad = false;
private _isGridInitialized = false;
private _isGridHavingFilters = false;
private _isDatasetInitialized = false;
private _isPaginationInitialized = false;
private _isLocalGrid = true;
Expand Down Expand Up @@ -169,9 +168,6 @@ export class AureliaSlickgridCustomElement {

attached() {
this.initialization();
if (this.columnDefinitions.findIndex((col) => col.filterable) > -1) {
this._isGridHavingFilters = true;
}
this._isGridInitialized = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const filterServiceStub = {
} as unknown as FilterService;

const sortServiceStub = {
clearSortByColumnId: jest.fn(),
clearSorting: jest.fn(),
emitSortChanged: jest.fn(),
getCurrentColumnSorts: jest.fn(),
Expand Down Expand Up @@ -404,58 +405,15 @@ describe('headerMenuExtension', () => {
expect(filterSpy).toHaveBeenCalledWith(expect.anything(), columnsMock[0].id);
});

it('should trigger the command "clear-sort" and expect Sort Service to call "onBackendSortChanged" being called without the sorted column', () => {
const mockSortedCols: ColumnSort[] = [{ columnId: 'field1', sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { columnId: 'field2', sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }];
const previousSortSpy = jest.spyOn(sortServiceStub, 'getCurrentColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols);
const backendSortSpy = jest.spyOn(sortServiceStub, 'onBackendSortChanged');
it('should trigger the command "clear-sort" and expect "clearSortByColumnId" being called with event and column id', () => {
const clearSortSpy = jest.spyOn(sortServiceStub, 'clearSortByColumnId');
const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand');
const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns');

const instance = extension.register();
instance.onCommand.notify({ column: columnsMock[0], grid: gridStub, command: 'clear-sort' }, new Slick.EventData(), gridStub);

expect(previousSortSpy).toHaveBeenCalled();
expect(backendSortSpy).toHaveBeenCalledWith(expect.anything(), { multiColumnSort: true, sortCols: [mockSortedCols[1]], grid: gridStub });
expect(onCommandSpy).toHaveBeenCalled();
expect(setSortSpy).toHaveBeenCalled();
});

it('should trigger the command "clear-sort" and expect Sort Service to call "onLocalSortChanged" being called without the sorted column', () => {
const copyGridOptionsMock = { ...gridOptionsMock, backendServiceApi: undefined } as unknown as GridOption;
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
const mockSortedCols: ColumnSort[] = [{ columnId: 'field1', sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { columnId: 'field2', sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }];
const previousSortSpy = jest.spyOn(sortServiceStub, 'getCurrentColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols);
const localSortSpy = jest.spyOn(sortServiceStub, 'onLocalSortChanged');
const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand');
const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns');

const instance = extension.register();
instance.onCommand.notify({ column: columnsMock[0], grid: gridStub, command: 'clear-sort' }, new Slick.EventData(), gridStub);

expect(previousSortSpy).toHaveBeenCalled();
expect(localSortSpy).toHaveBeenCalledWith(gridStub, dataViewStub, [mockSortedCols[1]], true);
expect(clearSortSpy).toHaveBeenCalledWith(expect.anything(), columnsMock[0].id);
expect(onCommandSpy).toHaveBeenCalled();
expect(setSortSpy).toHaveBeenCalled();
});

it('should trigger the command "clear-sort" and expect "onSort" event triggered when no DataView is provided', () => {
const copyGridOptionsMock = { ...gridOptionsMock, backendServiceApi: undefined } as unknown as GridOption;
const mockSortedCols: ColumnSort[] = [{ columnId: 'field1', sortAsc: true, sortCol: { id: 'field1', field: 'field1' } }, { columnId: 'field2', sortAsc: false, sortCol: { id: 'field2', field: 'field2' } }];

jest.spyOn(SharedService.prototype, 'dataView', 'get').mockReturnValue(undefined);
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
const previousSortSpy = jest.spyOn(sortServiceStub, 'getCurrentColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols);
const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.headerMenu, 'onCommand');
const setSortSpy = jest.spyOn(SharedService.prototype.grid, 'setSortColumns');
const gridSortSpy = jest.spyOn(gridStub.onSort, 'notify');

const instance = extension.register();
instance.onCommand.notify({ column: columnsMock[0], grid: gridStub, command: 'clear-sort' }, new Slick.EventData(), gridStub);

expect(previousSortSpy).toHaveBeenCalled();
expect(onCommandSpy).toHaveBeenCalled();
expect(setSortSpy).toHaveBeenCalled();
expect(gridSortSpy).toHaveBeenCalledWith([mockSortedCols[1]]);
});

it('should trigger the command "sort-asc" and expect Sort Service to call "onBackendSortChanged" being called without the sorted column', () => {
Expand Down
29 changes: 2 additions & 27 deletions src/aurelia-slickgrid/extensions/headerMenuExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,34 +301,9 @@ export class HeaderMenuExtension implements Extension {
}

/** Clear the Sort on the current column (if it's actually sorted) */
private clearColumnSort(e: Event, args: MenuCommandItemCallbackArgs) {
private clearColumnSort(event: Event, args: MenuCommandItemCallbackArgs) {
if (args && args.column && this.sharedService) {
// get previously sorted columns
const allSortedCols: ColumnSort[] = this.sortService.getCurrentColumnSorts();
const sortedColsWithoutCurrent: ColumnSort[] = this.sortService.getCurrentColumnSorts(args.column.id + '');

if (Array.isArray(allSortedCols) && Array.isArray(sortedColsWithoutCurrent) && allSortedCols.length !== sortedColsWithoutCurrent.length) {
if (this.sharedService.gridOptions.backendServiceApi) {
this.sortService.onBackendSortChanged(e, { multiColumnSort: true, sortCols: sortedColsWithoutCurrent, grid: this.sharedService.grid });
} else if (this.sharedService.dataView) {
this.sortService.onLocalSortChanged(this.sharedService.grid, this.sharedService.dataView, sortedColsWithoutCurrent, true);
} else {
// when using customDataView, we will simply send it as a onSort event with notify
const isMultiSort = this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.multiColumnSort || false;
const sortOutput = isMultiSort ? sortedColsWithoutCurrent : sortedColsWithoutCurrent[0];
args.grid.onSort.notify(sortOutput);
}

// update the this.sharedService.gridObj sortColumns array which will at the same add the visual sort icon(s) on the UI
const updatedSortColumns: ColumnSort[] = sortedColsWithoutCurrent.map((col) => {
return {
columnId: col && col.sortCol && col.sortCol.id,
sortAsc: col && col.sortAsc,
sortCol: col && col.sortCol,
};
});
this.sharedService.grid.setSortColumns(updatedSortColumns); // add sort icon in UI
}
this.sortService.clearSortByColumnId(event, args.column.id);
}
}

Expand Down
122 changes: 122 additions & 0 deletions src/aurelia-slickgrid/services/__tests__/sort.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,128 @@ describe('SortService', () => {
expect(spy).toHaveBeenCalled();
});

describe('clearSortByColumnId method', () => {
let mockSortedCols: ColumnSort[];
const mockColumns = [{ id: 'firstName', field: 'firstName' }, { id: 'lastName', field: 'lastName' }] as Column[];

beforeEach(() => {
mockSortedCols = [
{ sortCol: { id: 'firstName', field: 'firstName', width: 100 }, sortAsc: false, grid: gridStub },
{ sortCol: { id: 'lastName', field: 'lastName', width: 100 }, sortAsc: true, grid: gridStub },
];
gridOptionMock.backendServiceApi = {
service: backendServiceStub,
process: () => new Promise((resolve) => resolve(jest.fn()))
};
jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns);
});

it('should expect Sort Service to call "onBackendSortChanged" being called without the sorted column', () => {
const previousSortSpy = jest.spyOn(service, 'getCurrentColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols);
const backendSortSpy = jest.spyOn(service, 'onBackendSortChanged');
const setSortSpy = jest.spyOn(gridStub, 'setSortColumns');

const mockMouseEvent = new Event('mouseup');
service.bindBackendOnSort(gridStub, dataViewStub);
service.clearSortByColumnId(mockMouseEvent, 'firstName');

expect(previousSortSpy).toHaveBeenCalled();
expect(backendSortSpy).toHaveBeenCalledWith(mockMouseEvent, { multiColumnSort: true, sortCols: [mockSortedCols[1]], grid: gridStub });
expect(setSortSpy).toHaveBeenCalled();
});

it('should expect Sort Service to call "onLocalSortChanged" being called without the sorted column (firstName DESC)', () => {
gridOptionMock.backendServiceApi = undefined;
const previousSortSpy = jest.spyOn(service, 'getCurrentColumnSorts').mockReturnValue([mockSortedCols[0]]).mockReturnValueOnce(mockSortedCols);
const localSortSpy = jest.spyOn(service, 'onLocalSortChanged');
const emitSortChangedSpy = jest.spyOn(service, 'emitSortChanged');
const setSortSpy = jest.spyOn(gridStub, 'setSortColumns');

const mockMouseEvent = new Event('mouseup');
service.bindLocalOnSort(gridStub, dataViewStub);
service.clearSortByColumnId(mockMouseEvent, 'firstName');

expect(previousSortSpy).toHaveBeenCalled();
expect(localSortSpy).toHaveBeenCalledWith(gridStub, dataViewStub, [mockSortedCols[0]], true, true);
expect(emitSortChangedSpy).toHaveBeenCalledWith('local', [{ columnId: 'firstName', direction: 'DESC' }]);
expect(setSortSpy).toHaveBeenCalled();
});

it('should expect Sort Service to call "onLocalSortChanged" being called without the sorted column (lastName ASC)', () => {
gridOptionMock.backendServiceApi = undefined;
const previousSortSpy = jest.spyOn(service, 'getCurrentColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols);
const localSortSpy = jest.spyOn(service, 'onLocalSortChanged');
const emitSortChangedSpy = jest.spyOn(service, 'emitSortChanged');
const setSortSpy = jest.spyOn(gridStub, 'setSortColumns');

const mockMouseEvent = new Event('mouseup');
service.bindLocalOnSort(gridStub, dataViewStub);
service.clearSortByColumnId(mockMouseEvent, 'lastName');

expect(previousSortSpy).toHaveBeenCalled();
expect(localSortSpy).toHaveBeenCalledWith(gridStub, dataViewStub, [mockSortedCols[1]], true, true);
expect(emitSortChangedSpy).toHaveBeenCalledWith('local', [{ columnId: 'lastName', direction: 'ASC' }]);
expect(setSortSpy).toHaveBeenCalled();
});

it('should expect "onSort" event triggered when no DataView is provided', () => {
gridOptionMock.backendServiceApi = undefined;
const previousSortSpy = jest.spyOn(service, 'getCurrentColumnSorts').mockReturnValue([mockSortedCols[1]]).mockReturnValueOnce(mockSortedCols);
const setSortSpy = jest.spyOn(gridStub, 'setSortColumns');
const gridSortSpy = jest.spyOn(gridStub.onSort, 'notify');

const mockMouseEvent = new Event('mouseup');
service.bindLocalOnSort(gridStub, null);
service.clearSortByColumnId(mockMouseEvent, 'firstName');

expect(previousSortSpy).toHaveBeenCalled();
expect(setSortSpy).toHaveBeenCalled();
expect(gridSortSpy).toHaveBeenCalledWith(mockSortedCols[1]);
});

it('should expect Sort Service to call "onLocalSortChanged" with empty array then also "sortLocalGridByDefaultSortFieldId" when there is no more columns left to sort', () => {
gridOptionMock.backendServiceApi = undefined;
const previousSortSpy = jest.spyOn(service, 'getCurrentColumnSorts').mockReturnValue([]).mockReturnValueOnce([mockSortedCols[0]]);
const localSortSpy = jest.spyOn(service, 'onLocalSortChanged');
const emitSortChangedSpy = jest.spyOn(service, 'emitSortChanged');
const sortDefaultSpy = jest.spyOn(service, 'sortLocalGridByDefaultSortFieldId');
const setSortSpy = jest.spyOn(gridStub, 'setSortColumns');

const mockMouseEvent = new Event('mouseup');
service.bindLocalOnSort(gridStub, dataViewStub);
service.clearSortByColumnId(mockMouseEvent, 'firstName');

expect(previousSortSpy).toHaveBeenCalled();
expect(localSortSpy).toHaveBeenNthCalledWith(1, gridStub, dataViewStub, [], true, true);
expect(localSortSpy).toHaveBeenNthCalledWith(2, gridStub, dataViewStub, [{ clearSortTriggered: true, sortAsc: true, sortCol: { field: 'id', id: 'id' } }]);
expect(emitSortChangedSpy).toHaveBeenCalledWith('local', []);
expect(setSortSpy).toHaveBeenCalled();
expect(sortDefaultSpy).toHaveBeenCalled();
});

it('should expect Sort Service to call "onLocalSortChanged" with empty array then also "sortLocalGridByDefaultSortFieldId" with custom Id when there is no more columns left to sort', () => {
gridOptionMock.backendServiceApi = undefined;
gridOptionMock.defaultColumnSortFieldId = 'customId';
const mockSortedCol = { sortCol: { id: 'firstName', field: 'firstName', width: 100 }, sortAsc: false, grid: gridStub };
const previousSortSpy = jest.spyOn(service, 'getCurrentColumnSorts').mockReturnValue([]).mockReturnValueOnce([mockSortedCol]);
const localSortSpy = jest.spyOn(service, 'onLocalSortChanged');
const emitSortChangedSpy = jest.spyOn(service, 'emitSortChanged');
const sortDefaultSpy = jest.spyOn(service, 'sortLocalGridByDefaultSortFieldId');
const setSortSpy = jest.spyOn(gridStub, 'setSortColumns');

const mockMouseEvent = new Event('mouseup');
service.bindLocalOnSort(gridStub, dataViewStub);
service.clearSortByColumnId(mockMouseEvent, 'firstName');

expect(previousSortSpy).toHaveBeenCalled();
expect(localSortSpy).toHaveBeenNthCalledWith(1, gridStub, dataViewStub, [], true, true);
expect(emitSortChangedSpy).toHaveBeenCalledWith('local', []);
expect(localSortSpy).toHaveBeenNthCalledWith(2, gridStub, dataViewStub, [{ clearSortTriggered: true, sortAsc: true, sortCol: { field: 'customId', id: 'customId' } }]);
expect(setSortSpy).toHaveBeenCalled();
expect(sortDefaultSpy).toHaveBeenCalled();
});
});

describe('clearSorting method', () => {
let mockSortedCol: ColumnSort;
const mockColumns = [{ id: 'lastName', field: 'lastName' }, { id: 'firstName', field: 'firstName' }] as Column[];
Expand Down
Loading

0 comments on commit 3f09823

Please sign in to comment.