diff --git a/src/plugins/combobox/index.ts b/src/plugins/combobox/index.ts index f8e3ed7..d8d6cff 100644 --- a/src/plugins/combobox/index.ts +++ b/src/plugins/combobox/index.ts @@ -19,6 +19,7 @@ import { IComboBox, IComboBoxOptions, IComboBoxItemAttr, + QueryTransformer, } from '../combobox/interfaces'; import HSBasePlugin from '../base-plugin'; @@ -34,6 +35,7 @@ class HSComboBox extends HSBasePlugin implements IComboBox { apiDataPart: string | null; apiQuery: string | null; apiSearchQuery: string | null; + apiSearchQueryTransformer: ((query: string) => string) | null; apiHeaders: {}; apiGroupField: string | null; outputItemTemplate: string | null; @@ -50,6 +52,7 @@ class HSComboBox extends HSBasePlugin implements IComboBox { private readonly output: HTMLElement | null; private readonly itemsWrapper: HTMLElement | null; private items: HTMLElement[] | []; + private selectedItemElement: HTMLElement | null; private tabs: HTMLElement[] | []; private readonly toggle: HTMLElement | null; private readonly toggleClose: HTMLElement | null; @@ -87,6 +90,7 @@ class HSComboBox extends HSBasePlugin implements IComboBox { this.apiDataPart = concatOptions?.apiDataPart ?? null; this.apiQuery = concatOptions?.apiQuery ?? null; this.apiSearchQuery = concatOptions?.apiSearchQuery ?? null; + this.apiSearchQueryTransformer = this.parseApiQueryTransformer(concatOptions?.apiSearchQueryTransformer); this.apiHeaders = concatOptions?.apiHeaders ?? {}; this.apiGroupField = concatOptions?.apiGroupField ?? null; this.outputItemTemplate = @@ -150,7 +154,20 @@ class HSComboBox extends HSBasePlugin implements IComboBox { this.init(); } + private parseApiQueryTransformer(query: QueryTransformer | string | null): QueryTransformer | null { + + if (!query) return null; + + if (typeof query === 'string') { + return eval(query) as QueryTransformer; + } + + return query; + } + private init() { + // So that not to dpeendon preloading whole library. + window.$hsComboBoxCollection = window.$hsComboBoxCollection ?? []; this.createCollection(window.$hsComboBoxCollection, this); this.build(); @@ -239,10 +256,10 @@ class HSComboBox extends HSBasePlugin implements IComboBox { const equality = params?.group?.name && group ? group === params.group.name && - elI.getAttribute('data-hs-combo-box-search-text') === - obj[elI.getAttribute('data-hs-combo-box-output-item-field')] + elI.getAttribute('data-hs-combo-box-search-text') === + obj[elI.getAttribute('data-hs-combo-box-output-item-field')] : elI.getAttribute('data-hs-combo-box-search-text') === - obj[elI.getAttribute('data-hs-combo-box-output-item-field')]; + obj[elI.getAttribute('data-hs-combo-box-output-item-field')]; return equality; }); @@ -319,7 +336,9 @@ class HSComboBox extends HSBasePlugin implements IComboBox { try { const query = `${this.apiQuery}`; - const searchQuery = `${this.apiSearchQuery}=${this.value.toLowerCase()}`; + const initialSearchQuery = `${this.apiSearchQuery}=${this.value.toLowerCase()}`; + const searchQuery = this.apiSearchQueryTransformer ? this.apiSearchQueryTransformer(this.value) : initialSearchQuery; + let url = this.apiUrl; if (this.apiQuery && this.apiSearchQuery) { url += `?${searchQuery}&${query}`; @@ -376,6 +395,12 @@ class HSComboBox extends HSBasePlugin implements IComboBox { } private jsonItemsRender(items: any) { + + // Bullet proofing. + if (Array.isArray(items)) { + return + } + items.forEach((item: never, index: number) => { // TODO:: test without checking below // if (this.isItemExists(item)) return false; @@ -401,6 +426,7 @@ class HSComboBox extends HSBasePlugin implements IComboBox { item[el.getAttribute('data-hs-combo-box-output-item-field')] ?? '', ); }); + // FIXME: Move to combobox options newItem .querySelectorAll('[data-hs-combo-box-output-item-attr]') .forEach((el) => { @@ -416,8 +442,7 @@ class HSComboBox extends HSBasePlugin implements IComboBox { if (this.groupingType === 'tabs' || this.groupingType === 'default') { newItem.setAttribute( 'data-hs-combo-box-output-item', - `{"group": {"name": "${item[this.apiGroupField]}", "title": "${ - item[this.apiGroupField] + `{"group": {"name": "${item[this.apiGroupField]}", "title": "${item[this.apiGroupField] }"}}`, ); } @@ -426,13 +451,13 @@ class HSComboBox extends HSBasePlugin implements IComboBox { if (!this.preventSelection) { (newItem as HTMLElement).addEventListener('click', () => { + this.selectedItemElement = newItem; + this.setSelectedByValue(this.valuesBySelector(newItem)); this.close( (newItem as HTMLElement) .querySelector('[data-hs-combo-box-value]') .getAttribute('data-hs-combo-box-search-text'), ); - - this.setSelectedByValue(this.valuesBySelector(newItem)); }); } @@ -645,6 +670,7 @@ class HSComboBox extends HSBasePlugin implements IComboBox { }); } + // FIXME: Does not go through the setSelectedByValue method. private setValue(val: string) { this.selected = val; this.value = val; @@ -666,12 +692,12 @@ class HSComboBox extends HSBasePlugin implements IComboBox { ? this.selectedGroup === 'all' ? this.items : this.items.filter((f: HTMLElement) => { - const { group } = JSON.parse( - f.getAttribute('data-hs-combo-box-output-item'), - ); + const { group } = JSON.parse( + f.getAttribute('data-hs-combo-box-output-item'), + ); - return group.name === this.selectedGroup; - }) + return group.name === this.selectedGroup; + }) : this.items; if (this.groupingType === 'tabs' && this.selectedGroup !== 'all') { @@ -739,7 +765,6 @@ class HSComboBox extends HSBasePlugin implements IComboBox { this.setSelectedByValue([this.selected]); } - // Public methods private setValueAndOpen(val: string) { this.value = val; @@ -748,6 +773,15 @@ class HSComboBox extends HSBasePlugin implements IComboBox { } } + private setValueAndClear(val: string | null) { + if (val) this.setValue(val); + else this.setValue(this.selected); + + if (this.outputPlaceholder) this.destroyOutputPlaceholder(); + } + + + // Public methods public open(val?: string) { if (this.animationInProcess) return false; @@ -771,13 +805,6 @@ class HSComboBox extends HSBasePlugin implements IComboBox { this.isOpened = true; } - private setValueAndClear(val: string | null) { - if (val) this.setValue(val); - else this.setValue(this.selected); - - if (this.outputPlaceholder) this.destroyOutputPlaceholder(); - } - public close(val?: string | null) { if (this.animationInProcess) return false; @@ -803,18 +830,36 @@ class HSComboBox extends HSBasePlugin implements IComboBox { afterTransition(this.output, () => { this.output.style.display = 'none'; - this.setValueAndClear(val); - this.animationInProcess = false; }); if (this.input.value !== '') this.el.classList.add('has-value'); else this.el.classList.remove('has-value'); + this.isOpened = false; } + setSearchQueryTransformer(transformer: (query: string) => string) { + this.apiSearchQueryTransformer = transformer; + } + + public selectedItem(): HTMLElement | null { + return this.selectedItemElement; + } + + selectedValue(): string | null { + return this.selected; + } + + selectedAttr(attr: string): string | null { + return this.selectedItemElement + ? this.selectedItemElement.querySelector(`[${attr}]`)?.getAttribute(attr) ?? null + : null; + } + + public recalculateDirection() { if ( isEnoughSpace( @@ -918,13 +963,13 @@ class HSComboBox extends HSBasePlugin implements IComboBox { const preparedItems = isReversed ? Array.from( - output.querySelectorAll(':scope > *:not(.--exclude-accessibility)'), - ) - .filter((el) => (el as HTMLElement).style.display !== 'none') - .reverse() + output.querySelectorAll(':scope > *:not(.--exclude-accessibility)'), + ) + .filter((el) => (el as HTMLElement).style.display !== 'none') + .reverse() : Array.from( - output.querySelectorAll(':scope > *:not(.--exclude-accessibility)'), - ).filter((el) => (el as HTMLElement).style.display !== 'none'); + output.querySelectorAll(':scope > *:not(.--exclude-accessibility)'), + ).filter((el) => (el as HTMLElement).style.display !== 'none'); const items = preparedItems.filter( (el: any) => !el.classList.contains('disabled'), ); @@ -1058,7 +1103,7 @@ class HSComboBox extends HSBasePlugin implements IComboBox { (el) => !isParentOrElementHidden(el.element.el) && (evt.target as HTMLElement).closest('[data-hs-combo-box]') === - el.element.el, + el.element.el, ); const link: HTMLAnchorElement = opened.element.el.querySelector( @@ -1080,8 +1125,8 @@ class HSComboBox extends HSBasePlugin implements IComboBox { opened.element.close( !opened.element.preventSelection ? (evt.target as HTMLElement) - .querySelector('[data-hs-combo-box-value]') - .getAttribute('data-hs-combo-box-search-text') + .querySelector('[data-hs-combo-box-value]') + .getAttribute('data-hs-combo-box-search-text') : null, ); } diff --git a/src/plugins/combobox/interfaces.ts b/src/plugins/combobox/interfaces.ts index 9a62d11..209e27f 100644 --- a/src/plugins/combobox/interfaces.ts +++ b/src/plugins/combobox/interfaces.ts @@ -6,6 +6,7 @@ export interface IComboBoxOptions { apiDataPart?: string | null; apiQuery?: string | null; apiSearchQuery?: string | null; + apiSearchQueryTransformer?: QueryTransformer | string | null; apiHeaders?: {}; apiGroupField?: string | null; outputItemTemplate?: string | null; @@ -24,10 +25,15 @@ export interface IComboBox { open(): void; close(): void; + selectedItem(): HTMLElement | null; + selectedValue(): string | null; + selectedAttr(attr: string): string | null; recalculateDirection(): void; } export interface IComboBoxItemAttr { valueFrom: string; attr: string; -} \ No newline at end of file +} + +export type QueryTransformer = (query: string) => string; \ No newline at end of file