← back to Dw Theme Hamburger
assets/recently-viewed.js
227 lines
/**
* recently-viewed.js - DW Recently Viewed Carousel — cards link directly to product pages
* Requires: recently-viewed-tracker.js (window.DWRecentlyViewed)
* Section: #rv-section | Track: #rv-track
*/
(function () {
'use strict';
var CARD_LIMIT = 8;
var _activeCard = null;
/* HELPER: Check if a price is real and non-zero. */
function formatPrice(rawPrice) {
if (!rawPrice) return '';
var cleaned = String(rawPrice).replace(/[\$,\s]/g, '');
if (!cleaned) return '';
var num = parseFloat(cleaned);
if (isNaN(num) || num <= 0) return '';
return num.toFixed(2);
}
/* Fetch live price from Shopify storefront for products with missing/zero price.
Updates the DOM element AND fixes the localStorage entry so it does not recur. */
function fetchLivePrice(handle, priceElement) {
if (!handle || !priceElement) return;
fetch('/products/' + encodeURIComponent(handle) + '.js', { credentials: 'same-origin' })
.then(function (res) { return res.ok ? res.json() : null; })
.then(function (data) {
if (!data) return;
// Find highest non-sample variant price (in cents)
var bestPrice = 0;
if (Array.isArray(data.variants)) {
for (var i = 0; i < data.variants.length; i++) {
var v = data.variants[i];
var titleLower = (v.title || '').toLowerCase();
if (titleLower === 'sample' || titleLower.indexOf('sample') >= 0) continue;
if (v.price > bestPrice) bestPrice = v.price;
}
}
// Fallback to price_max (already in cents in .js endpoint)
if (bestPrice === 0 && data.price_max > 0) bestPrice = data.price_max;
if (bestPrice > 0) {
var dollars = (bestPrice / 100).toFixed(2);
priceElement.textContent = '$' + dollars;
// Fix localStorage so this product shows correct price next time
try {
var items = JSON.parse(localStorage.getItem('dw_recently_viewed') || '[]');
for (var j = 0; j < items.length; j++) {
if (items[j].handle === handle) { items[j].price = dollars; break; }
}
localStorage.setItem('dw_recently_viewed', JSON.stringify(items));
} catch (_) {}
}
})
.catch(function () {});
}
function init() {
if (typeof window.DWRecentlyViewed === 'undefined' ||
typeof window.DWRecentlyViewed.getProducts !== 'function'
) { return hideSection(); }
var products = window.DWRecentlyViewed.getProducts(CARD_LIMIT);
if (!products || products.length === 0) { return hideSection(); }
renderCards(products);
var s = document.getElementById('rv-section');
if (s) s.style.display = '';
initArrows();
injectJsonLd(products);
}
function hideSection() {
var s = document.getElementById('rv-section');
if (s) s.style.display = 'none';
}
function renderCards(products) {
var track = document.getElementById('rv-track');
if (!track) return;
var frag = document.createDocumentFragment();
for (var i = 0; i < products.length; i++) {
frag.appendChild(createCard(products[i], i));
}
track.appendChild(frag);
updateArrowVisibility();
}
function createCard(product, index) {
var item = document.createElement('div');
item.className = 'rv-track__item';
item.setAttribute('role', 'listitem');
var card = document.createElement('a');
card.className = 'rv-card';
card.href = product.url || '/products/' + product.handle;
card.setAttribute('aria-label', product.title);
var imgWrap = document.createElement('div');
imgWrap.className = 'rv-card__img-wrap';
var img = document.createElement('img');
img.className = 'rv-card__img';
img.src = product.featuredImage || '';
img.alt = '';
img.loading = index < 3 ? 'eager' : 'lazy';
img.decoding = 'async';
img.onerror = function () {
this.parentNode.style.background = '#ece9e4';
this.style.display = 'none';
};
imgWrap.appendChild(img);
var body = document.createElement('div');
body.className = 'rv-card__body';
var vendor = document.createElement('p');
vendor.className = 'rv-card__vendor';
vendor.textContent = product.vendor || '';
var title = document.createElement('h3');
title.className = 'rv-card__title';
title.textContent = product.title || '';
var price = document.createElement('p');
price.className = 'rv-card__price';
// FIX: Use robust formatPrice - catches 0, 0.00, $0.00, empty, null
var formattedPrice = formatPrice(product.price);
if (formattedPrice) {
price.textContent = '$' + formattedPrice;
} else {
price.textContent = '';
// Fetch live price for products with stale/zero cached price
fetchLivePrice(product.handle, price);
}
body.appendChild(vendor);
body.appendChild(title);
body.appendChild(price);
card.appendChild(imgWrap);
card.appendChild(body);
item.appendChild(card);
return item;
}
function initArrows() {
var track = document.getElementById('rv-track');
var btnLeft = document.getElementById('rv-arrow-left');
var btnRight = document.getElementById('rv-arrow-right');
if (!track) return;
if (btnLeft) btnLeft.addEventListener('click', function () { scrollTrack('left'); });
if (btnRight) btnRight.addEventListener('click', function () { scrollTrack('right'); });
track.addEventListener('scroll', updateArrowVisibility, { passive: true });
if (typeof ResizeObserver !== 'undefined') {
new ResizeObserver(updateArrowVisibility).observe(track);
} else {
window.addEventListener('resize', updateArrowVisibility, { passive: true });
}
}
function scrollTrack(dir) {
var track = document.getElementById('rv-track');
if (!track) return;
var card = track.querySelector('.rv-card');
var w = card ? card.getBoundingClientRect().width : 200;
var gap = parseFloat(
getComputedStyle(document.documentElement).getPropertyValue('--rv-gap')
) || 20;
track.scrollBy({ left: dir === 'left' ? -(w + gap) : (w + gap), behavior: 'smooth' });
}
function updateArrowVisibility() {
var track = document.getElementById('rv-track');
var btnLeft = document.getElementById('rv-arrow-left');
var btnRight = document.getElementById('rv-arrow-right');
if (!track) return;
var atStart = track.scrollLeft <= 2;
var atEnd = track.scrollLeft + track.clientWidth >= track.scrollWidth - 2;
setArrowHidden(btnLeft, atStart);
setArrowHidden(btnRight, atEnd);
}
function setArrowHidden(btn, hide) {
if (!btn) return;
btn.classList.toggle('rv-arrow--hidden', hide);
if (hide) { btn.setAttribute('aria-hidden', 'true'); }
else { btn.removeAttribute('aria-hidden'); }
}
function scheduleBtnReset(btn, ms) {
setTimeout(function () { resetSampleBtn(btn); }, ms);
}
function injectJsonLd(products) {
var items = [];
for (var i = 0; i < products.length; i++) {
var p = products[i];
items.push({
'@type': 'ListItem',
position: i + 1,
name: p.title || '',
url: p.url || '',
image: p.featuredImage || ''
});
}
var script = document.createElement('script');
script.type = 'application/ld+json';
script.textContent = JSON.stringify({
'@context': 'https://schema.org',
'@type': 'ItemList',
name: 'Recently Viewed Products',
itemListElement: items
});
document.head.appendChild(script);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
}());