diff --git a/packages/common/src/editors/dualInputEditor.ts b/packages/common/src/editors/dualInputEditor.ts index 23172aa07..be4ed04fb 100644 --- a/packages/common/src/editors/dualInputEditor.ts +++ b/packages/common/src/editors/dualInputEditor.ts @@ -15,7 +15,7 @@ import { SlickGrid, SlickNamespace, } from '../interfaces/index'; -import { debounce, getDescendantProperty, setDeepValue } from '../services/utilities'; +import { getDescendantProperty, setDeepValue } from '../services/utilities'; import { floatValidator, integerValidator, textValidator } from '../editorValidators'; import { BindingEventService } from '../services/bindingEvent.service'; @@ -40,6 +40,7 @@ export class DualInputEditor implements Editor { protected _rightFieldName!: string; protected _originalLeftValue!: string | number; protected _originalRightValue!: string | number; + protected _timer?: NodeJS.Timeout; /** is the Editor disabled? */ disabled = false; @@ -538,7 +539,8 @@ export class DualInputEditor implements Editor { const compositeEditorOptions = this.args?.compositeEditorOptions; if (compositeEditorOptions) { const typingDelay = this.gridOptions?.editorTypingDebounce ?? 500; - debounce(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay)(); + clearTimeout(this._timer as NodeJS.Timeout); + this._timer = setTimeout(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay); } } } diff --git a/packages/common/src/editors/floatEditor.ts b/packages/common/src/editors/floatEditor.ts index a91ee7929..2035342b4 100644 --- a/packages/common/src/editors/floatEditor.ts +++ b/packages/common/src/editors/floatEditor.ts @@ -1,6 +1,6 @@ import { KeyCode } from '../enums/index'; import { Column, ColumnEditor, CompositeEditorOption, Editor, EditorArguments, EditorValidator, EditorValidationResult, GridOption, SlickGrid, SlickNamespace, } from '../interfaces/index'; -import { debounce, getDescendantProperty, setDeepValue } from '../services/utilities'; +import { getDescendantProperty, setDeepValue } from '../services/utilities'; import { floatValidator } from '../editorValidators/floatValidator'; import { BindingEventService } from '../services/bindingEvent.service'; @@ -19,6 +19,7 @@ export class FloatEditor implements Editor { protected _isValueTouched = false; protected _lastInputKeyEvent?: KeyboardEvent; protected _originalValue?: number | string; + protected _timer?: NodeJS.Timeout; /** is the Editor disabled? */ disabled = false; @@ -367,7 +368,8 @@ export class FloatEditor implements Editor { const compositeEditorOptions = this.args.compositeEditorOptions; if (compositeEditorOptions) { const typingDelay = this.gridOptions?.editorTypingDebounce ?? 500; - debounce(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay)(); + clearTimeout(this._timer as NodeJS.Timeout); + this._timer = setTimeout(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay); } } } diff --git a/packages/common/src/editors/integerEditor.ts b/packages/common/src/editors/integerEditor.ts index 315e39ad2..0a19444e4 100644 --- a/packages/common/src/editors/integerEditor.ts +++ b/packages/common/src/editors/integerEditor.ts @@ -1,6 +1,6 @@ import { KeyCode } from '../enums/index'; import { Column, ColumnEditor, CompositeEditorOption, Editor, EditorArguments, EditorValidator, EditorValidationResult, GridOption, SlickGrid, SlickNamespace, } from './../interfaces/index'; -import { debounce, getDescendantProperty, setDeepValue } from '../services/utilities'; +import { getDescendantProperty, setDeepValue } from '../services/utilities'; import { integerValidator } from '../editorValidators/integerValidator'; import { BindingEventService } from '../services/bindingEvent.service'; @@ -17,6 +17,7 @@ export class IntegerEditor implements Editor { protected _isValueTouched = false; protected _input!: HTMLInputElement | undefined; protected _originalValue?: number | string; + protected _timer?: NodeJS.Timeout; /** is the Editor disabled? */ disabled = false; @@ -330,7 +331,8 @@ export class IntegerEditor implements Editor { const compositeEditorOptions = this.args.compositeEditorOptions; if (compositeEditorOptions) { const typingDelay = this.gridOptions?.editorTypingDebounce ?? 500; - debounce(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay)(); + clearTimeout(this._timer as NodeJS.Timeout); + this._timer = setTimeout(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay); } } } diff --git a/packages/common/src/editors/longTextEditor.ts b/packages/common/src/editors/longTextEditor.ts index ee9b11e7d..eeca7ccfc 100644 --- a/packages/common/src/editors/longTextEditor.ts +++ b/packages/common/src/editors/longTextEditor.ts @@ -15,7 +15,7 @@ import { SlickGrid, SlickNamespace, } from '../interfaces/index'; -import { debounce, getDescendantProperty, getHtmlElementOffset, getTranslationPrefix, setDeepValue, } from '../services/utilities'; +import { getDescendantProperty, getHtmlElementOffset, getTranslationPrefix, setDeepValue, } from '../services/utilities'; import { TranslaterService } from '../services/translater.service'; import { textValidator } from '../editorValidators/textValidator'; @@ -31,6 +31,7 @@ export class LongTextEditor implements Editor { protected _defaultTextValue: any; protected _isValueTouched = false; protected _locales: Locale; + protected _timer?: NodeJS.Timeout; protected _$textarea: any; protected _$currentLengthElm: any; protected _$wrapper: any; @@ -432,7 +433,8 @@ export class LongTextEditor implements Editor { // when using a Composite Editor, we'll want to add a debounce delay to avoid perf issue since Composite could affect other editors in the same form if (compositeEditorOptions) { const typingDelay = this.gridOptions?.editorTypingDebounce ?? 500; - debounce(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay)(); + clearTimeout(this._timer as NodeJS.Timeout); + this._timer = setTimeout(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay); } } diff --git a/packages/common/src/editors/textEditor.ts b/packages/common/src/editors/textEditor.ts index 8217a7c7b..e4f4b0a4d 100644 --- a/packages/common/src/editors/textEditor.ts +++ b/packages/common/src/editors/textEditor.ts @@ -1,6 +1,6 @@ import { KeyCode } from '../enums/keyCode.enum'; import { Column, ColumnEditor, CompositeEditorOption, Editor, EditorArguments, EditorValidator, EditorValidationResult, GridOption, SlickGrid, SlickNamespace, } from '../interfaces/index'; -import { debounce, getDescendantProperty, setDeepValue } from '../services/utilities'; +import { getDescendantProperty, setDeepValue } from '../services/utilities'; import { textValidator } from '../editorValidators/textValidator'; import { BindingEventService } from '../services/bindingEvent.service'; @@ -17,6 +17,7 @@ export class TextEditor implements Editor { protected _isValueTouched = false; protected _lastInputKeyEvent?: KeyboardEvent; protected _originalValue?: string; + protected _timer?: NodeJS.Timeout; /** is the Editor disabled? */ disabled = false; @@ -308,7 +309,8 @@ export class TextEditor implements Editor { const compositeEditorOptions = this.args.compositeEditorOptions; if (compositeEditorOptions) { const typingDelay = this.gridOptions?.editorTypingDebounce ?? 500; - debounce(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay)(); + clearTimeout(this._timer as NodeJS.Timeout); + this._timer = setTimeout(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay); } } } diff --git a/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts b/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts index 042893e6c..1ba324a70 100644 --- a/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts @@ -1,5 +1,5 @@ import { FieldType, OperatorType } from '../../enums/index'; -import { Column, FilterArguments, GridOption, SlickGrid } from '../../interfaces/index'; +import { BackendServiceApi, Column, FilterArguments, GridOption, SlickGrid } from '../../interfaces/index'; import { Filters } from '../index'; import { CompoundInputFilter } from '../compoundInputFilter'; import { TranslateServiceStub } from '../../../../../test/translateServiceStub'; @@ -215,6 +215,44 @@ describe('CompoundInputFilter', () => { expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['a'], shouldTriggerQuery: true }); }); + it('should trigger the callback method with a delay when "filterTypingDebounce" is set in grid options and user types something in the input', (done) => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); + gridOptionMock.filterTypingDebounce = 2; + + filter.init(filterArguments); + const filterInputElm = divContainer.querySelector('.search-filter.filter-duration input') as HTMLInputElement; + + filterInputElm.focus(); + filterInputElm.value = 'a'; + filterInputElm.dispatchEvent(new (window.window as any).Event('input', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); + + setTimeout(() => { + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['a'], shouldTriggerQuery: true }); + done(); + }, 2); + }); + + it('should trigger the callback method with a delay when BackendService is used with a "filterTypingDebounce" is set in grid options and user types something in the input', (done) => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); + gridOptionMock.defaultBackendServiceFilterTypingDebounce = 2; + gridOptionMock.backendServiceApi = { + filterTypingDebounce: 2, + service: {} + } as unknown as BackendServiceApi; + + filter.init(filterArguments); + const filterInputElm = divContainer.querySelector('.search-filter.filter-duration input') as HTMLInputElement; + + filterInputElm.focus(); + filterInputElm.value = 'a'; + filterInputElm.dispatchEvent(new (window.window as any).Event('input', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); + + setTimeout(() => { + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['a'], shouldTriggerQuery: true }); + done(); + }, 2); + }); + it('should create the input filter with a default search term when passed as a filter argument', () => { filterArguments.searchTerms = ['xyz']; diff --git a/packages/common/src/filters/__tests__/inputFilter.spec.ts b/packages/common/src/filters/__tests__/inputFilter.spec.ts index ae1b26f65..aa7dae6d9 100644 --- a/packages/common/src/filters/__tests__/inputFilter.spec.ts +++ b/packages/common/src/filters/__tests__/inputFilter.spec.ts @@ -1,5 +1,5 @@ import { InputFilter } from '../inputFilter'; -import { GridOption, FilterArguments, Column, SlickGrid } from '../../interfaces/index'; +import { GridOption, FilterArguments, Column, SlickGrid, BackendServiceApi } from '../../interfaces/index'; import { Filters } from '..'; const containerId = 'demo-container'; @@ -164,6 +164,43 @@ describe('InputFilter', () => { expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['a'], shouldTriggerQuery: true }); }); + it('should trigger the callback method with a delay when "filterTypingDebounce" is set in grid options and user types something in the input', (done) => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); + gridOptionMock.filterTypingDebounce = 2; + + filter.init(filterArguments); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; + + filterElm.focus(); + filterElm.value = 'a'; + filterElm.dispatchEvent(new (window.window as any).Event('input', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); + + setTimeout(() => { + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['a'], shouldTriggerQuery: true }); + done(); + }, 2); + }); + + it('should trigger the callback method with a delay when BackendService is used with a "filterTypingDebounce" is set in grid options and user types something in the input', (done) => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); + gridOptionMock.defaultBackendServiceFilterTypingDebounce = 2; + gridOptionMock.backendServiceApi = { + service: {} + } as unknown as BackendServiceApi; + + filter.init(filterArguments); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; + + filterElm.focus(); + filterElm.value = 'a'; + filterElm.dispatchEvent(new (window.window as any).Event('input', { key: 'a', keyCode: 97, bubbles: true, cancelable: true })); + + setTimeout(() => { + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['a'], shouldTriggerQuery: true }); + done(); + }, 2); + }); + it('should create the input filter with a default search term when passed as a filter argument', () => { filterArguments.searchTerms = ['xyz']; diff --git a/packages/common/src/filters/compoundInputFilter.ts b/packages/common/src/filters/compoundInputFilter.ts index 190e4f030..511612cdf 100644 --- a/packages/common/src/filters/compoundInputFilter.ts +++ b/packages/common/src/filters/compoundInputFilter.ts @@ -16,6 +16,7 @@ import { TranslaterService } from '../services/translater.service'; export class CompoundInputFilter implements Filter { protected _clearFilterTriggered = false; + protected _debounceTypingDelay = 0; protected _shouldTriggerQuery = true; protected _inputType = 'text'; protected $filterElm: any; @@ -26,6 +27,7 @@ export class CompoundInputFilter implements Filter { searchTerms: SearchTerm[] = []; columnDef!: Column; callback!: FilterCallback; + timer?: NodeJS.Timeout; constructor(protected readonly translaterService: TranslaterService) { } @@ -83,6 +85,11 @@ export class CompoundInputFilter implements Filter { this.operator = args.operator || ''; this.searchTerms = (args.hasOwnProperty('searchTerms') ? args.searchTerms : []) || []; + // analyze if we have any keyboard debounce delay (do we wait for user to finish typing before querying) + // it is used by default for a backend service but is optional when using local dataset + const backendApi = this.gridOptions?.backendServiceApi; + this._debounceTypingDelay = (backendApi ? (backendApi?.filterTypingDebounce ?? this.gridOptions?.defaultBackendServiceFilterTypingDebounce) : this.gridOptions?.filterTypingDebounce) ?? 0; + // filter input can only have 1 search term, so we will use the 1st array index if it exist const searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms.length >= 0) ? this.searchTerms[0] : ''; @@ -92,7 +99,7 @@ export class CompoundInputFilter implements Filter { // step 3, subscribe to the input change event and run the callback when that happens // also add/remove "filled" class for styling purposes - this.$filterInputElm.on('keyup input', this.onTriggerEvent.bind(this)); + this.$filterInputElm.on('keyup input blur', this.onTriggerEvent.bind(this)); this.$selectOperatorElm.on('change', this.onTriggerEvent.bind(this)); } @@ -246,14 +253,14 @@ export class CompoundInputFilter implements Filter { } /** Event trigger, could be called by the Operator dropdown or the input itself */ - protected onTriggerEvent(e: KeyboardEvent | undefined) { + protected onTriggerEvent(event: KeyboardEvent | undefined) { // we'll use the "input" event for everything (keyup, change, mousewheel & spinner) // with 1 small exception, we need to use the keyup event to handle ENTER key, everything will be processed by the "input" event - if (e && e.type === 'keyup' && e.key !== 'Enter') { + if (event && event.type === 'keyup' && event.key !== 'Enter') { return; } if (this._clearFilterTriggered) { - this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery }); + this.callback(event, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery }); this.$filterElm.removeClass('filled'); } else { const selectedOperator = this.$selectOperatorElm.find('option:selected').val(); @@ -264,7 +271,15 @@ export class CompoundInputFilter implements Filter { } (value !== null && value !== undefined && value !== '') ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled'); - this.callback(e, { columnDef: this.columnDef, searchTerms: (value ? [value] : null), operator: selectedOperator || '', shouldTriggerQuery: this._shouldTriggerQuery }); + const callbackArgs = { columnDef: this.columnDef, searchTerms: (value ? [value] : null), operator: selectedOperator || '', shouldTriggerQuery: this._shouldTriggerQuery }; + const typingDelay = (event?.key === 'Enter' || event?.type === 'blur') ? 0 : this._debounceTypingDelay; + + if (typingDelay > 0) { + clearTimeout(this.timer as NodeJS.Timeout); + this.timer = setTimeout(() => this.callback(event, callbackArgs), typingDelay); + } else { + this.callback(event, callbackArgs); + } } // reset both flags for next use diff --git a/packages/common/src/filters/inputFilter.ts b/packages/common/src/filters/inputFilter.ts index 7977245f7..e7d39a1c5 100644 --- a/packages/common/src/filters/inputFilter.ts +++ b/packages/common/src/filters/inputFilter.ts @@ -11,8 +11,10 @@ import { OperatorType, OperatorString, SearchTerm } from '../enums/index'; export class InputFilter implements Filter { protected _clearFilterTriggered = false; + protected _debounceTypingDelay = 0; protected _shouldTriggerQuery = true; protected _inputType = 'text'; + protected _timer?: NodeJS.Timeout; protected $filterElm: any; grid!: SlickGrid; searchTerms: SearchTerm[] = []; @@ -70,6 +72,11 @@ export class InputFilter implements Filter { this.columnDef = args.columnDef; this.searchTerms = (args.hasOwnProperty('searchTerms') ? args.searchTerms : []) || []; + // analyze if we have any keyboard debounce delay (do we wait for user to finish typing before querying) + // it is used by default for a backend service but is optional when using local dataset + const backendApi = this.gridOptions?.backendServiceApi; + this._debounceTypingDelay = (backendApi ? (backendApi?.filterTypingDebounce ?? this.gridOptions?.defaultBackendServiceFilterTypingDebounce) : this.gridOptions?.filterTypingDebounce) ?? 0; + // filter input can only have 1 search term, so we will use the 1st array index if it exist const searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms.length >= 0) ? this.searchTerms[0] : ''; @@ -81,7 +88,7 @@ export class InputFilter implements Filter { // step 3, subscribe to the input event and run the callback when that happens // also add/remove "filled" class for styling purposes - this.$filterElm.on('keyup input', this.handleInputChange.bind(this)); + this.$filterElm.on('keyup input blur', this.handleInputChange.bind(this)); } /** @@ -161,24 +168,32 @@ export class InputFilter implements Filter { return $filterElm; } - protected handleInputChange(e: any) { + protected handleInputChange(event: KeyboardEvent & { target: any; }) { // we'll use the "input" event for everything (keyup, change, mousewheel & spinner) // with 1 small exception, we need to use the keyup event to handle ENTER key, everything will be processed by the "input" event - if (e && e.type === 'keyup' && e.key !== 'Enter') { + if (event && event.type === 'keyup' && event.key !== 'Enter') { return; } - let value = e && e.target && e.target.value || ''; - const enableWhiteSpaceTrim = this.gridOptions.enableFilterTrimWhiteSpace || this.columnFilter.enableTrimWhiteSpace; - if (typeof value === 'string' && enableWhiteSpaceTrim) { - value = value.trim(); - } if (this._clearFilterTriggered) { - this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery }); + this.callback(event, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery }); this.$filterElm.removeClass('filled'); } else { + let value = event?.target?.value ?? ''; + const enableWhiteSpaceTrim = this.gridOptions.enableFilterTrimWhiteSpace || this.columnFilter.enableTrimWhiteSpace; + if (typeof value === 'string' && enableWhiteSpaceTrim) { + value = value.trim(); + } value === '' ? this.$filterElm.removeClass('filled') : this.$filterElm.addClass('filled'); - this.callback(e, { columnDef: this.columnDef, operator: this.operator, searchTerms: [value], shouldTriggerQuery: this._shouldTriggerQuery }); + const callbackArgs = { columnDef: this.columnDef, operator: this.operator, searchTerms: [value], shouldTriggerQuery: this._shouldTriggerQuery }; + const typingDelay = (event?.key === 'Enter' || event?.type === 'blur') ? 0 : this._debounceTypingDelay; + + if (typingDelay > 0) { + clearTimeout(this._timer as NodeJS.Timeout); + this._timer = setTimeout(() => this.callback(event, callbackArgs), typingDelay); + } else { + this.callback(event, callbackArgs); + } } // reset both flags for next use diff --git a/packages/common/src/global-grid-options.ts b/packages/common/src/global-grid-options.ts index 86ba82d89..1a4d96514 100644 --- a/packages/common/src/global-grid-options.ts +++ b/packages/common/src/global-grid-options.ts @@ -92,6 +92,7 @@ export const GlobalGridOptions: GridOption = { }, datasetIdPropertyName: 'id', defaultFilter: Filters.input, + defaultBackendServiceFilterTypingDebounce: 500, enableFilterTrimWhiteSpace: false, // do we want to trim white spaces on all Filters? defaultFilterPlaceholder: '🔍', defaultFilterRangeOperator: OperatorType.rangeInclusive, @@ -100,6 +101,7 @@ export const GlobalGridOptions: GridOption = { defaultSlickgridEventPrefix: '', editable: false, editorTypingDebounce: 450, + filterTypingDebounce: 0, enableEmptyDataWarningMessage: true, emptyDataWarning: { className: 'slick-empty-data-warning', diff --git a/packages/common/src/interfaces/backendServiceApi.interface.ts b/packages/common/src/interfaces/backendServiceApi.interface.ts index 6784cdc10..50e107c46 100644 --- a/packages/common/src/interfaces/backendServiceApi.interface.ts +++ b/packages/common/src/interfaces/backendServiceApi.interface.ts @@ -1,7 +1,7 @@ import { BackendService } from './backendService.interface'; export interface BackendServiceApi { - /** How long to wait until we start querying backend to avoid sending too many requests to backend server. Default to 750ms */ + /** How long to wait until we start querying backend to avoid sending too many requests to backend server. Default to 500ms */ filterTypingDebounce?: number; /** Backend Service Options */ diff --git a/packages/common/src/interfaces/gridOption.interface.ts b/packages/common/src/interfaces/gridOption.interface.ts index 43a5ab2df..a55aaa32e 100644 --- a/packages/common/src/interfaces/gridOption.interface.ts +++ b/packages/common/src/interfaces/gridOption.interface.ts @@ -150,6 +150,9 @@ export interface GridOption { syncGridSelectionWithBackendService?: boolean; }; + /** Defaults to 500, how long to wait between each characters that the user types before processing the filtering process when using a Backend Service? */ + defaultBackendServiceFilterTypingDebounce?: number; + /** Defaults to 'id', what is the default column field id to sort when calling clear sorting */ defaultColumnSortFieldId?: string; @@ -339,6 +342,12 @@ export interface GridOption { /** @deprecated Please use "textExportOptions" Some default options to set for the export service */ exportOptions?: TextExportOption; + /** + * Default to 0ms, how long to wait between each characters that the user types before processing the filtering process (only applies for local/in-memory grid). + * NOTE: please note that the BackendServiceApi has its own `filterTypingDebounce` within the `BackendServiceApi` options which is set to 500ms. + */ + filterTypingDebounce?: number; + /** Defaults to 25, which is the grid footer row panel height */ footerRowHeight?: number; diff --git a/packages/common/src/services/__tests__/filter.service.spec.ts b/packages/common/src/services/__tests__/filter.service.spec.ts index 71f15da13..6fe0dff97 100644 --- a/packages/common/src/services/__tests__/filter.service.spec.ts +++ b/packages/common/src/services/__tests__/filter.service.spec.ts @@ -908,25 +908,6 @@ describe('FilterService', () => { expect(spy).toHaveBeenCalled(); }); - - it('should execute "processOnFilterChanged" method when "shouldTriggerQuery" is set to True and "debounceTypingDelay" is bigger than 0', () => { - jest.useFakeTimers(); - gridOptionMock.backendServiceApi!.filterTypingDebounce = 50; - const spy = jest.spyOn(gridOptionMock.backendServiceApi!.service, 'processOnFilterChanged').mockReturnValue('backend query'); - - service.init(gridStub); - const mockEvent = new CustomEvent('input'); - Object.defineProperty(mockEvent, 'target', { writable: true, configurable: true, value: { value: 'John' } }); - service.onBackendFilterChange(mockEvent as any, { grid: gridStub, shouldTriggerQuery: true }); - - expect(spy).not.toHaveBeenCalled(); - - jest.runTimersToTime(49); - expect(spy).not.toHaveBeenCalled(); - - jest.runTimersToTime(50); - expect(spy).toHaveBeenCalled(); - }); }); describe('populateColumnFilterSearchTermPresets method', () => { diff --git a/packages/common/src/services/__tests__/utilities.spec.ts b/packages/common/src/services/__tests__/utilities.spec.ts index cf3c12e01..4afc7f7a1 100644 --- a/packages/common/src/services/__tests__/utilities.spec.ts +++ b/packages/common/src/services/__tests__/utilities.spec.ts @@ -9,7 +9,6 @@ import { convertHierarchicalViewToParentChildArray, convertParentChildArrayToHierarchicalView, decimalFormatted, - debounce, deepCopy, emptyElement, emptyObject, @@ -338,25 +337,6 @@ describe('Service/Utilies', () => { }); }); - describe('debounce method', () => { - it('should execute a function after a given waiting time', () => { - jest.useFakeTimers(); - - const func = jest.fn(); - const debouncedFunction = debounce(func, 100); - - debouncedFunction(); - expect(func).not.toBeCalled(); - - jest.runTimersToTime(50); - expect(func).not.toBeCalled(); - - jest.runTimersToTime(100); - expect(func).toBeCalled(); - expect(func.mock.calls.length).toBe(1); - }); - }); - describe('deepCopy method', () => { it('should return original input when it is not an object neither an array', () => { const msg = 'hello world'; diff --git a/packages/common/src/services/filter.service.ts b/packages/common/src/services/filter.service.ts index 9a61ee174..536304dd1 100644 --- a/packages/common/src/services/filter.service.ts +++ b/packages/common/src/services/filter.service.ts @@ -30,16 +30,13 @@ import { SlickNamespace, } from './../interfaces/index'; import { executeBackendCallback, refreshBackendDataset } from './backend-utilities'; -import { debounce, deepCopy, getDescendantProperty, mapOperatorByFieldType } from './utilities'; +import { deepCopy, getDescendantProperty, mapOperatorByFieldType } from './utilities'; import { PubSubService } from '../services/pubSub.service'; import { SharedService } from './shared.service'; // using external non-typed js libraries declare const Slick: SlickNamespace; -// timer for keeping track of user typing waits -const DEFAULT_BACKEND_FILTER_TYPING_DEBOUNCE = 500; - interface OnSearchChangeEvent { clearFilterTriggered?: boolean; shouldTriggerQuery?: boolean; @@ -629,30 +626,11 @@ export class FilterService { backendApi.preProcess(); } - // only add a delay when user is typing, on select dropdown filter (or "Clear Filter") it will execute right away - let debounceTypingDelay = 0; - const isTriggeredByClearFilter = args && args.clearFilterTriggered; // was it trigger by a "Clear Filter" command? - - const eventType = event && event.type; - const eventKeyCode = event && event.keyCode; - if (!isTriggeredByClearFilter && eventKeyCode !== KeyCode.ENTER && (eventType === 'input' || eventType === 'keyup' || eventType === 'keydown')) { - debounceTypingDelay = backendApi?.filterTypingDebounce ?? DEFAULT_BACKEND_FILTER_TYPING_DEBOUNCE; - } - // query backend, except when it's called by a ClearFilters then we won't if (args?.shouldTriggerQuery) { - // call the service to get a query back - if (debounceTypingDelay > 0) { - debounce(() => { - const query = backendApi.service.processOnFilterChanged(event, args); - const totalItems = this._gridOptions && this._gridOptions.pagination && this._gridOptions.pagination.totalItems || 0; - executeBackendCallback(backendApi, query, args, startTime, totalItems, this.emitFilterChanged.bind(this)); - }, debounceTypingDelay)(); - } else { - const query = backendApi.service.processOnFilterChanged(event, args); - const totalItems = this._gridOptions && this._gridOptions.pagination && this._gridOptions.pagination.totalItems || 0; - executeBackendCallback(backendApi, query, args, startTime, totalItems, this.emitFilterChanged.bind(this)); - } + const query = await backendApi.service.processOnFilterChanged(event, args); + const totalItems = this._gridOptions && this._gridOptions.pagination && this._gridOptions.pagination.totalItems || 0; + executeBackendCallback(backendApi, query, args, startTime, totalItems, this.emitFilterChanged.bind(this)); } } diff --git a/packages/common/src/services/utilities.ts b/packages/common/src/services/utilities.ts index 259e28cd5..99bd1d838 100644 --- a/packages/common/src/services/utilities.ts +++ b/packages/common/src/services/utilities.ts @@ -142,22 +142,6 @@ export function convertHierarchicalViewToParentChildArrayByReference(hi } } -/** - * Debounce create a new function g, which when called will delay the invocation of the original function f until n milliseconds after it was last called. - * @param func: function to debounce - * @param waitFor: waiting time in milliseconds - */ -export function debounce any | void)>(func: F, waitFor: number) { - const timeout = 0; - - const debounced = (...args: any) => { - clearTimeout(timeout); - setTimeout(() => func(...args), waitFor); - }; - - return debounced as (...args: Parameters) => ReturnType; -} - /** * Create an immutable clone of an array or object * (c) 2019 Chris Ferdinandi, MIT License, https://gomakethings.com diff --git a/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip b/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip index 9dacc07c5..1cc89d514 100644 Binary files a/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip and b/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip differ diff --git a/packages/vanilla-bundle/src/salesforce-global-grid-options.ts b/packages/vanilla-bundle/src/salesforce-global-grid-options.ts index 8c05ea5b4..92deb3a3c 100644 --- a/packages/vanilla-bundle/src/salesforce-global-grid-options.ts +++ b/packages/vanilla-bundle/src/salesforce-global-grid-options.ts @@ -26,6 +26,7 @@ export const SalesforceGlobalGridOptions = { sanitizeDataExport: true, }, enableCellNavigation: true, + filterTypingDebounce: 250, formatterOptions: { minDecimal: 0, maxDecimal: 2,