diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts index 92718e34c..65f8fed97 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts @@ -131,7 +131,7 @@ export class Example11 { initializeGrid() { this.columnDefinitions = [ { - id: 'title', name: 'Title', field: 'title', sortable: true, type: FieldType.string, + id: 'title', name: 'Title', field: 'title', sortable: true, editor: { model: Editors.text, massUpdate: true, required: true, alwaysSaveOnEnterKey: true, validator: myCustomTitleValidator, }, filterable: true, formatter: Formatters.multiple, params: { formatters: [Formatters.uppercase, Formatters.bold] }, diff --git a/packages/common/src/filters/__tests__/inputFilter.spec.ts b/packages/common/src/filters/__tests__/inputFilter.spec.ts index b2e7f71fa..ce899e5a3 100644 --- a/packages/common/src/filters/__tests__/inputFilter.spec.ts +++ b/packages/common/src/filters/__tests__/inputFilter.spec.ts @@ -69,69 +69,115 @@ describe('InputFilter', () => { expect(filterElm.placeholder).toBe(testValue); }); - it('should call "setValues" and expect that value to be in the callback when triggered', () => { - const spyCallback = jest.spyOn(filterArguments, 'callback'); + describe('setValues method', () => { + afterEach(() => { + filter.destroy(); + }); - filter.init(filterArguments); - filter.setValues('abc'); - const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; + it('should call "setValues" and expect that value to be in the callback when triggered', () => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filterElm.focus(); - filterElm.dispatchEvent(new (window.window as any).Event('keyup', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filter.init(filterArguments); + filter.setValues('abc'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; - expect(filterFilledElms.length).toBe(1); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); - }); + filterElm.focus(); + filterElm.dispatchEvent(new (window.window as any).Event('keyup', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); - it('should call "setValues" and expect that value to be in the callback when triggered by ENTER key', () => { - const spyCallback = jest.spyOn(filterArguments, 'callback'); + expect(filterFilledElms.length).toBe(1); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); + }); - filter.init(filterArguments); - filter.setValues('abc'); - const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; + it('should call "setValues" and expect that value to be in the callback when triggered by ENTER key', () => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filterElm.focus(); - const event = new (window.window as any).Event('keyup', { bubbles: true, cancelable: true }); - event.key = 'Enter'; - filterElm.dispatchEvent(event); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filter.init(filterArguments); + filter.setValues('abc'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; - expect(filterFilledElms.length).toBe(1); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); - }); + filterElm.focus(); + const event = new (window.window as any).Event('keyup', { bubbles: true, cancelable: true }); + event.key = 'Enter'; + filterElm.dispatchEvent(event); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); - it('should call "setValues" with an operator and with extra spaces at the beginning of the searchTerms and trim value when "enableFilterTrimWhiteSpace" is enabled in grid options', () => { - gridOptionMock.enableFilterTrimWhiteSpace = true; - const spyCallback = jest.spyOn(filterArguments, 'callback'); + expect(filterFilledElms.length).toBe(1); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); + }); - filter.init(filterArguments); - filter.setValues(' abc ', 'EQ'); - const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; + it('should call "setValues" with an operator and with extra spaces at the beginning of the searchTerms and trim value when "enableFilterTrimWhiteSpace" is enabled in grid options', () => { + gridOptionMock.enableFilterTrimWhiteSpace = true; + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filterElm.focus(); - filterElm.dispatchEvent(new (window.window as any).Event('keyup', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filter.init(filterArguments); + filter.setValues(' abc ', 'EQ'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; - expect(filterFilledElms.length).toBe(1); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['abc'], shouldTriggerQuery: true }); - }); + filterElm.focus(); + filterElm.dispatchEvent(new (window.window as any).Event('keyup', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); - it('should call "setValues" with extra spaces at the beginning of the searchTerms and trim value when "enableTrimWhiteSpace" is enabled in the column filter', () => { - gridOptionMock.enableFilterTrimWhiteSpace = false; - mockColumn.filter!.enableTrimWhiteSpace = true; - const spyCallback = jest.spyOn(filterArguments, 'callback'); + expect(filterFilledElms.length).toBe(1); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['abc'], shouldTriggerQuery: true }); + }); - filter.init(filterArguments); - filter.setValues(' abc '); - const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; + it('should call "setValues" with extra spaces at the beginning of the searchTerms and trim value when "enableTrimWhiteSpace" is enabled in the column filter', () => { + gridOptionMock.enableFilterTrimWhiteSpace = false; + mockColumn.filter!.enableTrimWhiteSpace = true; + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filterElm.focus(); - filterElm.dispatchEvent(new (window.window as any).Event('keyup', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filter.init(filterArguments); + filter.setValues(' abc '); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; + + filterElm.focus(); + filterElm.dispatchEvent(new (window.window as any).Event('keyup', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + + expect(filterFilledElms.length).toBe(1); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); + }); + + it('should call "setValues" and include an operator and expect the operator to show up in the output search string shown in the filter input text value', () => { + filter.init(filterArguments); + + filter.setValues('abc', '<>'); + expect(filter.getValue()).toBe('<>abc'); + + filter.setValues('abc', '!='); + expect(filter.getValue()).toBe('!=abc'); + + filter.setValues('abc', '='); + expect(filter.getValue()).toBe('=abc'); + + filter.setValues('abc', '=='); + expect(filter.getValue()).toBe('==abc'); + + filter.setValues(123, '<'); + expect(filter.getValue()).toBe('<123'); + + filter.setValues(123, '<='); + expect(filter.getValue()).toBe('<=123'); + + filter.setValues(123, '>'); + expect(filter.getValue()).toBe('>123'); + + filter.setValues(123, '>='); + expect(filter.getValue()).toBe('>=123'); + + filter.setValues('abc', 'EndsWith'); + expect(filter.getValue()).toBe('*abc'); + + filter.setValues('abc', '*z'); + expect(filter.getValue()).toBe('*abc'); + + filter.setValues('abc', 'StartsWith'); + expect(filter.getValue()).toBe('abc*'); - expect(filterFilledElms.length).toBe(1); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); + filter.setValues('abc', 'a*'); + expect(filter.getValue()).toBe('abc*'); + }); }); it('should trigger the callback method when user types something in the input', () => { diff --git a/packages/common/src/filters/inputFilter.ts b/packages/common/src/filters/inputFilter.ts index 235ae782a..f5099efe3 100644 --- a/packages/common/src/filters/inputFilter.ts +++ b/packages/common/src/filters/inputFilter.ts @@ -115,10 +115,17 @@ export class InputFilter implements Filter { this.$filterElm = null; } + getValue() { + return this.$filterElm.val(); + } + /** Set value(s) on the DOM element */ setValues(values: SearchTerm | SearchTerm[], operator?: OperatorType | OperatorString) { - if (values) { - this.$filterElm.val(values); + const searchValues = Array.isArray(values) ? values : [values]; + let searchValue: SearchTerm = ''; + for (const value of searchValues) { + searchValue = operator ? this.addOptionalOperatorIntoSearchString(value, operator) : value; + this.$filterElm.val(searchValue); } // set the operator when defined @@ -129,6 +136,47 @@ export class InputFilter implements Filter { // protected functions // ------------------ + /** + * When loading the search string from the outside into the input text field, we should also add the prefix/suffix of the operator. + * We do this so that if it was loaded by a Grid Presets then we should also add the operator into the search string + * Let's take these 3 examples: + * 1. (operator: '>=', searchTerms:[55]) should display as ">=55" + * 2. (operator: 'StartsWith', searchTerms:['John']) should display as "John*" + * 3. (operator: 'EndsWith', searchTerms:['John']) should display as "*John" + * @param operator - operator string + */ + protected addOptionalOperatorIntoSearchString(inputValue: SearchTerm, operator: OperatorType | OperatorString): string { + let searchTermPrefix = ''; + let searchTermSuffix = ''; + let outputValue = inputValue === undefined || inputValue === null ? '' : `${inputValue}`; + + if (operator && outputValue) { + switch (operator) { + case '<>': + case '!=': + case '=': + case '==': + case '>': + case '>=': + case '<': + case '<=': + searchTermPrefix = operator; + break; + case 'EndsWith': + case '*z': + searchTermPrefix = '*'; + break; + case 'StartsWith': + case 'a*': + searchTermSuffix = '*'; + break; + } + outputValue = `${searchTermPrefix}${outputValue}${searchTermSuffix}`; + } + + return outputValue; + } + /** * Create the HTML template as a string */ diff --git a/packages/common/src/services/filter.service.ts b/packages/common/src/services/filter.service.ts index 11511e742..9a641a024 100644 --- a/packages/common/src/services/filter.service.ts +++ b/packages/common/src/services/filter.service.ts @@ -855,7 +855,7 @@ export class FilterService { let searchTerms: SearchTerm[] | undefined; let operator: OperatorString | OperatorType | undefined; const newFilter: Filter | undefined = this.filterFactory.createFilter(columnDef.filter); - operator = (columnDef && columnDef.filter && columnDef.filter.operator) || (newFilter && newFilter.operator); + operator = columnDef?.filter?.operator ?? newFilter?.operator; if (this._columnFilters[columnDef.id]) { searchTerms = this._columnFilters[columnDef.id].searchTerms || undefined; @@ -889,7 +889,7 @@ export class FilterService { // when hiding/showing (with Column Picker or Grid Menu), it will try to re-create yet again the filters (since SlickGrid does a re-render) // we need to also set again the values in the DOM elements if the values were set by a searchTerm(s) if (searchTerms && newFilter.setValues) { - newFilter.setValues(searchTerms); + newFilter.setValues(searchTerms, operator); } } }