Live Search ポップオーバー CIF コンポーネント live-search-popover
Live Search ポップオーバーは、検索フィールドに入力したときの Live Search の結果を含む要素です。
このトピックでは、このコンポーネントを AEM サイトに統合する方法について説明します。
ファイル構造 file-strucure
CIF コンポーネントを有効にするには、ファイルを編集して作成する必要があります。
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/clientlibs/.content.xml
.content.xml
ファイルを作成します。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
css.txt
ファイルを作成します。code language-text #base=css searchbar.css
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/clientlibs/css/searchbar.css
searchbar.css
ファイルを作成します。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
js.txt
ファイルを作成します。code language-text js/searchbar.js
-
ui.apps/src/main/content/jcr_root/apps/venia/components/commerce/searchbar/clientlibs/js/searchbar.js
searchbar.js
ファイルを作成します。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
searchbar.html
ファイルを作成します。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
com.adobe.cq.commerce.core.components.internal.servlets.ProductPageRedirectServlet.cfg.json
ファイルを作成します。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
searchbar.js
ファイルの 19~20 行目を編集して、describe
をdescribe.skip
に変更します。code language-javascript describe.skip('Venia Searchbar Component', () => {
-