diff --git a/packages/api-client/src/recommendationWidgetsAPI/types.ts b/packages/api-client/src/recommendationWidgetsAPI/types.ts index 3b66149..1187cfb 100644 --- a/packages/api-client/src/recommendationWidgetsAPI/types.ts +++ b/packages/api-client/src/recommendationWidgetsAPI/types.ts @@ -27,6 +27,7 @@ export interface WidgetRequestType { brSeg?: string; segment?: string; cdp_segments?: string; + view_id?: string; } export interface GetCategoryWidgetRequest extends WidgetRequestType { diff --git a/packages/frontend/vanilla-js/CHANGELOG.md b/packages/frontend/vanilla-js/CHANGELOG.md index 935553a..941e78c 100644 --- a/packages/frontend/vanilla-js/CHANGELOG.md +++ b/packages/frontend/vanilla-js/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. +## [3.2.0] - 2024-12-03 + +### Changed + +- Support view_id for recommendations and search APIs. +- Support additional parameters for search and autosuggest APIs. +- Refactored code to avoid repeated calls to get the global config. +- Rewritten pixel events listeners using custom events, to support Shopify webpixels implementation. +- Default pixel events handlers are now packaged separately, so Shopify connectors can implement its own pixel events handlers. + +## Fixed + +Infinite scroll paging issues (https://bloomreach.atlassian.net/browse/DCONN-79). + ## [3.1.4] - 2024-10-08 ### Changed diff --git a/packages/frontend/vanilla-js/package.json b/packages/frontend/vanilla-js/package.json index 42007a7..861c98d 100644 --- a/packages/frontend/vanilla-js/package.json +++ b/packages/frontend/vanilla-js/package.json @@ -1,6 +1,6 @@ { "name": "@bloomreach/discovery-ui-js", - "version": "3.1.4", + "version": "3.2.0", "main": "index.js", "type": "module", "scripts": { diff --git a/packages/frontend/vanilla-js/rollup.config.js b/packages/frontend/vanilla-js/rollup.config.js index d039705..44b35c3 100644 --- a/packages/frontend/vanilla-js/rollup.config.js +++ b/packages/frontend/vanilla-js/rollup.config.js @@ -81,7 +81,8 @@ const baseConfig = { const inputs = { autosuggest: 'src/modules/autosuggest-entry-point.ts', category: 'src/modules/category-entry-point.ts', - events: 'src/modules/product-events-entry-point.ts', + 'pixel-events': 'src/modules/pixel-events-entry-point.ts', + 'product-events': 'src/modules/product-events-entry-point.ts', recommendations: 'src/modules/recommendations-entry-point.ts', search: 'src/modules/search-entry-point.ts', }; @@ -109,7 +110,7 @@ const buildConfigs = Object.keys(inputs).reduce((allConfigs, moduleName) => { outputStyle: 'compressed' }), optimizeLodashImports(), - terser() + // terser() ], } ]; diff --git a/packages/frontend/vanilla-js/src/listeners/autosuggest/category-link-element.ts b/packages/frontend/vanilla-js/src/listeners/autosuggest/category-link-element.ts index 43c8a2a..ea169e6 100644 --- a/packages/frontend/vanilla-js/src/listeners/autosuggest/category-link-element.ts +++ b/packages/frontend/vanilla-js/src/listeners/autosuggest/category-link-element.ts @@ -1,5 +1,6 @@ import { PARAMETER_NAME_PAGE } from '../../constants'; import { updateCurrentAutosuggestRequestState } from '../../modules/builders'; +import type { AutosuggestModuleConfig } from '../../types'; import { getAutosuggestResultsContainerElement, getAutosuggestSearchInputElement, @@ -8,7 +9,7 @@ import { declare const window: any; -function buildCategoryLinkElementClickListener() { +function buildCategoryLinkElementClickListener(config: AutosuggestModuleConfig) { return (event: Event) => { event.preventDefault(); const clickedElement = event.target as HTMLAnchorElement; @@ -17,7 +18,7 @@ function buildCategoryLinkElementClickListener() { if (window.BloomreachModules && window.BloomreachModules.search) { updateParameterInUrl(PARAMETER_NAME_PAGE, '1'); window.BloomreachModules.search.load(categoryId).then(() => { - getAutosuggestSearchInputElement().value = clickedElement?.textContent || ''; + getAutosuggestSearchInputElement(config).value = clickedElement?.textContent || ''; getAutosuggestResultsContainerElement().innerHTML = ''; updateCurrentAutosuggestRequestState({ last_template_data: null }); @@ -27,12 +28,12 @@ function buildCategoryLinkElementClickListener() { }; } -export function addCategoryLinkElementClickListener() { +export function addCategoryLinkElementClickListener(config: AutosuggestModuleConfig) { getAutosuggestResultsContainerElement() .querySelectorAll('.blm-autosuggest__suggestion-term-link--category') .forEach((categoryLinkElement) => { if (!categoryLinkElement.getAttribute('hasListener')) { - categoryLinkElement.addEventListener('click', buildCategoryLinkElementClickListener()); + categoryLinkElement.addEventListener('click', buildCategoryLinkElementClickListener(config)); categoryLinkElement.setAttribute('hasListener', 'true'); } }); diff --git a/packages/frontend/vanilla-js/src/listeners/autosuggest/form-element.ts b/packages/frontend/vanilla-js/src/listeners/autosuggest/form-element.ts index 8bacb78..5ecd167 100644 --- a/packages/frontend/vanilla-js/src/listeners/autosuggest/form-element.ts +++ b/packages/frontend/vanilla-js/src/listeners/autosuggest/form-element.ts @@ -1,29 +1,19 @@ -import { findUpElementByTagName, getAutosuggestSearchInputElement } from '../../utils'; +import type { AutosuggestModuleConfig } from '../../types'; +import { getAutosuggestSearchFormElement, getAutosuggestSearchInputElement } from '../../utils'; -declare const window: any; - -function buildFormElementSubmitListener() { - return () => { - const searchData = { - q: getAutosuggestSearchInputElement().value, - catalogs: [{name: 'example_en'}] - }; - - (window.BrTrk || {}) - ?.getTracker() - ?.logEvent('suggest', 'submit', searchData, {}, true); - }; -} - -export function addFormElementSubmitListener() { - const element = (findUpElementByTagName( - getAutosuggestSearchInputElement(), - 'form' - ) as HTMLFormElement); +export function addFormElementSubmitListener(config: AutosuggestModuleConfig) { + const element = getAutosuggestSearchFormElement(config); if (element && !element.getAttribute('hasListener')) { element.addEventListener( 'submit', - buildFormElementSubmitListener() + () => element.dispatchEvent(new CustomEvent('brSuggestSubmit', + { + bubbles: true, + detail: { + q: getAutosuggestSearchInputElement(config).value, + } + } + )) ); element.setAttribute('hasListener', 'true'); } diff --git a/packages/frontend/vanilla-js/src/listeners/autosuggest/search-input-element.ts b/packages/frontend/vanilla-js/src/listeners/autosuggest/search-input-element.ts index f3410ff..74c42d8 100644 --- a/packages/frontend/vanilla-js/src/listeners/autosuggest/search-input-element.ts +++ b/packages/frontend/vanilla-js/src/listeners/autosuggest/search-input-element.ts @@ -14,6 +14,7 @@ import { findUpElementWithClassName } from '../../utils'; import autosuggestTemplate from '../../templates/autosuggest.ejs'; +import type { AutosuggestModuleConfig } from '../../types'; function buildSearchInputElementBlurListener() { return () => { @@ -53,14 +54,13 @@ function buildSearchInputElementFocusListener() { }; } -function buildSearchInputElementKeyupListener() { +function buildSearchInputElementKeyupListener(searchInputElement: HTMLElement, config: AutosuggestModuleConfig) { return (event: KeyboardEvent) => { const query = (event.target as HTMLInputElement).value; - const searchInputElement = getAutosuggestSearchInputElement(); if (query.length >= AUTOSUGGEST_MINIMUM_QUERY_LENGTH) { searchInputElement.dataset.originalQuery = query; - suggest(query).catch(console.error); + suggest(query, config).catch(console.error); } else { getAutosuggestResultsContainerElement().innerHTML = ''; searchInputElement.dataset.originalQuery = ''; @@ -69,13 +69,12 @@ function buildSearchInputElementKeyupListener() { }; } -function addSearchInputElementBlurListener() { +function addSearchInputElementBlurListener(element: HTMLElement) { if (!document.body.getAttribute('hasMousedownListener')) { document.body.addEventListener('mousedown', buildGeneralClickListenerForSearchInputBlur()); document.body.setAttribute('hasMousedownListener', 'true'); } - const element = getAutosuggestSearchInputElement(); if (!element.getAttribute('hasBlurListener')) { element.addEventListener( 'blur', @@ -85,8 +84,7 @@ function addSearchInputElementBlurListener() { } } -function addSearchInputElementFocusListener() { - const element = getAutosuggestSearchInputElement(); +function addSearchInputElementFocusListener(element: HTMLElement) { if (!element.getAttribute('hasFocusListener')) { element.addEventListener( 'focus', @@ -96,21 +94,21 @@ function addSearchInputElementFocusListener() { } } -function addSearchInputElementKeyupListener() { - const element = getAutosuggestSearchInputElement(); +function addSearchInputElementKeyupListener(element: HTMLElement, config: AutosuggestModuleConfig) { if (!element.getAttribute('hasKeyupListener')) { element.addEventListener( 'keyup', // @ts-ignore - debounce(buildSearchInputElementKeyupListener(), 500) as EventListener + debounce(buildSearchInputElementKeyupListener(element, config), 500) as EventListener ); element.setAttribute('hasKeyupListener', 'true'); } } -export function addSearchInputElementListeners() { - addSearchInputElementBlurListener(); - addSearchInputElementFocusListener(); - addSearchInputElementKeyupListener(); +export function addSearchInputElementListeners(config: AutosuggestModuleConfig) { + const element = getAutosuggestSearchInputElement(config); + addSearchInputElementBlurListener(element); + addSearchInputElementFocusListener(element); + addSearchInputElementKeyupListener(element, config); } diff --git a/packages/frontend/vanilla-js/src/listeners/autosuggest/suggestion-term-element.ts b/packages/frontend/vanilla-js/src/listeners/autosuggest/suggestion-term-element.ts index 873808d..e3a032d 100644 --- a/packages/frontend/vanilla-js/src/listeners/autosuggest/suggestion-term-element.ts +++ b/packages/frontend/vanilla-js/src/listeners/autosuggest/suggestion-term-element.ts @@ -1,32 +1,23 @@ +import type { AutosuggestModuleConfig } from '../../types'; import { getAutosuggestResultsContainerElement, getAutosuggestSearchInputElement } from '../../utils'; -declare const window: any; - -function buildSuggestionTermElementClickListener(suggestionTermElement: HTMLElement) { - return () => { - const { suggestionText } = suggestionTermElement.dataset; - const { originalQuery } = getAutosuggestSearchInputElement().dataset; - - const suggestionData = { - aq: originalQuery, - q: suggestionText, - catalogs: [{name: 'example_en'}] - }; - - ;(window.BrTrk || {}) - ?.getTracker() - ?.logEvent('suggest', 'click', suggestionData, {}, true); - }; -} - -export function addSuggestionTermElementClickListener() { +export function addSuggestionTermElementClickListener(config: AutosuggestModuleConfig) { getAutosuggestResultsContainerElement() .querySelectorAll('.blm-autosuggest__suggestion-term-link') .forEach((suggestionTermElement: HTMLElement) => { if (!suggestionTermElement.getAttribute('hasListener')) { - suggestionTermElement.addEventListener( - 'click', - buildSuggestionTermElementClickListener(suggestionTermElement) + const { suggestionText } = suggestionTermElement.dataset; + const { originalQuery } = getAutosuggestSearchInputElement(config).dataset; + suggestionTermElement.addEventListener('click', () => suggestionTermElement.dispatchEvent( + new CustomEvent('brSuggestClick', + { + bubbles: true, + detail: { + aq: originalQuery, + q: suggestionText, + } + } + )) ); suggestionTermElement.setAttribute('hasListener', 'true'); } diff --git a/packages/frontend/vanilla-js/src/listeners/index.ts b/packages/frontend/vanilla-js/src/listeners/index.ts deleted file mode 100644 index 8a40b62..0000000 --- a/packages/frontend/vanilla-js/src/listeners/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './autosuggest'; -export * from './search'; diff --git a/packages/frontend/vanilla-js/src/listeners/pixel-events/add-to-cart.ts b/packages/frontend/vanilla-js/src/listeners/pixel-events/add-to-cart.ts new file mode 100644 index 0000000..b566569 --- /dev/null +++ b/packages/frontend/vanilla-js/src/listeners/pixel-events/add-to-cart.ts @@ -0,0 +1,38 @@ + +declare const window: any; + +export function addCartClickAddEventListener() { + document.addEventListener( + 'brCartClickAdd', + (event: CustomEvent) => { + event.stopImmediatePropagation(); + const { prod_id, sku } = event.detail; + (window.BrTrk || {})?.getTracker()?.logEvent('cart', 'click-add', { + prod_id, + sku, + }); + } + ); +} + +export function addCartWidgetAddEventListener() { + document.addEventListener('brCartWidgetAdd', (event: CustomEvent) => { + event.stopImmediatePropagation(); + const { wrid, wid, wty, item_id, wq, sku } = event.detail; + const eventData = { + wrid, + wid, + wty, + item_id, + sku, + ...(wq ? { wq } : {}), + }; + + (window.BrTrk || {})?.getTracker()?.logEvent( + 'cart', + 'widget-add', + eventData + ); + }); +} + diff --git a/packages/frontend/vanilla-js/src/listeners/pixel-events/index.ts b/packages/frontend/vanilla-js/src/listeners/pixel-events/index.ts new file mode 100644 index 0000000..c99e15f --- /dev/null +++ b/packages/frontend/vanilla-js/src/listeners/pixel-events/index.ts @@ -0,0 +1,6 @@ +export * from './add-to-cart'; +export * from './quickview'; +export * from './search'; +export * from './suggest'; +export * from './widget-click'; +export * from './widget-view'; diff --git a/packages/frontend/vanilla-js/src/listeners/pixel-events/quickview.ts b/packages/frontend/vanilla-js/src/listeners/pixel-events/quickview.ts new file mode 100644 index 0000000..bb048b6 --- /dev/null +++ b/packages/frontend/vanilla-js/src/listeners/pixel-events/quickview.ts @@ -0,0 +1,17 @@ + +declare const window: any; + +export function addProductQuickviewEventListener() { + document.addEventListener( + 'brProductQuickview', + (event: CustomEvent) => { + event.stopImmediatePropagation(); + const { prod_id, prod_name, sku } = event.detail; + (window.BrTrk || {})?.getTracker()?.logEvent('product', 'quickview', { + prod_id, + prod_name, + sku, + }); + } + ); +} diff --git a/packages/frontend/vanilla-js/src/listeners/pixel-events/search.ts b/packages/frontend/vanilla-js/src/listeners/pixel-events/search.ts new file mode 100644 index 0000000..e081532 --- /dev/null +++ b/packages/frontend/vanilla-js/src/listeners/pixel-events/search.ts @@ -0,0 +1,14 @@ +declare const window: any; + +export function addSearchSubmitEventListener() { + document.addEventListener('brSuggestSubmit', (event: CustomEvent) => { + event.stopImmediatePropagation(); + const searchData = { + q: event.detail.q, + }; + + (window.BrTrk || {}) + ?.getTracker() + ?.logEvent('suggest', 'submit', searchData, {}, true); + }); +} diff --git a/packages/frontend/vanilla-js/src/listeners/pixel-events/suggest.ts b/packages/frontend/vanilla-js/src/listeners/pixel-events/suggest.ts new file mode 100644 index 0000000..e8ab641 --- /dev/null +++ b/packages/frontend/vanilla-js/src/listeners/pixel-events/suggest.ts @@ -0,0 +1,17 @@ +declare const window: any; + +export function addSuggestClickEventListener() { + document.addEventListener('brSuggestClick', (event: CustomEvent) => { + event.stopImmediatePropagation(); + const { aq, q } = event.detail; + + const suggestionData = { + aq, + q, + }; + + (window.BrTrk || {}) + ?.getTracker() + ?.logEvent('suggest', 'click', suggestionData, {}, true); + }); +} diff --git a/packages/frontend/vanilla-js/src/listeners/pixel-events/widget-click.ts b/packages/frontend/vanilla-js/src/listeners/pixel-events/widget-click.ts new file mode 100644 index 0000000..6527a3f --- /dev/null +++ b/packages/frontend/vanilla-js/src/listeners/pixel-events/widget-click.ts @@ -0,0 +1,21 @@ +declare const window: any; + +export function addWidgetClickEventListener() { + document.addEventListener('brWidgetClick', (event: CustomEvent) => { + event.stopImmediatePropagation(); + const { wrid, wid, wty, item_id, wq } = event.detail; + const eventData = { + wrid, + wid, + wty, + item_id, + ...(wq ? { wq } : {}), + }; + + (window.BrTrk || {})?.getTracker()?.logEvent( + 'widget', + 'widget-click', + eventData + ); + }); +} diff --git a/packages/frontend/vanilla-js/src/listeners/pixel-events/widget-view.ts b/packages/frontend/vanilla-js/src/listeners/pixel-events/widget-view.ts new file mode 100644 index 0000000..cb03171 --- /dev/null +++ b/packages/frontend/vanilla-js/src/listeners/pixel-events/widget-view.ts @@ -0,0 +1,20 @@ +declare const window: any; + +export function addWidgetViewEventListener() { + document.addEventListener('brWidgetView', (event: CustomEvent) => { + event.stopImmediatePropagation(); + const { wrid, wid, wty, wq } = event.detail; + const eventData = { + wrid, + wid, + wty, + ...(wq ? { wq } : {}), + }; + + (window.BrTrk || {})?.getTracker()?.logEvent( + 'widget', + 'widget-view', + eventData + ); + }); +} diff --git a/packages/frontend/vanilla-js/src/listeners/product-events/add-to-cart-element.ts b/packages/frontend/vanilla-js/src/listeners/product-events/add-to-cart-element.ts index 8c7b8de..27aac68 100644 --- a/packages/frontend/vanilla-js/src/listeners/product-events/add-to-cart-element.ts +++ b/packages/frontend/vanilla-js/src/listeners/product-events/add-to-cart-element.ts @@ -1,16 +1,17 @@ -declare const window: any; - function buildAddToCartElementClickListener(addToCartElement: HTMLElement) { const { blmAddToCartSku, blmAddToCartProdId } = addToCartElement.dataset; return () => { - (window.BrTrk || {})?.getTracker()?.logEvent('cart', 'click-add', { - prod_id: blmAddToCartProdId, - sku: blmAddToCartSku - }); + addToCartElement.dispatchEvent(new CustomEvent('brCartClickAdd', { + bubbles: true, + detail: { + prod_id: blmAddToCartProdId, + sku: blmAddToCartSku, + } + })); }; } diff --git a/packages/frontend/vanilla-js/src/listeners/product-events/quickview-element.ts b/packages/frontend/vanilla-js/src/listeners/product-events/quickview-element.ts index 96b9bf2..2c13197 100644 --- a/packages/frontend/vanilla-js/src/listeners/product-events/quickview-element.ts +++ b/packages/frontend/vanilla-js/src/listeners/product-events/quickview-element.ts @@ -1,6 +1,4 @@ -declare const window: any; - function buildQuickviewElementClickListener(quickviewElement: HTMLElement) { const { blmQuickviewSku, @@ -9,11 +7,14 @@ function buildQuickviewElementClickListener(quickviewElement: HTMLElement) { } = quickviewElement.dataset; return () => { - (window.BrTrk || {})?.getTracker()?.logEvent('product', 'quickview', { - prod_id: blmQuickviewProdId, - prod_name: blmQuickviewProdName, - sku: blmQuickviewSku - }); + quickviewElement.dispatchEvent(new CustomEvent('brProductQuickview', { + bubbles: true, + detail: { + prod_id: blmQuickviewProdId, + prod_name: blmQuickviewProdName, + sku: blmQuickviewSku, + } + })); }; } diff --git a/packages/frontend/vanilla-js/src/listeners/recommendations/widget-add-to-cart-button.ts b/packages/frontend/vanilla-js/src/listeners/recommendations/widget-add-to-cart-button.ts index ee1d5d5..434f417 100644 --- a/packages/frontend/vanilla-js/src/listeners/recommendations/widget-add-to-cart-button.ts +++ b/packages/frontend/vanilla-js/src/listeners/recommendations/widget-add-to-cart-button.ts @@ -1,8 +1,6 @@ import { getCurrentRecommendationsUiState } from '../../modules/builders'; -declare const window: any; - -function buildWidgetAddToCartButtonClickListener(parameters: { +function buildWidgetAddToCartButtonClickListener(widgetElement: HTMLElement, parameters: { widgetRid: string, widgetId: string, widgetType: string, @@ -22,11 +20,12 @@ function buildWidgetAddToCartButtonClickListener(parameters: { }; return () => { - (window.BrTrk || {})?.getTracker()?.logEvent( - 'cart', - 'widget-add', - widgetAddToCartEventData - ); + widgetElement.dispatchEvent(new CustomEvent('brCartWidgetAdd', { + bubbles: true, + detail: { + ...widgetAddToCartEventData, + } + })); }; } @@ -51,7 +50,7 @@ export function addWidgetAddToCartButtonClickListener() { if (!addToCartElement.getAttribute('hasListener')) { addToCartElement.addEventListener( 'click', - buildWidgetAddToCartButtonClickListener({ + buildWidgetAddToCartButtonClickListener(addToCartElement, { widgetRid, widgetId, widgetType, diff --git a/packages/frontend/vanilla-js/src/listeners/recommendations/widget-link-element.ts b/packages/frontend/vanilla-js/src/listeners/recommendations/widget-link-element.ts index 207ef4b..eb858ca 100644 --- a/packages/frontend/vanilla-js/src/listeners/recommendations/widget-link-element.ts +++ b/packages/frontend/vanilla-js/src/listeners/recommendations/widget-link-element.ts @@ -1,9 +1,7 @@ import { getCurrentRecommendationsUiState } from '../../modules/builders'; import { findUpElementWithClassName } from '../../utils'; -declare const window: any; - -function buildWidgetLinkElementClickListener(parameters: { +function buildWidgetLinkElementClickListener(widgetElement: HTMLElement, parameters: { widgetRid: string, widgetId: string, widgetType: string, @@ -19,11 +17,14 @@ function buildWidgetLinkElementClickListener(parameters: { ...(parameters.query ? {wq: parameters.query} : {}) - } + }; - ;(window.BrTrk || {}) - ?.getTracker() - ?.logEvent('widget', 'widget-click', widgetClickEventData, true); + widgetElement.dispatchEvent(new CustomEvent('brWidgetClick', { + bubbles: true, + detail: { + ...widgetClickEventData, + } + })); }; } @@ -52,7 +53,7 @@ export function addWidgetLinkElementClickListener() { if (!linkElement.getAttribute('hasListener')) { linkElement.addEventListener( 'click', - buildWidgetLinkElementClickListener({ + buildWidgetLinkElementClickListener(linkElement, { widgetRid, widgetId, widgetType, diff --git a/packages/frontend/vanilla-js/src/listeners/search/clear-all-selected-facets-button.ts b/packages/frontend/vanilla-js/src/listeners/search/clear-all-selected-facets-button.ts index 1700afa..a93556f 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/clear-all-selected-facets-button.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/clear-all-selected-facets-button.ts @@ -1,8 +1,9 @@ import { PARAMETER_NAME_FACETS, PARAMETER_NAME_PAGE } from '../../constants'; import { initiateSearch, updateCurrentSearchRequestState } from '../../modules/builders'; +import type { SearchModuleConfig } from '../../types'; import { buildPriceUrlParameterObject, resetLoadingIndicator, updateMultipleInstanceParametersInUrl, updateParameterInUrl } from '../../utils'; -function buildClearAllSelectedFacetsButtonClickListener() { +function buildClearAllSelectedFacetsButtonClickListener(config: SearchModuleConfig) { return () => { resetLoadingIndicator(); updateMultipleInstanceParametersInUrl( @@ -15,17 +16,17 @@ function buildClearAllSelectedFacetsButtonClickListener() { price_range_min_value: 0, price_range_max_value: 0, }); - initiateSearch({ toReplace: true }).catch(console.error); + initiateSearch(config, { toReplace: true }).catch(console.error); }; } -export function addClearAllSelectedFacetsButtonClickListener() { +export function addClearAllSelectedFacetsButtonClickListener(config: SearchModuleConfig) { const selectedFiltersClearAllButton = document.querySelector( '.blm-product-search-selected-filters__clear-all' ); if (selectedFiltersClearAllButton) { if (!selectedFiltersClearAllButton.getAttribute('hasListener')) { - selectedFiltersClearAllButton.addEventListener('click', buildClearAllSelectedFacetsButtonClickListener()); + selectedFiltersClearAllButton.addEventListener('click', buildClearAllSelectedFacetsButtonClickListener(config)); selectedFiltersClearAllButton.setAttribute('hasListener', 'true'); } } diff --git a/packages/frontend/vanilla-js/src/listeners/search/clear-price-range-value-button.ts b/packages/frontend/vanilla-js/src/listeners/search/clear-price-range-value-button.ts index c3bf11b..1f578a8 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/clear-price-range-value-button.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/clear-price-range-value-button.ts @@ -1,8 +1,9 @@ import { PARAMETER_NAME_FACETS, PARAMETER_NAME_PAGE } from '../../constants'; import { updateCurrentSearchRequestState, initiateSearch, getCurrentSearchRequestState } from '../../modules/builders'; +import type { SearchModuleConfig } from '../../types'; import { updateMultipleInstanceParametersInUrl, getCheckedFacetValues, updateParameterInUrl, resetLoadingIndicator } from '../../utils'; -function buildClearPriceRangeValueButtonClickListener() { +function buildClearPriceRangeValueButtonClickListener(config: SearchModuleConfig) { return () => { resetLoadingIndicator(); updateMultipleInstanceParametersInUrl( @@ -16,11 +17,11 @@ function buildClearPriceRangeValueButtonClickListener() { price_range_max_value: 0, price_range_min_value: 0 }); - initiateSearch({ toReplace: true }).catch(console.error); + initiateSearch(config, { toReplace: true }).catch(console.error); }; } -export function addClearPriceRangeValueButtonClickListener() { +export function addClearPriceRangeValueButtonClickListener(config: SearchModuleConfig) { const currentSearchRequestState = getCurrentSearchRequestState(); const priceRangeValueClearButton = document.querySelector( @@ -28,7 +29,7 @@ export function addClearPriceRangeValueButtonClickListener() { ); if (priceRangeValueClearButton) { if (!priceRangeValueClearButton.getAttribute('hasListener')) { - priceRangeValueClearButton.addEventListener('click', buildClearPriceRangeValueButtonClickListener()); + priceRangeValueClearButton.addEventListener('click', buildClearPriceRangeValueButtonClickListener(config)); priceRangeValueClearButton.setAttribute('hasListener', 'true'); } } diff --git a/packages/frontend/vanilla-js/src/listeners/search/facet-checkbox.ts b/packages/frontend/vanilla-js/src/listeners/search/facet-checkbox.ts index 0abc63e..3f3c8e6 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/facet-checkbox.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/facet-checkbox.ts @@ -1,8 +1,9 @@ import { PARAMETER_NAME_FACETS, PARAMETER_NAME_PAGE } from '../../constants'; import { initiateSearch, updateCurrentSearchRequestState } from '../../modules/builders'; +import type { SearchModuleConfig } from '../../types'; import { resetLoadingIndicator, updateMultipleInstanceParametersInUrl, getCheckedFacetValues, buildPriceUrlParameterObject, updateParameterInUrl } from '../../utils'; -function buildFacetCheckboxChangeListener() { +function buildFacetCheckboxChangeListener(config: SearchModuleConfig) { return () => { resetLoadingIndicator(); @@ -30,18 +31,18 @@ function buildFacetCheckboxChangeListener() { price_range_min_value: 0, price_range_max_value: 0, }); - initiateSearch({ toReplace: true }).catch(console.error); + initiateSearch(config, { toReplace: true }).catch(console.error); }; } -export function addFacetCheckboxChangeListener() { +export function addFacetCheckboxChangeListener(config: SearchModuleConfig) { const facetCheckboxes = document.querySelectorAll( '.blm-product-search-filter-item__checkbox' ); if (facetCheckboxes) { facetCheckboxes.forEach((checkbox) => { if (!checkbox.getAttribute('hasListener')) { - checkbox.addEventListener('change', buildFacetCheckboxChangeListener()); + checkbox.addEventListener('change', buildFacetCheckboxChangeListener(config)); checkbox.setAttribute('hasListener', 'true'); } }); diff --git a/packages/frontend/vanilla-js/src/listeners/search/facet-search-input.ts b/packages/frontend/vanilla-js/src/listeners/search/facet-search-input.ts index 5635084..35b0451 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/facet-search-input.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/facet-search-input.ts @@ -1,7 +1,8 @@ import { debounce } from 'lodash'; import { getLoadMoreFacetGroupsElement, resetFacetGroups } from '../../utils'; +import type { SearchModuleConfig } from '../../types'; -function buildFacetSearchInputChangeListener() { +function buildFacetSearchInputChangeListener(config: SearchModuleConfig) { return (event: Event) => { const inputValue = ( (event?.target as HTMLInputElement)?.value || '' @@ -38,12 +39,12 @@ function buildFacetSearchInputChangeListener() { getLoadMoreFacetGroupsElement().style.display = 'none'; if (!inputValue) { - resetFacetGroups(); + resetFacetGroups(config); } }; } -export function addFacetSearchInputChangeListener() { +export function addFacetSearchInputChangeListener(config: SearchModuleConfig) { const facetSearchInput = document.querySelector( '#blm-product-search-search-filters__input' ); @@ -52,7 +53,7 @@ export function addFacetSearchInputChangeListener() { facetSearchInput.addEventListener( 'input', // @ts-ignore - debounce(buildFacetSearchInputChangeListener(), 500) as EventListener + debounce(buildFacetSearchInputChangeListener(config), 500) as EventListener ); facetSearchInput.setAttribute('hasInputListener', 'true'); } diff --git a/packages/frontend/vanilla-js/src/listeners/search/groupby-select.ts b/packages/frontend/vanilla-js/src/listeners/search/groupby-select.ts index 8e2bd31..8f6315f 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/groupby-select.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/groupby-select.ts @@ -1,19 +1,20 @@ import { PARAMETER_NAME_GROUPBY } from '../../constants'; import { initiateSearch, getCurrentSearchRequestState } from '../../modules/builders'; +import type { SearchModuleConfig } from '../../types'; import { updateParameterInUrl, resetLoadingIndicator } from '../../utils'; -function buildGroupbySelectChangeListener() { +function buildGroupbySelectChangeListener(config: SearchModuleConfig) { return (event: Event) => { updateParameterInUrl( PARAMETER_NAME_GROUPBY, (event?.target as HTMLSelectElement)?.value ); resetLoadingIndicator(); - initiateSearch({ toReplace: true }).catch(console.error); + initiateSearch(config, { toReplace: true }).catch(console.error); }; } -export function addGroupbySelectChangeListener() { +export function addGroupbySelectChangeListener(config: SearchModuleConfig) { const currentSearchRequestState = getCurrentSearchRequestState(); const groupbySelector = document.querySelector( @@ -22,7 +23,7 @@ export function addGroupbySelectChangeListener() { if (groupbySelector) { if (!groupbySelector.getAttribute('hasListener')) { - groupbySelector.addEventListener('change', buildGroupbySelectChangeListener()); + groupbySelector.addEventListener('change', buildGroupbySelectChangeListener(config)); groupbySelector.setAttribute('hasListener', 'true'); } } diff --git a/packages/frontend/vanilla-js/src/listeners/search/load-more-facet-groups-button.ts b/packages/frontend/vanilla-js/src/listeners/search/load-more-facet-groups-button.ts index bcfd7e2..24bfdc2 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/load-more-facet-groups-button.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/load-more-facet-groups-button.ts @@ -1,15 +1,16 @@ +import type { SearchModuleConfig } from '../../types'; import { getLoadMoreFacetGroupsElement, loadMoreFacetGroups } from '../../utils'; -function buildLoadMoreFacetGroupsButtonClickListener() { +function buildLoadMoreFacetGroupsButtonClickListener(config: SearchModuleConfig) { return () => { - loadMoreFacetGroups(); + loadMoreFacetGroups(config); }; } -export function addLoadMoreFacetGroupsButtonClickListener() { +export function addLoadMoreFacetGroupsButtonClickListener(config: SearchModuleConfig) { const element = getLoadMoreFacetGroupsElement(); if (element && !element.getAttribute('hasListener')) { - element.addEventListener('click', buildLoadMoreFacetGroupsButtonClickListener()); + element.addEventListener('click', buildLoadMoreFacetGroupsButtonClickListener(config)); element.setAttribute('hasListener', 'true'); } } diff --git a/packages/frontend/vanilla-js/src/listeners/search/load-more-facet-values-button.ts b/packages/frontend/vanilla-js/src/listeners/search/load-more-facet-values-button.ts index 3bd4af6..79a4352 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/load-more-facet-values-button.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/load-more-facet-values-button.ts @@ -1,7 +1,6 @@ -import { buildSearchConfig } from '../../utils'; +import type { SearchModuleConfig } from '../../types'; -function buildLoadMoreFacetValuesButtonClickListener() { - const config = buildSearchConfig(); +function buildLoadMoreFacetValuesButtonClickListener(config: SearchModuleConfig) { const numberOfDisplayedFacetValues = Number(config.search?.initial_number_of_facet_values); let showFilterItems = numberOfDisplayedFacetValues; const incrementFilterBy = numberOfDisplayedFacetValues; @@ -29,12 +28,12 @@ function buildLoadMoreFacetValuesButtonClickListener() { }; } -export function addLoadMoreFacetValuesButtonClickListener() { +export function addLoadMoreFacetValuesButtonClickListener(config: SearchModuleConfig) { document .querySelectorAll('.blm-product-search-load-more') .forEach(item => { if (!item.getAttribute('hasListener')) { - item.addEventListener('click', buildLoadMoreFacetValuesButtonClickListener()); + item.addEventListener('click', buildLoadMoreFacetValuesButtonClickListener(config)); item.setAttribute('hasListener', 'true'); } }); diff --git a/packages/frontend/vanilla-js/src/listeners/search/page-size-select.ts b/packages/frontend/vanilla-js/src/listeners/search/page-size-select.ts index 7c0e750..8bc28d0 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/page-size-select.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/page-size-select.ts @@ -1,8 +1,9 @@ import { PARAMETER_NAME_SIZE, PARAMETER_NAME_PAGE } from '../../constants'; import { initiateSearch, getCurrentSearchRequestState } from '../../modules/builders'; +import type { SearchModuleConfig } from '../../types'; import { updateParameterInUrl, resetLoadingIndicator } from '../../utils'; -function buildPageSizeSelectChangeListener() { +function buildPageSizeSelectChangeListener(config: SearchModuleConfig) { return (event: Event) => { updateParameterInUrl( PARAMETER_NAME_SIZE, @@ -10,11 +11,11 @@ function buildPageSizeSelectChangeListener() { ); updateParameterInUrl(PARAMETER_NAME_PAGE, '1'); resetLoadingIndicator(); - initiateSearch({ toReplace: true }).catch(console.error); + initiateSearch(config, { toReplace: true }).catch(console.error); }; } -export function addPageSizeSelectChangeListener() { +export function addPageSizeSelectChangeListener(config: SearchModuleConfig) { const currentSearchRequestState = getCurrentSearchRequestState(); // Listen to page size select field changes @@ -23,7 +24,7 @@ export function addPageSizeSelectChangeListener() { ); if (sizeSelector) { if (!sizeSelector.getAttribute('hasListener')) { - sizeSelector.addEventListener('change', buildPageSizeSelectChangeListener()); + sizeSelector.addEventListener('change', buildPageSizeSelectChangeListener(config)); sizeSelector.setAttribute('hasListener', 'true'); } } diff --git a/packages/frontend/vanilla-js/src/listeners/search/pagination-container.ts b/packages/frontend/vanilla-js/src/listeners/search/pagination-container.ts index 8c25a75..0befd82 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/pagination-container.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/pagination-container.ts @@ -1,8 +1,9 @@ import { PARAMETER_NAME_PAGE } from '../../constants'; import { initiateSearch, getCurrentSearchRequestState } from '../../modules/builders'; +import type { SearchModuleConfig } from '../../types'; import { decrementParameterInUrl, incrementParameterInUrl, resetLoadingIndicator, updateParameterInUrl } from '../../utils'; -function buildPaginationContainerClickListener() { +function buildPaginationContainerClickListener(config: SearchModuleConfig) { return (event: Event) => { resetLoadingIndicator(); const clickedPaginationValue = (event.target as HTMLButtonElement) @@ -22,12 +23,12 @@ function buildPaginationContainerClickListener() { clickedPaginationValue ); } - initiateSearch({ toReplace: true }).catch(console.error); + initiateSearch(config, { toReplace: true }).catch(console.error); } }; } -export function addPaginationContainerClickListener() { +export function addPaginationContainerClickListener(config: SearchModuleConfig) { const currentSearchRequestState = getCurrentSearchRequestState(); // Listen to pagination events @@ -36,7 +37,7 @@ export function addPaginationContainerClickListener() { ); if (paginationContainer) { if (!paginationContainer.getAttribute('hasListener')) { - paginationContainer.addEventListener('click', buildPaginationContainerClickListener()); + paginationContainer.addEventListener('click', buildPaginationContainerClickListener(config)); paginationContainer.setAttribute('hasListener', 'true'); } } diff --git a/packages/frontend/vanilla-js/src/listeners/search/price-range-slider.ts b/packages/frontend/vanilla-js/src/listeners/search/price-range-slider.ts index 0b6d448..e9304c2 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/price-range-slider.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/price-range-slider.ts @@ -1,8 +1,9 @@ import { PARAMETER_NAME_FACETS, PARAMETER_NAME_PAGE } from '../../constants'; import { initiateSearch, getCurrentSearchRequestState } from '../../modules/builders'; -import { resetLoadingIndicator, updateMultipleInstanceParametersInUrl, getCheckedFacetValues, buildPriceUrlParameterObject, updateParameterInUrl, buildSearchConfig } from '../../utils'; +import type { SearchModuleConfig } from '../../types'; +import { resetLoadingIndicator, updateMultipleInstanceParametersInUrl, getCheckedFacetValues, buildPriceUrlParameterObject, updateParameterInUrl } from '../../utils'; -function buildPriceRangeChangeListener() { +function buildPriceRangeChangeListener(config: SearchModuleConfig) { return () => { resetLoadingIndicator(); @@ -14,12 +15,11 @@ function buildPriceRangeChangeListener() { } ); updateParameterInUrl(PARAMETER_NAME_PAGE, '1'); - initiateSearch({ toReplace: true }).catch(console.error); + initiateSearch(config, { toReplace: true }).catch(console.error); }; } -function buildPriceRangeInputListener(querySelector: string) { - const config = buildSearchConfig(); +function buildPriceRangeInputListener(querySelector: string, config: SearchModuleConfig) { return (event: Event) => { const sliderValue = document.querySelector(querySelector); if (sliderValue) { @@ -28,7 +28,7 @@ function buildPriceRangeInputListener(querySelector: string) { }; } -export function addPriceRangeChangeListeners() { +export function addPriceRangeChangeListeners(config: SearchModuleConfig) { const currentSearchRequestState = getCurrentSearchRequestState(); const priceRangeLowerBoundaryInput = document.querySelector( @@ -40,13 +40,13 @@ export function addPriceRangeChangeListeners() { if (priceRangeLowerBoundaryInput && priceRangeUpperBoundaryInput) { if (!priceRangeLowerBoundaryInput.getAttribute('hasListener')) { - priceRangeLowerBoundaryInput.addEventListener('change', buildPriceRangeChangeListener()); - priceRangeLowerBoundaryInput.addEventListener('input', buildPriceRangeInputListener('.blm-range-slider__values--min')); + priceRangeLowerBoundaryInput.addEventListener('change', buildPriceRangeChangeListener(config)); + priceRangeLowerBoundaryInput.addEventListener('input', buildPriceRangeInputListener('.blm-range-slider__values--min', config)); priceRangeLowerBoundaryInput.setAttribute('hasListener', 'true'); } if (!priceRangeUpperBoundaryInput.getAttribute('hasListener')) { - priceRangeUpperBoundaryInput.addEventListener('change', buildPriceRangeChangeListener()); - priceRangeUpperBoundaryInput.addEventListener('input', buildPriceRangeInputListener('.blm-range-slider__values--max')); + priceRangeUpperBoundaryInput.addEventListener('change', buildPriceRangeChangeListener(config)); + priceRangeUpperBoundaryInput.addEventListener('input', buildPriceRangeInputListener('.blm-range-slider__values--max', config)); priceRangeUpperBoundaryInput.setAttribute('hasListener', 'true'); } } diff --git a/packages/frontend/vanilla-js/src/listeners/search/scroll.ts b/packages/frontend/vanilla-js/src/listeners/search/scroll.ts index 8cc88ea..4374df5 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/scroll.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/scroll.ts @@ -2,7 +2,6 @@ import { PARAMETER_NAME_PAGE } from '../../constants'; import { initiateSearch } from '../../modules/builders'; import type { SearchModuleConfig } from '../../types'; import { - buildSearchConfig, decrementParameterInUrl, getSearchResultsContainerElement, incrementParameterInUrl @@ -11,8 +10,12 @@ import { declare const window: any; // @ts-ignore function buildIntersectionListener(config: SearchModuleConfig): IntersectionObserverCallback { - return (entries) => { - if (entries[0].intersectionRatio <= 0) { + return (entries: IntersectionObserverEntry[]) => { + if (!entries[0]?.isIntersecting) { + return; + } + + if (!entries[0].target.querySelector('.blm-scroll-indicator__loading')) { return; } @@ -21,7 +24,7 @@ function buildIntersectionListener(config: SearchModuleConfig): IntersectionObse connectorConfigObject.start = currentStart + config.search.items_per_page; incrementParameterInUrl(PARAMETER_NAME_PAGE); - initiateSearch().catch(error => { + initiateSearch(config).catch(error => { connectorConfigObject.start = currentStart; decrementParameterInUrl(PARAMETER_NAME_PAGE); console.error(error); @@ -29,14 +32,12 @@ function buildIntersectionListener(config: SearchModuleConfig): IntersectionObse }; } -export function addScrollListener() { - const config = buildSearchConfig(); - +export function addScrollListener(config: SearchModuleConfig) { if ( config.search?.infinite_scroll && !document.querySelector('.blm-scroll-indicator') ) { - const searchResultsContainerElement = getSearchResultsContainerElement(); + const searchResultsContainerElement = getSearchResultsContainerElement(config); const indicatorElement = document.createElement('div'); indicatorElement.classList.add('blm-scroll-indicator'); const loaderElement = document.createElement('div'); diff --git a/packages/frontend/vanilla-js/src/listeners/search/sort-select.ts b/packages/frontend/vanilla-js/src/listeners/search/sort-select.ts index 07de338..cbdefeb 100644 --- a/packages/frontend/vanilla-js/src/listeners/search/sort-select.ts +++ b/packages/frontend/vanilla-js/src/listeners/search/sort-select.ts @@ -1,19 +1,20 @@ import { PARAMETER_NAME_SORT } from '../../constants'; import { initiateSearch, getCurrentSearchRequestState } from '../../modules/builders'; +import type { SearchModuleConfig } from '../../types'; import { updateParameterInUrl, resetLoadingIndicator } from '../../utils'; -function buildSortSelectChangeListener() { +function buildSortSelectChangeListener(config: SearchModuleConfig) { return (event: Event) => { updateParameterInUrl( PARAMETER_NAME_SORT, (event?.target as HTMLSelectElement)?.value ); resetLoadingIndicator(); - initiateSearch({ toReplace: true }).catch(console.error); + initiateSearch(config, { toReplace: true }).catch(console.error); }; } -export function addSortSelectChangeListener() { +export function addSortSelectChangeListener(config: SearchModuleConfig) { const currentSearchRequestState = getCurrentSearchRequestState(); const sortSelector = document.querySelector( @@ -22,7 +23,7 @@ export function addSortSelectChangeListener() { if (sortSelector) { if (!sortSelector.getAttribute('hasListener')) { - sortSelector.addEventListener('change', buildSortSelectChangeListener()); + sortSelector.addEventListener('change', buildSortSelectChangeListener(config)); sortSelector.setAttribute('hasListener', 'true'); } } diff --git a/packages/frontend/vanilla-js/src/mappers/autosuggest.ts b/packages/frontend/vanilla-js/src/mappers/autosuggest.ts index 9758f6d..de907c4 100644 --- a/packages/frontend/vanilla-js/src/mappers/autosuggest.ts +++ b/packages/frontend/vanilla-js/src/mappers/autosuggest.ts @@ -5,21 +5,21 @@ import type { import ejs from 'ejs'; import { AUTOSUGGEST_TYPED_QUERY_TEMPLATE } from '../constants'; import type * as TemplateData from '../types/autosuggest-template-data'; -import { buildAutosuggestConfig } from '../utils'; +import type { AutosuggestModuleConfig } from '../types'; export function mapAutosuggestApiResponse( - responseData: AutosuggestResponseV1 | AutosuggestResponseV2 + responseData: AutosuggestResponseV1 | AutosuggestResponseV2, + config: AutosuggestModuleConfig, ): TemplateData.AutosuggestTemplateData { return isV2Response(responseData) - ? mapV2Response(responseData) - : mapV1Response(responseData); + ? mapV2Response(responseData, config) + : mapV1Response(responseData, config); } export function mapV2Response( - responseData: AutosuggestResponseV2 + responseData: AutosuggestResponseV2, + config: AutosuggestModuleConfig, ): TemplateData.AutosuggestTemplateData { - const config = buildAutosuggestConfig(); - const productSuggestions = responseData?.suggestionGroups?.[0]?.searchSuggestions || []; const suggestions = responseData?.suggestionGroups?.[0]?.querySuggestions || []; const categorySuggestions = responseData?.suggestionGroups?.[0]?.attributeSuggestions || []; @@ -77,9 +77,9 @@ function isV2Response( } function mapV1Response( - responseData: AutosuggestResponseV1 + responseData: AutosuggestResponseV1, + config: AutosuggestModuleConfig, ): TemplateData.AutosuggestTemplateData { - const config = buildAutosuggestConfig(); const mappedApiResponse = { ...(responseData.response.q ? { originalQuery: responseData.response.q } diff --git a/packages/frontend/vanilla-js/src/mappers/recommendations.ts b/packages/frontend/vanilla-js/src/mappers/recommendations.ts index 8dde1b4..786e6e2 100644 --- a/packages/frontend/vanilla-js/src/mappers/recommendations.ts +++ b/packages/frontend/vanilla-js/src/mappers/recommendations.ts @@ -1,12 +1,11 @@ -import { RecommendationWidgetsResponseV2 } from '@bloomreach/discovery-api-client/src/recommendationWidgetsAPI'; -import type { RecommendationsTemplateData } from '../types'; -import { buildRecommendationsConfig } from '../utils'; +import type { RecommendationWidgetsResponseV2 } from '@bloomreach/discovery-api-client/src/recommendationWidgetsAPI'; +import type { RecommendationsModuleConfig, RecommendationsTemplateData } from '../types'; export function mapRecommendationsApiResponse( - responseData: RecommendationWidgetsResponseV2 + responseData: RecommendationWidgetsResponseV2, + config: RecommendationsModuleConfig ): RecommendationsTemplateData { - const config = buildRecommendationsConfig(); return { config, products: [ diff --git a/packages/frontend/vanilla-js/src/mappers/search.ts b/packages/frontend/vanilla-js/src/mappers/search.ts index 503b9c8..90e7dfe 100644 --- a/packages/frontend/vanilla-js/src/mappers/search.ts +++ b/packages/frontend/vanilla-js/src/mappers/search.ts @@ -15,20 +15,20 @@ import type { SearchResponseFacetCountsV3FacetsStats as V3FacetsStats, } from '@bloomreach/discovery-api-client/src/getSearchResultsAPI'; import type * as TemplateData from '../types/search-template-data'; -import { buildSearchConfig } from '../utils'; +import type { SearchModuleConfig } from '../types'; export function mapSearchApiResponse( - responseData: SearchResponse + responseData: SearchResponse, + config: SearchModuleConfig, ): TemplateData.SearchTemplateData { - const config = buildSearchConfig(); const facets = responseData?.facet_counts ? ( isV3Facets(responseData.facet_counts) ? mapFacetsV3(responseData.facet_counts) : mapFacets(responseData.facet_counts, responseData.stats) ) : { facets: [] }; return { ...facets, - products: processDocs(responseData.response?.docs || []), + products: processDocs(responseData.response?.docs || [], config), // TODO delete this in case we don't need any other attribute from the grouped response /* ...(responseData?.group_response ? { @@ -58,7 +58,7 @@ export function mapSearchApiResponse( ...responseData.group_response?.[groupCategoryId], groups: responseData.group_response?.[groupCategoryId]?.groups?.map(group => ({ title: group.groupValue, - products: processDocs((group?.doclist?.docs || [])), + products: processDocs((group?.doclist?.docs || []), config), })) || [] }; }, {} as TemplateData.GroupedProducts) @@ -211,8 +211,7 @@ function mapPriceStatsV3(facets?: SearchResponseFacetCountsV3Facets[]): Pick { return [ diff --git a/packages/frontend/vanilla-js/src/modules/autosuggest-entry-point.ts b/packages/frontend/vanilla-js/src/modules/autosuggest-entry-point.ts index 41e4e7b..86e8328 100644 --- a/packages/frontend/vanilla-js/src/modules/autosuggest-entry-point.ts +++ b/packages/frontend/vanilla-js/src/modules/autosuggest-entry-point.ts @@ -10,4 +10,4 @@ window.BloomreachModules = { autosuggest: autosuggestModule, }; -autosuggestModule.load().catch(console.error); +// window.autosuggestReady = autosuggestModule.load().catch(console.error); diff --git a/packages/frontend/vanilla-js/src/modules/builders/autosuggest.ts b/packages/frontend/vanilla-js/src/modules/builders/autosuggest.ts index cc304c6..471b577 100644 --- a/packages/frontend/vanilla-js/src/modules/builders/autosuggest.ts +++ b/packages/frontend/vanilla-js/src/modules/builders/autosuggest.ts @@ -2,6 +2,7 @@ import ejs from 'ejs'; import invariant from 'tiny-invariant'; import type { AutosuggestModule, + AutosuggestModuleConfig, CurrentAutosuggestRequestState, CurrentAutosuggestUiState, } from '../../types'; @@ -9,6 +10,7 @@ import type { import * as listeners from '../../listeners/autosuggest'; import { buildAutosuggestConfig, + formatAdditionalParams, getAutosuggestResultsContainerElement, getAutosuggestSearchInputElement, injectAutosuggestDynamicStyles, @@ -43,29 +45,29 @@ export function buildAutosuggestModule(): AutosuggestModule { getCurrentAutosuggestUiState: () => currentAutosuggestUiState, load: async () => { - if (!areRequirementsMet()) { + const config = buildAutosuggestConfig(); + if (!areRequirementsMet(config)) { return; } - listeners.addSearchInputElementListeners(); - listeners.addFormElementSubmitListener(); + listeners.addSearchInputElementListeners(config); + listeners.addFormElementSubmitListener(config); - getAutosuggestSearchInputElement().setAttribute('autocomplete', 'off'); + getAutosuggestSearchInputElement(config).setAttribute('autocomplete', 'off'); }, }; } -export async function suggest(query: string): Promise { +export async function suggest(query: string, config: AutosuggestModuleConfig): Promise { updateCurrentAutosuggestRequestState({ request_id: generateRequestId(), }); - const apiCallParameters = buildApiCallParameters(query); + const apiCallParameters = buildApiCallParameters(query, config); // todo remediate typescript issue // @ts-ignore const results = await getSuggestions(apiCallParameters); - const templateData = mapAutosuggestApiResponse(results); - const config = buildAutosuggestConfig(); + const templateData = mapAutosuggestApiResponse(results, config); updateCurrentAutosuggestRequestState({ last_template_data: templateData }); @@ -74,12 +76,11 @@ export async function suggest(query: string): Promise { templateData ); - listeners.addCategoryLinkElementClickListener(); - listeners.addSuggestionTermElementClickListener(); + listeners.addCategoryLinkElementClickListener(config); + listeners.addSuggestionTermElementClickListener(config); } -function buildApiCallParameters(query: string) { - const config = buildAutosuggestConfig(); +function buildApiCallParameters(query: string, config: AutosuggestModuleConfig) { const urlParameters = new URLSearchParams(window.location.search as string); const currentAutosuggestRequestState = getCurrentAutosuggestRequestState(); const apiParameters: Partial = { @@ -92,7 +93,8 @@ function buildApiCallParameters(query: string) { ref_url: config.ref_url, url: config.url, request_type: config.request_type, - catalog_views: config.autosuggest?.catalog_views + catalog_views: config.autosuggest?.catalog_views, + ...formatAdditionalParams(config.autosuggest?.additional_parameters), }; // add URL parameters @@ -129,19 +131,17 @@ export function updateCurrentAutosuggestUiState(state: Partial { + listeners.addCartClickAddEventListener(); + listeners.addProductQuickviewEventListener(); + listeners.addSearchSubmitEventListener(); + listeners.addSuggestClickEventListener(); + listeners.addCartWidgetAddEventListener(); + listeners.addWidgetClickEventListener(); + listeners.addWidgetViewEventListener(); + }, + }; +} diff --git a/packages/frontend/vanilla-js/src/modules/builders/recommendations.ts b/packages/frontend/vanilla-js/src/modules/builders/recommendations.ts index f0a01fa..ab72cca 100644 --- a/packages/frontend/vanilla-js/src/modules/builders/recommendations.ts +++ b/packages/frontend/vanilla-js/src/modules/builders/recommendations.ts @@ -96,10 +96,10 @@ export function buildRecommendationsModule(): RecommendationsModule { }).then((widgetResponse: RecommendationWidgetsResponseV2) => { const widgetElement = (widgetData.node as HTMLElement); - const config = buildRecommendationsConfig(); + const config = buildRecommendationsConfig(widgetElement); // build template data - const templateData = mapRecommendationsApiResponse(widgetResponse); + const templateData = mapRecommendationsApiResponse(widgetResponse, config); const widgetAttributes: DOMStringMap = widgetElement.dataset; templateData.config.number_of_items_to_show = Number(widgetAttributes.numberOfItemsToShow); @@ -139,7 +139,7 @@ export function buildRecommendationsModule(): RecommendationsModule { } function buildApiCallParameters(widgetNode: Node): GetWidgetRequest { - const config = buildRecommendationsConfig(); + const config = buildRecommendationsConfig(widgetNode); const urlParameters = new URLSearchParams(window.location.search as string); const currentRecommendationsRequestState = getCurrentRecommendationsRequestState(); @@ -167,7 +167,8 @@ function buildApiCallParameters(widgetNode: Node): GetWidgetRequest { url: config.url ?? '', rows: Number(numberOfItemsToFetch) || DEFAULT_PAGE_SIZE, start: DEFAULT_START, - ...formatAdditionalParams(additionalParams) + ...(config.view_id ? { view_id: config.view_id } : {}), + ...formatAdditionalParams(additionalParams || config.widget.additional_parameters), }; // add URL parameters @@ -278,13 +279,14 @@ function logWidgetViewEvent(widgetElement: HTMLElement) { wid: widgetId, wty: widgetType, ...(widgetElement.dataset.query ? { wq: widgetElement.dataset.query } : {}) - } - ; (window.BrTrk || {})?.getTracker()?.logEvent( - 'widget', - 'widget-view', - widgetViewEventData, - true - ); + }; + + widgetElement.dispatchEvent(new CustomEvent('brWidgetView', { + bubbles: true, + detail: { + ...widgetViewEventData, + } + })); } function setupCarousel(widgetElement: HTMLElement, templateData: RecommendationsTemplateData) { diff --git a/packages/frontend/vanilla-js/src/modules/builders/search.ts b/packages/frontend/vanilla-js/src/modules/builders/search.ts index 8170b04..1d86a90 100644 --- a/packages/frontend/vanilla-js/src/modules/builders/search.ts +++ b/packages/frontend/vanilla-js/src/modules/builders/search.ts @@ -3,7 +3,8 @@ import type { ProductSearchModule, CurrentSearchRequestState, SearchTemplateData, - Facet + Facet, + SearchModuleConfig } from '../../types'; import { PARAMETER_NAME_SIZE, @@ -13,7 +14,7 @@ import { MAX_COLOR_SWATCHES, } from '../../constants'; -import * as listeners from '../../listeners'; +import * as listeners from '../../listeners/search'; import { resetFacetGroups, getSearchResultsContainerElement, @@ -29,7 +30,8 @@ import { decrementParameterInUrl, buildSearchConfig, applyKeywordRedirection, - buildPaginationData + buildPaginationData, + formatAdditionalParams } from '../../utils'; import { mapSearchApiResponse } from '../../mappers'; import * as ejs from 'ejs'; @@ -60,7 +62,9 @@ export function buildProductSearchModule({ isCategoryPage } = { isCategoryPage: updateCurrentSearchRequestState({ category_to_load: categoryToLoad }); } - if (!areRequirementsMet()) { + const config = buildSearchConfig(); + + if (!areRequirementsMet(config)) { return; } @@ -70,29 +74,27 @@ export function buildProductSearchModule({ isCategoryPage } = { isCategoryPage: // so it needs to be here before the first actual API call afterElementsLoaded(() => { // Add a class to show that the module's content has loaded - getSearchResultsContainerElement().classList.add( + getSearchResultsContainerElement(config).classList.add( 'blm-has-loaded' ); setupSavingScrollPosition(); // if infinite scroll is on then add intersection observer - listeners.addScrollListener(); + listeners.addScrollListener(config); - addChangeListeners(); - }); + addChangeListeners(config); + }, config); // initiate search with config values and URL parameters - await initiateSearch(); + await initiateSearch(config); restoreScrollPosition(); }, }; } -function areRequirementsMet() { - const config = buildSearchConfig(); - +function areRequirementsMet(config: SearchModuleConfig) { invariant(config.account_id, 'account_id must be set'); invariant(config.domain_key, 'domain_key must be set'); invariant( @@ -105,11 +107,11 @@ function areRequirementsMet() { ); // this checks if the element is in the DOM - getSearchResultsContainerElement(); + getSearchResultsContainerElement(config); const urlParameters = new URLSearchParams(window.location.search as string); const searchPageHasQueryToLoad = config.search.is_search_page && - urlParameters.has(config.default_search_parameter); + (urlParameters.has(config.default_search_parameter) || config.search.test_query); const categoryPageHasCategoryToLoad = config.search.is_category_page && (urlParameters.has(config.default_search_parameter) || config.search.category_id); @@ -124,14 +126,12 @@ function storeSegmentationPixelData() { } } -export async function initiateSearch(options = { toReplace: false }) { +export async function initiateSearch(config: SearchModuleConfig, options = { toReplace: false }) { updateCurrentSearchRequestState({ request_id: generateRequestId(), }); - const config = buildSearchConfig(); - - const apiCallParameters = buildApiCallParameters(); + const apiCallParameters = buildApiCallParameters(config); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument // @ts-ignore @@ -143,7 +143,7 @@ export async function initiateSearch(options = { toReplace: false }) { } // builds template data - const templateData = buildTemplateData(response); + const templateData = buildTemplateData(response, config); // takes care of scroll loader const scrollLoader = document.querySelector( @@ -162,7 +162,7 @@ export async function initiateSearch(options = { toReplace: false }) { if (currentSearchRequestState.is_first_request || !config.search.infinite_scroll || options.toReplace) { - getSearchResultsContainerElement().innerHTML = ejs.render( + getSearchResultsContainerElement(config).innerHTML = ejs.render( (config.search?.template || '') .replace( '%%-PRODUCT_LIST_TEMPLATE-%%', @@ -202,7 +202,7 @@ export function updateCurrentSearchRequestState(state: Partial void) { +function afterElementsLoaded(afterLoadCallback: () => void, config: SearchModuleConfig) { const mutationObserverConfig = { childList: true, subtree: true }; const mutationObserverCallback = (mutationsList: MutationRecord[]) => { const productListAdded = mutationsList.find( @@ -223,45 +223,44 @@ function afterElementsLoaded(afterLoadCallback: () => void) { const observer = new MutationObserver(mutationObserverCallback); observer.observe( - getSearchResultsContainerElement(), + getSearchResultsContainerElement(config), mutationObserverConfig ); } -function addChangeListeners() { +function addChangeListeners(config: SearchModuleConfig) { // When we're going back in history, we want to initiate // the search again according to the current URL state window.onpopstate = async () => { - await initiateSearch({ toReplace: true }); + await initiateSearch(config, { toReplace: true }); }; - listeners.addPriceRangeChangeListeners(); - listeners.addClearPriceRangeValueButtonClickListener(); + listeners.addPriceRangeChangeListeners(config); + listeners.addClearPriceRangeValueButtonClickListener(config); listeners.addClearSelectedFacetButtonClickListener(); - listeners.addClearAllSelectedFacetsButtonClickListener(); + listeners.addClearAllSelectedFacetsButtonClickListener(config); if (document.querySelector('.blm-product-search-sidebar')) { listeners.addSidebarControlButtonClickListener(); - listeners.addFacetCheckboxChangeListener(); - listeners.addLoadMoreFacetGroupsButtonClickListener(); - listeners.addLoadMoreFacetValuesButtonClickListener(); - listeners.addFacetSearchInputChangeListener(); + listeners.addFacetCheckboxChangeListener(config); + listeners.addLoadMoreFacetGroupsButtonClickListener(config); + listeners.addLoadMoreFacetValuesButtonClickListener(config); + listeners.addFacetSearchInputChangeListener(config); // Show the initial number of facets on load - resetFacetGroups(); + resetFacetGroups(config); } - listeners.addPageSizeSelectChangeListener(); - listeners.addSortSelectChangeListener(); - listeners.addGroupbySelectChangeListener(); - listeners.addPaginationContainerClickListener(); + listeners.addPageSizeSelectChangeListener(config); + listeners.addSortSelectChangeListener(config); + listeners.addGroupbySelectChangeListener(config); + listeners.addPaginationContainerClickListener(config); listeners.addSwatchElementHoverListener(); } -function buildTemplateData(response: SearchResponse): SearchTemplateData { - const config = buildSearchConfig(); +function buildTemplateData(response: SearchResponse, config: SearchModuleConfig): SearchTemplateData { // map values from API response - const templateData = mapSearchApiResponse(response); + const templateData = mapSearchApiResponse(response, config); // add stored keyword redirection info const storedKeywordRedirect = JSON.parse( @@ -355,8 +354,7 @@ function buildTemplateData(response: SearchResponse): SearchTemplateData { return templateData; } -function buildApiCallParameters() { - const config = buildSearchConfig(); +function buildApiCallParameters(config: SearchModuleConfig) { const urlParameters = new URLSearchParams(window.location.search as string); const currentSearchRequestState = getCurrentSearchRequestState(); const apiParameters: Partial = { @@ -364,10 +362,11 @@ function buildApiCallParameters() { ...(config.search?.groupby ? { groupby: config.search.groupby } : {}), ...(config.search?.group_limit ? { group_limit: config.search.group_limit } : {}), q: urlParameters.get(config.default_search_parameter || '') || + config.search.test_query || config.search.category_id || '', rows: config.search?.items_per_page, - sort: config?.sort, + sort: config.sort, start: config.start, account_id: config.account_id, domain_key: config.domain_key, @@ -380,6 +379,8 @@ function buildApiCallParameters() { fl: config.search?.fields, 'facet.range': config['facet.range'], 'stats.field': config['stats.field'], + ...(config.view_id ? { view_id: config.view_id } : {}), + ...(formatAdditionalParams(config.search.additional_parameters)), }; const pageUrlParameter = urlParameters.get(PARAMETER_NAME_PAGE); diff --git a/packages/frontend/vanilla-js/src/modules/category-entry-point.ts b/packages/frontend/vanilla-js/src/modules/category-entry-point.ts index 2346c83..b890ca0 100644 --- a/packages/frontend/vanilla-js/src/modules/category-entry-point.ts +++ b/packages/frontend/vanilla-js/src/modules/category-entry-point.ts @@ -10,4 +10,4 @@ window.BloomreachModules = { search: categoryModule, }; -categoryModule.load().catch(console.error); +// window.categoryReady = categoryModule.load().catch(console.error); diff --git a/packages/frontend/vanilla-js/src/modules/pixel-events-entry-point.ts b/packages/frontend/vanilla-js/src/modules/pixel-events-entry-point.ts new file mode 100644 index 0000000..efc74bf --- /dev/null +++ b/packages/frontend/vanilla-js/src/modules/pixel-events-entry-point.ts @@ -0,0 +1,12 @@ +import { globalBloomreachModules } from './global-bloomreach-modules'; +import { buildPixelEventsModule } from './builders'; + +declare const window: any; +const pixelEventsModule = buildPixelEventsModule(); + +window.BloomreachModules = { + ...globalBloomreachModules, + pixelEvents: pixelEventsModule, +}; + +// pixelEventsModule.load().catch(console.error); diff --git a/packages/frontend/vanilla-js/src/modules/product-events-entry-point.ts b/packages/frontend/vanilla-js/src/modules/product-events-entry-point.ts index 9c3d5b1..86343cc 100644 --- a/packages/frontend/vanilla-js/src/modules/product-events-entry-point.ts +++ b/packages/frontend/vanilla-js/src/modules/product-events-entry-point.ts @@ -6,7 +6,7 @@ const productEventsModule = buildProductEventsModule(); window.BloomreachModules = { ...globalBloomreachModules, - events: productEventsModule, + productEvents: productEventsModule, }; -productEventsModule.load().catch(console.error); +// pproductsEventsModule.load().catch(console.error); diff --git a/packages/frontend/vanilla-js/src/modules/recommendations-entry-point.ts b/packages/frontend/vanilla-js/src/modules/recommendations-entry-point.ts index 9dd7c90..551eae9 100644 --- a/packages/frontend/vanilla-js/src/modules/recommendations-entry-point.ts +++ b/packages/frontend/vanilla-js/src/modules/recommendations-entry-point.ts @@ -10,4 +10,4 @@ window.BloomreachModules = { pathwaysRecommendations: recommendationsModule, }; -recommendationsModule.load().catch(console.error); +// window.recommendationsReady = recommendationsModule.load().catch(console.error); diff --git a/packages/frontend/vanilla-js/src/modules/search-entry-point.ts b/packages/frontend/vanilla-js/src/modules/search-entry-point.ts index d2252fe..536f6ff 100644 --- a/packages/frontend/vanilla-js/src/modules/search-entry-point.ts +++ b/packages/frontend/vanilla-js/src/modules/search-entry-point.ts @@ -10,4 +10,4 @@ window.BloomreachModules = { search: productSearchModule, }; -productSearchModule.load().catch(console.error); +// window.searchReady = productSearchModule.load().catch(console.error); diff --git a/packages/frontend/vanilla-js/src/types/connector-config.ts b/packages/frontend/vanilla-js/src/types/connector-config.ts index ff59165..490e85e 100644 --- a/packages/frontend/vanilla-js/src/types/connector-config.ts +++ b/packages/frontend/vanilla-js/src/types/connector-config.ts @@ -4,6 +4,7 @@ export interface ConnectorConfig { account_id: number; domain_key: string; auth_key: string; + view_id?: string; tracking_cookie?: string; ref_url?: string; url?: string; @@ -30,6 +31,7 @@ export interface ConnectorConfig { groupby?: string; group_limit?: number; force_v3_facets?: boolean; + additional_parameters?: string; }; autosuggest?: { enabled: boolean; @@ -41,6 +43,7 @@ export interface ConnectorConfig { template?: string; catalog_views?: string; sort?: SortByOptions; + additional_parameters?: string; }; category?: { enabled: boolean; @@ -62,10 +65,12 @@ export interface ConnectorConfig { groupby?: string; group_limit?: number; force_v3_facets?: boolean; + additional_parameters?: string; }; widget?: { endpoint?: string; fields?: string; template?: string; + additional_parameters?: string; } } diff --git a/packages/frontend/vanilla-js/src/types/index.ts b/packages/frontend/vanilla-js/src/types/index.ts index 8285a15..b60a35a 100644 --- a/packages/frontend/vanilla-js/src/types/index.ts +++ b/packages/frontend/vanilla-js/src/types/index.ts @@ -5,6 +5,7 @@ export * from './autosuggest-template-data'; export * from './connector-config'; export * from './current-request-state'; export * from './current-ui-state'; +export * from './pixel-events'; export * from './product-events'; export * from './recommendations'; export * from './recommendations-module-config'; diff --git a/packages/frontend/vanilla-js/src/types/pixel-events.ts b/packages/frontend/vanilla-js/src/types/pixel-events.ts new file mode 100644 index 0000000..3b8a43b --- /dev/null +++ b/packages/frontend/vanilla-js/src/types/pixel-events.ts @@ -0,0 +1,3 @@ +export interface PixelEventsModule { + load: () => Promise, +} diff --git a/packages/frontend/vanilla-js/src/types/search-module-config.ts b/packages/frontend/vanilla-js/src/types/search-module-config.ts index 0adf04b..10e04cd 100644 --- a/packages/frontend/vanilla-js/src/types/search-module-config.ts +++ b/packages/frontend/vanilla-js/src/types/search-module-config.ts @@ -11,5 +11,6 @@ export interface SearchModuleConfig extends WithRequiredProp (option1.value > option2.value ? 1 : -1) - ); + // config.search?.sorting_options?.sort?.( + // (option1, option2) => (option1.value > option2.value ? 1 : -1) + // ); if (config.search) { config.search = { @@ -139,8 +139,9 @@ export function buildSearchConfig() { return config; } -export function buildRecommendationsConfig() { +export function buildRecommendationsConfig(widgetNode: Node) { const baseConfig = buildBaseConfig(); + const { endpoint, fields }: DOMStringMap = (widgetNode as HTMLElement).dataset; const config: RecommendationsModuleConfig = { ...baseConfig, @@ -148,7 +149,9 @@ export function buildRecommendationsConfig() { endpoint: '', fields: '', template: recommendationWidgetTemplate, - ...(baseConfig?.widget ?? {}) + ...(baseConfig?.widget ?? {}), + ...(endpoint ? { endpoint } : {}), + ...(fields ? { fields } : {}), } }; diff --git a/packages/frontend/vanilla-js/src/utils/dom.ts b/packages/frontend/vanilla-js/src/utils/dom.ts index 8ddd1e9..d5a8624 100644 --- a/packages/frontend/vanilla-js/src/utils/dom.ts +++ b/packages/frontend/vanilla-js/src/utils/dom.ts @@ -1,6 +1,6 @@ import invariant from 'tiny-invariant'; import { getCurrentSearchRequestState } from '../modules/builders'; -import { buildSearchConfig, buildAutosuggestConfig } from './config'; +import type { AutosuggestModuleConfig, SearchModuleConfig } from '../types'; export function findUpElementWithClassName( startElement: Node, @@ -53,10 +53,9 @@ export function hideAllDynamicFacetGroups() { }); } -export function loadMoreFacetGroups(numberOfFacetGroupsParameter?: number) { +export function loadMoreFacetGroups(config: SearchModuleConfig, numberOfFacetGroupsParameter?: number) { let i = 0; let numberOfHiddenBoxWithVisibleChildren = 0; - const config = buildSearchConfig(); const numberOfFacetGroups = Number( numberOfFacetGroupsParameter || config.search?.initial_number_of_facets ); @@ -100,13 +99,12 @@ export function getLoadMoreFacetGroupsElement(): HTMLElement { return element as HTMLElement; } -export function resetFacetGroups() { - const config = buildSearchConfig(); +export function resetFacetGroups(config: SearchModuleConfig) { const numberOfDisplayedFacetGroups = Number(config.search?.initial_number_of_facets); const numberOfDisplayedFacetValues = Number(config.search?.initial_number_of_facet_values); hideAllDynamicFacetGroups(); - loadMoreFacetGroups(numberOfDisplayedFacetGroups - 1); + loadMoreFacetGroups(config, numberOfDisplayedFacetGroups - 1); // init facet items visibility document @@ -118,8 +116,7 @@ export function resetFacetGroups() { getLoadMoreFacetGroupsElement().removeAttribute('style'); } -export function getSearchResultsContainerElement(): HTMLElement { - const config = buildSearchConfig(); +export function getSearchResultsContainerElement(config: SearchModuleConfig): HTMLElement { invariant( config.search?.selector, 'the selector of search results container element must be set' @@ -137,8 +134,7 @@ export function getSearchResultsListContainerElement(): HTMLElement { return searchResultsListContainerElement as HTMLElement; } -export function getAutosuggestSearchInputElement(): HTMLInputElement { - const config = buildAutosuggestConfig(); +export function getAutosuggestSearchInputElement(config: AutosuggestModuleConfig): HTMLInputElement { invariant( config.autosuggest?.selector, 'the selector of search results container element must be set' @@ -156,6 +152,10 @@ export function getAutosuggestResultsContainerElement(): HTMLElement { return autosuggestResultsContainerElement as HTMLElement; } +export function getAutosuggestSearchFormElement(config: AutosuggestModuleConfig): HTMLFormElement { + return findUpElementByTagName(getAutosuggestSearchInputElement(config), 'form') as HTMLFormElement; +} + export function resetLoadingIndicator() { const scrollIndicator = document.querySelector( '.blm-scroll-indicator' @@ -247,7 +247,7 @@ export function setupSavingScrollPosition() { }; } -export function injectAutosuggestDynamicStyles(): void { +export function injectAutosuggestDynamicStyles(config: AutosuggestModuleConfig): void { if (!getAutosuggestResultsContainerElement()) { const searchResultsContainerStyles = document.createElement('style'); searchResultsContainerStyles.innerHTML = `.blm-autosuggest-search-results { @@ -255,18 +255,18 @@ export function injectAutosuggestDynamicStyles(): void { position: absolute; z-index: 100; left: 0; - transform: translateY(${getAutosuggestSearchInputElement().offsetHeight}px); + transform: translateY(${getAutosuggestSearchInputElement(config).offsetHeight}px); }`; document.head.appendChild(searchResultsContainerStyles); } } -export function injectAutosuggestResultsContainer(): void { +export function injectAutosuggestResultsContainer(config: AutosuggestModuleConfig): void { if (!getAutosuggestResultsContainerElement()) { const searchResultsContainerElement = document.createElement('div'); searchResultsContainerElement.classList.add( 'blm-autosuggest-search-results' ); - getAutosuggestSearchInputElement().parentElement?.appendChild(searchResultsContainerElement); + getAutosuggestSearchInputElement(config).parentElement?.appendChild(searchResultsContainerElement); } } diff --git a/packages/frontend/vanilla-js/src/utils/url.ts b/packages/frontend/vanilla-js/src/utils/url.ts index cd522e4..a970289 100644 --- a/packages/frontend/vanilla-js/src/utils/url.ts +++ b/packages/frontend/vanilla-js/src/utils/url.ts @@ -88,7 +88,7 @@ export function decrementParameterInUrl(parameterName: string): void { updateParameterInUrl(parameterName, (oldValue) => { if (!oldValue) return '1'; let newValue = Number.parseInt(oldValue, 10); - return (--newValue).toString(); + return Math.max(1, --newValue).toString(); }); }