501 lines
18 KiB
JavaScript
501 lines
18 KiB
JavaScript
if (!customElements.get('quick-order-list')) {
|
|
customElements.define(
|
|
'quick-order-list',
|
|
class QuickOrderList extends BulkAdd {
|
|
cartUpdateUnsubscriber = undefined;
|
|
hasPendingQuantityUpdate = false;
|
|
constructor() {
|
|
super();
|
|
this.isListInsideModal = this.closest('bulk-modal');
|
|
|
|
this.stickyHeaderElement = document.querySelector('sticky-header');
|
|
if (this.stickyHeaderElement) {
|
|
this.stickyHeader = {
|
|
height: this.stickyHeaderElement.offsetHeight,
|
|
type: `${this.stickyHeaderElement.getAttribute('data-sticky-type')}`,
|
|
};
|
|
}
|
|
|
|
this.totalBar = this.getTotalBar();
|
|
if (this.totalBar) {
|
|
this.totalBarPosition = window.innerHeight - this.totalBar.offsetHeight;
|
|
|
|
this.handleResize = this.handleResize.bind(this);
|
|
window.addEventListener('resize', this.handleResize);
|
|
}
|
|
|
|
this.querySelector('form').addEventListener('submit', (event) => event.preventDefault());
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.cartUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.cartUpdate, async (event) => {
|
|
// skip if cart event was triggered by this section
|
|
if (event.source === this.id) return;
|
|
|
|
this.toggleTableLoading(true);
|
|
await this.refresh();
|
|
this.toggleTableLoading(false);
|
|
});
|
|
|
|
this.initEventListeners();
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
this.cartUpdateUnsubscriber?.();
|
|
window.removeEventListener('resize', this.handleResize);
|
|
}
|
|
|
|
handleResize() {
|
|
this.totalBarPosition = window.innerHeight - this.totalBar.offsetHeight;
|
|
this.stickyHeader.height = this.stickyHeaderElement ? this.stickyHeaderElement.offsetHeight : 0;
|
|
}
|
|
|
|
initEventListeners() {
|
|
this.querySelectorAll('.pagination__item').forEach((link) => {
|
|
link.addEventListener('click', async (event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
const url = new URL(event.currentTarget.href);
|
|
|
|
this.toggleTableLoading(true);
|
|
await this.refresh(url.searchParams.get('page') || '1');
|
|
this.scrollTop();
|
|
this.toggleTableLoading(false);
|
|
});
|
|
});
|
|
|
|
this.querySelector('.quick-order-list__contents').addEventListener(
|
|
'keyup',
|
|
this.handleScrollIntoView.bind(this)
|
|
);
|
|
|
|
this.quickOrderListTable.addEventListener('keydown', this.handleSwitchVariantOnEnter.bind(this));
|
|
|
|
this.initVariantEventListeners();
|
|
}
|
|
|
|
initVariantEventListeners() {
|
|
this.allInputsArray = Array.from(this.querySelectorAll('input[type="number"]'));
|
|
|
|
this.querySelectorAll('quantity-input').forEach((qty) => {
|
|
const debouncedOnChange = debounce(this.onChange.bind(this), BulkAdd.ASYNC_REQUEST_DELAY, true);
|
|
qty.addEventListener('change', (event) => {
|
|
this.hasPendingQuantityUpdate = true;
|
|
debouncedOnChange(event);
|
|
});
|
|
});
|
|
|
|
this.querySelectorAll('.quick-order-list-remove-button').forEach((button) => {
|
|
button.addEventListener('click', (event) => {
|
|
event.preventDefault();
|
|
this.toggleLoading(true);
|
|
this.startQueue(button.dataset.index, 0);
|
|
});
|
|
});
|
|
}
|
|
|
|
get currentPage() {
|
|
return this.querySelector('.pagination-wrapper')?.dataset?.page ?? '1';
|
|
}
|
|
|
|
get cartVariantsForProduct() {
|
|
return JSON.parse(this.querySelector('[data-cart-contents]')?.innerHTML || '[]');
|
|
}
|
|
|
|
getCartQuantityForLine(id) {
|
|
const cartQuantity = super.getCartQuantityForLine(id);
|
|
if (cartQuantity > 0) return cartQuantity;
|
|
|
|
return this.cartVariantsForProduct.includes(parseInt(id, 10)) ? 1 : 0;
|
|
}
|
|
|
|
onChange(event) {
|
|
const inputValue = parseInt(event.target.value);
|
|
this.cleanErrorMessageOnType(event);
|
|
if (inputValue == 0) {
|
|
event.target.setAttribute('value', inputValue);
|
|
this.startQueue(event.target.dataset.index, inputValue);
|
|
} else {
|
|
this.validateQuantity(event);
|
|
}
|
|
}
|
|
|
|
cleanErrorMessageOnType(event) {
|
|
const handleKeydown = () => {
|
|
event.target.setCustomValidity(' ');
|
|
event.target.reportValidity();
|
|
event.target.removeEventListener('keydown', handleKeydown);
|
|
};
|
|
|
|
event.target.addEventListener('keydown', handleKeydown);
|
|
}
|
|
|
|
validateInput(target) {
|
|
const targetValue = parseInt(target.value);
|
|
const targetMin = parseInt(target.dataset.min);
|
|
const targetStep = parseInt(target.step);
|
|
|
|
if (target.max) {
|
|
return (
|
|
targetValue == 0 ||
|
|
(targetValue >= targetMin && targetValue <= parseInt(target.max) && targetValue % targetStep == 0)
|
|
);
|
|
} else {
|
|
return targetValue == 0 || (targetValue >= targetMin && targetValue % targetStep == 0);
|
|
}
|
|
}
|
|
|
|
get quickOrderListTable() {
|
|
return this.querySelector('.quick-order-list__table');
|
|
}
|
|
|
|
getSectionsToRender() {
|
|
return [
|
|
{
|
|
id: this.id,
|
|
section: this.dataset.section,
|
|
selector: `#${this.id}`,
|
|
},
|
|
{
|
|
id: 'cart-icon-bubble',
|
|
section: 'cart-icon-bubble',
|
|
selector: '#shopify-section-cart-icon-bubble',
|
|
},
|
|
{
|
|
id: `quick-order-list-live-region-text-${this.dataset.productId}`,
|
|
section: 'cart-live-region-text',
|
|
selector: '.shopify-section',
|
|
},
|
|
{
|
|
id: 'CartDrawer',
|
|
selector: '.drawer__inner',
|
|
section: 'cart-drawer',
|
|
},
|
|
];
|
|
}
|
|
|
|
toggleTableLoading(enable) {
|
|
this.quickOrderListTable.classList.toggle('quick-order-list__container--disabled', enable);
|
|
this.toggleLoading(enable);
|
|
}
|
|
|
|
async refresh(pageNumber = null) {
|
|
const url = this.dataset.url || window.location.pathname;
|
|
|
|
return fetch(`${url}?section_id=${this.dataset.section}&page=${pageNumber || this.currentPage}`)
|
|
.then((response) => response.text())
|
|
.then((responseText) => {
|
|
const html = new DOMParser().parseFromString(responseText, 'text/html');
|
|
const responseQuickOrderList = html.querySelector(`#${this.id}`);
|
|
|
|
if (!responseQuickOrderList) {
|
|
return;
|
|
}
|
|
|
|
this.innerHTML = responseQuickOrderList.innerHTML;
|
|
this.initEventListeners();
|
|
})
|
|
.catch((e) => {
|
|
console.error(e);
|
|
});
|
|
}
|
|
|
|
renderSections(parsedState) {
|
|
const { items, sections } = parsedState;
|
|
|
|
this.getSectionsToRender().forEach(({ id, selector, section }) => {
|
|
const sectionElement = document.getElementById(id);
|
|
if (!sectionElement) return;
|
|
|
|
const newSection = new DOMParser().parseFromString(sections[section], 'text/html').querySelector(selector);
|
|
|
|
if (section === this.dataset.section) {
|
|
if (this.queue.length > 0 || this.hasPendingQuantityUpdate) return;
|
|
|
|
const focusedElement = document.activeElement;
|
|
let focusTarget = focusedElement?.dataset?.target;
|
|
if (focusTarget?.includes('remove')) {
|
|
focusTarget = focusedElement.closest('quantity-popover')?.querySelector('[data-target*="increment-"]')
|
|
?.dataset.target;
|
|
}
|
|
|
|
const total = this.getTotalBar();
|
|
if (total) {
|
|
total.innerHTML = newSection.querySelector('.quick-order-list__total').innerHTML;
|
|
}
|
|
|
|
const table = this.quickOrderListTable;
|
|
const newTable = newSection.querySelector('.quick-order-list__table');
|
|
|
|
// only update variants if they are from the active page
|
|
const shouldUpdateVariants =
|
|
this.currentPage === (newSection.querySelector('.pagination-wrapper')?.dataset.page ?? '1');
|
|
if (newTable && shouldUpdateVariants) {
|
|
table.innerHTML = newTable.innerHTML;
|
|
|
|
const newFocusTarget = this.querySelector(`[data-target='${focusTarget}']`);
|
|
if (newFocusTarget) {
|
|
newFocusTarget?.focus({ preventScroll: true });
|
|
}
|
|
|
|
this.initVariantEventListeners();
|
|
}
|
|
} else if (section === 'cart-drawer') {
|
|
sectionElement.closest('cart-drawer')?.classList.toggle('is-empty', items.length === 0);
|
|
sectionElement.querySelector(selector).innerHTML = newSection.innerHTML;
|
|
} else {
|
|
sectionElement.innerHTML = newSection.innerHTML;
|
|
}
|
|
});
|
|
}
|
|
|
|
getTotalBar() {
|
|
return this.querySelector('.quick-order-list__total');
|
|
}
|
|
|
|
scrollTop() {
|
|
const { top } = this.getBoundingClientRect();
|
|
|
|
if (this.isListInsideModal) {
|
|
this.scrollIntoView();
|
|
} else {
|
|
window.scrollTo({ top: top + window.scrollY - (this.stickyHeader?.height || 0), behavior: 'instant' });
|
|
}
|
|
}
|
|
|
|
scrollQuickOrderListTable(target) {
|
|
const inputTopBorder = target.getBoundingClientRect().top;
|
|
const inputBottomBorder = target.getBoundingClientRect().bottom;
|
|
|
|
if (this.isListInsideModal) {
|
|
const totalBarCrossesInput = inputBottomBorder > this.totalBar.getBoundingClientRect().top;
|
|
const tableHeadCrossesInput =
|
|
inputTopBorder < this.querySelector('.quick-order-list__table thead').getBoundingClientRect().bottom;
|
|
|
|
if (totalBarCrossesInput || tableHeadCrossesInput) {
|
|
this.scrollToCenter(target);
|
|
}
|
|
} else {
|
|
const stickyHeaderBottomBorder = this.stickyHeaderElement?.getBoundingClientRect().bottom;
|
|
const totalBarCrossesInput = inputBottomBorder > this.totalBarPosition;
|
|
const inputOutsideOfViewPort =
|
|
inputBottomBorder < this.querySelector('.variant-item__quantity-wrapper').offsetHeight;
|
|
const stickyHeaderCrossesInput =
|
|
this.stickyHeaderElement &&
|
|
this.stickyHeader.type !== 'on-scroll-up' &&
|
|
this.stickyHeader.height > inputTopBorder;
|
|
const stickyHeaderScrollupCrossesInput =
|
|
this.stickyHeaderElement &&
|
|
this.stickyHeader.type === 'on-scroll-up' &&
|
|
this.stickyHeader.height > inputTopBorder &&
|
|
stickyHeaderBottomBorder > 0;
|
|
|
|
if (
|
|
totalBarCrossesInput ||
|
|
inputOutsideOfViewPort ||
|
|
stickyHeaderCrossesInput ||
|
|
stickyHeaderScrollupCrossesInput
|
|
) {
|
|
this.scrollToCenter(target);
|
|
}
|
|
}
|
|
}
|
|
|
|
scrollToCenter(target) {
|
|
target.scrollIntoView({
|
|
block: 'center',
|
|
behavior: 'smooth',
|
|
});
|
|
}
|
|
|
|
handleScrollIntoView(event) {
|
|
if ((event.key === 'Tab' || event.key === 'Enter') && this.allInputsArray.length !== 1) {
|
|
this.scrollQuickOrderListTable(event.target);
|
|
}
|
|
}
|
|
|
|
handleSwitchVariantOnEnter(event) {
|
|
if (event.key !== 'Enter' || event.target.tagName !== 'INPUT') return;
|
|
|
|
event.preventDefault();
|
|
event.target.blur();
|
|
|
|
if (!this.validateInput(event.target) || this.allInputsArray.length <= 1) return;
|
|
|
|
const currentIndex = this.allInputsArray.indexOf(event.target);
|
|
const offset = event.shiftKey ? -1 : 1;
|
|
const nextIndex = (currentIndex + offset + this.allInputsArray.length) % this.allInputsArray.length;
|
|
|
|
this.allInputsArray[nextIndex]?.select();
|
|
}
|
|
|
|
updateMultipleQty(items) {
|
|
if (this.queue.length == 0) this.hasPendingQuantityUpdate = false;
|
|
|
|
this.toggleLoading(true);
|
|
const url = this.dataset.url || window.location.pathname;
|
|
const linesUpdate = this.startCartLinesUpdate(items);
|
|
|
|
const body = JSON.stringify({
|
|
updates: items,
|
|
sections: this.getSectionsToRender().map(({ section }) => section),
|
|
sections_url: `${url}?page=${this.currentPage}`,
|
|
});
|
|
|
|
this.updateMessage();
|
|
this.setErrorMessage();
|
|
|
|
fetch(`${routes.cart_update_url}`, { ...fetchConfig(), ...{ body } })
|
|
.then((response) => response.json())
|
|
.then((parsedState) => {
|
|
if (parsedState.errors) {
|
|
throw Object.assign(new Error(parsedState.errors), { code: 'INVALID' });
|
|
}
|
|
|
|
linesUpdate?.resolve(parsedState);
|
|
this.renderSections(parsedState);
|
|
publish(PUB_SUB_EVENTS.cartUpdate, {
|
|
source: this.id,
|
|
cartData: parsedState,
|
|
});
|
|
})
|
|
.catch((e) => {
|
|
if (e.code !== 'INVALID') console.error(e);
|
|
this.dispatchCartErrorEvent(
|
|
e.code === 'INVALID' ? e.message : window.cartStrings.error,
|
|
e.code || 'SERVICE_UNAVAILABLE'
|
|
);
|
|
linesUpdate?.reject(e);
|
|
this.setErrorMessage(e.code === 'INVALID' ? e.message : window.cartStrings.error);
|
|
})
|
|
.finally(() => {
|
|
this.queue.length === 0 && this.toggleLoading(false);
|
|
this.setRequestStarted(false);
|
|
});
|
|
}
|
|
|
|
setErrorMessage(message = null) {
|
|
this.errorMessageTemplate =
|
|
this.errorMessageTemplate ??
|
|
document.getElementById(`QuickOrderListErrorTemplate-${this.dataset.productId}`).cloneNode(true);
|
|
const errorElements = document.querySelectorAll('.quick-order-list-error');
|
|
|
|
errorElements.forEach((errorElement) => {
|
|
errorElement.innerHTML = '';
|
|
if (!message) return;
|
|
const updatedMessageElement = this.errorMessageTemplate.cloneNode(true);
|
|
updatedMessageElement.content.querySelector('.quick-order-list-error-message').innerText = message;
|
|
errorElement.appendChild(updatedMessageElement.content);
|
|
});
|
|
}
|
|
|
|
updateMessage(quantity = null) {
|
|
const messages = this.querySelectorAll('.quick-order-list__message-text');
|
|
const icons = this.querySelectorAll('.quick-order-list__message-icon');
|
|
|
|
if (quantity === null || isNaN(quantity)) {
|
|
messages.forEach((message) => (message.innerHTML = ''));
|
|
icons.forEach((icon) => icon.classList.add('hidden'));
|
|
return;
|
|
}
|
|
|
|
const isQuantityNegative = quantity < 0;
|
|
const absQuantity = Math.abs(quantity);
|
|
|
|
const textTemplate = isQuantityNegative
|
|
? absQuantity === 1
|
|
? window.quickOrderListStrings.itemRemoved
|
|
: window.quickOrderListStrings.itemsRemoved
|
|
: quantity === 1
|
|
? window.quickOrderListStrings.itemAdded
|
|
: window.quickOrderListStrings.itemsAdded;
|
|
|
|
messages.forEach((msg) => (msg.innerHTML = textTemplate.replace('[quantity]', absQuantity)));
|
|
|
|
if (!isQuantityNegative) {
|
|
icons.forEach((i) => i.classList.remove('hidden'));
|
|
}
|
|
}
|
|
|
|
updateError(updatedValue, id) {
|
|
let message = '';
|
|
if (typeof updatedValue === 'undefined') {
|
|
message = window.cartStrings.error;
|
|
} else {
|
|
message = window.cartStrings.quantityError.replace('[quantity]', updatedValue);
|
|
}
|
|
this.updateLiveRegions(id, message);
|
|
}
|
|
|
|
updateLiveRegions(id, message) {
|
|
const variantItemErrorDesktop = document.getElementById(`Quick-order-list-item-error-desktop-${id}`);
|
|
if (variantItemErrorDesktop) {
|
|
variantItemErrorDesktop.querySelector('.variant-item__error-text').innerHTML = message;
|
|
variantItemErrorDesktop.closest('tr').classList.remove('hidden');
|
|
}
|
|
if (variantItemErrorMobile)
|
|
variantItemErrorMobile.querySelector('.variant-item__error-text').innerHTML = message;
|
|
|
|
this.querySelector('#shopping-cart-variant-item-status').setAttribute('aria-hidden', true);
|
|
|
|
const cartStatus = document.getElementById('quick-order-list-live-region-text');
|
|
cartStatus.setAttribute('aria-hidden', false);
|
|
|
|
setTimeout(() => {
|
|
cartStatus.setAttribute('aria-hidden', true);
|
|
}, 1000);
|
|
}
|
|
|
|
toggleLoading(loading, target = this) {
|
|
target.querySelector('#shopping-cart-variant-item-status').toggleAttribute('aria-hidden', !loading);
|
|
target
|
|
.querySelectorAll('.variant-remove-total .loading__spinner')
|
|
?.forEach((spinner) => spinner.classList.toggle('hidden', !loading));
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
if (!customElements.get('quick-order-list-remove-all-button')) {
|
|
customElements.define(
|
|
'quick-order-list-remove-all-button',
|
|
class QuickOrderListRemoveAllButton extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.quickOrderList = this.closest('quick-order-list');
|
|
|
|
this.actions = {
|
|
confirm: 'confirm',
|
|
remove: 'remove',
|
|
cancel: 'cancel',
|
|
};
|
|
|
|
this.addEventListener('click', (event) => {
|
|
event.preventDefault();
|
|
if (this.dataset.action === this.actions.confirm) {
|
|
this.toggleConfirmation(false, true);
|
|
} else if (this.dataset.action === this.actions.remove) {
|
|
const items = this.quickOrderList.cartVariantsForProduct.reduce(
|
|
(acc, variantId) => ({ ...acc, [variantId]: 0 }),
|
|
{}
|
|
);
|
|
|
|
this.quickOrderList.updateMultipleQty(items);
|
|
this.toggleConfirmation(true, false);
|
|
} else if (this.dataset.action === this.actions.cancel) {
|
|
this.toggleConfirmation(true, false);
|
|
}
|
|
});
|
|
}
|
|
|
|
toggleConfirmation(showConfirmation, showInfo) {
|
|
this.quickOrderList
|
|
.querySelector('.quick-order-list-total__confirmation')
|
|
.classList.toggle('hidden', showConfirmation);
|
|
this.quickOrderList.querySelector('.quick-order-list-total__info').classList.toggle('hidden', showInfo);
|
|
}
|
|
}
|
|
);
|
|
}
|