From 3a80996bec96e465d23a30387af707289f4089e3 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 27 May 2021 23:54:44 -0400 Subject: [PATCH] feat(filters): convert jQuery to native elements on multiple filters --- .../common/src/filters/compoundDateFilter.ts | 16 +-- .../common/src/filters/compoundInputFilter.ts | 128 ++++++++++-------- .../common/src/filters/dateRangeFilter.ts | 4 +- packages/common/src/filters/inputFilter.ts | 96 +++++++------ .../common/src/filters/inputMaskFilter.ts | 71 +++++----- .../common/src/filters/nativeSelectFilter.ts | 2 +- .../services/__tests__/filter.service.spec.ts | 9 +- 7 files changed, 171 insertions(+), 155 deletions(-) diff --git a/packages/common/src/filters/compoundDateFilter.ts b/packages/common/src/filters/compoundDateFilter.ts index 31532d7f4..014bc6fd2 100644 --- a/packages/common/src/filters/compoundDateFilter.ts +++ b/packages/common/src/filters/compoundDateFilter.ts @@ -140,12 +140,8 @@ export class CompoundDateFilter implements Filter { destroyObjectDomElementProps(this.flatInstance); } } - if (this._filterElm) { - this._filterElm.remove(); - } - if (this._selectOperatorElm) { - this._selectOperatorElm.remove(); - } + this._filterElm?.remove?.(); + this._selectOperatorElm?.remove?.(); } hide() { @@ -225,14 +221,16 @@ export class CompoundDateFilter implements Filter { }; // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) - if (outputFormat && (outputFormat === 'Z' || outputFormat.toLowerCase().indexOf('h') > -1)) { + if (outputFormat && (outputFormat === 'Z' || outputFormat.toLowerCase().includes('h'))) { pickerOptions.enableTime = true; } // merge options with optional user's custom options this._flatpickrOptions = { ...pickerOptions, ...userFilterOptions }; - let placeholder = (this.gridOptions) ? (this.gridOptions.defaultFilterPlaceholder || '') : ''; - if (this.columnFilter && this.columnFilter.placeholder) { + + // create the DOM element & add an ID and filter class + let placeholder = this.gridOptions?.defaultFilterPlaceholder ?? ''; + if (this.columnFilter?.placeholder) { placeholder = this.columnFilter.placeholder; } diff --git a/packages/common/src/filters/compoundInputFilter.ts b/packages/common/src/filters/compoundInputFilter.ts index 71b0823b0..c9660df2e 100644 --- a/packages/common/src/filters/compoundInputFilter.ts +++ b/packages/common/src/filters/compoundInputFilter.ts @@ -11,18 +11,20 @@ import { OperatorDetail, SlickGrid, } from '../interfaces/index'; -import { buildSelectOperatorHtmlString } from './filterUtilities'; -import { getTranslationPrefix, mapOperatorToShorthandDesignation } from '../services/utilities'; +import { buildSelectOperator } from './filterUtilities'; +import { emptyElement, getTranslationPrefix, mapOperatorToShorthandDesignation } from '../services/utilities'; +import { BindingEventService } from '../services/bindingEvent.service'; import { TranslaterService } from '../services/translater.service'; export class CompoundInputFilter implements Filter { + protected _bindEventService: BindingEventService; protected _clearFilterTriggered = false; protected _debounceTypingDelay = 0; protected _shouldTriggerQuery = true; protected _inputType = 'text'; - protected $filterElm: any; - protected $filterInputElm: any; - protected $selectOperatorElm: any; + protected _filterElm!: HTMLDivElement; + protected _filterInputElm!: HTMLInputElement; + protected _selectOperatorElm!: HTMLSelectElement; protected _operator?: OperatorType | OperatorString; grid!: SlickGrid; searchTerms: SearchTerm[] = []; @@ -30,14 +32,16 @@ export class CompoundInputFilter implements Filter { callback!: FilterCallback; timer?: NodeJS.Timeout; - constructor(protected readonly translaterService: TranslaterService) { } + constructor(protected readonly translaterService: TranslaterService) { + this._bindEventService = new BindingEventService(); + } /** Getter for the Grid Options pulled through the Grid Object */ protected get gridOptions(): GridOption { return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {}; } - /** Getter for the Filter Operator */ + /** Getter for the Column Filter */ get columnFilter(): ColumnFilter { return this.columnDef && this.columnDef.filter || {}; } @@ -83,7 +87,7 @@ export class CompoundInputFilter implements Filter { this.grid = args.grid; this.callback = args.callback; this.columnDef = args.columnDef; - this.operator = args.operator || ''; + this.operator = args.operator as OperatorString; 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) @@ -96,25 +100,25 @@ export class CompoundInputFilter implements Filter { // step 1, create the DOM Element of the filter which contain the compound Operator+Input // and initialize it if searchTerm is filled - this.$filterElm = this.createDomElement(searchTerm); + this._filterElm = this.createDomElement(searchTerm); - // step 3, subscribe to the input change event and run the callback when that happens + // step 3, subscribe to the keyup event and run the callback when that happens // also add/remove "filled" class for styling purposes // we'll use all necessary events to cover the following (keyup, change, mousewheel & spinner) - this.$filterInputElm.on('keyup blur change wheel', this.onTriggerEvent.bind(this)); - this.$selectOperatorElm.on('change', this.onTriggerEvent.bind(this)); + this._bindEventService.bind(this._filterInputElm, ['keyup', 'blur', 'change', 'wheel'], this.onTriggerEvent.bind(this)); + this._bindEventService.bind(this._selectOperatorElm, 'change', this.onTriggerEvent.bind(this)); } /** * Clear the filter value */ clear(shouldTriggerQuery = true) { - if (this.$filterElm && this.$selectOperatorElm) { + if (this._filterElm && this._selectOperatorElm) { this._clearFilterTriggered = true; this._shouldTriggerQuery = shouldTriggerQuery; this.searchTerms = []; - this.$selectOperatorElm.val(0); - this.$filterInputElm.val(''); + this._selectOperatorElm.value = ''; + this._filterInputElm.value = ''; this.onTriggerEvent(undefined); } } @@ -123,26 +127,23 @@ export class CompoundInputFilter implements Filter { * destroy the filter */ destroy() { - if (this.$filterElm && this.$selectOperatorElm) { - this.$filterElm.off('keyup blur change wheel').remove(); - this.$selectOperatorElm.off('change'); - } - this.$filterElm = null; - this.$selectOperatorElm = null; + this._bindEventService.unbindAll(); + this._filterElm?.remove?.(); + this._selectOperatorElm?.remove?.(); } - /** Set value(s) on the DOM element */ + /** Set value(s) on the DOM element */ setValues(values: SearchTerm[] | SearchTerm, operator?: OperatorType | OperatorString) { if (values) { const newValue = Array.isArray(values) ? values[0] : values; - this.$filterInputElm.val(newValue); + this._filterInputElm.value = `${newValue ?? ''}`; } // set the operator, in the DOM as well, when defined this.operator = operator || this.defaultOperator; - if (operator && this.$selectOperatorElm) { + if (operator && this._selectOperatorElm) { const operatorShorthand = mapOperatorToShorthandDesignation(this.operator); - this.$selectOperatorElm.val(operatorShorthand); + this._selectOperatorElm.value = operatorShorthand; } } @@ -150,13 +151,23 @@ export class CompoundInputFilter implements Filter { // protected functions // ------------------ - protected buildInputHtmlString() { + protected buildInputElement(): HTMLInputElement { const columnId = this.columnDef?.id ?? ''; - let placeholder = (this.gridOptions) ? (this.gridOptions.defaultFilterPlaceholder || '') : ''; - if (this.columnFilter && this.columnFilter.placeholder) { + + // create the DOM element & add an ID and filter class + let placeholder = this.gridOptions?.defaultFilterPlaceholder ?? ''; + if (this.columnFilter?.placeholder) { placeholder = this.columnFilter.placeholder; } - return ``; + + const inputElm = document.createElement('input'); + inputElm.type = this._inputType || 'text'; + inputElm.className = `form-control compound-input filter-${columnId}`; + inputElm.autocomplete = 'off'; + inputElm.placeholder = placeholder; + inputElm.setAttribute('role', 'presentation'); + + return inputElm; } /** Get the available operator option values to populate the operator select dropdown list */ @@ -212,16 +223,22 @@ export class CompoundInputFilter implements Filter { */ protected createDomElement(searchTerm?: SearchTerm) { const columnId = this.columnDef?.id ?? ''; - const $headerElm = this.grid.getHeaderRowColumn(columnId); - $($headerElm).empty(); + const headerElm = this.grid.getHeaderRowColumn(columnId); + emptyElement(headerElm); // create the DOM Select dropdown for the Operator - const selectOperatorHtmlString = buildSelectOperatorHtmlString(this.getOperatorOptionValues()); - this.$selectOperatorElm = $(selectOperatorHtmlString); - this.$filterInputElm = $(this.buildInputHtmlString()); - const $filterContainerElm = $(`
`); - const $containerInputGroup = $(`
`); - const $operatorInputGroupAddon = $(`
`); + this._selectOperatorElm = buildSelectOperator(this.getOperatorOptionValues()); + this._filterInputElm = this.buildInputElement(); + const emptySpanElm = document.createElement('span'); + + const filterContainerElm = document.createElement('div'); + filterContainerElm.className = `form-group search-filter filter-${columnId}`; + + const containerInputGroupElm = document.createElement('div'); + containerInputGroupElm.className = 'input-group'; + + const operatorInputGroupAddonElm = document.createElement('div'); + operatorInputGroupAddonElm.className = 'input-group-addon input-group-prepend operator'; /* the DOM element final structure will be
@@ -231,54 +248,55 @@ export class CompoundInputFilter implements Filter {
*/ - $operatorInputGroupAddon.append(this.$selectOperatorElm); - $containerInputGroup.append($operatorInputGroupAddon); - $containerInputGroup.append(this.$filterInputElm); + operatorInputGroupAddonElm.appendChild(this._selectOperatorElm); + containerInputGroupElm.appendChild(operatorInputGroupAddonElm); + containerInputGroupElm.appendChild(this._filterInputElm); + containerInputGroupElm.appendChild(emptySpanElm); // create the DOM element & add an ID and filter class - $filterContainerElm.append($containerInputGroup); + filterContainerElm.appendChild(containerInputGroupElm); - this.$filterInputElm.val(searchTerm); - this.$filterInputElm.data('columnId', columnId); + this._filterInputElm.value = `${searchTerm ?? ''}`; + this._filterInputElm.dataset.columnid = `${columnId}`; if (this.operator) { const operatorShorthand = mapOperatorToShorthandDesignation(this.operator); - this.$selectOperatorElm.val(operatorShorthand); + this._selectOperatorElm.value = operatorShorthand; } // if there's a search term, we will add the "filled" class for styling purposes if (searchTerm) { - $filterContainerElm.addClass('filled'); + this._filterInputElm.classList.add('filled'); } // append the new DOM element to the header row - if ($filterContainerElm && typeof $filterContainerElm.appendTo === 'function') { - $filterContainerElm.appendTo($headerElm); + if (filterContainerElm) { + headerElm.appendChild(filterContainerElm); } - return $filterContainerElm; + return filterContainerElm; } /** * Event trigger, could be called by the Operator dropdown or the input itself and we will cover the following (keyup, change, mousewheel & spinner) * We will trigger the Filter Service callback from this handler */ - protected onTriggerEvent(event: KeyboardEvent | undefined) { + protected onTriggerEvent(event: Event | undefined) { if (this._clearFilterTriggered) { this.callback(event, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery }); - this.$filterElm.removeClass('filled'); + this._filterElm.classList.remove('filled'); } else { const eventType = event?.type ?? ''; - const selectedOperator = this.$selectOperatorElm.find('option:selected').val(); - let value = this.$filterInputElm.val() as string; + const selectedOperator = this._selectOperatorElm.value as OperatorString; + let value = this._filterInputElm.value as string; const enableWhiteSpaceTrim = this.gridOptions.enableFilterTrimWhiteSpace || this.columnFilter.enableTrimWhiteSpace; if (typeof value === 'string' && enableWhiteSpaceTrim) { value = value.trim(); } - (value !== null && value !== undefined && value !== '') ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled'); - const callbackArgs = { columnDef: this.columnDef, searchTerms: (value ? [value] : null), operator: selectedOperator || '', shouldTriggerQuery: this._shouldTriggerQuery }; - const typingDelay = (eventType === 'keyup' && event?.key !== 'Enter') ? this._debounceTypingDelay : 0; + (value !== null && value !== undefined && value !== '') ? this._filterElm.classList.add('filled') : this._filterElm.classList.remove('filled'); + const callbackArgs = { columnDef: this.columnDef, searchTerms: (value ? [value] : null), operator: selectedOperator, shouldTriggerQuery: this._shouldTriggerQuery }; + const typingDelay = (eventType === 'keyup' && (event as KeyboardEvent)?.key !== 'Enter') ? this._debounceTypingDelay : 0; if (typingDelay > 0) { clearTimeout(this.timer as NodeJS.Timeout); diff --git a/packages/common/src/filters/dateRangeFilter.ts b/packages/common/src/filters/dateRangeFilter.ts index 254787a62..6ccfc4bbc 100644 --- a/packages/common/src/filters/dateRangeFilter.ts +++ b/packages/common/src/filters/dateRangeFilter.ts @@ -132,9 +132,7 @@ export class DateRangeFilter implements Filter { destroyObjectDomElementProps(this.flatInstance); } } - if (this._filterElm) { - this._filterElm.remove(); - } + this._filterElm?.remove?.(); } hide() { diff --git a/packages/common/src/filters/inputFilter.ts b/packages/common/src/filters/inputFilter.ts index f5099efe3..a023d843a 100644 --- a/packages/common/src/filters/inputFilter.ts +++ b/packages/common/src/filters/inputFilter.ts @@ -8,20 +8,25 @@ import { SlickGrid, } from '../interfaces/index'; import { OperatorType, OperatorString, SearchTerm } from '../enums/index'; +import { BindingEventService } from '../services/bindingEvent.service'; +import { emptyElement } from '../services'; export class InputFilter implements Filter { + protected _bindEventService: BindingEventService; protected _clearFilterTriggered = false; protected _debounceTypingDelay = 0; protected _shouldTriggerQuery = true; protected _inputType = 'text'; protected _timer?: NodeJS.Timeout; - protected $filterElm: any; + protected _filterElm!: HTMLInputElement; grid!: SlickGrid; searchTerms: SearchTerm[] = []; columnDef!: Column; callback!: FilterCallback; - constructor() { } + constructor() { + this._bindEventService = new BindingEventService(); + } /** Getter for the Column Filter */ get columnFilter(): ColumnFilter { @@ -80,28 +85,25 @@ export class InputFilter implements Filter { // 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] : ''; - // step 1, create HTML string template - const filterTemplate = this.buildTemplateHtmlString(); - - // step 2, create the DOM Element of the filter & initialize it if searchTerm is filled - this.$filterElm = this.createDomElement(filterTemplate, searchTerm); + // step 1, create the DOM Element of the filter & initialize it if searchTerm is filled + this._filterElm = this.createDomElement(searchTerm); - // step 3, subscribe to the input event and run the callback when that happens + // step 2, subscribe to the input event and run the callback when that happens // also add/remove "filled" class for styling purposes // we'll use all necessary events to cover the following (keyup, change, mousewheel & spinner) - this.$filterElm.on('keyup blur change wheel', this.handleInputChange.bind(this)); + this._bindEventService.bind(this._filterElm, ['keyup', 'blur', 'change', 'wheel'], this.handleInputChange.bind(this)); } /** * Clear the filter value */ clear(shouldTriggerQuery = true) { - if (this.$filterElm) { + if (this._filterElm) { this._clearFilterTriggered = true; this._shouldTriggerQuery = shouldTriggerQuery; this.searchTerms = []; - this.$filterElm.val(''); - this.$filterElm.trigger('change'); + this._filterElm.value = ''; + this._filterElm.dispatchEvent(new Event('change')); } } @@ -109,14 +111,12 @@ export class InputFilter implements Filter { * destroy the filter */ destroy() { - if (this.$filterElm) { - this.$filterElm.off('keyup blur change wheel').remove(); - } - this.$filterElm = null; + this._bindEventService.unbindAll(); + this._filterElm?.remove?.(); } - getValue() { - return this.$filterElm.val(); + getValue(): string { + return this._filterElm.value; } /** Set value(s) on the DOM element */ @@ -125,7 +125,7 @@ export class InputFilter implements Filter { let searchValue: SearchTerm = ''; for (const value of searchValues) { searchValue = operator ? this.addOptionalOperatorIntoSearchString(value, operator) : value; - this.$filterElm.val(searchValue); + this._filterElm.value = `${searchValue ?? ''}`; } // set the operator when defined @@ -177,64 +177,62 @@ export class InputFilter implements Filter { return outputValue; } - /** - * Create the HTML template as a string - */ - protected buildTemplateHtmlString() { - const columnId = this.columnDef?.id ?? ''; - let placeholder = (this.gridOptions) ? (this.gridOptions.defaultFilterPlaceholder || '') : ''; - if (this.columnFilter && this.columnFilter.placeholder) { - placeholder = this.columnFilter.placeholder; - } - return ``; - } - /** * From the html template string, create a DOM element - * @param filterTemplate + * @param {Object} searchTerm - filter search term + * @returns {Object} DOM element filter */ - protected createDomElement(filterTemplate: string, searchTerm?: SearchTerm) { + protected createDomElement(searchTerm?: SearchTerm) { const columnId = this.columnDef?.id ?? ''; - const $headerElm = this.grid.getHeaderRowColumn(columnId); - $($headerElm).empty(); + const headerElm = this.grid.getHeaderRowColumn(columnId); + emptyElement(headerElm); // create the DOM element & add an ID and filter class - const $filterElm = $(filterTemplate); + let placeholder = this.gridOptions?.defaultFilterPlaceholder ?? ''; + if (this.columnFilter?.placeholder) { + placeholder = this.columnFilter.placeholder; + } + + const inputElm = document.createElement('input'); + inputElm.type = this._inputType || 'text'; + inputElm.className = `form-control search-filter filter-${columnId}`; + inputElm.autocomplete = 'off'; + inputElm.placeholder = placeholder; + inputElm.setAttribute('role', 'presentation'); - $filterElm.val(searchTerm as string); - $filterElm.data('columnId', columnId); + inputElm.value = (searchTerm ?? '') as string; + inputElm.dataset.columnid = `${columnId}`; // if there's a search term, we will add the "filled" class for styling purposes if (searchTerm) { - $filterElm.addClass('filled'); + inputElm.classList.add('filled'); } - // append the new DOM element to the header row - if ($filterElm && typeof $filterElm.appendTo === 'function') { - $filterElm.appendTo($headerElm); - } + // append the new DOM element to the header row & an empty span + headerElm.appendChild(inputElm); + headerElm.appendChild(document.createElement('span')); - return $filterElm; + return inputElm; } /** * Event handler to cover the following (keyup, change, mousewheel & spinner) * We will trigger the Filter Service callback from this handler */ - protected handleInputChange(event: KeyboardEvent & { target: any; }) { + protected handleInputChange(event: Event) { if (this._clearFilterTriggered) { this.callback(event, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery }); - this.$filterElm.removeClass('filled'); + this._filterElm.classList.remove('filled'); } else { const eventType = event?.type ?? ''; - let value = event?.target?.value ?? ''; + let value = (event?.target as HTMLInputElement)?.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'); + value === '' ? this._filterElm.classList.remove('filled') : this._filterElm.classList.add('filled'); const callbackArgs = { columnDef: this.columnDef, operator: this.operator, searchTerms: [value], shouldTriggerQuery: this._shouldTriggerQuery }; - const typingDelay = (eventType === 'keyup' && event?.key !== 'Enter') ? this._debounceTypingDelay : 0; + const typingDelay = (eventType === 'keyup' && (event as KeyboardEvent)?.key !== 'Enter') ? this._debounceTypingDelay : 0; if (typingDelay > 0) { clearTimeout(this._timer as NodeJS.Timeout); diff --git a/packages/common/src/filters/inputMaskFilter.ts b/packages/common/src/filters/inputMaskFilter.ts index 9c20568d5..b4b2644d7 100644 --- a/packages/common/src/filters/inputMaskFilter.ts +++ b/packages/common/src/filters/inputMaskFilter.ts @@ -42,47 +42,50 @@ export class InputMaskFilter extends InputFilter { // 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] : ''; - // step 1, create HTML string template - const filterTemplate = this.buildTemplateHtmlString(); + // step 1, create the DOM Element of the filter & initialize it if searchTerm is filled + this._filterElm = this.createDomElement(searchTerm); - // step 2, create the DOM Element of the filter & initialize it if searchTerm is filled - this.$filterElm = this.createDomElement(filterTemplate, searchTerm); - - // step 3, subscribe to the keyup blur change event and run the callback when that happens + // step 2, subscribe to the input event and run the callback when that happens // also add/remove "filled" class for styling purposes + // we'll use all necessary events to cover the following (keyup, change, mousewheel & spinner) + this._bindEventService.bind(this._filterElm, ['keyup', 'blur', 'change'], this.handleInputChange.bind(this)); + } - this.$filterElm.on('keyup blur change', (e: any) => { - let value = ''; - if (e && e.target && e.target.value) { - let targetValue = e.target.value; - const enableWhiteSpaceTrim = this.gridOptions.enableFilterTrimWhiteSpace || this.columnFilter.enableTrimWhiteSpace; - if (typeof targetValue === 'string' && enableWhiteSpaceTrim) { - targetValue = targetValue.trim(); - } + /** + * Event handler to cover the following (keyup, change, mousewheel & spinner) + * We will trigger the Filter Service callback from this handler + */ + protected handleInputChange(event: Event) { + let value = ''; + if ((event?.target as HTMLInputElement)?.value) { + let targetValue = (event?.target as HTMLInputElement)?.value ?? ''; + const enableWhiteSpaceTrim = this.gridOptions.enableFilterTrimWhiteSpace || this.columnFilter.enableTrimWhiteSpace; + if (typeof targetValue === 'string' && enableWhiteSpaceTrim) { + targetValue = targetValue.trim(); + } - // if it has a mask, we need to do a bit more work - // and replace the filter string by the masked output without triggering an event - const unmaskedValue = this.unmaskValue(targetValue); - const maskedValue = this.maskValue(unmaskedValue); - value = unmaskedValue; + // if it has a mask, we need to do a bit more work + // and replace the filter string by the masked output without triggering an event + const unmaskedValue = this.unmaskValue(targetValue); + const maskedValue = this.maskValue(unmaskedValue); + value = unmaskedValue; - if (e.keyCode >= 48) { - this.$filterElm.val(maskedValue); // replace filter string with masked string - e.preventDefault(); - } + if ((event as KeyboardEvent)?.keyCode >= 48) { + this._filterElm.value = maskedValue; // replace filter string with masked string + event.preventDefault(); } + } - if (this._clearFilterTriggered) { - this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery }); - this.$filterElm.removeClass('filled'); - } else { - this.$filterElm.addClass('filled'); - this.callback(e, { columnDef: this.columnDef, operator: this.operator, searchTerms: [value], shouldTriggerQuery: this._shouldTriggerQuery }); - } - // reset both flags for next use - this._clearFilterTriggered = false; - this._shouldTriggerQuery = true; - }); + if (this._clearFilterTriggered) { + this.callback(event, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery }); + this._filterElm.classList.remove('filled'); + } else { + this._filterElm.classList.add('filled'); + this.callback(event, { columnDef: this.columnDef, operator: this.operator, searchTerms: [value], shouldTriggerQuery: this._shouldTriggerQuery }); + } + // reset both flags for next use + this._clearFilterTriggered = false; + this._shouldTriggerQuery = true; } /** From a regular string, we will use the mask to output a new string */ diff --git a/packages/common/src/filters/nativeSelectFilter.ts b/packages/common/src/filters/nativeSelectFilter.ts index 4105b12e2..bfe2c1149 100644 --- a/packages/common/src/filters/nativeSelectFilter.ts +++ b/packages/common/src/filters/nativeSelectFilter.ts @@ -72,7 +72,7 @@ export class NativeSelectFilter implements Filter { // filter input can only have 1 search term, so we will use the 1st array index if it exist let searchTerm = (Array.isArray(this.searchTerms) && this.searchTerms.length >= 0) ? this.searchTerms[0] : ''; if (typeof searchTerm === 'boolean' || typeof searchTerm === 'number') { - searchTerm = `${searchTerm}`; + searchTerm = `${searchTerm ?? ''}`; } // step 1, create HTML string template diff --git a/packages/common/src/services/__tests__/filter.service.spec.ts b/packages/common/src/services/__tests__/filter.service.spec.ts index 9def3ec2e..53c011b3a 100644 --- a/packages/common/src/services/__tests__/filter.service.spec.ts +++ b/packages/common/src/services/__tests__/filter.service.spec.ts @@ -19,7 +19,7 @@ import { SlickGrid, SlickNamespace, } from '../../interfaces/index'; -import { Filters } from '../../filters'; +import { Filters, InputFilter } from '../../filters'; import { FilterService } from '../filter.service'; import { FilterFactory } from '../../filters/filterFactory'; import { getParsedSearchTermsByFieldType } from '../../filter-conditions'; @@ -133,6 +133,7 @@ describe('FilterService', () => { const filterFactory = new FilterFactory(slickgridConfig, translateService, collectionService); service = new FilterService(filterFactory, pubSubServiceStub, sharedService, backendUtilityService, rxjsResourceStub); slickgridEventHandler = service.eventHandler; + jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(div); }); afterEach(() => { @@ -175,7 +176,7 @@ describe('FilterService', () => { expect(columnFilters).toEqual({}); expect(filterMetadataArray.length).toBe(1); - expect(filterMetadataArray[0]).toContainEntry(['$filterElm', expect.anything()]); + expect(filterMetadataArray[0] instanceof InputFilter).toBeTruthy(); expect(filterMetadataArray[0]).toContainEntry(['searchTerms', []]); }); @@ -261,7 +262,7 @@ describe('FilterService', () => { expect(columnFilters).toEqual({}); expect(filterMetadataArray.length).toBe(1); - expect(filterMetadataArray[0]).toContainEntry(['$filterElm', expect.anything()]); + expect(filterMetadataArray[0] instanceof InputFilter).toBeTruthy(); expect(filterMetadataArray[0]).toContainEntry(['searchTerms', []]); }); @@ -1564,7 +1565,7 @@ describe('FilterService', () => { expect(spySetSortCols).toHaveBeenCalledWith([{ columnId: 'file', sortAsc: true }]); expect(columnFilters).toEqual({}); expect(filterMetadataArray.length).toBe(1); - expect(filterMetadataArray[0]).toContainEntry(['$filterElm', expect.anything()]); + expect(filterMetadataArray[0] instanceof InputFilter).toBeTruthy(); expect(filterMetadataArray[0]).toContainEntry(['searchTerms', []]); });