import { createDomElement, htmlEncodeWithPadding } from '@slickgrid-universal/utils'; import { getCollectionFromObjectWhenEnabled } from '../commonEditorFilter/commonEditorFilterUtils.js'; import { Constants } from '../constants.js'; import type { SlickGrid } from '../core/slickGrid.js'; import { applyHtmlToElement } from '../core/utils.js'; import type { Column, GridOption, Locale, OperatorDetail } from '../interfaces/index.js'; import type { Observable, RxJsFacade, Subject, Subscription } from '../services/rxjsFacade.js'; import type { TranslaterService } from '../services/translater.service.js'; import { fetchAsPromise, getTranslationPrefix } from '../services/utilities.js'; /** * Create and return a select dropdown HTML element with a list of Operators with descriptions * @param {Array} optionValues - list of operators and their descriptions * @returns {Object} selectElm - Select Dropdown HTML Element */ export function buildSelectOperator(optionValues: OperatorDetail[], grid: SlickGrid): HTMLSelectElement { const selectElm = createDomElement('select', { className: 'form-control', tabIndex: 0 }); for (const option of optionValues) { const optionElm = document.createElement('option'); optionElm.value = option.operator; applyHtmlToElement( optionElm, `${htmlEncodeWithPadding(option.operatorAlt || option.operator, 3)}${option.descAlt || option.desc}`, grid.getOptions() ); selectElm.appendChild(optionElm); } return selectElm; } /** * When user use a CollectionAsync we will use the returned collection to render the filter DOM element * and reinitialize filter collection with this new collection */ export function renderDomElementFromCollectionAsync( collection: any[], columnDef: Column, renderDomElementCallback: (collection: any) => void ): void { const columnFilter = columnDef?.filter ?? {}; collection = getCollectionFromObjectWhenEnabled(collection, columnFilter); if (!Array.isArray(collection)) { throw new Error( 'Something went wrong while trying to pull the collection from the "collectionAsync" call in the Filter, the collection is not a valid array.' ); } // copy over the array received from the async call to the "collection" as the new collection to use // this has to be BEFORE the `collectionObserver().subscribe` to avoid going into an infinite loop columnFilter.collection = collection; // recreate Multiple Select after getting async collection renderDomElementCallback(collection); } export async function renderCollectionOptionsAsync( collectionAsync: Promise | Observable | Subject, columnDef: Column, renderDomElementCallback: (collection: any) => void, rxjs?: RxJsFacade, subscriptions?: Subscription[] ): Promise { const columnFilter = columnDef?.filter ?? {}; let awaitedCollection: any = null; if (collectionAsync) { const isObservable = rxjs?.isObservable(collectionAsync) ?? false; awaitedCollection = await fetchAsPromise(collectionAsync, rxjs); awaitedCollection = getCollectionFromObjectWhenEnabled(awaitedCollection, columnFilter); if (!Array.isArray(awaitedCollection)) { throw new Error( 'Something went wrong while trying to pull the collection from the "collectionAsync" call in the Filter, the collection is not a valid array.' ); } // copy over the array received from the async call to the "collection" as the new collection to use // this has to be BEFORE the `collectionObserver().subscribe` to avoid going into an infinite loop columnFilter.collection = awaitedCollection; // recreate Multiple Select after getting async collection renderDomElementCallback(awaitedCollection); // because we accept Promises & HttpClient Observable only execute once // we will re-create an RxJs Subject which will replace the "collectionAsync" which got executed once anyway // doing this provide the user a way to call a "collectionAsync.next()" if (isObservable) { createCollectionAsyncSubject(columnDef, renderDomElementCallback, rxjs, subscriptions); } } return awaitedCollection; } /** Create or recreate an Observable Subject and reassign it to the "collectionAsync" object so user can call a "collectionAsync.next()" on it */ export function createCollectionAsyncSubject( columnDef: Column, renderDomElementCallback: (collection: any) => void, rxjs?: RxJsFacade, subscriptions?: Subscription[] ): void { const columnFilter = columnDef?.filter ?? {}; const newCollectionAsync = rxjs?.createSubject(); columnFilter.collectionAsync = newCollectionAsync; if (subscriptions && newCollectionAsync) { subscriptions.push( newCollectionAsync.subscribe((collection) => renderDomElementFromCollectionAsync(collection, columnDef, renderDomElementCallback)) ); } } /** Get Locale, Translated or a Default Text if first two aren't detected */ function getOutputText( translationKey: string, localeText: string, defaultText: string, gridOptions: GridOption, translaterService?: TranslaterService ): string { if (gridOptions?.enableTranslate && translaterService?.translate) { const translationPrefix = getTranslationPrefix(gridOptions); return translaterService.translate(`${translationPrefix}${translationKey}`); } const locales = gridOptions.locales || Constants.locales; return locales?.[localeText as keyof Locale] ?? defaultText; } /** returns common list of string related operators and their associated translation descriptions */ 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) }, ]; return operatorList; } /** returns common list of numeric related operators and their associated translation descriptions */ 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) }, ]; return operatorList; } // internal function to apply Operator detail alternate texts when they exists export function applyOperatorAltTextWhenExists( gridOptions: GridOption, operatorDetailList: OperatorDetail[], filterType: 'text' | 'numeric' ): void { 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 || ''; } } } }