1386 lines
47 KiB
JavaScript
1386 lines
47 KiB
JavaScript
function getFocusableElements(container) {
|
|
return Array.from(
|
|
container.querySelectorAll(
|
|
"summary, a[href], button:enabled, [tabindex]:not([tabindex^='-']), [draggable], area, input:not([type=hidden]):enabled, select:enabled, textarea:enabled, object, iframe"
|
|
)
|
|
);
|
|
}
|
|
|
|
document.querySelectorAll('[id^="Details-"] summary').forEach((summary) => {
|
|
summary.setAttribute('role', 'button');
|
|
summary.setAttribute('aria-expanded', summary.parentNode.hasAttribute('open'));
|
|
|
|
if (summary.nextElementSibling.getAttribute('id')) {
|
|
summary.setAttribute('aria-controls', summary.nextElementSibling.id);
|
|
}
|
|
|
|
summary.addEventListener('click', (event) => {
|
|
event.currentTarget.setAttribute('aria-expanded', !event.currentTarget.closest('details').hasAttribute('open'));
|
|
});
|
|
|
|
if (summary.closest('header-drawer, menu-drawer')) return;
|
|
summary.parentElement.addEventListener('keyup', onKeyUpEscape);
|
|
});
|
|
|
|
const trapFocusHandlers = {};
|
|
|
|
function trapFocus(container, elementToFocus = container) {
|
|
var elements = getFocusableElements(container);
|
|
var first = elements[0];
|
|
var last = elements[elements.length - 1];
|
|
|
|
removeTrapFocus();
|
|
|
|
trapFocusHandlers.focusin = (event) => {
|
|
if (event.target !== container && event.target !== last && event.target !== first) return;
|
|
|
|
document.addEventListener('keydown', trapFocusHandlers.keydown);
|
|
};
|
|
|
|
trapFocusHandlers.focusout = function () {
|
|
document.removeEventListener('keydown', trapFocusHandlers.keydown);
|
|
};
|
|
|
|
trapFocusHandlers.keydown = function (event) {
|
|
if (event.code.toUpperCase() !== 'TAB') return; // If not TAB key
|
|
// On the last focusable element and tab forward, focus the first element.
|
|
if (event.target === last && !event.shiftKey) {
|
|
event.preventDefault();
|
|
first.focus();
|
|
}
|
|
|
|
// On the first focusable element and tab backward, focus the last element.
|
|
if ((event.target === container || event.target === first) && event.shiftKey) {
|
|
event.preventDefault();
|
|
last.focus();
|
|
}
|
|
};
|
|
|
|
document.addEventListener('focusout', trapFocusHandlers.focusout);
|
|
document.addEventListener('focusin', trapFocusHandlers.focusin);
|
|
|
|
elementToFocus.focus();
|
|
|
|
if (
|
|
elementToFocus.tagName === 'INPUT' &&
|
|
['search', 'text', 'email', 'url'].includes(elementToFocus.type) &&
|
|
elementToFocus.value
|
|
) {
|
|
elementToFocus.setSelectionRange(0, elementToFocus.value.length);
|
|
}
|
|
}
|
|
|
|
// Here run the querySelector to figure out if the browser supports :focus-visible or not and run code based on it.
|
|
try {
|
|
document.querySelector(':focus-visible');
|
|
} catch (e) {
|
|
focusVisiblePolyfill();
|
|
}
|
|
|
|
function focusVisiblePolyfill() {
|
|
const navKeys = [
|
|
'ARROWUP',
|
|
'ARROWDOWN',
|
|
'ARROWLEFT',
|
|
'ARROWRIGHT',
|
|
'TAB',
|
|
'ENTER',
|
|
'SPACE',
|
|
'ESCAPE',
|
|
'HOME',
|
|
'END',
|
|
'PAGEUP',
|
|
'PAGEDOWN',
|
|
];
|
|
let currentFocusedElement = null;
|
|
let mouseClick = null;
|
|
|
|
window.addEventListener('keydown', (event) => {
|
|
if (navKeys.includes(event.code.toUpperCase())) {
|
|
mouseClick = false;
|
|
}
|
|
});
|
|
|
|
window.addEventListener('mousedown', (event) => {
|
|
mouseClick = true;
|
|
});
|
|
|
|
window.addEventListener(
|
|
'focus',
|
|
() => {
|
|
if (currentFocusedElement) currentFocusedElement.classList.remove('focused');
|
|
|
|
if (mouseClick) return;
|
|
|
|
currentFocusedElement = document.activeElement;
|
|
currentFocusedElement.classList.add('focused');
|
|
},
|
|
true
|
|
);
|
|
}
|
|
|
|
function pauseAllMedia() {
|
|
document.querySelectorAll('.js-youtube').forEach((video) => {
|
|
video.contentWindow.postMessage('{"event":"command","func":"' + 'pauseVideo' + '","args":""}', '*');
|
|
});
|
|
document.querySelectorAll('.js-vimeo').forEach((video) => {
|
|
video.contentWindow.postMessage('{"method":"pause"}', '*');
|
|
});
|
|
document.querySelectorAll('video').forEach((video) => video.pause());
|
|
document.querySelectorAll('product-model').forEach((model) => {
|
|
if (model.modelViewerUI) model.modelViewerUI.pause();
|
|
});
|
|
}
|
|
|
|
function removeTrapFocus(elementToFocus = null) {
|
|
document.removeEventListener('focusin', trapFocusHandlers.focusin);
|
|
document.removeEventListener('focusout', trapFocusHandlers.focusout);
|
|
document.removeEventListener('keydown', trapFocusHandlers.keydown);
|
|
|
|
if (elementToFocus) elementToFocus.focus();
|
|
}
|
|
|
|
function onKeyUpEscape(event) {
|
|
if (event.code.toUpperCase() !== 'ESCAPE') return;
|
|
|
|
const openDetailsElement = event.target.closest('details[open]');
|
|
if (!openDetailsElement) return;
|
|
|
|
const summaryElement = openDetailsElement.querySelector('summary');
|
|
openDetailsElement.removeAttribute('open');
|
|
summaryElement.setAttribute('aria-expanded', false);
|
|
summaryElement.focus();
|
|
}
|
|
|
|
class QuantityInput extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.input = this.querySelector('input');
|
|
this.changeEvent = new Event('change', { bubbles: true });
|
|
this.input.addEventListener('change', this.onInputChange.bind(this));
|
|
this.querySelectorAll('button').forEach((button) =>
|
|
button.addEventListener('click', this.onButtonClick.bind(this))
|
|
);
|
|
}
|
|
|
|
quantityUpdateUnsubscriber = undefined;
|
|
|
|
connectedCallback() {
|
|
this.validateQtyRules();
|
|
this.quantityUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.quantityUpdate, this.validateQtyRules.bind(this));
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
if (this.quantityUpdateUnsubscriber) {
|
|
this.quantityUpdateUnsubscriber();
|
|
}
|
|
}
|
|
|
|
onInputChange(event) {
|
|
this.validateQtyRules();
|
|
}
|
|
|
|
onButtonClick(event) {
|
|
event.preventDefault();
|
|
const previousValue = this.input.value;
|
|
|
|
event.target.name === 'plus' ? this.input.stepUp() : this.input.stepDown();
|
|
if (previousValue !== this.input.value) this.input.dispatchEvent(this.changeEvent);
|
|
}
|
|
|
|
validateQtyRules() {
|
|
const value = parseInt(this.input.value);
|
|
if (this.input.min) {
|
|
const min = parseInt(this.input.min);
|
|
const buttonMinus = this.querySelector(".quantity__button[name='minus']");
|
|
buttonMinus.classList.toggle('disabled', value <= min);
|
|
}
|
|
if (this.input.max) {
|
|
const max = parseInt(this.input.max);
|
|
const buttonPlus = this.querySelector(".quantity__button[name='plus']");
|
|
buttonPlus.classList.toggle('disabled', value >= max);
|
|
}
|
|
}
|
|
}
|
|
|
|
customElements.define('quantity-input', QuantityInput);
|
|
|
|
function debounce(fn, wait) {
|
|
let t;
|
|
return (...args) => {
|
|
clearTimeout(t);
|
|
t = setTimeout(() => fn.apply(this, args), wait);
|
|
};
|
|
}
|
|
|
|
function throttle(fn, delay) {
|
|
let lastCall = 0;
|
|
return function (...args) {
|
|
const now = new Date().getTime();
|
|
if (now - lastCall < delay) {
|
|
return;
|
|
}
|
|
lastCall = now;
|
|
return fn(...args);
|
|
};
|
|
}
|
|
|
|
function fetchConfig(type = 'json') {
|
|
return {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', Accept: `application/${type}` },
|
|
};
|
|
}
|
|
|
|
/*
|
|
* Shopify Common JS
|
|
*
|
|
*/
|
|
if (typeof window.Shopify == 'undefined') {
|
|
window.Shopify = {};
|
|
}
|
|
|
|
Shopify.bind = function (fn, scope) {
|
|
return function () {
|
|
return fn.apply(scope, arguments);
|
|
};
|
|
};
|
|
|
|
Shopify.setSelectorByValue = function (selector, value) {
|
|
for (var i = 0, count = selector.options.length; i < count; i++) {
|
|
var option = selector.options[i];
|
|
if (value == option.value || value == option.innerHTML) {
|
|
selector.selectedIndex = i;
|
|
return i;
|
|
}
|
|
}
|
|
};
|
|
|
|
Shopify.addListener = function (target, eventName, callback) {
|
|
target.addEventListener
|
|
? target.addEventListener(eventName, callback, false)
|
|
: target.attachEvent('on' + eventName, callback);
|
|
};
|
|
|
|
Shopify.postLink = function (path, options) {
|
|
options = options || {};
|
|
var method = options['method'] || 'post';
|
|
var params = options['parameters'] || {};
|
|
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', method);
|
|
form.setAttribute('action', path);
|
|
|
|
for (var key in params) {
|
|
var hiddenField = document.createElement('input');
|
|
hiddenField.setAttribute('type', 'hidden');
|
|
hiddenField.setAttribute('name', key);
|
|
hiddenField.setAttribute('value', params[key]);
|
|
form.appendChild(hiddenField);
|
|
}
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
document.body.removeChild(form);
|
|
};
|
|
|
|
Shopify.CountryProvinceSelector = function (country_domid, province_domid, options) {
|
|
this.countryEl = document.getElementById(country_domid);
|
|
this.provinceEl = document.getElementById(province_domid);
|
|
this.provinceContainer = document.getElementById(options['hideElement'] || province_domid);
|
|
|
|
Shopify.addListener(this.countryEl, 'change', Shopify.bind(this.countryHandler, this));
|
|
|
|
this.initCountry();
|
|
this.initProvince();
|
|
};
|
|
|
|
Shopify.CountryProvinceSelector.prototype = {
|
|
initCountry: function () {
|
|
var value = this.countryEl.getAttribute('data-default');
|
|
Shopify.setSelectorByValue(this.countryEl, value);
|
|
this.countryHandler();
|
|
},
|
|
|
|
initProvince: function () {
|
|
var value = this.provinceEl.getAttribute('data-default');
|
|
if (value && this.provinceEl.options.length > 0) {
|
|
Shopify.setSelectorByValue(this.provinceEl, value);
|
|
}
|
|
},
|
|
|
|
countryHandler: function (e) {
|
|
var opt = this.countryEl.options[this.countryEl.selectedIndex];
|
|
var raw = opt.getAttribute('data-provinces');
|
|
var provinces = JSON.parse(raw);
|
|
|
|
this.clearOptions(this.provinceEl);
|
|
if (provinces && provinces.length == 0) {
|
|
this.provinceContainer.style.display = 'none';
|
|
} else {
|
|
for (var i = 0; i < provinces.length; i++) {
|
|
var opt = document.createElement('option');
|
|
opt.value = provinces[i][0];
|
|
opt.innerHTML = provinces[i][1];
|
|
this.provinceEl.appendChild(opt);
|
|
}
|
|
|
|
this.provinceContainer.style.display = '';
|
|
}
|
|
},
|
|
|
|
clearOptions: function (selector) {
|
|
while (selector.firstChild) {
|
|
selector.removeChild(selector.firstChild);
|
|
}
|
|
},
|
|
|
|
setOptions: function (selector, values) {
|
|
for (var i = 0, count = values.length; i < values.length; i++) {
|
|
var opt = document.createElement('option');
|
|
opt.value = values[i];
|
|
opt.innerHTML = values[i];
|
|
selector.appendChild(opt);
|
|
}
|
|
},
|
|
};
|
|
|
|
class MenuDrawer extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
|
|
this.mainDetailsToggle = this.querySelector('details');
|
|
|
|
this.addEventListener('keyup', this.onKeyUp.bind(this));
|
|
this.addEventListener('focusout', this.onFocusOut.bind(this));
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
this.querySelectorAll('summary').forEach((summary) =>
|
|
summary.addEventListener('click', this.onSummaryClick.bind(this))
|
|
);
|
|
this.querySelectorAll(
|
|
'button:not(.localization-selector):not(.country-selector__close-button):not(.country-filter__reset-button)'
|
|
).forEach((button) => button.addEventListener('click', this.onCloseButtonClick.bind(this))
|
|
);
|
|
}
|
|
|
|
onKeyUp(event) {
|
|
if (event.code.toUpperCase() !== 'ESCAPE') return;
|
|
|
|
const openDetailsElement = event.target.closest('details[open]');
|
|
if (!openDetailsElement) return;
|
|
|
|
openDetailsElement === this.mainDetailsToggle
|
|
? this.closeMenuDrawer(event, this.mainDetailsToggle.querySelector('summary'))
|
|
: this.closeSubmenu(openDetailsElement);
|
|
}
|
|
|
|
onSummaryClick(event) {
|
|
const summaryElement = event.currentTarget;
|
|
const detailsElement = summaryElement.parentNode;
|
|
const parentMenuElement = detailsElement.closest('.has-submenu');
|
|
const isOpen = detailsElement.hasAttribute('open');
|
|
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
|
|
function addTrapFocus() {
|
|
trapFocus(summaryElement.nextElementSibling, detailsElement.querySelector('button'));
|
|
summaryElement.nextElementSibling.removeEventListener('transitionend', addTrapFocus);
|
|
}
|
|
|
|
if (detailsElement === this.mainDetailsToggle) {
|
|
if (isOpen) event.preventDefault();
|
|
isOpen ? this.closeMenuDrawer(event, summaryElement) : this.openMenuDrawer(summaryElement);
|
|
|
|
if (window.matchMedia('(max-width: 990px)')) {
|
|
document.documentElement.style.setProperty('--viewport-height', `${window.innerHeight}px`);
|
|
}
|
|
} else {
|
|
setTimeout(() => {
|
|
detailsElement.classList.add('menu-opening');
|
|
summaryElement.setAttribute('aria-expanded', true);
|
|
parentMenuElement && parentMenuElement.classList.add('submenu-open');
|
|
!reducedMotion || reducedMotion.matches
|
|
? addTrapFocus()
|
|
: summaryElement.nextElementSibling.addEventListener('transitionend', addTrapFocus);
|
|
}, 100);
|
|
}
|
|
}
|
|
|
|
openMenuDrawer(summaryElement) {
|
|
setTimeout(() => {
|
|
this.mainDetailsToggle.classList.add('menu-opening');
|
|
});
|
|
summaryElement.setAttribute('aria-expanded', true);
|
|
trapFocus(this.mainDetailsToggle, summaryElement);
|
|
document.body.classList.add(`overflow-hidden-${this.dataset.breakpoint}`);
|
|
}
|
|
|
|
closeMenuDrawer(event, elementToFocus = false) {
|
|
if (event === undefined) return;
|
|
|
|
this.mainDetailsToggle.classList.remove('menu-opening');
|
|
this.mainDetailsToggle.querySelectorAll('details').forEach((details) => {
|
|
details.removeAttribute('open');
|
|
details.classList.remove('menu-opening');
|
|
});
|
|
this.mainDetailsToggle.querySelectorAll('.submenu-open').forEach((submenu) => {
|
|
submenu.classList.remove('submenu-open');
|
|
});
|
|
document.body.classList.remove(`overflow-hidden-${this.dataset.breakpoint}`);
|
|
removeTrapFocus(elementToFocus);
|
|
this.closeAnimation(this.mainDetailsToggle);
|
|
|
|
if (event instanceof KeyboardEvent) elementToFocus?.setAttribute('aria-expanded', false);
|
|
}
|
|
|
|
onFocusOut() {
|
|
setTimeout(() => {
|
|
if (this.mainDetailsToggle.hasAttribute('open') && !this.mainDetailsToggle.contains(document.activeElement))
|
|
this.closeMenuDrawer();
|
|
});
|
|
}
|
|
|
|
onCloseButtonClick(event) {
|
|
const detailsElement = event.currentTarget.closest('details');
|
|
this.closeSubmenu(detailsElement);
|
|
}
|
|
|
|
closeSubmenu(detailsElement) {
|
|
const parentMenuElement = detailsElement.closest('.submenu-open');
|
|
parentMenuElement && parentMenuElement.classList.remove('submenu-open');
|
|
detailsElement.classList.remove('menu-opening');
|
|
detailsElement.querySelector('summary').setAttribute('aria-expanded', false);
|
|
removeTrapFocus(detailsElement.querySelector('summary'));
|
|
this.closeAnimation(detailsElement);
|
|
}
|
|
|
|
closeAnimation(detailsElement) {
|
|
let animationStart;
|
|
|
|
const handleAnimation = (time) => {
|
|
if (animationStart === undefined) {
|
|
animationStart = time;
|
|
}
|
|
|
|
const elapsedTime = time - animationStart;
|
|
|
|
if (elapsedTime < 400) {
|
|
window.requestAnimationFrame(handleAnimation);
|
|
} else {
|
|
detailsElement.removeAttribute('open');
|
|
if (detailsElement.closest('details[open]')) {
|
|
trapFocus(detailsElement.closest('details[open]'), detailsElement.querySelector('summary'));
|
|
}
|
|
}
|
|
};
|
|
|
|
window.requestAnimationFrame(handleAnimation);
|
|
}
|
|
}
|
|
|
|
customElements.define('menu-drawer', MenuDrawer);
|
|
|
|
class HeaderDrawer extends MenuDrawer {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
openMenuDrawer(summaryElement) {
|
|
this.header = this.header || document.querySelector('.section-header');
|
|
this.borderOffset =
|
|
this.borderOffset || this.closest('.header-wrapper').classList.contains('header-wrapper--border-bottom') ? 1 : 0;
|
|
document.documentElement.style.setProperty(
|
|
'--header-bottom-position',
|
|
`${parseInt(this.header.getBoundingClientRect().bottom - this.borderOffset)}px`
|
|
);
|
|
this.header.classList.add('menu-open');
|
|
|
|
setTimeout(() => {
|
|
this.mainDetailsToggle.classList.add('menu-opening');
|
|
});
|
|
|
|
summaryElement.setAttribute('aria-expanded', true);
|
|
window.addEventListener('resize', this.onResize);
|
|
trapFocus(this.mainDetailsToggle, summaryElement);
|
|
document.body.classList.add(`overflow-hidden-${this.dataset.breakpoint}`);
|
|
}
|
|
|
|
closeMenuDrawer(event, elementToFocus) {
|
|
if (!elementToFocus) return;
|
|
super.closeMenuDrawer(event, elementToFocus);
|
|
this.header.classList.remove('menu-open');
|
|
window.removeEventListener('resize', this.onResize);
|
|
}
|
|
|
|
onResize = () => {
|
|
this.header &&
|
|
document.documentElement.style.setProperty(
|
|
'--header-bottom-position',
|
|
`${parseInt(this.header.getBoundingClientRect().bottom - this.borderOffset)}px`
|
|
);
|
|
document.documentElement.style.setProperty('--viewport-height', `${window.innerHeight}px`);
|
|
};
|
|
}
|
|
|
|
customElements.define('header-drawer', HeaderDrawer);
|
|
|
|
class ModalDialog extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.querySelector('[id^="ModalClose-"]').addEventListener('click', this.hide.bind(this, false));
|
|
this.addEventListener('keyup', (event) => {
|
|
if (event.code.toUpperCase() === 'ESCAPE') this.hide();
|
|
});
|
|
if (this.classList.contains('media-modal')) {
|
|
this.addEventListener('pointerup', (event) => {
|
|
if (event.pointerType === 'mouse' && !event.target.closest('deferred-media, product-model')) this.hide();
|
|
});
|
|
} else {
|
|
this.addEventListener('click', (event) => {
|
|
if (event.target === this) this.hide();
|
|
});
|
|
}
|
|
}
|
|
|
|
connectedCallback() {
|
|
if (this.moved) return;
|
|
this.moved = true;
|
|
document.body.appendChild(this);
|
|
}
|
|
|
|
show(opener) {
|
|
this.openedBy = opener;
|
|
const popup = this.querySelector('.template-popup');
|
|
document.body.classList.add('overflow-hidden');
|
|
this.setAttribute('open', '');
|
|
if (popup) popup.loadContent();
|
|
trapFocus(this, this.querySelector('[role="dialog"]'));
|
|
window.pauseAllMedia();
|
|
}
|
|
|
|
hide() {
|
|
document.body.classList.remove('overflow-hidden');
|
|
document.body.dispatchEvent(new CustomEvent('modalClosed'));
|
|
this.removeAttribute('open');
|
|
removeTrapFocus(this.openedBy);
|
|
window.pauseAllMedia();
|
|
}
|
|
}
|
|
customElements.define('modal-dialog', ModalDialog);
|
|
|
|
class ModalOpener extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
|
|
const button = this.querySelector('button');
|
|
|
|
if (!button) return;
|
|
button.addEventListener('click', () => {
|
|
const modal = document.querySelector(this.getAttribute('data-modal'));
|
|
if (modal) modal.show(button);
|
|
});
|
|
}
|
|
}
|
|
customElements.define('modal-opener', ModalOpener);
|
|
|
|
class DeferredMedia extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
const poster = this.querySelector('[id^="Deferred-Poster-"]');
|
|
if (!poster) return;
|
|
poster.addEventListener('click', this.loadContent.bind(this));
|
|
}
|
|
|
|
loadContent(focus = true) {
|
|
window.pauseAllMedia();
|
|
if (!this.getAttribute('loaded')) {
|
|
const content = document.createElement('div');
|
|
content.appendChild(this.querySelector('template').content.firstElementChild.cloneNode(true));
|
|
|
|
this.setAttribute('loaded', true);
|
|
const deferredElement = this.appendChild(content.querySelector('video, model-viewer, iframe'));
|
|
if (focus) deferredElement.focus();
|
|
if (deferredElement.nodeName == 'VIDEO' && deferredElement.getAttribute('autoplay')) {
|
|
// force autoplay for safari
|
|
deferredElement.play();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
customElements.define('deferred-media', DeferredMedia);
|
|
|
|
class SliderComponent extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.slider = this.querySelector('[id^="Slider-"]');
|
|
this.sliderItems = this.querySelectorAll('[id^="Slide-"]');
|
|
this.enableSliderLooping = false;
|
|
this.currentPageElement = this.querySelector('.slider-counter--current');
|
|
this.pageTotalElement = this.querySelector('.slider-counter--total');
|
|
this.prevButton = this.querySelector('button[name="previous"]');
|
|
this.nextButton = this.querySelector('button[name="next"]');
|
|
|
|
if (!this.slider || !this.nextButton) return;
|
|
|
|
this.initPages();
|
|
const resizeObserver = new ResizeObserver((entries) => this.initPages());
|
|
resizeObserver.observe(this.slider);
|
|
|
|
this.slider.addEventListener('scroll', this.update.bind(this));
|
|
this.prevButton.addEventListener('click', this.onButtonClick.bind(this));
|
|
this.nextButton.addEventListener('click', this.onButtonClick.bind(this));
|
|
}
|
|
|
|
initPages() {
|
|
this.sliderItemsToShow = Array.from(this.sliderItems).filter((element) => element.clientWidth > 0);
|
|
if (this.sliderItemsToShow.length < 2) return;
|
|
this.sliderItemOffset = this.sliderItemsToShow[1].offsetLeft - this.sliderItemsToShow[0].offsetLeft;
|
|
this.slidesPerPage = Math.floor(
|
|
(this.slider.clientWidth - this.sliderItemsToShow[0].offsetLeft) / this.sliderItemOffset
|
|
);
|
|
this.totalPages = this.sliderItemsToShow.length - this.slidesPerPage + 1;
|
|
this.update();
|
|
}
|
|
|
|
resetPages() {
|
|
this.sliderItems = this.querySelectorAll('[id^="Slide-"]');
|
|
this.initPages();
|
|
}
|
|
|
|
update() {
|
|
// Temporarily prevents unneeded updates resulting from variant changes
|
|
// This should be refactored as part of https://github.com/Shopify/dawn/issues/2057
|
|
if (!this.slider || !this.nextButton) return;
|
|
|
|
const previousPage = this.currentPage;
|
|
this.currentPage = Math.round(this.slider.scrollLeft / this.sliderItemOffset) + 1;
|
|
|
|
if (this.currentPageElement && this.pageTotalElement) {
|
|
this.currentPageElement.textContent = this.currentPage;
|
|
this.pageTotalElement.textContent = this.totalPages;
|
|
}
|
|
|
|
if (this.currentPage != previousPage) {
|
|
this.dispatchEvent(
|
|
new CustomEvent('slideChanged', {
|
|
detail: {
|
|
currentPage: this.currentPage,
|
|
currentElement: this.sliderItemsToShow[this.currentPage - 1],
|
|
},
|
|
})
|
|
);
|
|
}
|
|
|
|
if (this.enableSliderLooping) return;
|
|
|
|
if (this.isSlideVisible(this.sliderItemsToShow[0]) && this.slider.scrollLeft === 0) {
|
|
this.prevButton.setAttribute('disabled', 'disabled');
|
|
} else {
|
|
this.prevButton.removeAttribute('disabled');
|
|
}
|
|
|
|
if (this.isSlideVisible(this.sliderItemsToShow[this.sliderItemsToShow.length - 1])) {
|
|
this.nextButton.setAttribute('disabled', 'disabled');
|
|
} else {
|
|
this.nextButton.removeAttribute('disabled');
|
|
}
|
|
}
|
|
|
|
isSlideVisible(element, offset = 0) {
|
|
const lastVisibleSlide = this.slider.clientWidth + this.slider.scrollLeft - offset;
|
|
return element.offsetLeft + element.clientWidth <= lastVisibleSlide && element.offsetLeft >= this.slider.scrollLeft;
|
|
}
|
|
|
|
onButtonClick(event) {
|
|
event.preventDefault();
|
|
const step = event.currentTarget.dataset.step || 1;
|
|
this.slideScrollPosition =
|
|
event.currentTarget.name === 'next'
|
|
? this.slider.scrollLeft + step * this.sliderItemOffset
|
|
: this.slider.scrollLeft - step * this.sliderItemOffset;
|
|
this.setSlidePosition(this.slideScrollPosition);
|
|
}
|
|
|
|
setSlidePosition(position) {
|
|
this.slider.scrollTo({
|
|
left: position,
|
|
});
|
|
}
|
|
}
|
|
|
|
customElements.define('slider-component', SliderComponent);
|
|
|
|
class SlideshowComponent extends SliderComponent {
|
|
constructor() {
|
|
super();
|
|
this.sliderControlWrapper = this.querySelector('.slider-buttons');
|
|
this.enableSliderLooping = true;
|
|
|
|
if (!this.sliderControlWrapper) return;
|
|
|
|
this.sliderFirstItemNode = this.slider.querySelector('.slideshow__slide');
|
|
if (this.sliderItemsToShow.length > 0) this.currentPage = 1;
|
|
|
|
this.announcementBarSlider = this.querySelector('.announcement-bar-slider');
|
|
// Value below should match --duration-announcement-bar CSS value
|
|
this.announcerBarAnimationDelay = this.announcementBarSlider ? 250 : 0;
|
|
|
|
this.sliderControlLinksArray = Array.from(this.sliderControlWrapper.querySelectorAll('.slider-counter__link'));
|
|
this.sliderControlLinksArray.forEach((link) => link.addEventListener('click', this.linkToSlide.bind(this)));
|
|
this.slider.addEventListener('scroll', this.setSlideVisibility.bind(this));
|
|
this.setSlideVisibility();
|
|
|
|
if (this.announcementBarSlider) {
|
|
this.announcementBarArrowButtonWasClicked = false;
|
|
|
|
this.reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
this.reducedMotion.addEventListener('change', () => {
|
|
if (this.slider.getAttribute('data-autoplay') === 'true') this.setAutoPlay();
|
|
});
|
|
|
|
[this.prevButton, this.nextButton].forEach((button) => {
|
|
button.addEventListener(
|
|
'click',
|
|
() => {
|
|
this.announcementBarArrowButtonWasClicked = true;
|
|
},
|
|
{ once: true }
|
|
);
|
|
});
|
|
}
|
|
|
|
if (this.slider.getAttribute('data-autoplay') === 'true') this.setAutoPlay();
|
|
}
|
|
|
|
setAutoPlay() {
|
|
this.autoplaySpeed = this.slider.dataset.speed * 1000;
|
|
this.addEventListener('mouseover', this.focusInHandling.bind(this));
|
|
this.addEventListener('mouseleave', this.focusOutHandling.bind(this));
|
|
this.addEventListener('focusin', this.focusInHandling.bind(this));
|
|
this.addEventListener('focusout', this.focusOutHandling.bind(this));
|
|
|
|
if (this.querySelector('.slideshow__autoplay')) {
|
|
this.sliderAutoplayButton = this.querySelector('.slideshow__autoplay');
|
|
this.sliderAutoplayButton.addEventListener('click', this.autoPlayToggle.bind(this));
|
|
this.autoplayButtonIsSetToPlay = true;
|
|
this.play();
|
|
} else {
|
|
this.reducedMotion.matches || this.announcementBarArrowButtonWasClicked ? this.pause() : this.play();
|
|
}
|
|
}
|
|
|
|
onButtonClick(event) {
|
|
super.onButtonClick(event);
|
|
this.wasClicked = true;
|
|
|
|
const isFirstSlide = this.currentPage === 1;
|
|
const isLastSlide = this.currentPage === this.sliderItemsToShow.length;
|
|
|
|
if (!isFirstSlide && !isLastSlide) {
|
|
this.applyAnimationToAnnouncementBar(event.currentTarget.name);
|
|
return;
|
|
}
|
|
|
|
if (isFirstSlide && event.currentTarget.name === 'previous') {
|
|
this.slideScrollPosition =
|
|
this.slider.scrollLeft + this.sliderFirstItemNode.clientWidth * this.sliderItemsToShow.length;
|
|
} else if (isLastSlide && event.currentTarget.name === 'next') {
|
|
this.slideScrollPosition = 0;
|
|
}
|
|
|
|
this.setSlidePosition(this.slideScrollPosition);
|
|
|
|
this.applyAnimationToAnnouncementBar(event.currentTarget.name);
|
|
}
|
|
|
|
setSlidePosition(position) {
|
|
if (this.setPositionTimeout) clearTimeout(this.setPositionTimeout);
|
|
this.setPositionTimeout = setTimeout(() => {
|
|
this.slider.scrollTo({
|
|
left: position,
|
|
});
|
|
}, this.announcerBarAnimationDelay);
|
|
}
|
|
|
|
update() {
|
|
super.update();
|
|
this.sliderControlButtons = this.querySelectorAll('.slider-counter__link');
|
|
this.prevButton.removeAttribute('disabled');
|
|
|
|
if (!this.sliderControlButtons.length) return;
|
|
|
|
this.sliderControlButtons.forEach((link) => {
|
|
link.classList.remove('slider-counter__link--active');
|
|
link.removeAttribute('aria-current');
|
|
});
|
|
this.sliderControlButtons[this.currentPage - 1].classList.add('slider-counter__link--active');
|
|
this.sliderControlButtons[this.currentPage - 1].setAttribute('aria-current', true);
|
|
}
|
|
|
|
autoPlayToggle() {
|
|
this.togglePlayButtonState(this.autoplayButtonIsSetToPlay);
|
|
this.autoplayButtonIsSetToPlay ? this.pause() : this.play();
|
|
this.autoplayButtonIsSetToPlay = !this.autoplayButtonIsSetToPlay;
|
|
}
|
|
|
|
focusOutHandling(event) {
|
|
if (this.sliderAutoplayButton) {
|
|
const focusedOnAutoplayButton =
|
|
event.target === this.sliderAutoplayButton || this.sliderAutoplayButton.contains(event.target);
|
|
if (!this.autoplayButtonIsSetToPlay || focusedOnAutoplayButton) return;
|
|
this.play();
|
|
} else if (!this.reducedMotion.matches && !this.announcementBarArrowButtonWasClicked) {
|
|
this.play();
|
|
}
|
|
}
|
|
|
|
focusInHandling(event) {
|
|
if (this.sliderAutoplayButton) {
|
|
const focusedOnAutoplayButton =
|
|
event.target === this.sliderAutoplayButton || this.sliderAutoplayButton.contains(event.target);
|
|
if (focusedOnAutoplayButton && this.autoplayButtonIsSetToPlay) {
|
|
this.play();
|
|
} else if (this.autoplayButtonIsSetToPlay) {
|
|
this.pause();
|
|
}
|
|
} else if (this.announcementBarSlider.contains(event.target)) {
|
|
this.pause();
|
|
}
|
|
}
|
|
|
|
play() {
|
|
this.slider.setAttribute('aria-live', 'off');
|
|
clearInterval(this.autoplay);
|
|
this.autoplay = setInterval(this.autoRotateSlides.bind(this), this.autoplaySpeed);
|
|
}
|
|
|
|
pause() {
|
|
this.slider.setAttribute('aria-live', 'polite');
|
|
clearInterval(this.autoplay);
|
|
}
|
|
|
|
togglePlayButtonState(pauseAutoplay) {
|
|
if (pauseAutoplay) {
|
|
this.sliderAutoplayButton.classList.add('slideshow__autoplay--paused');
|
|
this.sliderAutoplayButton.setAttribute('aria-label', window.accessibilityStrings.playSlideshow);
|
|
} else {
|
|
this.sliderAutoplayButton.classList.remove('slideshow__autoplay--paused');
|
|
this.sliderAutoplayButton.setAttribute('aria-label', window.accessibilityStrings.pauseSlideshow);
|
|
}
|
|
}
|
|
|
|
autoRotateSlides() {
|
|
const slideScrollPosition =
|
|
this.currentPage === this.sliderItems.length ? 0 : this.slider.scrollLeft + this.sliderItemOffset;
|
|
|
|
this.setSlidePosition(slideScrollPosition);
|
|
this.applyAnimationToAnnouncementBar();
|
|
}
|
|
|
|
setSlideVisibility(event) {
|
|
this.sliderItemsToShow.forEach((item, index) => {
|
|
const linkElements = item.querySelectorAll('a');
|
|
if (index === this.currentPage - 1) {
|
|
if (linkElements.length)
|
|
linkElements.forEach((button) => {
|
|
button.removeAttribute('tabindex');
|
|
});
|
|
item.setAttribute('aria-hidden', 'false');
|
|
item.removeAttribute('tabindex');
|
|
} else {
|
|
if (linkElements.length)
|
|
linkElements.forEach((button) => {
|
|
button.setAttribute('tabindex', '-1');
|
|
});
|
|
item.setAttribute('aria-hidden', 'true');
|
|
item.setAttribute('tabindex', '-1');
|
|
}
|
|
});
|
|
this.wasClicked = false;
|
|
}
|
|
|
|
applyAnimationToAnnouncementBar(button = 'next') {
|
|
if (!this.announcementBarSlider) return;
|
|
|
|
const itemsCount = this.sliderItems.length;
|
|
const increment = button === 'next' ? 1 : -1;
|
|
|
|
const currentIndex = this.currentPage - 1;
|
|
let nextIndex = (currentIndex + increment) % itemsCount;
|
|
nextIndex = nextIndex === -1 ? itemsCount - 1 : nextIndex;
|
|
|
|
const nextSlide = this.sliderItems[nextIndex];
|
|
const currentSlide = this.sliderItems[currentIndex];
|
|
|
|
const animationClassIn = 'announcement-bar-slider--fade-in';
|
|
const animationClassOut = 'announcement-bar-slider--fade-out';
|
|
|
|
const isFirstSlide = currentIndex === 0;
|
|
const isLastSlide = currentIndex === itemsCount - 1;
|
|
|
|
const shouldMoveNext = (button === 'next' && !isLastSlide) || (button === 'previous' && isFirstSlide);
|
|
const direction = shouldMoveNext ? 'next' : 'previous';
|
|
|
|
currentSlide.classList.add(`${animationClassOut}-${direction}`);
|
|
nextSlide.classList.add(`${animationClassIn}-${direction}`);
|
|
|
|
setTimeout(() => {
|
|
currentSlide.classList.remove(`${animationClassOut}-${direction}`);
|
|
nextSlide.classList.remove(`${animationClassIn}-${direction}`);
|
|
}, this.announcerBarAnimationDelay * 2);
|
|
}
|
|
|
|
linkToSlide(event) {
|
|
event.preventDefault();
|
|
const slideScrollPosition =
|
|
this.slider.scrollLeft +
|
|
this.sliderFirstItemNode.clientWidth *
|
|
(this.sliderControlLinksArray.indexOf(event.currentTarget) + 1 - this.currentPage);
|
|
this.slider.scrollTo({
|
|
left: slideScrollPosition,
|
|
});
|
|
}
|
|
}
|
|
|
|
customElements.define('slideshow-component', SlideshowComponent);
|
|
|
|
class VariantSelects extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.addEventListener('change', this.onVariantChange);
|
|
}
|
|
|
|
onVariantChange(event) {
|
|
this.updateOptions();
|
|
this.updateMasterId();
|
|
this.updateSelectedSwatchValue(event);
|
|
this.toggleAddButton(true, '', false);
|
|
this.updatePickupAvailability();
|
|
this.removeErrorMessage();
|
|
this.filterImageVariant();
|
|
this.updateVariantStatuses();
|
|
|
|
if (!this.currentVariant) {
|
|
this.toggleAddButton(true, '', true);
|
|
this.setUnavailable();
|
|
} else {
|
|
this.updateMedia();
|
|
this.updateURL();
|
|
this.updateVariantInput();
|
|
this.renderProductInfo();
|
|
this.updateShareUrl();
|
|
}
|
|
}
|
|
|
|
filterImageVariant() {
|
|
//console.log('thumbnail updated', this.currentVariant);
|
|
/*
|
|
{
|
|
"id": 48818041848084,
|
|
"title": "Gold",
|
|
"option1": "Gold",
|
|
"option2": null,
|
|
"option3": null,
|
|
"sku": "",
|
|
"requires_shipping": false,
|
|
"taxable": false,
|
|
"featured_image": {
|
|
"id": 46282594517268,
|
|
"product_id": 9377687798036,
|
|
"position": 8,
|
|
"created_at": "2024-05-27T13:30:31-04:00",
|
|
"updated_at": "2024-05-27T13:30:33-04:00",
|
|
"alt": "Gold",
|
|
"width": 260,
|
|
"height": 260,
|
|
"src": "//quickstart-e8822a71.myshopify.com/cdn/shop/files/90578.gif?v=1716831033",
|
|
"variant_ids": [
|
|
48818041848084
|
|
]
|
|
},
|
|
"available": true,
|
|
"name": "Gift Card - Gold",
|
|
"public_title": "Gold",
|
|
"options": [
|
|
"Gold"
|
|
],
|
|
"price": 1000,
|
|
"weight": 0,
|
|
"compare_at_price": null,
|
|
"inventory_management": null,
|
|
"barcode": "",
|
|
"featured_media": {
|
|
"alt": "Gold",
|
|
"id": 38794319593748,
|
|
"position": 11,
|
|
"preview_image": {
|
|
"aspect_ratio": 1,
|
|
"height": 260,
|
|
"width": 260,
|
|
"src": "//quickstart-e8822a71.myshopify.com/cdn/shop/files/90578.gif?v=1716831033"
|
|
}
|
|
},
|
|
"requires_selling_plan": false,
|
|
"selling_plan_allocations": [],
|
|
"quantity_rule": {
|
|
"min": 1,
|
|
"max": null,
|
|
"increment": 1
|
|
}
|
|
}*/
|
|
|
|
console.log('this.currentVariant.featured_image', this.currentVariant.featured_image);
|
|
console.log('this.currentVariant.featured_image.alt', this.currentVariant.featured_image.alt);
|
|
|
|
if (this.currentVariant.featured_image && this.currentVariant.featured_image.alt) {
|
|
// just for info: document.querySelectorAll('[thumbnail-alt = '${test}']') // to select all elements with the alt tag
|
|
|
|
// only show the image with the alt tag that matches the variant title
|
|
document.querySelectorAll('[thumbnail-alt]').forEach((thumbnail) => {
|
|
console.log(thumbnail.getAttribute('thumbnail-alt'));
|
|
if (thumbnail.getAttribute('thumbnail-alt') === this.currentVariant.featured_image.alt) {
|
|
thumbnail.style.display = 'block';
|
|
} else {
|
|
thumbnail.style.display = 'none';
|
|
}
|
|
}
|
|
);
|
|
|
|
document.querySelectorAll('[thumbnail-alt-mobile]').forEach((thumbnail) => {
|
|
console.log(thumbnail.getAttribute('thumbnail-alt-mobile'));
|
|
if (thumbnail.getAttribute('thumbnail-alt-mobile') === this.currentVariant.featured_image.alt) {
|
|
// check if has class "is-active" to show the image
|
|
if (thumbnail.classList.contains('is-active') === false) {
|
|
thumbnail.classList.add('is-active');
|
|
}
|
|
thumbnail.style.display = '';
|
|
} else {
|
|
// check if has class "is-active" to hide the image
|
|
if (thumbnail.classList.contains('is-active') === true) {
|
|
thumbnail.classList.remove('is-active');
|
|
}
|
|
thumbnail.style.display = 'none';
|
|
}
|
|
|
|
}
|
|
);
|
|
|
|
} else {
|
|
// show all thumbnails
|
|
document.querySelectorAll('[thumbnail-alt]').forEach((thumbnail) => {
|
|
thumbnail.style.display = 'block';
|
|
});
|
|
}
|
|
}
|
|
|
|
updateOptions() {
|
|
this.options = Array.from(this.querySelectorAll('select, fieldset'), (element) => {
|
|
if (element.tagName === 'SELECT') {
|
|
return element.value;
|
|
}
|
|
if (element.tagName === 'FIELDSET') {
|
|
return Array.from(element.querySelectorAll('input')).find((radio) => radio.checked)?.value;
|
|
}
|
|
});
|
|
}
|
|
|
|
updateMasterId() {
|
|
this.currentVariant = this.getVariantData().find((variant) => {
|
|
return !variant.options
|
|
.map((option, index) => {
|
|
return this.options[index] === option;
|
|
})
|
|
.includes(false);
|
|
});
|
|
}
|
|
|
|
updateSelectedSwatchValue({ target }) {
|
|
const { name, value, tagName } = target;
|
|
|
|
if (tagName === 'SELECT' && target.selectedOptions.length) {
|
|
const swatchValue = target.selectedOptions[0].dataset.optionSwatchValue;
|
|
const selectedDropdownSwatchValue = this.querySelector(`[data-selected-dropdown-swatch="${name}"] > .swatch`);
|
|
if (!selectedDropdownSwatchValue) return;
|
|
if (swatchValue) {
|
|
selectedDropdownSwatchValue.style.setProperty('--swatch--background', swatchValue);
|
|
selectedDropdownSwatchValue.classList.remove('swatch--unavailable');
|
|
} else {
|
|
selectedDropdownSwatchValue.style.setProperty('--swatch--background', 'unset');
|
|
selectedDropdownSwatchValue.classList.add('swatch--unavailable');
|
|
}
|
|
} else if (tagName === 'INPUT' && target.type === 'radio') {
|
|
const selectedSwatchValue = this.querySelector(`[data-selected-swatch-value="${name}"]`);
|
|
if (selectedSwatchValue) selectedSwatchValue.innerHTML = value;
|
|
}
|
|
}
|
|
|
|
updateMedia() {
|
|
console.log('updateMedia', this.currentVariant);
|
|
|
|
if (!this.currentVariant) return;
|
|
if (!this.currentVariant.featured_media) return;
|
|
|
|
const mediaGalleries = document.querySelectorAll(`[id^="MediaGallery-${this.dataset.section}"]`);
|
|
mediaGalleries.forEach((mediaGallery) => {
|
|
mediaGallery.setActiveMedia(`${this.dataset.section}-${this.currentVariant.featured_media.id}`, true);
|
|
|
|
// how to get second image id from the gallery
|
|
console.log('mediaGallery', mediaGallery);
|
|
|
|
|
|
}
|
|
|
|
);
|
|
|
|
const modalContent = document.querySelector(`#ProductModal-${this.dataset.section} .product-media-modal__content`);
|
|
if (!modalContent) return;
|
|
const newMediaModal = modalContent.querySelector(`[data-media-id="${this.currentVariant.featured_media.id}"]`);
|
|
modalContent.prepend(newMediaModal);
|
|
}
|
|
|
|
updateURL() {
|
|
if (!this.currentVariant || this.dataset.updateUrl === 'false') return;
|
|
window.history.replaceState({}, '', `${this.dataset.url}?variant=${this.currentVariant.id}`);
|
|
}
|
|
|
|
updateShareUrl() {
|
|
const shareButton = document.getElementById(`Share-${this.dataset.section}`);
|
|
if (!shareButton || !shareButton.updateUrl) return;
|
|
shareButton.updateUrl(`${window.shopUrl}${this.dataset.url}?variant=${this.currentVariant.id}`);
|
|
}
|
|
|
|
updateVariantInput() {
|
|
const productForms = document.querySelectorAll(
|
|
`#product-form-${this.dataset.section}, #product-form-installment-${this.dataset.section}`
|
|
);
|
|
productForms.forEach((productForm) => {
|
|
const input = productForm.querySelector('input[name="id"]');
|
|
input.value = this.currentVariant.id;
|
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
});
|
|
}
|
|
|
|
updateVariantStatuses() {
|
|
const selectedOptionOneVariants = this.variantData.filter(
|
|
(variant) => this.querySelector(':checked').value === variant.option1
|
|
);
|
|
const inputWrappers = [...this.querySelectorAll('.product-form__input')];
|
|
inputWrappers.forEach((option, index) => {
|
|
if (index === 0) return;
|
|
const optionInputs = [...option.querySelectorAll('input[type="radio"], option')];
|
|
const previousOptionSelected = inputWrappers[index - 1].querySelector(':checked').value;
|
|
const availableOptionInputsValue = selectedOptionOneVariants
|
|
.filter((variant) => variant.available && variant[`option${index}`] === previousOptionSelected)
|
|
.map((variantOption) => variantOption[`option${index + 1}`]);
|
|
this.setInputAvailability(optionInputs, availableOptionInputsValue);
|
|
});
|
|
}
|
|
|
|
setInputAvailability(elementList, availableValuesList) {
|
|
elementList.forEach((element) => {
|
|
const value = element.getAttribute('value');
|
|
const availableElement = availableValuesList.includes(value);
|
|
|
|
if (element.tagName === 'INPUT') {
|
|
element.classList.toggle('disabled', !availableElement);
|
|
} else if (element.tagName === 'OPTION') {
|
|
element.innerText = availableElement
|
|
? value
|
|
: window.variantStrings.unavailable_with_option.replace('[value]', value);
|
|
}
|
|
});
|
|
}
|
|
|
|
updatePickupAvailability() {
|
|
const pickUpAvailability = document.querySelector('pickup-availability');
|
|
if (!pickUpAvailability) return;
|
|
|
|
if (this.currentVariant && this.currentVariant.available) {
|
|
pickUpAvailability.fetchAvailability(this.currentVariant.id);
|
|
} else {
|
|
pickUpAvailability.removeAttribute('available');
|
|
pickUpAvailability.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
removeErrorMessage() {
|
|
const section = this.closest('section');
|
|
if (!section) return;
|
|
|
|
const productForm = section.querySelector('product-form');
|
|
if (productForm) productForm.handleErrorMessage();
|
|
}
|
|
|
|
renderProductInfo() {
|
|
const requestedVariantId = this.currentVariant.id;
|
|
const sectionId = this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section;
|
|
|
|
fetch(
|
|
`${this.dataset.url}?variant=${requestedVariantId}§ion_id=${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section
|
|
}`
|
|
)
|
|
.then((response) => response.text())
|
|
.then((responseText) => {
|
|
// prevent unnecessary ui changes from abandoned selections
|
|
if (this.currentVariant.id !== requestedVariantId) return;
|
|
|
|
const html = new DOMParser().parseFromString(responseText, 'text/html');
|
|
const destination = document.getElementById(`price-${this.dataset.section}`);
|
|
const source = html.getElementById(
|
|
`price-${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`
|
|
);
|
|
const skuSource = html.getElementById(
|
|
`Sku-${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`
|
|
);
|
|
const skuDestination = document.getElementById(`Sku-${this.dataset.section}`);
|
|
const inventorySource = html.getElementById(
|
|
`Inventory-${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`
|
|
);
|
|
const inventoryDestination = document.getElementById(`Inventory-${this.dataset.section}`);
|
|
|
|
const volumePricingSource = html.getElementById(
|
|
`Volume-${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`
|
|
);
|
|
|
|
const pricePerItemDestination = document.getElementById(`Price-Per-Item-${this.dataset.section}`);
|
|
const pricePerItemSource = html.getElementById(
|
|
`Price-Per-Item-${this.dataset.originalSection ? this.dataset.originalSection : this.dataset.section}`
|
|
);
|
|
|
|
const volumePricingDestination = document.getElementById(`Volume-${this.dataset.section}`);
|
|
const qtyRules = document.getElementById(`Quantity-Rules-${this.dataset.section}`);
|
|
const volumeNote = document.getElementById(`Volume-Note-${this.dataset.section}`);
|
|
|
|
if (volumeNote) volumeNote.classList.remove('hidden');
|
|
if (volumePricingDestination) volumePricingDestination.classList.remove('hidden');
|
|
if (qtyRules) qtyRules.classList.remove('hidden');
|
|
|
|
if (source && destination) destination.innerHTML = source.innerHTML;
|
|
if (inventorySource && inventoryDestination) inventoryDestination.innerHTML = inventorySource.innerHTML;
|
|
if (skuSource && skuDestination) {
|
|
skuDestination.innerHTML = skuSource.innerHTML;
|
|
skuDestination.classList.toggle('hidden', skuSource.classList.contains('hidden'));
|
|
}
|
|
|
|
if (volumePricingSource && volumePricingDestination) {
|
|
volumePricingDestination.innerHTML = volumePricingSource.innerHTML;
|
|
}
|
|
|
|
if (pricePerItemSource && pricePerItemDestination) {
|
|
pricePerItemDestination.innerHTML = pricePerItemSource.innerHTML;
|
|
pricePerItemDestination.classList.toggle('hidden', pricePerItemSource.classList.contains('hidden'));
|
|
}
|
|
|
|
const price = document.getElementById(`price-${this.dataset.section}`);
|
|
|
|
if (price) price.classList.remove('hidden');
|
|
|
|
if (inventoryDestination) inventoryDestination.classList.toggle('hidden', inventorySource.innerText === '');
|
|
|
|
const addButtonUpdated = html.getElementById(`ProductSubmitButton-${sectionId}`);
|
|
this.toggleAddButton(
|
|
addButtonUpdated ? addButtonUpdated.hasAttribute('disabled') : true,
|
|
window.variantStrings.soldOut
|
|
);
|
|
|
|
publish(PUB_SUB_EVENTS.variantChange, {
|
|
data: {
|
|
sectionId,
|
|
html,
|
|
variant: this.currentVariant,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
toggleAddButton(disable = true, text, modifyClass = true) {
|
|
const productForm = document.getElementById(`product-form-${this.dataset.section}`);
|
|
if (!productForm) return;
|
|
const addButton = productForm.querySelector('[name="add"]');
|
|
const addButtonText = productForm.querySelector('[name="add"] > span');
|
|
if (!addButton) return;
|
|
|
|
if (disable) {
|
|
addButton.setAttribute('disabled', 'disabled');
|
|
if (text) addButtonText.textContent = text;
|
|
} else {
|
|
addButton.removeAttribute('disabled');
|
|
addButtonText.textContent = window.variantStrings.addToCart;
|
|
}
|
|
|
|
if (!modifyClass) return;
|
|
}
|
|
|
|
setUnavailable() {
|
|
const button = document.getElementById(`product-form-${this.dataset.section}`);
|
|
const addButton = button.querySelector('[name="add"]');
|
|
const addButtonText = button.querySelector('[name="add"] > span');
|
|
const price = document.getElementById(`price-${this.dataset.section}`);
|
|
const inventory = document.getElementById(`Inventory-${this.dataset.section}`);
|
|
const sku = document.getElementById(`Sku-${this.dataset.section}`);
|
|
const pricePerItem = document.getElementById(`Price-Per-Item-${this.dataset.section}`);
|
|
const volumeNote = document.getElementById(`Volume-Note-${this.dataset.section}`);
|
|
const volumeTable = document.getElementById(`Volume-${this.dataset.section}`);
|
|
const qtyRules = document.getElementById(`Quantity-Rules-${this.dataset.section}`);
|
|
|
|
if (!addButton) return;
|
|
addButtonText.textContent = window.variantStrings.unavailable;
|
|
if (price) price.classList.add('hidden');
|
|
if (inventory) inventory.classList.add('hidden');
|
|
if (sku) sku.classList.add('hidden');
|
|
if (pricePerItem) pricePerItem.classList.add('hidden');
|
|
if (volumeNote) volumeNote.classList.add('hidden');
|
|
if (volumeTable) volumeTable.classList.add('hidden');
|
|
if (qtyRules) qtyRules.classList.add('hidden');
|
|
}
|
|
|
|
getVariantData() {
|
|
this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent);
|
|
return this.variantData;
|
|
}
|
|
}
|
|
|
|
customElements.define('variant-selects', VariantSelects);
|
|
|
|
class ProductRecommendations extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
connectedCallback() {
|
|
const handleIntersection = (entries, observer) => {
|
|
if (!entries[0].isIntersecting) return;
|
|
observer.unobserve(this);
|
|
|
|
fetch(this.dataset.url)
|
|
.then((response) => response.text())
|
|
.then((text) => {
|
|
const html = document.createElement('div');
|
|
html.innerHTML = text;
|
|
const recommendations = html.querySelector('product-recommendations');
|
|
|
|
if (recommendations && recommendations.innerHTML.trim().length) {
|
|
this.innerHTML = recommendations.innerHTML;
|
|
}
|
|
|
|
if (!this.querySelector('slideshow-component') && this.classList.contains('complementary-products')) {
|
|
this.remove();
|
|
}
|
|
|
|
if (html.querySelector('.grid__item')) {
|
|
this.classList.add('product-recommendations--loaded');
|
|
}
|
|
})
|
|
.catch((e) => {
|
|
console.error(e);
|
|
});
|
|
};
|
|
|
|
new IntersectionObserver(handleIntersection.bind(this), { rootMargin: '0px 0px 400px 0px' }).observe(this);
|
|
}
|
|
}
|
|
|
|
customElements.define('product-recommendations', ProductRecommendations);
|