diff --git a/packages/common/src/interfaces/columnFilter.interface.ts b/packages/common/src/interfaces/columnFilter.interface.ts
index b54677dff..539024ec5 100644
--- a/packages/common/src/interfaces/columnFilter.interface.ts
+++ b/packages/common/src/interfaces/columnFilter.interface.ts
@@ -129,6 +129,9 @@ export interface ColumnFilter {
*/
skipCompoundOperatorFilterWithNullInput?: boolean;
+ /** Target element selector from which the filter was triggered from. */
+ targetSelector?: string;
+
/** What is the Field Type that can be used by the Filter (as precedence over the "type" set the column definition) */
type?: typeof FieldType[keyof typeof FieldType];
diff --git a/packages/common/src/interfaces/currentFilter.interface.ts b/packages/common/src/interfaces/currentFilter.interface.ts
index 67fcaa414..7e97f444c 100644
--- a/packages/common/src/interfaces/currentFilter.interface.ts
+++ b/packages/common/src/interfaces/currentFilter.interface.ts
@@ -13,4 +13,7 @@ export interface CurrentFilter {
/** Filter search terms */
searchTerms?: SearchTerm[];
+
+ /** Target element selector from which the filter was triggered from. */
+ targetSelector?: string;
}
diff --git a/packages/common/src/interfaces/filterChangedArgs.interface.ts b/packages/common/src/interfaces/filterChangedArgs.interface.ts
index 17fc065ee..b5978cd07 100644
--- a/packages/common/src/interfaces/filterChangedArgs.interface.ts
+++ b/packages/common/src/interfaces/filterChangedArgs.interface.ts
@@ -13,4 +13,5 @@ export interface FilterChangedArgs {
operator: OperatorType | OperatorString;
searchTerms: SearchTerm[];
shouldTriggerQuery?: boolean;
+ targetSelector?: string;
}
diff --git a/packages/common/src/interfaces/searchColumnFilter.interface.ts b/packages/common/src/interfaces/searchColumnFilter.interface.ts
index fd014d3a7..12a2de43f 100644
--- a/packages/common/src/interfaces/searchColumnFilter.interface.ts
+++ b/packages/common/src/interfaces/searchColumnFilter.interface.ts
@@ -32,4 +32,7 @@ export interface SearchColumnFilter {
/** What is the Field Type that can be used by the Filter (as precedence over the "type" set the column definition) */
type: typeof FieldType[keyof typeof FieldType];
+
+ /** Target element selector from which the filter was triggered from. */
+ targetSelector?: string;
}
diff --git a/packages/common/src/services/__tests__/domUtilities.spec.ts b/packages/common/src/services/__tests__/domUtilities.spec.ts
index 8f861c55a..e95e6ef2c 100644
--- a/packages/common/src/services/__tests__/domUtilities.spec.ts
+++ b/packages/common/src/services/__tests__/domUtilities.spec.ts
@@ -6,6 +6,7 @@ import {
findFirstElementAttribute,
getElementOffsetRelativeToParent,
getHtmlElementOffset,
+ getSelectorStringFromElement,
htmlEncode,
htmlEntityDecode,
sanitizeHtmlToText,
@@ -130,6 +131,37 @@ describe('Service/domUtilies', () => {
});
});
+ describe('getSelectorStringFromElement() method', () => {
+ it('should return html element selector without classes when div is created without classes', () => {
+ const result = getSelectorStringFromElement(null);
+
+ expect(result).toBe('');
+ });
+
+ it('should return html element selector without classes when div is created without classes', () => {
+ const tmpDiv = document.createElement('div');
+ const result = getSelectorStringFromElement(tmpDiv);
+
+ expect(result).toBe('div');
+ });
+
+ it('should return html element selector with a single class name when exists', () => {
+ const tmpDiv = document.createElement('div');
+ tmpDiv.className = 'some-class'
+ const result = getSelectorStringFromElement(tmpDiv);
+
+ expect(result).toBe('div.some-class');
+ });
+
+ it('should return html element selector with multiple classes when exists', () => {
+ const tmpDiv = document.createElement('div');
+ tmpDiv.className = 'some-class other-class yet-more'
+ const result = getSelectorStringFromElement(tmpDiv);
+
+ expect(result).toBe('div.some-class.other-class.yet-more');
+ });
+ });
+
describe('htmlEncode method', () => {
it('should return a encoded HTML string', () => {
const result = htmlEncode(`
Something
`);
diff --git a/packages/common/src/services/__tests__/filter.service.spec.ts b/packages/common/src/services/__tests__/filter.service.spec.ts
index c203a82ce..181f4e4cc 100644
--- a/packages/common/src/services/__tests__/filter.service.spec.ts
+++ b/packages/common/src/services/__tests__/filter.service.spec.ts
@@ -314,6 +314,8 @@ describe('FilterService', () => {
model: Filters.select, searchTerms: [true], collection: [{ value: true, label: 'True' }, { value: false, label: 'False' }],
}
} as Column;
+ const tmpDivElm = document.createElement('div');
+ tmpDivElm.className = 'some-classes';
const mockHeaderArgs = { grid: gridStub, column: mockColumn, node: document.getElementById(DOM_ELEMENT_ID), };
const mockSearchArgs = {
clearFilterTriggered: false,
@@ -322,6 +324,7 @@ describe('FilterService', () => {
columnDef: { id: 'firstName', field: 'firstName', filterable: true } as Column,
operator: 'EQ',
searchTerms: ['John'],
+ target: tmpDivElm,
grid: gridStub
};
sharedService.allColumns = [mockColumn];
@@ -352,7 +355,7 @@ describe('FilterService', () => {
});
it('should execute the search callback normally when a input change event is triggered and searchTerms are defined', (done) => {
- const expectationColumnFilter = { columnDef: mockColumn, columnId: 'firstName', operator: 'EQ', searchTerms: ['John'], parsedSearchTerms: ['John'], type: FieldType.string };
+ const expectationColumnFilter = { columnDef: mockColumn, columnId: 'firstName', operator: 'EQ', searchTerms: ['John'], parsedSearchTerms: ['John'], targetSelector: '', type: FieldType.string };
const spySearchChange = jest.spyOn(service.onSearchChange as any, 'notify');
const spyEmit = jest.spyOn(service, 'emitFilterChanged');
@@ -381,7 +384,7 @@ describe('FilterService', () => {
});
it('should execute the callback normally when a input change event is triggered and the searchTerm comes from this event.target', () => {
- const expectationColumnFilter = { columnDef: mockColumn, columnId: 'firstName', operator: 'EQ', searchTerms: ['John'], parsedSearchTerms: ['John'], type: FieldType.string };
+ const expectationColumnFilter = { columnDef: mockColumn, columnId: 'firstName', operator: 'EQ', searchTerms: ['John'], parsedSearchTerms: ['John'], targetSelector: '', type: FieldType.string };
const spySearchChange = jest.spyOn(service.onSearchChange as any, 'notify');
sharedService.allColumns = [mockColumn];
@@ -418,7 +421,7 @@ describe('FilterService', () => {
});
it('should NOT delete the column filters entry (from column filter object) even when searchTerms is empty when user set `emptySearchTermReturnAllValues` to False', () => {
- const expectationColumnFilter = { columnDef: mockColumn, columnId: 'firstName', operator: 'EQ', searchTerms: [''], parsedSearchTerms: [''], type: FieldType.string };
+ const expectationColumnFilter = { columnDef: mockColumn, columnId: 'firstName', operator: 'EQ', searchTerms: [''], parsedSearchTerms: [''], targetSelector: 'div.some-classes', type: FieldType.string };
const spySearchChange = jest.spyOn(service.onSearchChange as any, 'notify');
sharedService.allColumns = [mockColumn];
@@ -426,7 +429,11 @@ describe('FilterService', () => {
service.bindLocalOnFilter(gridStub);
mockArgs.column.filter = { emptySearchTermReturnAllValues: false };
gridStub.onHeaderRowCellRendered.notify(mockArgs as any, new Slick.EventData(), gridStub);
- service.getFiltersMetadata()[0].callback(new Event('input'), { columnDef: mockColumn, operator: 'EQ', searchTerms: [''], shouldTriggerQuery: true });
+ const tmpDivElm = document.createElement('div');
+ tmpDivElm.className = 'some-classes';
+ const inputEvent = new Event('input');
+ Object.defineProperty(inputEvent, 'target', { writable: true, configurable: true, value: tmpDivElm });
+ service.getFiltersMetadata()[0].callback(inputEvent, { columnDef: mockColumn, operator: 'EQ', searchTerms: [''], shouldTriggerQuery: true, target: tmpDivElm });
expect(service.getColumnFilters()).toContainEntry(['firstName', expectationColumnFilter]);
expect(spySearchChange).toHaveBeenCalledWith({
@@ -439,9 +446,9 @@ describe('FilterService', () => {
searchTerms: [''],
parsedSearchTerms: [''],
grid: gridStub,
- target: null,
+ target: tmpDivElm,
}, expect.anything());
- expect(service.getCurrentLocalFilters()).toEqual([{ columnId: 'firstName', operator: 'EQ', searchTerms: [''] }]);
+ expect(service.getCurrentLocalFilters()).toEqual([{ columnId: 'firstName', operator: 'EQ', searchTerms: [''], targetSelector: 'div.some-classes', }]);
});
it('should delete the column filters entry (from column filter object) when searchTerms is empty array and even when triggered event is undefined', () => {
@@ -495,7 +502,7 @@ describe('FilterService', () => {
describe('clearFilterByColumnId method', () => {
it('should clear the filter by passing a column id as argument on a backend grid', async () => {
- const filterExpectation = { columnDef: mockColumn2, columnId: 'lastName', operator: 'NE', searchTerms: ['Doe'], parsedSearchTerms: ['Doe'], type: FieldType.string };
+ const filterExpectation = { columnDef: mockColumn2, columnId: 'lastName', operator: 'NE', searchTerms: ['Doe'], parsedSearchTerms: ['Doe'], targetSelector: '', type: FieldType.string };
const newEvent = new CustomEvent(`mouseup`);
const spyClear = jest.spyOn(service.getFiltersMetadata()[0], 'clear');
const spyFilterChange = jest.spyOn(service, 'onBackendFilterChange');
@@ -516,8 +523,8 @@ describe('FilterService', () => {
});
it('should not call "onBackendFilterChange" method when the filter is previously empty', async () => {
- const filterFirstExpectation = { columnDef: mockColumn1, columnId: 'firstName', operator: 'EQ', searchTerms: ['John'], parsedSearchTerms: ['John'], type: FieldType.string };
- const filterLastExpectation = { columnDef: mockColumn2, columnId: 'lastName', operator: 'NE', searchTerms: ['Doe'], parsedSearchTerms: ['Doe'], type: FieldType.string };
+ const filterFirstExpectation = { columnDef: mockColumn1, columnId: 'firstName', operator: 'EQ', searchTerms: ['John'], parsedSearchTerms: ['John'], targetSelector: '', type: FieldType.string };
+ const filterLastExpectation = { columnDef: mockColumn2, columnId: 'lastName', operator: 'NE', searchTerms: ['Doe'], parsedSearchTerms: ['Doe'], targetSelector: '', type: FieldType.string };
const newEvent = new Event('mouseup');
const spyClear = jest.spyOn(service.getFiltersMetadata()[2], 'clear');
const spyFilterChange = jest.spyOn(service, 'onBackendFilterChange');
@@ -564,7 +571,7 @@ describe('FilterService', () => {
expect(service.getColumnFilters()).toEqual({});
expect(spyFilterChange).not.toHaveBeenCalled();
expect(spyEmitter).not.toHaveBeenCalled();
- expect(sharedService.columnDefinitions[0].filter.searchTerms).toBeUndefined();
+ expect(sharedService.columnDefinitions[0].filter!.searchTerms).toBeUndefined();
});
it('should execute the "onError" method when the Promise throws an error', (done) => {
@@ -597,7 +604,7 @@ describe('FilterService', () => {
gridOptionMock.backendServiceApi!.onError = () => jest.fn();
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');
const spyOnError = jest.spyOn(gridOptionMock.backendServiceApi as BackendServiceApi, 'onError');
- jest.spyOn(gridOptionMock.backendServiceApi, 'process').mockReturnValue(throwError(errorExpected));
+ jest.spyOn(gridOptionMock.backendServiceApi as BackendServiceApi, 'process').mockReturnValue(throwError(errorExpected));
backendUtilityService.addRxJsResource(rxjsResourceStub);
service.addRxJsResource(rxjsResourceStub);
@@ -648,7 +655,7 @@ describe('FilterService', () => {
expect(spyClear).toHaveBeenCalled();
expect(filterCountBefore).toBe(2);
expect(filterCountAfter).toBe(1);
- expect(service.getColumnFilters()).toEqual({ lastName: { columnDef: mockColumn2, columnId: 'lastName', operator: 'NE', searchTerms: ['Doe'], parsedSearchTerms: ['Doe'], type: FieldType.string } });
+ expect(service.getColumnFilters()).toEqual({ lastName: { columnDef: mockColumn2, columnId: 'lastName', operator: 'NE', searchTerms: ['Doe'], parsedSearchTerms: ['Doe'], targetSelector: '', type: FieldType.string } });
expect(spyEmitter).toHaveBeenCalledWith('local');
});
});
@@ -709,7 +716,7 @@ describe('FilterService', () => {
service.init(gridStub);
const parsedSearchTerms = getParsedSearchTermsByFieldType(searchTerms, 'text');
- const columnFilters = { firstName: { columnDef: undefined, columnId: 'firstName', operator: 'EQ', searchTerms, parsedSearchTerms, type: FieldType.string } } as ColumnFilters;
+ const columnFilters = { firstName: { columnDef: undefined as any, columnId: 'firstName', operator: 'EQ', searchTerms, parsedSearchTerms, targetSelector: '', type: FieldType.string } } as ColumnFilters;
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
expect(output).toBe(true);
@@ -836,7 +843,7 @@ describe('FilterService', () => {
const columnFilter = { columnDef: mockColumn1, columnId: 'firstName', type: FieldType.string };
const filterCondition = service.parseFormInputFilterConditions(searchTerms, columnFilter);
const parsedSearchTerms = getParsedSearchTermsByFieldType(filterCondition.searchTerms, 'text');
- const columnFilters = { firstName: { ...columnFilter, operator: filterCondition.operator, searchTerms: filterCondition.searchTerms, parsedSearchTerms } };
+ const columnFilters = { firstName: { ...columnFilter, operator: filterCondition.operator, searchTerms: filterCondition.searchTerms, parsedSearchTerms } } as ColumnFilters;
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
expect(output).toBe(true);
@@ -851,7 +858,7 @@ describe('FilterService', () => {
const columnFilter = { columnDef: mockColumn1, columnId: 'firstName', type: FieldType.string };
const filterCondition = service.parseFormInputFilterConditions(searchTerms, columnFilter);
const parsedSearchTerms = getParsedSearchTermsByFieldType(filterCondition.searchTerms, 'text');
- const columnFilters = { firstName: { ...columnFilter, operator: filterCondition.operator, searchTerms: filterCondition.searchTerms, parsedSearchTerms } };
+ const columnFilters = { firstName: { ...columnFilter, operator: filterCondition.operator, searchTerms: filterCondition.searchTerms, parsedSearchTerms } } as ColumnFilters;
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
expect(output).toBe(true);
@@ -866,7 +873,7 @@ describe('FilterService', () => {
const columnFilter = { columnDef: mockColumn1, columnId: 'firstName', type: FieldType.string };
const filterCondition = service.parseFormInputFilterConditions(searchTerms, columnFilter);
const parsedSearchTerms = getParsedSearchTermsByFieldType(filterCondition.searchTerms, 'text');
- const columnFilters = { firstName: { ...columnFilter, operator: filterCondition.operator, searchTerms: filterCondition.searchTerms, parsedSearchTerms } };
+ const columnFilters = { firstName: { ...columnFilter, operator: filterCondition.operator, searchTerms: filterCondition.searchTerms, parsedSearchTerms } } as ColumnFilters;
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
expect(output).toBe(true);
@@ -976,7 +983,7 @@ describe('FilterService', () => {
});
it('should throw an error when grid argument is undefined', (done) => {
- service.onBackendFilterChange(undefined as any, undefined).catch((error) => {
+ service.onBackendFilterChange(undefined as any, undefined as any).catch((error) => {
expect(error.message).toContain(`Something went wrong when trying to bind the "onBackendFilterChange(event, args)" function`);
done();
});
@@ -1311,14 +1318,14 @@ describe('FilterService', () => {
expect(emitSpy).toHaveBeenCalledWith('local');
expect(clearSpy).toHaveBeenCalledWith(false);
expect(service.getColumnFilters()).toEqual({
- firstName: { columnId: 'firstName', columnDef: mockColumn1, searchTerms: ['Jane'], operator: 'StartsWith', parsedSearchTerms: ['Jane'], type: FieldType.string },
+ firstName: { columnId: 'firstName', columnDef: mockColumn1, searchTerms: ['Jane'], operator: 'StartsWith', parsedSearchTerms: ['Jane'], targetSelector: '', type: FieldType.string },
});
expect(spySearchChange).toHaveBeenCalledWith({
clearFilterTriggered: undefined,
shouldTriggerQuery: true,
columnId: 'firstName',
columnDef: mockColumn1,
- columnFilters: { firstName: { columnDef: mockColumn1, columnId: 'firstName', operator: 'StartsWith', searchTerms: ['Jane'], parsedSearchTerms: ['Jane'], type: FieldType.string } },
+ columnFilters: { firstName: { columnDef: mockColumn1, columnId: 'firstName', operator: 'StartsWith', searchTerms: ['Jane'], parsedSearchTerms: ['Jane'], targetSelector: '', type: FieldType.string } },
operator: 'StartsWith',
searchTerms: ['Jane'],
parsedSearchTerms: ['Jane'],
@@ -1341,14 +1348,14 @@ describe('FilterService', () => {
expect(emitSpy).toHaveBeenCalledWith('local');
expect(clearSpy).toHaveBeenCalledWith(false);
expect(service.getColumnFilters()).toEqual({
- firstName: { columnId: 'firstName', columnDef: mockColumn1, searchTerms: ['Jane'], operator: 'StartsWith', parsedSearchTerms: ['Jane'], type: FieldType.string },
+ firstName: { columnId: 'firstName', columnDef: mockColumn1, searchTerms: ['Jane'], operator: 'StartsWith', parsedSearchTerms: ['Jane'], targetSelector: '', type: FieldType.string },
});
expect(spySearchChange).toHaveBeenCalledWith({
clearFilterTriggered: undefined,
shouldTriggerQuery: true,
columnId: 'firstName',
columnDef: mockColumn1,
- columnFilters: { firstName: { columnDef: mockColumn1, columnId: 'firstName', operator: 'StartsWith', searchTerms: ['Jane'], parsedSearchTerms: ['Jane'], type: FieldType.string } },
+ columnFilters: { firstName: { columnDef: mockColumn1, columnId: 'firstName', operator: 'StartsWith', searchTerms: ['Jane'], parsedSearchTerms: ['Jane'], targetSelector: '', type: FieldType.string } },
operator: 'StartsWith',
searchTerms: ['Jane'],
parsedSearchTerms: ['Jane'],
@@ -1650,7 +1657,7 @@ describe('FilterService', () => {
const filterElm = document.body.querySelector(`#${DOM_ELEMENT_ID}`);
expect(filterElm).toBeTruthy();
- expect(columnFilterMetadada.columnDef.id).toBe('name');
+ expect(columnFilterMetadada!.columnDef.id).toBe('name');
});
it('should Draw DOM Element Filter on custom HTML element by string id with searchTerms', async () => {
@@ -1664,7 +1671,7 @@ describe('FilterService', () => {
const filterElm = document.body.querySelector(`#${DOM_ELEMENT_ID}`);
expect(filterElm).toBeTruthy();
- expect(columnFilterMetadada.columnDef.id).toBe('firstName');
+ expect(columnFilterMetadada!.columnDef.id).toBe('firstName');
});
it('should Draw DOM Element Filter on custom HTML element by HTMLDivElement', async () => {
@@ -1674,12 +1681,12 @@ describe('FilterService', () => {
gridStub.onHeaderRowCellRendered.notify(mockArgs2 as any, new Slick.EventData(), gridStub);
await service.updateFilters(mockNewFilters);
- const filterContainerElm: HTMLDivElement = document.querySelector(`#${DOM_ELEMENT_ID}`);
+ const filterContainerElm = document.querySelector(`#${DOM_ELEMENT_ID}`) as HTMLDivElement;
const columnFilterMetadada = service.drawFilterTemplate('isActive', filterContainerElm);
const filterElm = document.body.querySelector(`#${DOM_ELEMENT_ID}`);
expect(filterElm).toBeTruthy();
- expect(columnFilterMetadada.columnDef.id).toBe('isActive');
+ expect(columnFilterMetadada!.columnDef.id).toBe('isActive');
});
it('should Draw DOM Element Filter on custom HTML element return null', async () => {
@@ -1689,7 +1696,7 @@ describe('FilterService', () => {
gridStub.onHeaderRowCellRendered.notify(mockArgs2 as any, new Slick.EventData(), gridStub);
await service.updateFilters(mockNewFilters);
- const filterContainerElm: HTMLDivElement = document.querySelector(`#${DOM_ELEMENT_ID}`);
+ const filterContainerElm = document.querySelector(`#${DOM_ELEMENT_ID}`) as HTMLDivElement;
const columnFilterMetadada1 = service.drawFilterTemplate('selector', filterContainerElm);
const columnFilterMetadada2 = service.drawFilterTemplate('name', `#not-exists`);
const columnFilterMetadada3 = service.drawFilterTemplate('invalid-column', filterContainerElm);
@@ -1797,7 +1804,7 @@ describe('FilterService', () => {
gridStub.onHeaderRowCellRendered.notify(mockArgs1 as any, new Slick.EventData(), gridStub);
gridStub.onHeaderRowCellRendered.notify(mockArgs2 as any, new Slick.EventData(), gridStub);
- const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['map'], parsedSearchTerms: ['map'], type: FieldType.string } } as ColumnFilters;
+ const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['map'], parsedSearchTerms: ['map'], targetSelector: '', type: FieldType.string } } as ColumnFilters;
await service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['map'] }], true, true, true);
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
@@ -1822,7 +1829,7 @@ describe('FilterService', () => {
gridStub.onHeaderRowCellRendered.notify(mockArgs1 as any, new Slick.EventData(), gridStub);
gridStub.onHeaderRowCellRendered.notify(mockArgs2 as any, new Slick.EventData(), gridStub);
- const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['map'], parsedSearchTerms: ['map'], type: FieldType.string } } as ColumnFilters;
+ const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['map'], parsedSearchTerms: ['map'], targetSelector: '', type: FieldType.string } } as ColumnFilters;
await service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['map'] }], true, true, true);
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
@@ -1847,7 +1854,7 @@ describe('FilterService', () => {
gridStub.onHeaderRowCellRendered.notify(mockArgs1 as any, new Slick.EventData(), gridStub);
gridStub.onHeaderRowCellRendered.notify(mockArgs2 as any, new Slick.EventData(), gridStub);
- const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', searchTerms: ['unknown'], type: FieldType.string } } as ColumnFilters;
+ const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', searchTerms: ['unknown'], targetSelector: '', type: FieldType.string } } as ColumnFilters;
await service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['unknown'] }], true, true, true);
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
@@ -1893,14 +1900,14 @@ describe('FilterService', () => {
.mockReturnValueOnce(dataset[6]);
const mockItem1 = { __parentId: 9, __treeLevel: 2, dateModified: '2015-02-26T16:50:00.123Z', file: 'todo.txt', id: 10, size: 0.4 };
- gridOptionMock.treeDataOptions.excludeChildrenWhenFilteringTree = false;
+ gridOptionMock.treeDataOptions!.excludeChildrenWhenFilteringTree = false;
service.init(gridStub);
service.bindLocalOnFilter(gridStub);
gridStub.onHeaderRowCellRendered.notify(mockArgs1 as any, new Slick.EventData(), gridStub);
gridStub.onHeaderRowCellRendered.notify(mockArgs2 as any, new Slick.EventData(), gridStub);
- const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['misc'], parsedSearchTerms: ['misc'], type: FieldType.string } } as ColumnFilters;
+ const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['misc'], parsedSearchTerms: ['misc'], targetSelector: '', type: FieldType.string } } as ColumnFilters;
await service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['misc'] }], true, true, true);
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
@@ -1919,14 +1926,14 @@ describe('FilterService', () => {
.mockReturnValueOnce(dataset[6]);
const mockItem1 = { __parentId: 9, __treeLevel: 2, dateModified: '2015-02-26T16:50:00.123Z', file: 'todo.txt', id: 10, size: 0.4 };
- gridOptionMock.treeDataOptions.excludeChildrenWhenFilteringTree = true;
+ gridOptionMock.treeDataOptions!.excludeChildrenWhenFilteringTree = true;
service.init(gridStub);
service.bindLocalOnFilter(gridStub);
gridStub.onHeaderRowCellRendered.notify(mockArgs1 as any, new Slick.EventData(), gridStub);
gridStub.onHeaderRowCellRendered.notify(mockArgs2 as any, new Slick.EventData(), gridStub);
- const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['misc'], parsedSearchTerms: ['misc'], type: FieldType.string } } as ColumnFilters;
+ const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['misc'], parsedSearchTerms: ['misc'], targetSelector: '', type: FieldType.string } } as ColumnFilters;
await service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['misc'] }], true, true, true);
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
@@ -1945,8 +1952,8 @@ describe('FilterService', () => {
.mockReturnValueOnce(dataset[6]);
const mockItem1 = { __parentId: 9, __treeLevel: 2, dateModified: '2015-02-26T16:50:00.123Z', file: 'todo.txt', id: 10, size: 0.4 };
- gridOptionMock.treeDataOptions.excludeChildrenWhenFilteringTree = false;
- gridOptionMock.treeDataOptions.autoApproveParentItemWhenTreeColumnIsValid = true;
+ gridOptionMock.treeDataOptions!.excludeChildrenWhenFilteringTree = false;
+ gridOptionMock.treeDataOptions!.autoApproveParentItemWhenTreeColumnIsValid = true;
service.init(gridStub);
service.bindLocalOnFilter(gridStub);
@@ -1954,7 +1961,7 @@ describe('FilterService', () => {
gridStub.onHeaderRowCellRendered.notify(mockArgs2 as any, new Slick.EventData(), gridStub);
const columnFilters = {
- file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['misc'], parsedSearchTerms: ['misc'], type: FieldType.string },
+ file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['misc'], parsedSearchTerms: ['misc'], targetSelector: '', type: FieldType.string },
} as ColumnFilters;
await service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['misc'] }], true, true, true);
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
@@ -1974,8 +1981,8 @@ describe('FilterService', () => {
.mockReturnValueOnce(dataset[6]);
const mockItem1 = { __parentId: 9, __treeLevel: 2, dateModified: '2015-02-26T16:50:00.123Z', file: 'todo.txt', id: 10, size: 0.4 };
- gridOptionMock.treeDataOptions.excludeChildrenWhenFilteringTree = false;
- gridOptionMock.treeDataOptions.autoApproveParentItemWhenTreeColumnIsValid = false;
+ gridOptionMock.treeDataOptions!.excludeChildrenWhenFilteringTree = false;
+ gridOptionMock.treeDataOptions!.autoApproveParentItemWhenTreeColumnIsValid = false;
service.init(gridStub);
service.bindLocalOnFilter(gridStub);
@@ -1984,7 +1991,7 @@ describe('FilterService', () => {
gridStub.onHeaderRowCellRendered.notify(mockArgs3 as any, new Slick.EventData(), gridStub);
const columnFilters = {
- size: { columnDef: mockColumn3, columnId: 'size', operator: '<', searchTerms: ['0.1'], parsedSearchTerms: ['0.1'], type: FieldType.string },
+ size: { columnDef: mockColumn3, columnId: 'size', operator: '<', searchTerms: ['0.1'], parsedSearchTerms: ['0.1'], targetSelector: '', type: FieldType.string },
} as ColumnFilters;
await service.updateFilters([{ columnId: 'size', operator: '<', searchTerms: ['0.1'] }], true, true, true);
const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters });
diff --git a/packages/common/src/services/domUtilities.ts b/packages/common/src/services/domUtilities.ts
index 9dce1c79e..534246b71 100644
--- a/packages/common/src/services/domUtilities.ts
+++ b/packages/common/src/services/domUtilities.ts
@@ -238,6 +238,14 @@ export function getHtmlElementOffset(element?: HTMLElement): HtmlElementPosition
return { top, left, bottom, right };
}
+export function getSelectorStringFromElement(elm?: HTMLElement | null) {
+ let selector = '';
+ if (elm?.localName) {
+ selector = elm?.className ? `${elm.localName}.${Array.from(elm.classList).join('.')}` : elm.localName;
+ }
+ return selector;
+}
+
export function findFirstElementAttribute(inputElm: Element | null | undefined, attributes: string[]): string | null {
if (inputElm) {
for (const attribute of attributes) {
diff --git a/packages/common/src/services/filter.service.ts b/packages/common/src/services/filter.service.ts
index 8550fe63b..32d5d6623 100644
--- a/packages/common/src/services/filter.service.ts
+++ b/packages/common/src/services/filter.service.ts
@@ -31,7 +31,7 @@ import {
SlickNamespace,
} from './../interfaces/index';
import { BackendUtilityService } from './backendUtility.service';
-import { sanitizeHtmlToText, } from '../services/domUtilities';
+import { getSelectorStringFromElement, sanitizeHtmlToText, } from '../services/domUtilities';
import { getDescendantProperty, mapOperatorByFieldType, } from './utilities';
import { SharedService } from './shared.service';
import { RxJsFacade, Subject } from './rxjsFacade';
@@ -669,6 +669,9 @@ export class FilterService {
if (columnFilter.operator) {
filter.operator = columnFilter.operator;
}
+ if (columnFilter.targetSelector) {
+ filter.targetSelector = columnFilter.targetSelector;
+ }
if (Array.isArray(filter.searchTerms) && filter.searchTerms.length > 0 && (!emptySearchTermReturnAllValues || filter.searchTerms[0] !== '')) {
currentFilters.push(filter);
}
@@ -1127,7 +1130,8 @@ export class FilterService {
columnId: colId,
columnDef,
parsedSearchTerms: [],
- type: fieldType
+ type: fieldType,
+ targetSelector: getSelectorStringFromElement(event?.target as HTMLElement | undefined)
};
const inputSearchConditions = this.parseFormInputFilterConditions(searchTerms, colFilter);
colFilter.operator = operator || inputSearchConditions.operator || mapOperatorByFieldType(fieldType);
diff --git a/packages/graphql/src/services/__tests__/graphql.service.spec.ts b/packages/graphql/src/services/__tests__/graphql.service.spec.ts
index 7666bfa3c..0d5533134 100644
--- a/packages/graphql/src/services/__tests__/graphql.service.spec.ts
+++ b/packages/graphql/src/services/__tests__/graphql.service.spec.ts
@@ -521,7 +521,7 @@ describe('GraphqlService', () => {
const querySpy = jest.spyOn(service, 'buildQuery');
const resetSpy = jest.spyOn(service, 'resetPaginationOptions');
const mockColumn = { id: 'gender', field: 'gender' } as Column;
- const mockColumnFilter = { columnDef: mockColumn, columnId: 'gender', operator: 'EQ', searchTerms: ['female'] } as ColumnFilter;
+ const mockColumnFilter = { columnDef: mockColumn, columnId: 'gender', operator: 'EQ', searchTerms: ['female'], targetSelector: 'div.some-classes' } as ColumnFilter;
const mockFilterChangedArgs = {
columnDef: mockColumn,
columnId: 'gender',
@@ -529,7 +529,8 @@ describe('GraphqlService', () => {
grid: gridStub,
operator: 'EQ',
searchTerms: ['female'],
- shouldTriggerQuery: true
+ shouldTriggerQuery: true,
+ targetSelector: 'div.some-classes'
} as FilterChangedArgs;
service.init(serviceOptions, paginationOptions, gridStub);
@@ -539,7 +540,7 @@ describe('GraphqlService', () => {
expect(removeSpaces(query)).toBe(removeSpaces(expectation));
expect(querySpy).toHaveBeenCalled();
expect(resetSpy).toHaveBeenCalled();
- expect(currentFilters).toEqual([{ columnId: 'gender', operator: 'EQ', searchTerms: ['female'] }]);
+ expect(currentFilters).toEqual([{ columnId: 'gender', operator: 'EQ', searchTerms: ['female'], targetSelector: 'div.some-classes' }]);
});
it('should return a query with a new filter when previous filters exists', () => {
@@ -550,7 +551,7 @@ describe('GraphqlService', () => {
const resetSpy = jest.spyOn(service, 'resetPaginationOptions');
const mockColumn = { id: 'gender', field: 'gender' } as Column;
const mockColumnName = { id: 'firstName', field: 'firstName' } as Column;
- const mockColumnFilter = { columnDef: mockColumn, columnId: 'gender', operator: 'EQ', searchTerms: ['female'] } as ColumnFilter;
+ const mockColumnFilter = { columnDef: mockColumn, columnId: 'gender', operator: 'EQ', searchTerms: ['female'], targetSelector: 'div.some-classes' } as ColumnFilter;
const mockColumnFilterName = { columnDef: mockColumnName, columnId: 'firstName', operator: 'StartsWith', searchTerms: ['John'] } as ColumnFilter;
const mockFilterChangedArgs = {
columnDef: mockColumn,
@@ -570,7 +571,7 @@ describe('GraphqlService', () => {
expect(querySpy).toHaveBeenCalled();
expect(resetSpy).toHaveBeenCalled();
expect(currentFilters).toEqual([
- { columnId: 'gender', operator: 'EQ', searchTerms: ['female'] },
+ { columnId: 'gender', operator: 'EQ', searchTerms: ['female'], targetSelector: 'div.some-classes' },
{ columnId: 'firstName', operator: 'StartsWith', searchTerms: ['John'] }
]);
});
diff --git a/packages/graphql/src/services/graphql.service.ts b/packages/graphql/src/services/graphql.service.ts
index e5e49f1b4..1b73b1271 100644
--- a/packages/graphql/src/services/graphql.service.ts
+++ b/packages/graphql/src/services/graphql.service.ts
@@ -632,6 +632,9 @@ export class GraphqlService implements BackendService {
if (filter.operator) {
tmpFilter.operator = filter.operator;
}
+ if (filter.targetSelector) {
+ tmpFilter.targetSelector = filter.targetSelector;
+ }
if (Array.isArray(filter.searchTerms)) {
tmpFilter.searchTerms = filter.searchTerms;
}
diff --git a/packages/odata/src/services/__tests__/grid-odata.service.spec.ts b/packages/odata/src/services/__tests__/grid-odata.service.spec.ts
index c977751e2..d8dd70fee 100644
--- a/packages/odata/src/services/__tests__/grid-odata.service.spec.ts
+++ b/packages/odata/src/services/__tests__/grid-odata.service.spec.ts
@@ -294,7 +294,8 @@ describe('GridOdataService', () => {
grid: gridStub,
operator: 'EQ',
searchTerms: ['female'],
- shouldTriggerQuery: true
+ shouldTriggerQuery: true,
+ targetSelector: 'div.some-classes'
} as FilterChangedArgs;
service.init(serviceOptions, paginationOptions, gridStub);
@@ -322,7 +323,8 @@ describe('GridOdataService', () => {
grid: gridStub,
operator: 'EQ',
searchTerms: ['female'],
- shouldTriggerQuery: true
+ shouldTriggerQuery: true,
+ targetSelector: 'div.some-classes'
} as FilterChangedArgs;
service.init(serviceOptions, paginationOptions, gridStub);
@@ -356,7 +358,8 @@ describe('GridOdataService', () => {
grid: gridStub,
operator: 'EQ',
searchTerms: ['female'],
- shouldTriggerQuery: true
+ shouldTriggerQuery: true,
+ targetSelector: 'div.some-classes'
} as FilterChangedArgs;
service.init(serviceOptions, paginationOptions, gridStub);
@@ -375,8 +378,8 @@ describe('GridOdataService', () => {
const resetSpy = jest.spyOn(service, 'resetPaginationOptions');
const mockColumn = { id: 'gender', field: 'gender' } as Column;
const mockColumnName = { id: 'firstName', field: 'firstName' } as Column;
- const mockColumnFilter = { columnDef: mockColumn, columnId: 'gender', operator: 'EQ', searchTerms: ['female'] } as ColumnFilter;
- const mockColumnFilterName = { columnDef: mockColumnName, columnId: 'firstName', operator: 'StartsWith', searchTerms: ['John'] } as ColumnFilter;
+ const mockColumnFilter = { columnDef: mockColumn, columnId: 'gender', operator: 'EQ', searchTerms: ['female'], targetSelector: 'div.some-classes' } as ColumnFilter;
+ const mockColumnFilterName = { columnDef: mockColumnName, columnId: 'firstName', operator: 'StartsWith', searchTerms: ['John'], targetSelector: 'div.some-classes' } as ColumnFilter;
const mockFilterChangedArgs = {
columnDef: mockColumn,
columnId: 'gender',
@@ -395,8 +398,8 @@ describe('GridOdataService', () => {
expect(querySpy).toHaveBeenCalled();
expect(resetSpy).toHaveBeenCalled();
expect(currentFilters).toEqual([
- { columnId: 'gender', operator: 'EQ', searchTerms: ['female'] },
- { columnId: 'firstName', operator: 'StartsWith', searchTerms: ['John'] }
+ { columnId: 'gender', operator: 'EQ', searchTerms: ['female'], targetSelector: 'div.some-classes' },
+ { columnId: 'firstName', operator: 'StartsWith', searchTerms: ['John'], targetSelector: 'div.some-classes' }
]);
});
});
@@ -1798,7 +1801,7 @@ describe('GridOdataService', () => {
serviceOptions.enableCount = true;
service.init(serviceOptions, paginationOptions, gridStub);
- service.postProcess({ '__count': 20 } );
+ service.postProcess({ '__count': 20 });
expect(paginationOptions.totalItems).toBe(20);
});
diff --git a/packages/odata/src/services/grid-odata.service.ts b/packages/odata/src/services/grid-odata.service.ts
index 8489dcd1a..e62e23fba 100644
--- a/packages/odata/src/services/grid-odata.service.ts
+++ b/packages/odata/src/services/grid-odata.service.ts
@@ -590,6 +590,9 @@ export class GridOdataService implements BackendService {
if (filter.operator) {
tmpFilter.operator = filter.operator;
}
+ if (filter.targetSelector) {
+ tmpFilter.targetSelector = filter.targetSelector;
+ }
if (Array.isArray(filter.searchTerms)) {
tmpFilter.searchTerms = filter.searchTerms;
}
diff --git a/test/cypress/e2e/example09.cy.js b/test/cypress/e2e/example09.cy.js
index 0b7020443..8aed405da 100644
--- a/test/cypress/e2e/example09.cy.js
+++ b/test/cypress/e2e/example09.cy.js
@@ -599,7 +599,7 @@ describe('Example 09 - OData Grid', { retries: 1 }, () => {
cy.window().then((win) => {
// expect(win.console.log).to.have.callCount(2);
- expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', operator: 'Contains', searchTerms: ['x'] }], type: 'filter' });
+ expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', operator: 'Contains', searchTerms: ['x'], targetSelector: 'input.form-control.filter-name.compound-input' }], type: 'filter' });
// expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: { pageNumber: 1, pageSize: 10 }, type: 'pagination' });
});
});
diff --git a/test/cypress/e2e/example15.cy.js b/test/cypress/e2e/example15.cy.js
index c9ab2c546..1bd1bfa42 100644
--- a/test/cypress/e2e/example15.cy.js
+++ b/test/cypress/e2e/example15.cy.js
@@ -563,7 +563,7 @@ describe('Example 15 - OData Grid using RxJS', { retries: 1 }, () => {
cy.window().then((win) => {
// expect(win.console.log).to.have.callCount(2);
- expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', operator: 'Contains', searchTerms: ['x'] }], type: 'filter' });
+ expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', operator: 'Contains', searchTerms: ['x'], targetSelector: 'input.form-control.filter-name.compound-input' }], type: 'filter' });
// expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: { pageNumber: 1, pageSize: 10 }, type: 'pagination' });
});
});