Compare Products on Results Page
Overview of customization
- Modify 3 Template JS templates to add compare feature:
- klevuTemplateLanding - Add a table to display compared products
- filters - To display selected products to compare
- productBlock - Add compare toggle to each product
- Add JavaScript to power the compare feature
- Set attributes to display for compare
- Tell Klevu to override default templates
- Powerup code for compare feature
- Add CSS to style the compare feature
Requirements
- You must have Klevu integrated in your store using Template JS
- You need the ability to modify the code in your store
- Compare feature will be added to the Search Result Landing Page and Category Pages
Add Custom Template code
Add the following code anywhere on your store.
This code only needs to be loaded on the pages where the compare feature will be present, so you may conditionally add this code only when these pages are accessed.
If you already have custom templates for klevuTemplateLanding, filters, and/or productBlock, we have added comments to show what code is necessary.
<script type="template/klevu" id="klevuLandingTemplateBaseCustom">
<div ku-container data-container-id="ku_landing_main_container" data-container-role="main">
<header ku-container data-container-id="ku_landing_main_header_container" data-container-role="header">
<section ku-block data-block-id="ku_landing_main_header_sub_panel"></section>
<section ku-block data-block-id="ku_landing_main_header_banner">
<%=helper.render('klevuLandingPromotionBanner',scope,data,"top") %>
<%=helper.render('klevuLandingPromotionBanner',scope,data,"bottom") %>
</section>
<section ku-block data-block-id="ku_landing_main_header_site_navigation"></section>
</header>
<div class="kuContainer">
<% if(!helper.hasResults(data,"productList") && !helper.hasFilters(data,"productList")) { %>
<%=helper.render('noResultsFoundLanding',scope) %>
<% } else { %>
<%= helper.render('klevuLandingTemplateSearchBar', scope) %>
<%=helper.render('klevuLandingImageSearchUploadBanner',scope,data) %>
<%= helper.render('tab-results', scope) %>
<%= helper.render('results',scope) %>
<% } %>
<div class="kuClearBoth"></div>
</div>
<footer ku-container data-container-id="ku_landing_main_footer_container" data-container-role="footer">
<section ku-block data-block-id="ku_landing_main_footer_sub_panel"></section>
<section ku-block data-block-id="ku_landing_main_footer_banner">
</section>
</footer>
</div>
<!-- start of compare customization -->
<div class="klevu-compare-table" id="klevu-compare-table">
<table class="klevu-compare-table__products">
<div class="klevu-compare-table-header">
<label class="toggle-differences"><input type="checkbox" id="toggleDifferences" <% if (klevu_compare_show_differences_checked) { %>checked<% } %> /><%= helper.translate("Show only differences")%></label>
<button id="klevu-close-compare">
<svg style="pointer-events:none;" enable-background="new 0 0 386.667 386.667" height="15" viewBox="0 0 386.667 386.667" width="15">
<path d="m386.667 45.564-45.564-45.564-147.77 147.769-147.769-147.769-45.564 45.564 147.769 147.769-147.769 147.77 45.564 45.564 147.769-147.769 147.769 147.769 45.564-45.564-147.768-147.77z"/>
</svg>
</button>
</div>
<thead>
<tr>
<th class="klevu-compare-table__label"></th>
<th><img src="" alt=""><span></span></th>
</tr>
</thead>
<tbody>
<tr>
<td class="klevu-compare-table__label">Label</td>
<td><div class="spec" spec-title="Label"></div></td>
</tr>
</tbody>
</table>
</div>
<!-- end of compare customization -->
</script>
<script type="template/klevu" id="klevuLandingTemplateFiltersCustom">
<% if(data.query[dataLocal].filters.length > 0 ) { %>
<div class="kuFilters" role="navigation" data-position="left" aria-label="Product Filters" tabindex="0">
<!-- start of compare customization -->
<div class="klevu-compare-widget klevu-compare-widget--klevu-hidden" id="klevu-compare-widget">
<ul class="klevu-compare-widget__images"></ul>
<div class="klevu-compare-widget__header">
<span><%= helper.translate("To compare")%><span class="klevu-compare-widget__selected-amount"></span></span>
<button id="klevu-compare-button" type="button" disabled><%= helper.translate("Compare")%></button>
</div>
</div>
<!-- end of compare customization -->
<h3 class="kuFiltersTitleHeading"><%=helper.translate("Narrow by")%></h3>
<% helper.each(data.query[dataLocal].filters,function(key,filter){ %>
<% if(filter.type == "OPTIONS"){ %>
<div class="kuFilterBox klevuFilter <%=(filter.multiselect)?'kuMulticheck':''%>" data-filter="<%=filter.key%>" <% if(filter.multiselect){ %> data-singleselect="false" <% } else { %> data-singleselect="true"<% } %>>
<div class="kuFilterHead <%=(filter.isCollapsed) ? 'kuExpand' : 'kuCollapse'%>">
<% var filter_label = (filter.label=="klevu_price") ? "price" : filter.label; %>
<%=helper.translate(filter_label)%>
</div>
<div data-optionCount="<%= filter.options.length %>" class="kuFilterNames <%= (filter.options.length <= 5 ) ? 'kuFilterShowAll': '' %> <%=(filter.isCollapsed) ? 'kuFilterCollapse' : ''%>">
<ul>
<% helper.each(filter.options,function(key,filterOption){ %>
<li <% if(filterOption.selected ==true) { %> class="kuSelected"<% } %>>
<a
target="_self"
href="#"
title="<%=helper.escapeHTML(filterOption.name)%>"
class="klevuFilterOption<% if(filterOption.selected ==true) { %> klevuFilterOptionActive<% } %>"
data-value="<%=helper.escapeHTML(filterOption.value)%>"
data-name="<%=helper.escapeHTML(filterOption.name)%>"
>
<span class="kuFilterIcon"></span>
<span class="kufacet-text"><%=filterOption.name%></span>
<% if(filterOption.selected ==true) { %>
<span class="kuFilterCancel">X</span>
<% } else { %>
<span class="kuFilterTotal"><%=filterOption.count%></span>
<% } %>
</a>
</li>
<% }); %>
</ul>
<% if(filter.options.length > 5 ) { %>
<div class="kuShowOpt" tabindex="-1">
<span class="kuFilterDot"></span><span class="kuFilterDot"></span><span class="kuFilterDot"></span>
</div>
<% } %>
</div>
</div>
<% } else if(filter.type == "SLIDER") { %>
<div class="kuFilterBox klevuFilter" data-filter="<%=filter.key%>">
<div class="kuFilterHead <%=(filter.isCollapsed) ? 'kuExpand' : 'kuCollapse'%>">
<% var filter_label = (filter.label=="klevu_price") ? "price" : filter.label; %>
<%=helper.translate(filter_label)%>
</div>
<div class="kuFilterNames sliderFilterNames <%=(filter.isCollapsed) ? 'kuFilterCollapse' : ''%>">
<div class="kuPriceSlider klevuSliderFilter" data-query = "<%=dataLocal%>">
<div data-querykey = "<%=dataLocal%>" class="noUi-target noUi-ltr noUi-horizontal noUi-background kuSliderFilter kuPriceRangeSliderFilter<%=dataLocal%>"></div>
</div>
</div>
</div>
<% } else if (filter.type == "RATING") { %>
<div class="kuFilterBox klevuFilter <%=(filter.multiselect)?'kuMulticheck':''%>" data-filter="<%=filter.key%>" <% if(filter.multiselect){ %> data-singleselect="false" <% } else { %> data-singleselect="true"<% } %>>
<div class="kuFilterHead <%=(filter.isCollapsed) ? 'kuExpand' : 'kuCollapse'%>">
<%=helper.translate(filter.label)%>
</div>
<div data-optionCount="<%= filter.options.length %>" class="kuFilterNames <%= (filter.options.length <= 5 ) ? 'kuFilterShowAll': '' %> <%=(filter.isCollapsed) ? 'kuFilterCollapse' : ''%>">
<ul>
<% helper.each(filter.options,function(key,filterOption){ %>
<li <% if(filterOption.selected ==true) { %> class="kuSelected"<% } %>>
<a
target="_self"
href="#"
title="<%=helper.escapeHTML(filterOption.name)%>"
class="klevuFilterOption<% if(filterOption.selected ==true) { %> klevuFilterOptionActive<% } %>"
data-value="<%=helper.escapeHTML(filterOption.value)%>"
data-name="<%=helper.escapeHTML(filterOption.name)%>"
>
<span class="kuFilterIcon"></span>
<span class="kufacet-text">
<div class="klevuFacetStars">
<div class="klevuFacetRating" style="width:<%=(20*Number(filterOption.name))%>%;"></div>
</div>
</span>
<% if(filterOption.selected ==true) { %>
<span class="kuFilterCancel">X</span>
<% } else { %>
<span class="kuFilterTotal"><%=filterOption.count%></span>
<% } %>
</a>
</li>
<% }); %>
</ul>
</div>
</div>
<% } else { %>
<!-- Other Facets -->
<% } %>
<% }); %>
<!-- <div class="kuFiltersFooter">
<a target="_self" href="javascript:void(0)" class="kuBtn kuFacetsSlideOut kuMobileFilterCloseBtn" role="button" tabindex="0" area-label=""><%=helper.translate("Close")%></a>
</div> -->
</div>
<% } %>
</script>
<script type="template/klevu" id="klevuLandingTemplateProductBlockCustom">
<%
var updatedProductName = dataLocal.name;
if(klevu.search.modules.kmcInputs.base.getSkuOnPageEnableValue()) {
if(klevu.dom.helpers.cleanUpSku(dataLocal.sku)) {
updatedProductName += klevu.dom.helpers.cleanUpSku(dataLocal.sku);
}
}
%>
<li ku-product-block class="klevuProduct" data-id="<%=dataLocal.id%>">
<div class="kuProdWrap">
<header ku-block data-block-id="ku_landing_result_item_header">
<%=helper.render('landingProductBadge', scope, data, dataLocal) %>
</header>
<% var desc = [dataLocal.summaryAttribute,dataLocal.packageText,dataLocal.summaryDescription].filter(function(el) { return el; }); desc = desc.join(" "); %>
<main ku-block data-block-id="ku_landing_result_item_info">
<div class="kuProdTop">
<div class="klevuImgWrap">
<a data-id="<%=dataLocal.id%>" href="<%=dataLocal.url%>" class="klevuProductClick kuTrackRecentView">
<img src="<%=dataLocal.image%>" origin="<%=dataLocal.image%>" onerror="klevu.dom.helpers.cleanUpProductImage(this)" alt="<%=updatedProductName%>" class="kuProdImg">
<%=helper.render('landingImageRollover', scope, data, dataLocal) %>
</a>
</div>
<!-- <div class="kuQuickView">
<button data-id="<%=dataLocal.id%>" class="kuBtn kuBtnLight kuQuickViewBtn" role="button" tabindex="0" area-label="">Quick view</button>
</div> -->
</div>
</main>
<footer ku-block="" data-block-id="ku_landing_result_item_footer">
<div class="kuProdBottom">
<div class="kuName kuClippedOne"><a data-id="<%=dataLocal.id%>" href="<%=dataLocal.url%>" class="klevuProductClick kuTrackRecentView" title="<%= updatedProductName %>"><%= updatedProductName %></a></div>
<% if(dataLocal.inStock && dataLocal.inStock != "yes") { %>
<%=helper.render('landingProductStock', scope, data, dataLocal) %>
<% } else { %>
<% if(klevu.search.modules.kmcInputs.base.getShowPrices()) { %>
<div class="kuPrice">
<%
var kuTotalVariants = klevu.dom.helpers.cleanUpPriceValue(dataLocal.totalVariants);
var kuStartPrice = klevu.dom.helpers.cleanUpPriceValue(dataLocal.startPrice,dataLocal.currency);
var kuSalePrice = klevu.dom.helpers.cleanUpPriceValue(dataLocal.salePrice,dataLocal.currency);
var kuPrice = klevu.dom.helpers.cleanUpPriceValue(dataLocal.price,dataLocal.currency);
%>
<% if(!Number.isNaN(kuTotalVariants) && !Number.isNaN(kuStartPrice)) { %>
<div class="kuSalePrice kuStartPrice kuClippedOne">
<span class="klevuQuickPriceGreyText"><%=helper.translate("Starting at")%></span>
<span><%=helper.processCurrency(dataLocal.currency,parseFloat(dataLocal.startPrice))%></span>
</div>
<% } else if(!Number.isNaN(kuSalePrice) && !Number.isNaN(kuPrice) && (kuPrice > kuSalePrice)){ %>
<span class="kuOrigPrice kuClippedOne">
<%= helper.processCurrency(dataLocal.currency,parseFloat(dataLocal.price)) %>
</span>
<span class="kuSalePrice kuSpecialPrice kuClippedOne">
<%=helper.processCurrency(dataLocal.currency,parseFloat(dataLocal.salePrice))%>
</span>
<% } else if(!Number.isNaN(kuSalePrice)) { %>
<span class="kuSalePrice kuSpecialPrice">
<%= helper.processCurrency(dataLocal.currency,parseFloat(dataLocal.salePrice)) %>
</span>
<% } else if(!Number.isNaN(kuPrice)) { %>
<span class="kuSalePrice">
<%= helper.processCurrency(dataLocal.currency,parseFloat(dataLocal.price)) %>
</span>
<% } %>
<%=helper.render('searchResultProductVATLabel', scope, data, dataLocal) %>
</div>
<% } %>
<% } %>
</div>
<div class="kuProdAdditional">
<div class="kuProdAdditionalData">
<% if(desc && desc.length) { %>
<div class="kuDesc kuClippedTwo"> <%=desc%> </div>
<% } %>
<%=helper.render('landingProductSwatch',scope,data,dataLocal) %>
<%=helper.render('klevuLandingProductRating',scope,data,dataLocal) %>
<% var isAddToCartEnabled = klevu.search.modules.kmcInputs.base.getAddToCartEnableValue(); %>
<% if(isAddToCartEnabled) { %>
<%=helper.render('landingPageProductAddToCart',scope,data,dataLocal) %>
<% } %>
</div>
</div>
</footer>
</div>
<!-- start of compare customization -->
<button class="klevu-compare" data-id="<%=dataLocal.id%>" data-itemGroupId="<%=dataLocal.itemGroupId%>">
<input id="klevuCompare_<%=dataLocal.id%>" class="klevu-compare__checkbox" type="checkbox" style="pointer-events:none"/>
<label for="klevuCompare_<%=dataLocal.id%>" class="klevu-compare__label"> <%= helper.translate("Compare")%> </label>
</button>
<!-- end of compare customization -->
</li>
</script>Add JavaScript Code
First, let’s add the code that will choose which attributes to callout when products are compared
// Array of keys you want to render in the Compare table
window.KEYS_TO_COMPARE = [
"inStock",
"grams",
"sku",
"brand",
"size",
"category"
// "tags",
// "type",
// "itemGroupId",
// "storeBaseCurrency",
// "inventory_item_id",
// "imageUrl",
// "currency",
// "id",
// "image",
// "url",
// "product_type",
// "name",
// "shortDesc",
];Next we need to tell Klevu to use our custom templates. Skip this step if you are already customizing all three of these templates (klevuTemplateLanding, filters, and productBlock). You may need to modify the code if you only have one or two of these templates.
klevu({
theme: {
setTemplates: {
compareBase: {
scope: "landing,catnav",
selector: "#klevuLandingTemplateBaseCustom",
name: "klevuTemplateLanding"
},
compareFilters: {
scope: "landing,catnav",
selector: "#klevuLandingTemplateFiltersCustom",
name: "filters"
},
compareProductBlock: {
scope: "landing,catnav",
selector: "#klevuLandingTemplateProductBlockCustom",
name: "productBlock"
}
}
}
});Now we need to add the code that will powerup the compare feature:
// This settings controls whether to Show only differences on page load or not.
window.klevu_compare_show_differences_checked = true;
klevu.afterTemplateRender('landing,catnav', function(data, scope){
/** COMPARE PRODUCTS FEATURE */
const docBody = document.getElementsByTagName('body')[0];
const klevuAPIkey = klevu.getGlobalSetting("search.apiKey", klevu.getGlobalSetting("global.apiKey"));
const placeholderImageUrl = 'https://js.klevu.com/klevu-js-v1/img-1-1/place-holder.jpg'; // Replace with your placeholder image URL
const klevuScope = klevu.getSetting(klevu, "settings.flags.setRemoteConfigLanding.build") ? 'landing' : 'catnav';
toggleCheckbox();
saveCompareDataToLocalStorage();
[...document.querySelectorAll('.klevu-compare')].forEach(element => {
klevu.event.attach(element, "click", clickHandler_addToCompareList);
});
docBody.addEventListener('click', function ({ target }) {
try {
if (target.matches('#klevu-compare-button, #klevu-compare-button *, #klevu-compare-table, #klevu-compare-table *')) {
docBody.classList.add('klevu-compare-table-shown');
} else {
docBody.classList.remove('klevu-compare-table-shown');
}
// close button
if (target.matches("#klevu-close-compare, #klevu-close-compare *")) {
docBody.classList.remove('klevu-compare-table-shown');
}
} catch (err) { }
})
function getKlevuCompareIdArray() {
return localStorage.getItem(`klevu-compare-ids-${klevuScope}-${klevuAPIkey}`) ? JSON.parse(localStorage.getItem(`klevu-compare-ids-${klevuScope}-${klevuAPIkey}`)) : [];
}
function saveIDsToLocalStorage(currID, forceOverride = false) {
if (forceOverride) {
// Save the updated data back to local storage
localStorage.setItem(`klevu-compare-ids-${klevuScope}-${klevuAPIkey}`, JSON.stringify(currID));
} else {
let existingIDs = getKlevuCompareIdArray();
if (!getKlevuCompareIdArray().includes(currID)) { // Check if the ID is not already in the existing data
existingIDs.push(currID); // Add the new ID to the existing data
} else if (getKlevuCompareIdArray().includes(currID)) {
const existingIndex = getKlevuCompareIdArray().indexOf(currID);
existingIDs.splice(existingIndex, 1); // Remove this ID from the existing data
}
// Save the updated data back to local storage
localStorage.setItem(`klevu-compare-ids-${klevuScope}-${klevuAPIkey}`, JSON.stringify(existingIDs));
}
};
function saveCompareDataToLocalStorage(data = null) {
localStorage.setItem(`klevu-compare-data-${klevuScope}-${klevuAPIkey}`, data);
// If no argument passed && there are some IDs in the storage, but no entries in local storage
if (data === null && getKlevuCompareIdArray() && !getKlevuCompareDataObject()) {
getAndSaveDataByIDs(getKlevuCompareIdArray());
}
}
function getKlevuCompareDataObject() {
return JSON.parse(localStorage.getItem(`klevu-compare-data-${klevuScope}-${klevuAPIkey}`))
}
function clearImgUrl(url) { return url.replace("/needtochange/", "/") };
function renderSideWidget() {
const selectedProductsAmount = getKlevuCompareIdArray().length || null;
const widgetElement = document.querySelector('#klevu-compare-widget');
const widgetCompareButtonElement = document.querySelector('#klevu-compare-button');
const selectedProductsAmountElement = document.querySelector('.klevu-compare-widget__selected-amount');
const selectedProductsImagesElement = document.querySelector('.klevu-compare-widget__images');
let widgetHTMLlistItem = '';
getKlevuCompareDataObject().map(item => {
const imageUrl = item.image ? clearImgUrl(item.image) : placeholderImageUrl;
widgetHTMLlistItem += `
<li class="klevu-compare-widget__item">
<button class="klevu-compare-widget__item-button" data-id="${item.id}" title="Remove from Klevu comparison">
<img class="klevu-compare-widget__item-image" alt="${item.name}" style="max-height:50px;max-width:40px;" src="${imageUrl}" onerror="this.onerror=null; this.src='${placeholderImageUrl}'" />
<i class="klevu-compare-widget__item-button-cross">×</i>
</button>
</li>`;
selectedProductsImagesElement.innerHTML = widgetHTMLlistItem;
});
if (selectedProductsAmount && widgetElement) {
selectedProductsAmountElement.innerHTML = selectedProductsAmount;
selectedProductsImagesElement.innerHTML = widgetHTMLlistItem;
widgetElement.classList.remove('klevu-compare-widget--klevu-hidden');
if (selectedProductsAmount > 1) {
widgetCompareButtonElement.disabled = false;
} else {
widgetCompareButtonElement.disabled = true;
}
} else if (widgetElement) {
widgetElement.classList.add('klevu-compare-widget--klevu-hidden');
}
}
function generateAddToCartHTML(item) {
// Check if the item is in stock
if (item.inStock !== "no") {
// Get the 'Add to Cart' caption
var addToCartCaption = klevu.getObjectPath(klevu.search, "modules.kmcInputs.kmcData.klevu_uc_userOptions.addToCartButton", "Add to cart");
var translatedCaption;
if (typeof klevu_uc_userOptions !== 'undefined' && klevu_uc_userOptions) {
translatedCaption = klevu_uc_userOptions.addToCartButton;
}
// Check if the 'Add to Cart' caption contains an image
if (addToCartCaption) {
return `
<div class="kuAddtocart" data-id="${item.id}">
<a target="_self" href="javascript:void(0)" class="kuBtn kuBtnDark kuLandingAddToCartBtn" role="button" tabindex="0" aria-label="Add to cart">${addToCartCaption}</a>
</div>
`;
}
}
return '';
}
function generatePriceHTML(data) {
var kuTotalVariants = klevu.dom.helpers.cleanUpPriceValue(data.totalVariants);
var kuStartPrice = klevu.dom.helpers.cleanUpPriceValue(data.startPrice, data.currency);
var kuSalePrice = klevu.dom.helpers.cleanUpPriceValue(data.salePrice, data.currency);
var kuPrice = klevu.dom.helpers.cleanUpPriceValue(data.price, data.currency);
var processCurrency = scope.kScope.template.getTemplateSettings().helpers.processCurrency;
let htmlParts = [''];
if (!isNaN(kuTotalVariants) && !isNaN(kuStartPrice)) {
htmlParts.push(`
<div class="kuSalePrice kuStartPrice kuClippedOne">
<span class="klevuQuickPriceGreyText">Starting at</span>
<span>${processCurrency(data.currency, parseFloat(data.startPrice))}</span>
</div>`);
} else if (!isNaN(kuSalePrice) && !isNaN(kuPrice) && (kuPrice > kuSalePrice)) {
htmlParts.push(`
<span class="kuOrigPrice kuClippedOne">
${processCurrency(data.currency, parseFloat(data.price))}
</span>
<span class="kuSalePrice kuSpecialPrice kuClippedOne">
${processCurrency(data.currency, parseFloat(data.salePrice))}
</span>`);
} else if (!isNaN(kuSalePrice)) {
htmlParts.push(`
<span class="kuSalePrice kuSpecialPrice">
${processCurrency(data.currency, parseFloat(data.salePrice))}
</span>`);
} else if (!isNaN(kuPrice)) {
htmlParts.push(`
<span class="kuSalePrice">
${processCurrency(data.currency, parseFloat(data.price))}
</span>`);
}
htmlParts.push('');
return htmlParts.join('');
}
function renderCompareProducts() {
const selectedProductsAmount = getKlevuCompareIdArray().length || null;
const tableElement = document.querySelector('#klevu-compare-table');
const tableHeadElement = tableElement.querySelector('thead tr');
const tableOptionElement = tableElement.querySelector('tbody');
let listItemHeadingHTML = ``;
let listItemOptionHTML = ``;
getKlevuCompareDataObject().forEach(item => {
const imageUrl = item.image ? clearImgUrl(item.image) : placeholderImageUrl;
listItemHeadingHTML += `<th>
<img src="${imageUrl}" alt="${item.name}">
<div class="kuName kuClippedOne">
<a data-id="${item.id}" href="${item.url}" class="klevuProductClick" title="${item.name}">${item.name}</a>
</div>
<div class="kuPrice">${generatePriceHTML(item)}</div>
<button class="klevu-compare-widget__item-button" data-id="${item.id}" title="Remove from Klevu comparison"><span>×</span></button>
${generateAddToCartHTML(item)}
</th>`;
});
try {
KEYS_TO_COMPARE.forEach(key => {
// Map out the values for this key from each product
const values = getKlevuCompareDataObject().map(item => item[key] || '');
// Check if all values are the same, ignoring case
const allValuesSame = values.every((val, _, arr) => val.toLowerCase() === arr[0].toLowerCase());
// Determine whether to add the 'hide' class based on the comparison
const trClass = allValuesSame && klevu_compare_show_differences_checked ? 'hide' : '';
const valueRows = values.map(value => `<td><span class="spec" spec-title="${key}">${value}</span></td>`).join('');
if (valueRows.trim()) { // Only add the row if it's not empty
listItemOptionHTML += `<tr class="${trClass}" data-name="${key}"><td class="klevu-compare-table__label">${key}</td>${valueRows}</tr>`;
}
});
} catch (error) {
console.log('KEYS_TO_COMPARE array error. Please declare the KEYS_TO_COMPARE Array!', error);
}
tableHeadElement.innerHTML = `<th class="klevu-compare-table__label"> </th> ${listItemHeadingHTML}`;
tableOptionElement.innerHTML = listItemOptionHTML;
if (selectedProductsAmount) {
// Add code for displaying the selected products amount
} else {
// Add code for handling no selected products
}
}
function toggleDifferences() {
const toggleDifferencesCheckbox = document.getElementById('toggleDifferences');
toggleDifferencesCheckbox.addEventListener('change', function () {
const rows = document.querySelectorAll('#klevu-compare-table tbody tr');
rows.forEach(row => {
const values = [...row.querySelectorAll('td span.spec')].map(spec => spec.textContent.trim().toLowerCase());
const allEqual = values.every(val => val === values[0]);
if (allEqual) {
row.classList.toggle('hide', this.checked);
}
});
});
}
function renderCompareWidget() {
renderSideWidget();
renderCompareProducts();
toggleDifferences();
}
function getAndSaveDataByIDs(idArray) {
/** SEND REQUEST - get products by IDs */
const idQueryString = `(${idArray.map(id => `id:"${id}"`).join(" OR ")})`;
var data = JSON.stringify({
recordQueries: [
{
settings: {
customANDQuery: idQueryString,
query: { term: "*" },
typeOfRecords: ["KLEVU_PRODUCT"],
fields: [],
limit: idArray.length,
},
id: "productSearch",
},
], context: { apiKeys: [klevuAPIkey] },
});
var xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
const results = JSON.parse(this.responseText).queryResults[0].records;
fireAfterRequest(results);
}
});
xhr.open("POST", klevu.settings.url.search);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(data); // Send the request
}
function toggleCheckbox(currentId = null) {
try {
if (currentId) {
// on click:
if (getKlevuCompareIdArray().includes(currentId)) {
document.querySelector(`.klevu-compare[data-id="${currentId}"]`).classList.add('klevu-selected');
document.querySelector(`.klevu-compare[data-id="${currentId}"] input`).checked = true;
} else {
document.querySelector(`.klevu-compare[data-id="${currentId}"]`).classList.remove('klevu-selected');
document.querySelector(`.klevu-compare[data-id="${currentId}"] input`).checked = false;
}
} else {
// on load:
[...document.querySelectorAll('.klevu-compare')].forEach(element => {
// if (getKlevuCompareIdArray().includes(element.dataset.itemgroupid)) {
if (getKlevuCompareIdArray().includes(element.dataset.id)) {
element.classList.add('klevu-selected');
element.querySelector('input.klevu-compare__checkbox').checked = true;
}
});
}
} catch (error) {
}
}
function removeFromKlevuComparison(id) {
[...document.querySelectorAll('.klevu-compare-widget__item-button')].forEach(element => {
element.addEventListener('click', function (ev) {
ev.preventDefault();
ev.stopPropagation();
// const currID = ev.target.closest('.klevu-compare-widget__item-button').dataset.itemgroupid;
const currID = ev.target.closest('.klevu-compare-widget__item-button').dataset.id;
const filteredData = getKlevuCompareIdArray().filter(val => val !== currID);
saveIDsToLocalStorage(filteredData, true);
getAndSaveDataByIDs(getKlevuCompareIdArray());
toggleCheckbox(currID);
// hide compare popup if there's 1 product
if (getKlevuCompareIdArray().length <= 1) {
docBody.classList.remove('klevu-compare-table-shown');
}
})
});
}
function clickHandler_addToCompareList(event) {
// const currentId = event.target.closest('.klevu-compare').dataset.itemgroupid;
const currentId = event.target.closest('.klevu-compare').dataset.id;
event.stopPropagation();
event.preventDefault();
saveIDsToLocalStorage(currentId);
toggleCheckbox(currentId);
getAndSaveDataByIDs(getKlevuCompareIdArray());
// renderCompareWidget();
}
function fireAfterRequest(results) {
saveCompareDataToLocalStorage(JSON.stringify(results));
renderCompareWidget();
removeFromKlevuComparison();
}
/** END OF COMPARE PRODUCTS FEATURE */
});Add CSS Code
The final step is to add the CSS code that will style the compare feature.
<style>
/* COMPARE FEATURE */
.kuSearchResultsPageContainer [ku-container] .klevu-compare,
.kuSearchResultsPageContainer .klevu-compare {
display: flex;
justify-content: center;
align-content: center;
position: absolute;
top: 0;
right: 0;
z-index: 2;
background-color: #fff;
padding: 3px 15px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--klevu-bgcolor);
border-radius: var(--klevu-buttonRadius);
}
.kuSearchResultsPageContainer [ku-container] #klevu-compare-button,
.kuSearchResultsPageContainer #klevu-compare-button {
color: #fff;
cursor: pointer;
background-color: var(--klevu-blue);
border: 0;
padding: 5px 8px;
}
.kuSearchResultsPageContainer [ku-container] #klevu-compare-button:disabled,
.kuSearchResultsPageContainer #klevu-compare-button:disabled {
cursor: no-drop;
opacity: 0.5;
}
.kuSearchResultsPageContainer [ku-container] .klevu-compare__checkbox,
.kuSearchResultsPageContainer .klevu-compare__checkbox {
margin: 0;
}
.kuSearchResultsPageContainer [ku-container] .klevu-compare__label,
.kuSearchResultsPageContainer .klevu-compare__label {
margin-left: 5px;
cursor: pointer;
}
.kuSearchResultsPageContainer [ku-container] div .klevu-compare-widget__selected-amount::before {
content: ':';
margin-left: 0;
margin-right: 5px;
}
.kuSearchResultsPageContainer [ku-container] div #klevu-compare-widget {
display: flex;
flex-direction: column;
gap: 10px;
margin: 10px auto;
}
.kuSearchResultsPageContainer [ku-container] div .klevu-compare-widget__header {
display: flex;
flex-direction: column;
gap: 10px;
}
.kuSearchResultsPageContainer [ku-container] div .klevu-compare-widget__images {
display: flex;
align-items: center;
gap: 0 10px;
margin: 0;
padding: 0;
overflow-x: auto;
}
.kuSearchResultsPageContainer [ku-container] div .klevu-compare-widget__item {
list-style: none;
border: 1px solid transparent;
}
.kuSearchResultsPageContainer [ku-container] div .klevu-compare-widget__item:hover {
list-style: none;
border: 1px solid #888;
}
.kuSearchResultsPageContainer [ku-container] div .klevu-compare-widget__item-button {
border: 0;
background-color: transparent;
padding: 0;
margin: 0;
cursor: pointer;
position: relative;
}
.kuSearchResultsPageContainer [ku-container] div .klevu-compare-widget__item-button-cross {
position: absolute;
font-style: normal;
top: -5px;
right: 0;
}
.kuSearchResultsPageContainer #klevu-close-compare {
background-color: #fff;
border: 0;
padding: 15px;
cursor: pointer;
z-index: 1;
border: 1px solid transparent;
display: flex;
align-items: center;
justify-content: center;
}
.kuSearchResultsPageContainer #klevu-close-compare:hover {
border: 1px solid var(--klevu-black)
}
.kuSearchResultsPageContainer #klevu-compare-table tr {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
}
.kuSearchResultsPageContainer #klevu-compare-table thead {
position: sticky;
top: 0;
}
.kuSearchResultsPageContainer #klevu-compare-table tr th .klevu-compare-widget__item-button {
position: absolute;
top: 0;
right: 0;
background-color: var(--klevu-blue);
color: #fff;
border: 0;
}
.kuSearchResultsPageContainer [ku-container] div #klevu-compare-widget.klevu-compare-widget--klevu-hidden {
display: none;
visibility: hidden;
}
.kuSearchResultsPageContainer .klevu-compare-table {
display: none;
visibility: hidden;
background: #fff;
max-height: calc(100vh - 10%);
overflow: auto;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 85vw;
z-index: 1000;
}
body.klevu-compare-table-shown .kuSearchResultsPageContainer .klevu-compare-table {
display: block;
visibility: visible;
}
body.klevu-compare-table-shown .kuSearchResultsPageContainer [data-container-role=main] .kuContainer {
filter: blur(3px);
}
body.klevu-compare-table-shown .kuSearchResultsPageContainer .klevuWrap::before {
background-color: #000;
opacity: 0.5;
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: block;
content: '';
z-index: 1;
}
.kuSearchResultsPageContainer .klevu-compare-table table {}
.kuSearchResultsPageContainer .klevu-compare-table th {
position: sticky;
/* Make th sticky to avoid JavaScript */
z-index: 1;
/* border: 1px solid #ccc; */
top: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.kuSearchResultsPageContainer .klevu-compare-table th:not(:last-child),
.kuSearchResultsPageContainer .klevu-compare-table td:not(:last-child) {
border-right: 1px solid #ccc;
}
.kuSearchResultsPageContainer .klevu-compare-table td,
.kuSearchResultsPageContainer .klevu-compare-table th {
padding: 10px;
min-width: 300px;
/* Add a minimum width below which no resize is allowed (for small screens). It can be whatever serves the design best */
text-align: center;
}
.kuSearchResultsPageContainer .klevu-compare-table .klevu-compare-table__products {
/* padding-top: 40px; */
max-height: calc(100vh - 10%);
overflow: auto;
padding: 15px;
}
.kuSearchResultsPageContainer .klevu-compare-table .klevu-compare-table__products th,
.kuSearchResultsPageContainer .klevu-compare-table .klevu-compare-table__products td {
width: 25%;
max-width: 300px;
overflow: hidden;
gap: 10px 0;
/* Depending on the number of products in comparison, different classes should be used (e.g. 3 products should have a .products-3 class with a width of 33.333%) */
}
.kuSearchResultsPageContainer .klevu-compare-table th {
background: #fff;
/* top: -2px; */
top: 0;
/* We need this to attach the "stuck" attribute */
}
.kuSearchResultsPageContainer .klevu-compare-table th h4 {
margin: 15px 0 0;
}
.kuSearchResultsPageContainer .klevu-compare-table th img {
max-height: calc(var(--klevu-product-image-height--desktop) - 50px) !important;
max-width: 100%;
object-fit: contain;
margin: 0 auto;
display: block;
transition: height 200ms ease-in-out;
}
.kuSearchResultsPageContainer .klevu-compare-table th .kuName {
display: block !important;
}
.kuSearchResultsPageContainer .klevu-compare-table th .kuPrice {
margin-top: auto;
text-align: center;
}
.kuSearchResultsPageContainer .klevu-compare-table th .kuBtn,
.kuSearchResultsPageContainer .klevu-compare-table th .kuAddtocart {
margin: 0;
}
.kuSearchResultsPageContainer .klevu-compare-table .klevu-compare-table__sticky img {
height: 100px;
}
.kuSearchResultsPageContainer .klevu-compare-table .klevu-compare-table__label {
font-weight: bold;
text-align: left;
min-width: 150px;
/* Labels should have a strict width, based on the design (150px is just for this demo) */
width: 150px;
/* In order to enforce the width we need to apply both a min-width and a width with the same value */
background: #eee;
}
.kuSearchResultsPageContainer .klevu-compare-table th.klevu-compare-table__label {
background: #fff;
border-color: #fff;
}
.kuSearchResultsPageContainer .klevu-compare-table .klevu-compare-table__heading {
background: #ccc;
width: 100%;
text-align: left;
position: relative;
}
.kuSearchResultsPageContainer .klevu-compare-table .klevu-compare-table__heading span {
position: sticky;
top: 0;
left: 10px;
}
.kuSearchResultsPageContainer .klevu-compare-table .spec {
word-break: break-all;
}
.kuSearchResultsPageContainer #klevu-compare-table .hide {
display: none;
visibility: hidden;
}
.kuSearchResultsPageContainer #klevu-compare-table .klevu-compare-table-header {
z-index: 2;
background-color: #fff;
position: sticky;
top: 0;
right: 0;
left: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
}
.kuSearchResultsPageContainer #klevu-compare-table .toggle-differences {
position: sticky;
top: 5px;
left: 5px;
padding: 10px 15px;
border: 1px solid var(--klevu-black);
}
/* @media all and (max-width: 600px) { */
.kuSearchResultsPageContainer .klevu-compare-table .klevu-compare-table__label {
display: none;
/* We want to hide the labels on small screens... */
}
.kuSearchResultsPageContainer .klevu-compare-table .spec::before {
/* ...and show them above each spec instead. */
content: attr(spec-title);
color: #999;
display: block;
text-transform: capitalize;
}
/* COMPARE FEATURE END */
</style>