Live Search Popover CIF Component live-search-popover
The Live Search Popover is the element that contains the Live Search results as you type in the search field.
This topic describes how to integrate this component into your AEM site.
File Structure file-strucure
To enable the CIF component, files must be edited and created.
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/clientlibs/.content.xml
Create the
.content.xml
file:code language-xml <?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" jcr:primaryType="cq:ClientLibraryFolder" allowProxy="{Boolean}true" categories="[venia.cif]" jsProcessor="[default:none,min:none]"/>
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/clientlibs/css.txt
Create the
css.txt
file:code language-text #base=css searchbar.css
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/clientlibs/css/searchbar.css
Create the
searchbar.css
file:code language-css .searchbar__root .action.search:before { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-size: 16px; line-height: 32px; color: #757575; /* content: "\e615"; */ font-family: "luma-icons"; margin: 0; vertical-align: top; display: inline-block; font-weight: normal; overflow: hidden; speak: none; text-align: center; } .searchbar__label { display: none; } .searchbar__root .action.search > span { border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } input.searchbar__input::placeholder { font-size: 14px; } input.searchbar__input { background: #fff; background-clip: padding-box; border: 1px solid #c2c2c2; border-radius: 1px; font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; height: 32px; line-height: 1.42857143; padding: 0 9px; vertical-align: baseline; width: 100%; max-width: 200px; box-sizing: border-box; cursor: text; } .search-autocomplete { position: absolute; } div.searchbar { width: 100% !important; } div.searchbar__fields.search { display: flex; justify-content: center; } .searchbar__form { justify-items: center !important; } @media all and (min-width: 769px) { .searchbar__form { justify-items: stretch !important; } div.searchbar { width: 8.333333% !important; margin: 0; } .searchbar__root { position: relative; z-index: 4; } .searchbar__root .searchbar__control { border-top: 0; margin: 0; padding: 0.75em 0; } .searchbar__root .searchbar__input { margin: 0; padding-right: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } }
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/clientlibs/js.txt
Create the
js.txt
file:code language-text js/searchbar.js
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/clientlibs/js/searchbar.js
Create the
searchbar.js
file:code language-javascript /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ Copyright 2023 Adobe ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ "use strict"; const dataServicesStorefrontInstanceContextQuery = ` query DataServicesStorefrontInstanceContext { dataServicesStorefrontInstanceContext { customer_group environment_id environment store_id store_view_id store_code store_view_code website_id website_name website_code store_url api_key store_name store_view_name base_currency_code store_view_currency_code catalog_extension_version } storeConfig { base_currency_code store_code } } `; const dataServicesMagentoExtensionContextQuery = ` query DataServicesStorefrontInstanceContext { dataServicesMagentoExtensionContext { magento_extension_version } } `; const dataServicesStoreConfigurationContextQuery = ` query DataServicesStoreConfigurationContext { dataServicesStoreConfigurationContext { currency_symbol currency_rate page_size page_size_options default_page_size_option display_out_of_stock allow_all_products locale min_query_length } } `; const getCookie = (cookieName) => { const cookie = document.cookie.match( `(^|[^;]+)\\s*${cookieName}\\s*=\\s*([^;]+)` ); return cookie ? cookie.pop() : ""; }; const getLoginToken = () => { const key = "M2_VENIA_BROWSER_PERSISTENCE__signin_token"; let token = getCookie("cif.userToken") || ""; try { const lsToken = JSON.parse(localStorage.getItem(key)); if (lsToken && lsToken.value) { const timestamp = new Date().getTime(); if (timestamp - lsToken.timeStored < lsToken.ttl * 1000) { token = lsToken.value.replace(/"/g, ""); } } } catch (e) { console.error(`Login token at ${key} is not valid JSON.`); } return token; }; async function getGraphQLQuery(query, variables = {}) { const graphqlEndpoint = `/api/graphql`; const headers = { "Content-Type": "application/json", }; const loginToken = getLoginToken(); if (loginToken) { headers["Authorization"] = `Bearer ${loginToken}`; } const response = await fetch(graphqlEndpoint, { method: "POST", headers, body: JSON.stringify({ query, variables, }), }).then((res) => res.json()); return response.data; } class SearchBar { constructor() { const stateObject = { dataServicesStorefrontInstanceContext: null, dataServicesStoreConfigurationContext: null, magentoExtensionVersion: null, storeConfig: null, }; this._state = stateObject; this._init(); } _init() { this._initLiveSearch(); } _injectStoreScript(src) { const script = document.createElement("script"); script.type = "text/javascript"; script.src = src; document.head.appendChild(script); } async _getStoreData() { const { dataServicesStorefrontInstanceContext, storeConfig } = (await getGraphQLQuery(dataServicesStorefrontInstanceContextQuery)) || {}; const { dataServicesStoreConfigurationContext } = (await getGraphQLQuery(dataServicesStoreConfigurationContextQuery)) || {}; this._state.dataServicesStorefrontInstanceContext = dataServicesStorefrontInstanceContext; this._state.dataServicesStoreConfigurationContext = dataServicesStoreConfigurationContext; this._state.storeConfig = storeConfig; if (!dataServicesStorefrontInstanceContext) { console.log("no dataServicesStorefrontInstanceContext"); return; } // set session storage to expose for widget sessionStorage.setItem( "WIDGET_STOREFRONT_INSTANCE_CONTEXT", JSON.stringify({ ...dataServicesStorefrontInstanceContext, ...dataServicesStoreConfigurationContext, }) ); } async _getMagentoExtensionVersion() { const { dataServicesMagentoExtensionContext } = (await getGraphQLQuery(dataServicesMagentoExtensionContextQuery)) || {}; this._state.magentoExtensionVersion = dataServicesMagentoExtensionContext?.magento_extension_version; if (!dataServicesMagentoExtensionContext) { console.log("no magentoExtensionVersion"); return; } } getStoreConfigMetadata() { const storeConfig = JSON.parse( document .querySelector("meta[name='store-config']") .getAttribute("content") ); const { storeRootUrl } = storeConfig; const redirectUrl = storeRootUrl.split(".html")[0]; return { storeConfig, redirectUrl }; } async _initLiveSearch() { await Promise.all([ this._getStoreData(), this._getMagentoExtensionVersion(), ]); if (!window.LiveSearchAutocomplete) { const liveSearchSrc = "https://livesearch-autocomplete.magento-ds.com/v0/LiveSearchAutocomplete.js"; this._injectStoreScript(liveSearchSrc); // wait until script is loaded await new Promise((resolve) => { const interval = setInterval(() => { if (window.LiveSearchAutocomplete) { clearInterval(interval); resolve(); } }, 200); }); } const { dataServicesStorefrontInstanceContext, dataServicesStoreConfigurationContext, } = this._state; if (!dataServicesStorefrontInstanceContext) { console.log("no dataServicesStorefrontInstanceContext"); return; } // initialize live-search new window.LiveSearchAutocomplete({ environmentId: dataServicesStorefrontInstanceContext.environment_id, websiteCode: dataServicesStorefrontInstanceContext.website_code, storeCode: dataServicesStorefrontInstanceContext.store_code, storeViewCode: dataServicesStorefrontInstanceContext.store_view_code, config: { pageSize: dataServicesStoreConfigurationContext.page_size, minQueryLength: dataServicesStoreConfigurationContext.min_query_length, currencySymbol: dataServicesStoreConfigurationContext.currency_symbol, currencyRate: dataServicesStoreConfigurationContext.currency_rate, displayOutOfStock: dataServicesStoreConfigurationContext.display_out_of_stock, allowAllProducts: dataServicesStoreConfigurationContext.allow_all_products, }, context: { customerGroup: dataServicesStorefrontInstanceContext.customer_group, }, route: ({ sku }) => { return `${ this.getStoreConfigMetadata().redirectUrl }.cifproductredirect.html/${sku}`; }, searchRoute: { route: `${this.getStoreConfigMetadata().redirectUrl}/search.html`, query: "search_query", }, }); const formEle = document.getElementById("search_mini_form"); formEle.setAttribute( "action", `${dataServicesStorefrontInstanceContext.store_url}catalogsearch/result` ); // initialize store event after live-search this._initMetrics(); } async _initMetrics() { // Magento Store event // wait until script is magentoStorefrontEvents is found await new Promise((resolve) => { const interval = setInterval(() => { if (window.magentoStorefrontEvents) { clearInterval(interval); resolve(); } }, 200); }); const mse = window.magentoStorefrontEvents; const { dataServicesStorefrontInstanceContext, storeConfig } = this._state; const { base_currency_code, catalog_extension_version, environment, environment_id, store_code, store_id, store_name, store_url, store_view_code, store_view_id, store_view_name, store_view_currency_code, website_code, website_id, website_name, } = dataServicesStorefrontInstanceContext; console.log("initializing magento extension"); mse.context.setMagentoExtension({ magentoExtensionVersion: this._state.magentoExtensionVersion, }); mse.context.setPage({ pageType: "pdp", maxXOffset: 0, maxYOffset: 0, minXOffset: 0, minYOffset: 0, ping_interval: 5, pings: 1, }); mse.context.setStorefrontInstance({ environmentId: environment_id, environment: environment, storeUrl: store_url, websiteId: website_id, websiteCode: website_code, storeId: store_id, storeCode: store_code, storeViewId: store_view_id, storeViewCode: store_view_code, websiteName: website_name, storeName: store_name, storeViewName: store_view_name, baseCurrencyCode: base_currency_code, storeViewCurrencyCode: store_view_currency_code, catalogExtensionVersion: catalog_extension_version, }); } } (function () { function onDocumentReady() { new SearchBar({}); } if (document.readyState !== "loading") { onDocumentReady(); } else { document.addEventListener("DOMContentLoaded", onDocumentReady); } })();
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/searchbar.html
Create the
searchbar.html
file:code language-html <!-- Livesearch popover --> <div data-sly-use.storeconfig="com.adobe.cq.commerce.core.components.models.storeconfigexporter.StoreConfigExporter" class="searchbar__root widget-search" > <div class="block-title" style="display: none"> <strong>Search</strong> </div> <div class="live-search-popover block-content"> <form class="searchbar__form" id="search_mini_form" method="get" style="width: auto" > <div class="searchbar__fields"> <label class="searchbar__label" for="search" data-role="minisearch-label" > <span>Search</span> </label> <div class="searchbar__control"> <input id="search" type="text" name="q" value="" placeholder="Search entire store here..." class="searchbar__input" maxlength="128" role="combobox" aria-haspopup="false" aria-autocomplete="both" autocomplete="off" aria-expanded="false" onchange="" /> <div id="search_autocomplete" class="search-autocomplete"></div> </div> </div> <div class="actions" style="display: none"> <button type="submit" title="Search" class="action search" aria-label="Search" > <span>Search</span> </button> </div> </form> </div> </div>
-
ui.config/src/main/content/jcr_root/apps/venia/osgiconfig/config/com.adobe.cq.commerce.core.components.internal.servlets.ProductPageRedirectServlet.cfg.json
Create the
com.adobe.cq.commerce.core.components.internal.servlets.ProductPageRedirectServlet.cfg.json
file:code language-json { "sling.servlet.resourceTypes": [ "core/cif/components/structure/page/v1/page", "core/cif/components/structure/page/v2/page", "core/cif/components/structure/page/v3/page" ] }
-
ui.tests/test-module/specs/venia/searchbar.js
Edit the
searchbar.js
file, Line 19-20, changingdescribe
todescribe.skip
:code language-javascript describe.skip('Venia Searchbar Component', () => {
-