Skip to content

Commit

Permalink
feat(common): add compoundOperatorAltTexts grid option (#1181)
Browse files Browse the repository at this point in the history
* feat(common): add `compoundOperatorAlternateTexts` grid option
- this new grid option `compoundOperatorAlternateTexts` will allow the user to override any or all texts shown in a Compound Filter `operator` select dropdown

* chore: rename grid option to `compoundOperatorAlternateTexts`
  • Loading branch information
ghiscoding authored Nov 8, 2023
1 parent aff51fe commit dc0aa5e
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 40 deletions.
5 changes: 5 additions & 0 deletions examples/vite-demo-vanilla-bundle/src/examples/example14.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 29 additions & 5 deletions packages/common/src/filters/__tests__/compoundDateFilter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const gridOptionMock = {
} as GridOption;

const gridStub = {
getOptions: () => gridOptionMock,
getOptions: jest.fn(),
getColumns: jest.fn(),
getHeaderRowColumn: jest.fn(),
render: jest.fn(),
Expand All @@ -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: '>' } };

Expand All @@ -56,6 +57,7 @@ describe('CompoundDateFilter', () => {

afterEach(() => {
filter.destroy();
jest.clearAllMocks();
});

it('should throw an error when trying to call init without any arguments', () => {
Expand Down Expand Up @@ -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);
Expand All @@ -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<HTMLSelectElement>('.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;
Expand Down
33 changes: 28 additions & 5 deletions packages/common/src/filters/__tests__/compoundInputFilter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const gridOptionMock = {
} as GridOption;

const gridStub = {
getOptions: () => gridOptionMock,
getOptions: jest.fn(),
getColumns: jest.fn(),
getHeaderRowColumn: jest.fn(),
render: jest.fn(),
Expand All @@ -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 = {
Expand Down Expand Up @@ -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);
Expand All @@ -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<HTMLSelectElement>('.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;
Expand Down
30 changes: 26 additions & 4 deletions packages/common/src/filters/__tests__/compoundSliderFilter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<HTMLSelectElement>('.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;
Expand Down
64 changes: 43 additions & 21 deletions packages/common/src/filters/filterUtilities.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -11,13 +10,13 @@ import type { TranslaterService } from '../services/translater.service';
* @param {Array<Object>} 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);
}

Expand Down Expand Up @@ -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 || '';
}
}
}
}
14 changes: 14 additions & 0 deletions packages/common/src/interfaces/gridOption.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
HeaderMenu,
ItemMetadata,
Locale,
OperatorDetailAlt,
Pagination,
ResizeByContentOption,
RowDetailView,
Expand Down Expand Up @@ -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<CompositeEditorOpenDetailOption>;

Expand Down
16 changes: 11 additions & 5 deletions packages/common/src/interfaces/operatorDetail.interface.ts
Original file line number Diff line number Diff line change
@@ -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;
}
export interface OperatorDetail extends OperatorDetailAlt {
operator: OperatorString;
desc: string;
}

0 comments on commit dc0aa5e

Please sign in to comment.