From c36808cd01a22130211e9258fb8b232e0bc4cd37 Mon Sep 17 00:00:00 2001 From: Sylvain Bellone Date: Thu, 5 Dec 2024 18:00:22 +0100 Subject: [PATCH] feat: newArrival attribute (#207) Today, we have a `newArrivalsCategory` attribute which contains hierarchical facets. But displaying two hierarchical facets on the UI can create confusion and duplicates the information. This PR adds a `newArrival` boolean attribute and a `toggleRefinement` widget which permits to show a simple refinement checkbox similar to SFRA default "New Arrival" checkbox. ### Changes - Refactored the `productModelCustomizer` to also handle the `newArrival` attribute - Added a `toggleRefinement` widget on the UI, and the CSS to have the same look and feel than SFRA - Also added to SiteGenesis with a simpler `labelText` and no custom CSS --- SFCC-406 SFCC-407 --- .../customization/productModelCustomizer.js | 46 +++++++++++++------ .../js/algolia/instantsearch-config.js | 20 ++++++++ .../algolia/productsearchrefinebar.isml | 2 +- .../static/default/css/algolia/index.css | 10 ++++ .../js/algolia/instantsearch-config.js | 31 +++++++++++-- .../search/searchResultsNoDecorator.isml | 1 + .../productModelCustomizer.test.js | 6 +++ 7 files changed, 97 insertions(+), 19 deletions(-) diff --git a/cartridges/int_algolia/cartridge/scripts/algolia/customization/productModelCustomizer.js b/cartridges/int_algolia/cartridge/scripts/algolia/customization/productModelCustomizer.js index 1a636c19..50607265 100644 --- a/cartridges/int_algolia/cartridge/scripts/algolia/customization/productModelCustomizer.js +++ b/cartridges/int_algolia/cartridge/scripts/algolia/customization/productModelCustomizer.js @@ -93,6 +93,32 @@ function createAlgoliaLocalizedCategoryObject(category) { return result; } +var NEW_ARRIVALS_CATEGORY_ID = 'newarrivals'; +var specialValueHandlers = { + newArrivalsCategory: function(productModel) { + if (!empty(productModel.categories)) { + for (var i = 0; i < productModel.categories.length; i += 1) { + var topLevelCategoryId = productModel.categories[i][productModel.categories[i].length - 1].id; + if (topLevelCategoryId === NEW_ARRIVALS_CATEGORY_ID) { + return createAlgoliaLocalizedCategoryObject(productModel.categories[i]); + } + } + } + return null; + }, + newArrival: function(productModel) { + if (!empty(productModel.categories)) { + for (var i = 0; i < productModel.categories.length; i += 1) { + var topLevelCategoryId = productModel.categories[i][productModel.categories[i].length - 1].id; + if (topLevelCategoryId === NEW_ARRIVALS_CATEGORY_ID) { + return true; + } + } + } + return false; + } +} + /** * Customize a Localized Algolia Product. * Add extra properties to the product model. @@ -100,22 +126,12 @@ function createAlgoliaLocalizedCategoryObject(category) { * @param {Array} algoliaAttributes - The attributes to index */ function customizeLocalizedProductModel(productModel, algoliaAttributes) { - var CATEGORY_ATTRIBUTE = 'newArrivalsCategory'; - var CATEGORY_ID = 'newarrivals'; - - if (algoliaAttributes.indexOf(CATEGORY_ATTRIBUTE) >= 0) { - productModel[CATEGORY_ATTRIBUTE] = null; - - if (!empty(productModel.categories)) { - for (var i = 0; i < productModel.categories.length; i += 1) { - var rootCategoryId = productModel.categories[i][productModel.categories[i].length - 1].id; - if (rootCategoryId === CATEGORY_ID) { - productModel[CATEGORY_ATTRIBUTE] = createAlgoliaLocalizedCategoryObject(productModel.categories[i]); - break; - } - } + var specialAttributes = ['newArrival', 'newArrivalsCategory']; + specialAttributes.forEach(function(attributeName) { + if (algoliaAttributes.indexOf(attributeName) >= 0 && specialValueHandlers[attributeName]) { + productModel[attributeName] = specialValueHandlers[attributeName](productModel); } - } + }) } module.exports = { diff --git a/cartridges/int_algolia_controllers/cartridge/static/default/js/algolia/instantsearch-config.js b/cartridges/int_algolia_controllers/cartridge/static/default/js/algolia/instantsearch-config.js index cdb2ea32..45566797 100644 --- a/cartridges/int_algolia_controllers/cartridge/static/default/js/algolia/instantsearch-config.js +++ b/cartridges/int_algolia_controllers/cartridge/static/default/js/algolia/instantsearch-config.js @@ -138,6 +138,17 @@ function enableInstantSearch(config) { panelTitle: algoliaData.strings.newArrivals }), + toggleRefinementWithPanel({ + container: '#algolia-newarrival-placeholder', + attribute: 'newArrival', + templates: { + labelText(data, { html }) { + return html` ${algoliaData.strings.newArrivals}`; + }, + }, + panelTitle: algoliaData.strings.newArrivals, + }), + refinementListWithPanel({ container: '#algolia-brand-list-placeholder', attribute: 'brand', @@ -330,6 +341,15 @@ function enableInstantSearch(config) { return withPanel(options.attribute, options.panelTitle)(instantsearch.widgets.refinementList)(options) } + /** + * Builds a refinement toggle with the Panel widget + * @param {Object} options Options object + * @returns {Object} The Panel widget + */ + function toggleRefinementWithPanel(options) { + return withPanel(options.attribute, options.panelTitle)(instantsearch.widgets.toggleRefinement)(options) + } + /** * Builds a range input with the Panel widget * @param {Object} options Options object diff --git a/cartridges/int_algolia_controllers/cartridge/templates/default/algolia/productsearchrefinebar.isml b/cartridges/int_algolia_controllers/cartridge/templates/default/algolia/productsearchrefinebar.isml index 43350f18..659ce6d2 100644 --- a/cartridges/int_algolia_controllers/cartridge/templates/default/algolia/productsearchrefinebar.isml +++ b/cartridges/int_algolia_controllers/cartridge/templates/default/algolia/productsearchrefinebar.isml @@ -8,6 +8,7 @@
+
@@ -16,4 +17,3 @@
- diff --git a/cartridges/int_algolia_sfra/cartridge/static/default/css/algolia/index.css b/cartridges/int_algolia_sfra/cartridge/static/default/css/algolia/index.css index 620aeb3e..776ce8a9 100644 --- a/cartridges/int_algolia_sfra/cartridge/static/default/css/algolia/index.css +++ b/cartridges/int_algolia_sfra/cartridge/static/default/css/algolia/index.css @@ -45,6 +45,16 @@ padding: 0 1em; } +.ais-ToggleRefinement-checkbox { + position: absolute; + opacity: 0; + height: 0; + width: 0; +} +.ais-ToggleRefinement-label { + cursor: pointer; +} + .auc-Recommend-item .col-12 { padding: 0; } diff --git a/cartridges/int_algolia_sfra/cartridge/static/default/js/algolia/instantsearch-config.js b/cartridges/int_algolia_sfra/cartridge/static/default/js/algolia/instantsearch-config.js index e14f5e96..79119d34 100644 --- a/cartridges/int_algolia_sfra/cartridge/static/default/js/algolia/instantsearch-config.js +++ b/cartridges/int_algolia_sfra/cartridge/static/default/js/algolia/instantsearch-config.js @@ -166,6 +166,22 @@ function enableInstantSearch(config) { panelTitle: algoliaData.strings.newArrivals }), + toggleRefinementWithPanel({ + container: '#algolia-newarrival-placeholder', + attribute: 'newArrival', + templates: { + labelText(data, { html }) { + return html` + + + ${algoliaData.strings.newArrivals} + + `; + }, + }, + panelTitle: algoliaData.strings.newArrivals + }), + refinementListWithPanel({ container: '#algolia-brand-list-placeholder', attribute: 'brand', @@ -303,7 +319,7 @@ function enableInstantSearch(config) { `; - }, + }, }, transformItems: function (items, { results }) { displaySwatches = false; @@ -556,6 +572,15 @@ function enableInstantSearch(config) { return withPanel(options.attribute, options.panelTitle)(instantsearch.widgets.refinementList)(options) } + /** + * Builds a refinement toggle with the Panel widget + * @param {Object} options Options object + * @returns {Object} The Panel widget + */ + function toggleRefinementWithPanel(options) { + return withPanel(options.attribute, options.panelTitle)(instantsearch.widgets.toggleRefinement)(options) + } + /** * Builds a range input with the Panel widget * @param {Object} options Options object @@ -663,7 +688,7 @@ function fetchPromoPrices(productIDs) { // Filter out already fetched product IDs const unfetchedProductIDs = productIDs.filter(id => !fetchedPrices.has(id)); - + if (unfetchedProductIDs.length === 0) return Promise.resolve(); return $.ajax({ @@ -796,4 +821,4 @@ function calculateDisplayPrice(item) { price: item.price, calloutMsg: '', } -} \ No newline at end of file +} diff --git a/cartridges/int_algolia_sfra/cartridge/templates/default/algolia/search/searchResultsNoDecorator.isml b/cartridges/int_algolia_sfra/cartridge/templates/default/algolia/search/searchResultsNoDecorator.isml index c563bc6e..5bd3e870 100644 --- a/cartridges/int_algolia_sfra/cartridge/templates/default/algolia/search/searchResultsNoDecorator.isml +++ b/cartridges/int_algolia_sfra/cartridge/templates/default/algolia/search/searchResultsNoDecorator.isml @@ -67,6 +67,7 @@
+
diff --git a/test/unit/int_algolia/scripts/algolia/customization/productModelCustomizer.test.js b/test/unit/int_algolia/scripts/algolia/customization/productModelCustomizer.test.js index e986f37f..09ff51b1 100644 --- a/test/unit/int_algolia/scripts/algolia/customization/productModelCustomizer.test.js +++ b/test/unit/int_algolia/scripts/algolia/customization/productModelCustomizer.test.js @@ -75,4 +75,10 @@ describe('customizeLocalizedProductModel (jobs v2)', () => { productModelCustomizer.customizeLocalizedProductModel(product, []); expect(product).not.toHaveProperty('newArrivalsCategory'); }); + + test('newArrival', () => { + productModelCustomizer.customizeLocalizedProductModel(product, ['newArrival']); + expect(product).toHaveProperty('newArrival'); + expect(product.newArrival).toBe(true); + }); });