diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example14.ts b/examples/vite-demo-vanilla-bundle/src/examples/example14.ts index bb3f75e71..1044b4a94 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example14.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example14.ts @@ -416,6 +416,11 @@ export default class Example14 { pageSize: 10, pageSizes: [10, 200, 500, 5000] }, + // you can change compound filter text/desc shown in operator dropdown + // compoundOperatorAltTexts: { + // numeric: { '=': { operatorAlt: 'eq', descAlt: 'alternate numeric equal description' } }, + // text: { '=': { operatorAlt: 'eq', descAlt: 'alternate text equal description' } } + // }, // resizing by cell content is opt-in // we first need to disable the 2 default flags to autoFit/autosize diff --git a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts index be3da690f..fd8a13e7b 100644 --- a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts @@ -20,7 +20,7 @@ const gridOptionMock = { } as GridOption; const gridStub = { - getOptions: () => gridOptionMock, + getOptions: jest.fn(), getColumns: jest.fn(), getHeaderRowColumn: jest.fn(), render: jest.fn(), @@ -41,6 +41,7 @@ describe('CompoundDateFilter', () => { divContainer.innerHTML = template; document.body.appendChild(divContainer); spyGetHeaderRow = jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(divContainer); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionMock); mockColumn = { id: 'finish', field: 'finish', filterable: true, outputType: FieldType.dateIso, filter: { model: Filters.compoundDate, operator: '>' } }; @@ -56,6 +57,7 @@ describe('CompoundDateFilter', () => { afterEach(() => { filter.destroy(); + jest.clearAllMocks(); }); it('should throw an error when trying to call init without any arguments', () => { @@ -394,10 +396,10 @@ describe('CompoundDateFilter', () => { mockColumn.outputType = null as any; filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z']; mockColumn.filter!.compoundOperatorList = [ - { operator: '', description: '' }, - { operator: '=', description: 'Equal to' }, - { operator: '<', description: 'Less than' }, - { operator: '>', description: 'Greater than' }, + { operator: '', desc: '' }, + { operator: '=', desc: 'Equal to' }, + { operator: '<', desc: 'Less than' }, + { operator: '>', desc: 'Greater than' }, ]; filter.init(filterArguments); @@ -409,6 +411,28 @@ describe('CompoundDateFilter', () => { expect(removeExtraSpaces(filterOperatorElm[0][3].textContent!)).toBe('> Greater than'); }); + it('should be able to change compound operator & description with alternate texts for the operator list showing up in the operator select dropdown options list', () => { + mockColumn.outputType = null as any; + filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z']; + jest.spyOn(gridStub, 'getOptions').mockReturnValue({ + ...gridOptionMock, compoundOperatorAltTexts: { + numeric: { '=': { operatorAlt: 'eq', descAlt: 'alternate numeric equal description' } }, + text: { '=': { operatorAlt: 'eq', descAlt: 'alternate text equal description' } } + } + }); + + filter.init(filterArguments); + const filterOperatorElm = divContainer.querySelectorAll('.input-group-prepend.operator select'); + + expect(filterOperatorElm[0][0].title).toBe(''); + expect(removeExtraSpaces(filterOperatorElm[0][1].textContent!)).toBe('eq alternate numeric equal description'); + expect(removeExtraSpaces(filterOperatorElm[0][2].textContent!)).toBe('< Less than'); + expect(removeExtraSpaces(filterOperatorElm[0][3].textContent!)).toBe('<= Less than or equal to'); + expect(removeExtraSpaces(filterOperatorElm[0][4].textContent!)).toBe('> Greater than'); + expect(removeExtraSpaces(filterOperatorElm[0][5].textContent!)).toBe('>= Greater than or equal to'); + expect(removeExtraSpaces(filterOperatorElm[0][6].textContent!)).toBe('<> Not equal to'); + }); + describe('with French I18N translations', () => { beforeEach(() => { gridOptionMock.enableTranslate = true; diff --git a/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts b/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts index 4cb5b7512..80d8b7f2b 100644 --- a/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts @@ -18,7 +18,7 @@ const gridOptionMock = { } as GridOption; const gridStub = { - getOptions: () => gridOptionMock, + getOptions: jest.fn(), getColumns: jest.fn(), getHeaderRowColumn: jest.fn(), render: jest.fn(), @@ -39,6 +39,7 @@ describe('CompoundInputFilter', () => { divContainer.innerHTML = template; document.body.appendChild(divContainer); spyGetHeaderRow = jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(divContainer); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionMock); mockColumn = { id: 'duration', field: 'duration', filterable: true, filter: { model: Filters.input, operator: 'EQ' } }; filterArguments = { @@ -373,10 +374,10 @@ describe('CompoundInputFilter', () => { mockColumn.outputType = null as any; filterArguments.searchTerms = ['xyz']; mockColumn.filter!.compoundOperatorList = [ - { operator: '', description: '' }, - { operator: '=', description: 'Equal to' }, - { operator: '<', description: 'Less than' }, - { operator: '>', description: 'Greater than' }, + { operator: '', desc: '' }, + { operator: '=', desc: 'Equal to' }, + { operator: '<', desc: 'Less than' }, + { operator: '>', desc: 'Greater than' }, ]; filter.init(filterArguments); @@ -388,6 +389,28 @@ describe('CompoundInputFilter', () => { expect(removeExtraSpaces(filterOperatorElm[0][3].textContent!)).toBe('> Greater than'); }); + it('should be able to change compound operator & description with alternate texts for the operator list showing up in the operator select dropdown options list', () => { + mockColumn.outputType = null as any; + filterArguments.searchTerms = ['xyz']; + jest.spyOn(gridStub, 'getOptions').mockReturnValue({ + ...gridOptionMock, compoundOperatorAltTexts: { + numeric: { '=': { operatorAlt: 'eq', descAlt: 'alternate numeric equal description' } }, + text: { '=': { operatorAlt: 'eq', descAlt: 'alternate text equal description' } } + } + }); + + filter.init(filterArguments); + const filterOperatorElm = divContainer.querySelectorAll('.search-filter.filter-duration select'); + + expect(filterOperatorElm[0][0].title).toBe(''); + expect(removeExtraSpaces(filterOperatorElm[0][0].textContent!)).toBe(' Contains'); + expect(removeExtraSpaces(filterOperatorElm[0][1].textContent!)).toBe('<> Not contains'); + expect(removeExtraSpaces(filterOperatorElm[0][2].textContent!)).toBe('eq alternate text equal description'); + expect(removeExtraSpaces(filterOperatorElm[0][3].textContent!)).toBe('!= Not equal to'); + expect(removeExtraSpaces(filterOperatorElm[0][4].textContent!)).toBe('a* Starts With'); + expect(removeExtraSpaces(filterOperatorElm[0][5].textContent!)).toBe('*z Ends With'); + }); + describe('with French I18N translations', () => { beforeEach(() => { gridOptionMock.enableTranslate = true; diff --git a/packages/common/src/filters/__tests__/compoundSliderFilter.spec.ts b/packages/common/src/filters/__tests__/compoundSliderFilter.spec.ts index 03ce5b9de..d396614eb 100644 --- a/packages/common/src/filters/__tests__/compoundSliderFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundSliderFilter.spec.ts @@ -335,10 +335,10 @@ describe('CompoundSliderFilter', () => { mockColumn.outputType = null as any; filterArguments.searchTerms = ['9']; mockColumn.filter!.compoundOperatorList = [ - { operator: '', description: '' }, - { operator: '=', description: 'Equal to' }, - { operator: '<', description: 'Less than' }, - { operator: '>', description: 'Greater than' }, + { operator: '', desc: '' }, + { operator: '=', desc: 'Equal to' }, + { operator: '<', desc: 'Less than' }, + { operator: '>', desc: 'Greater than' }, ]; filter.init(filterArguments); @@ -350,6 +350,28 @@ describe('CompoundSliderFilter', () => { expect(removeExtraSpaces(filterOperatorElm[0][3].textContent!)).toBe('> Greater than'); }); + it('should be able to change compound operator & description with alternate texts for the operator list showing up in the operator select dropdown options list', () => { + mockColumn.outputType = null as any; + filterArguments.searchTerms = ['9']; + jest.spyOn(gridStub, 'getOptions').mockReturnValueOnce({ + ...gridOptionMock, compoundOperatorAltTexts: { + numeric: { '=': { operatorAlt: 'eq', descAlt: 'alternate numeric equal description' } }, + text: { '=': { operatorAlt: 'eq', descAlt: 'alternate text equal description' } } + } + }); + + filter.init(filterArguments); + const filterOperatorElm = divContainer.querySelectorAll('.search-filter.filter-duration select'); + + expect(filterOperatorElm[0][0].title).toBe(''); + expect(removeExtraSpaces(filterOperatorElm[0][1].textContent!)).toBe('eq alternate numeric equal description'); + expect(removeExtraSpaces(filterOperatorElm[0][2].textContent!)).toBe('< Less than'); + expect(removeExtraSpaces(filterOperatorElm[0][3].textContent!)).toBe('<= Less than or equal to'); + expect(removeExtraSpaces(filterOperatorElm[0][4].textContent!)).toBe('> Greater than'); + expect(removeExtraSpaces(filterOperatorElm[0][5].textContent!)).toBe('>= Greater than or equal to'); + expect(removeExtraSpaces(filterOperatorElm[0][6].textContent!)).toBe('<> Not equal to'); + }); + describe('with French I18N translations', () => { beforeEach(() => { gridOptionMock.enableTranslate = true; diff --git a/packages/common/src/filters/filterUtilities.ts b/packages/common/src/filters/filterUtilities.ts index eb4677b66..b4ef803a9 100644 --- a/packages/common/src/filters/filterUtilities.ts +++ b/packages/common/src/filters/filterUtilities.ts @@ -1,6 +1,5 @@ import { Constants } from '../constants'; -import type { OperatorString } from '../enums/index'; -import type { Column, ColumnFilter, GridOption, Locale } from '../interfaces/index'; +import type { Column, ColumnFilter, GridOption, Locale, OperatorDetail } from '../interfaces/index'; import type { Observable, RxJsFacade, Subject, Subscription } from '../services/rxjsFacade'; import { createDomElement, htmlEncodedStringWithPadding, sanitizeTextByAvailableSanitizer, } from '../services/domUtilities'; import { castObservableToPromise, getDescendantProperty, getTranslationPrefix, } from '../services/utilities'; @@ -11,13 +10,13 @@ import type { TranslaterService } from '../services/translater.service'; * @param {Array} optionValues - list of operators and their descriptions * @returns {Object} selectElm - Select Dropdown HTML Element */ -export function buildSelectOperator(optionValues: Array<{ operator: OperatorString, description: string }>, gridOptions: GridOption): HTMLSelectElement { +export function buildSelectOperator(optionValues: OperatorDetail[], gridOptions: GridOption): HTMLSelectElement { const selectElm = createDomElement('select', { className: 'form-control' }); for (const option of optionValues) { const optionElm = document.createElement('option'); optionElm.value = option.operator; - optionElm.innerHTML = sanitizeTextByAvailableSanitizer(gridOptions, `${htmlEncodedStringWithPadding(option.operator, 3)}${option.description}`); + optionElm.innerHTML = sanitizeTextByAvailableSanitizer(gridOptions, `${htmlEncodedStringWithPadding(option.operatorAlt || option.operator, 3)}${option.descAlt || option.desc}`); selectElm.appendChild(optionElm); } @@ -138,26 +137,49 @@ function getOutputText(translationKey: string, localeText: string, defaultText: } /** returns common list of string related operators and their associated translation descriptions */ -export function compoundOperatorString(gridOptions: GridOption, translaterService?: TranslaterService) { - return [ - { operator: '' as OperatorString, description: getOutputText('CONTAINS', 'TEXT_CONTAINS', 'Contains', gridOptions, translaterService) }, - { operator: '<>' as OperatorString, description: getOutputText('NOT_CONTAINS', 'TEXT_NOT_CONTAINS', 'Not Contains', gridOptions, translaterService) }, - { operator: '=' as OperatorString, description: getOutputText('EQUALS', 'TEXT_EQUALS', 'Equals', gridOptions, translaterService) }, - { operator: '!=' as OperatorString, description: getOutputText('NOT_EQUAL_TO', 'TEXT_NOT_EQUAL_TO', 'Not equal to', gridOptions, translaterService) }, - { operator: 'a*' as OperatorString, description: getOutputText('STARTS_WITH', 'TEXT_STARTS_WITH', 'Starts with', gridOptions, translaterService) }, - { operator: '*z' as OperatorString, description: getOutputText('ENDS_WITH', 'TEXT_ENDS_WITH', 'Ends with', gridOptions, translaterService) }, +export function compoundOperatorString(gridOptions: GridOption, translaterService?: TranslaterService): OperatorDetail[] { + const operatorList: OperatorDetail[] = [ + { operator: '', desc: getOutputText('CONTAINS', 'TEXT_CONTAINS', 'Contains', gridOptions, translaterService) }, + { operator: '<>', desc: getOutputText('NOT_CONTAINS', 'TEXT_NOT_CONTAINS', 'Not Contains', gridOptions, translaterService) }, + { operator: '=', desc: getOutputText('EQUALS', 'TEXT_EQUALS', 'Equals', gridOptions, translaterService) }, + { operator: '!=', desc: getOutputText('NOT_EQUAL_TO', 'TEXT_NOT_EQUAL_TO', 'Not equal to', gridOptions, translaterService) }, + { operator: 'a*', desc: getOutputText('STARTS_WITH', 'TEXT_STARTS_WITH', 'Starts with', gridOptions, translaterService) }, + { operator: '*z', desc: getOutputText('ENDS_WITH', 'TEXT_ENDS_WITH', 'Ends with', gridOptions, translaterService) }, ]; + + // add alternate texts when provided + applyOperatorAltTextWhenExists(gridOptions, operatorList, 'text'); + + return operatorList; } /** returns common list of numeric related operators and their associated translation descriptions */ -export function compoundOperatorNumeric(gridOptions: GridOption, translaterService?: TranslaterService) { - return [ - { operator: '' as OperatorString, description: '' }, - { operator: '=' as OperatorString, description: getOutputText('EQUAL_TO', 'TEXT_EQUAL_TO', 'Equal to', gridOptions, translaterService) }, - { operator: '<' as OperatorString, description: getOutputText('LESS_THAN', 'TEXT_LESS_THAN', 'Less than', gridOptions, translaterService) }, - { operator: '<=' as OperatorString, description: getOutputText('LESS_THAN_OR_EQUAL_TO', 'TEXT_LESS_THAN_OR_EQUAL_TO', 'Less than or equal to', gridOptions, translaterService) }, - { operator: '>' as OperatorString, description: getOutputText('GREATER_THAN', 'TEXT_GREATER_THAN', 'Greater than', gridOptions, translaterService) }, - { operator: '>=' as OperatorString, description: getOutputText('GREATER_THAN_OR_EQUAL_TO', 'TEXT_GREATER_THAN_OR_EQUAL_TO', 'Greater than or equal to', gridOptions, translaterService) }, - { operator: '<>' as OperatorString, description: getOutputText('NOT_EQUAL_TO', 'TEXT_NOT_EQUAL_TO', 'Not equal to', gridOptions, translaterService) } +export function compoundOperatorNumeric(gridOptions: GridOption, translaterService?: TranslaterService): OperatorDetail[] { + const operatorList: OperatorDetail[] = [ + { operator: '', desc: '' }, + { operator: '=', desc: getOutputText('EQUAL_TO', 'TEXT_EQUAL_TO', 'Equal to', gridOptions, translaterService) }, + { operator: '<', desc: getOutputText('LESS_THAN', 'TEXT_LESS_THAN', 'Less than', gridOptions, translaterService) }, + { operator: '<=', desc: getOutputText('LESS_THAN_OR_EQUAL_TO', 'TEXT_LESS_THAN_OR_EQUAL_TO', 'Less than or equal to', gridOptions, translaterService) }, + { operator: '>', desc: getOutputText('GREATER_THAN', 'TEXT_GREATER_THAN', 'Greater than', gridOptions, translaterService) }, + { operator: '>=', desc: getOutputText('GREATER_THAN_OR_EQUAL_TO', 'TEXT_GREATER_THAN_OR_EQUAL_TO', 'Greater than or equal to', gridOptions, translaterService) }, + { operator: '<>', desc: getOutputText('NOT_EQUAL_TO', 'TEXT_NOT_EQUAL_TO', 'Not equal to', gridOptions, translaterService) } ]; + + // add alternate texts when provided + applyOperatorAltTextWhenExists(gridOptions, operatorList, 'numeric'); + + return operatorList; } + +// internal function to apply Operator detail alternate texts when they exists +function applyOperatorAltTextWhenExists(gridOptions: GridOption, operatorDetailList: OperatorDetail[], filterType: 'text' | 'numeric') { + if (gridOptions.compoundOperatorAltTexts) { + for (const opDetail of operatorDetailList) { + if (gridOptions.compoundOperatorAltTexts.hasOwnProperty(filterType)) { + const altTexts = gridOptions.compoundOperatorAltTexts[filterType]![opDetail.operator]; + opDetail['operatorAlt'] = altTexts?.operatorAlt || ''; + opDetail['descAlt'] = altTexts?.descAlt || ''; + } + } + } +} \ No newline at end of file diff --git a/packages/common/src/interfaces/gridOption.interface.ts b/packages/common/src/interfaces/gridOption.interface.ts index 22d813772..56cd75a93 100644 --- a/packages/common/src/interfaces/gridOption.interface.ts +++ b/packages/common/src/interfaces/gridOption.interface.ts @@ -28,6 +28,7 @@ import type { HeaderMenu, ItemMetadata, Locale, + OperatorDetailAlt, Pagination, ResizeByContentOption, RowDetailView, @@ -145,6 +146,19 @@ export interface GridOption { /** Column Picker Plugin options (columnTitle, forceFitTitle, syncResizeTitle) */ columnPicker?: ColumnPicker; + /** + * Compound Filters alternate texts, there are 2 filter categories that can be changed + * 1. text: CompoundInputFilter, CompoundInputPassword + * 2. numeric: CompoundDate, CompoundInputNumber, CompoundSlider + * + * For example + * `compoundOperatorAltTexts: { text: { 'a*': { operatorAlt: 'a..', descAlt: 'my alternate description' } }}` + */ + compoundOperatorAltTexts?: { + text?: { [operator in OperatorString]?: OperatorDetailAlt }; + numeric?: { [operator in OperatorString]?: OperatorDetailAlt }; + }; + /** Optionally provide global options to the Composite Editor instead of having to redeclare them every time you want to use it */ compositeEditorOptions?: Partial; diff --git a/packages/common/src/interfaces/operatorDetail.interface.ts b/packages/common/src/interfaces/operatorDetail.interface.ts index dc28c321d..2e9bf9e14 100644 --- a/packages/common/src/interfaces/operatorDetail.interface.ts +++ b/packages/common/src/interfaces/operatorDetail.interface.ts @@ -1,7 +1,13 @@ -import type { OperatorString, OperatorType } from '../enums/index'; +import type { OperatorString } from '../enums/index'; + +/** Operator detail alternate texts */ +export interface OperatorDetailAlt { + operatorAlt?: string; + descAlt?: string; +} /** Operator with its Description */ -export interface OperatorDetail { - operator: OperatorString | OperatorType; - description: string; -} \ No newline at end of file +export interface OperatorDetail extends OperatorDetailAlt { + operator: OperatorString; + desc: string; +}