From 715e26a9eb219808d5c899d418e1d596f9318f61 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 22 Jun 2026 16:59:19 +0000
Subject: [PATCH] =Debugging and implementing the Checkout.js logic. Will be uploading to test integration with square next.
---
assets/js/concise/Checkout.js | 422 ++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 307 insertions(+), 115 deletions(-)
diff --git a/assets/js/concise/Checkout.js b/assets/js/concise/Checkout.js
index e0990f0..594914e 100644
--- a/assets/js/concise/Checkout.js
+++ b/assets/js/concise/Checkout.js
@@ -21,7 +21,9 @@
this.checkout = document.querySelector('aside#cart');
this.provider = this.checkout?.querySelector('form')?.dataset.provider || '';
- this.isOpen = this.config.isOpen !== '1' || false;
+ this.isOpen = this.config.isOpen === '1' || false;
+ this.isOpen = true;
+
this.isLoggedIn = this.config.is_logged_in || false;
this.userEmail = this.config.user_email || '';
this.savedCards = [];
@@ -31,6 +33,7 @@
this.cache = new window.jvbCache('cart', { TTL: 8.64e+7 });
this.a11y = window.jvbA11y;
+ this.T = window.jvbTemplates;
this.initCart();
@@ -38,20 +41,38 @@
this.initElements();
this.init(); // provider-specific
this.initListeners();
+ this.defineTemplates();
if (this.isLoggedIn) {
this.loadSavedCards();
}
}
+ if (!this.isOpen)
+ {
+ this.closeShop();
+ }
+
this.popup = window.jvbPopup.registerPopup({
popup: this.checkout,
- toggle: this.toggle,
+ toggle: this.ui.toggle.btn,
name: 'Cart',
onOpen: this.maybeAddEmptyState.bind(this),
+ onClose: this.toggleButton.bind(this),
});
}
+ closeShop() {
+ document.querySelectorAll('[data-add-to-cart]').forEach(item => {
+ item.querySelectorAll('button, input').forEach(i => {
+ i.disabled = true;
+ i.title = 'Sorry, we are currently closed.';
+ });
+
+ });
+ }
+
+
/*****************************************************************
* ABSTRACT — subclasses must implement
*****************************************************************/
@@ -81,20 +102,30 @@
*****************************************************************/
async initCart() {
- this.cartItems = await this.cache.get('cart') ?? new Map();
- if (this.cartItems.size > 0) {
+ let stored = await this.cache.get('cart') ?? new Map();
+ if (stored.size > 0) {
+ this.cartItems = stored;
+ this.updateToggle();
this.notifyRestoredCart();
}
}
saveCart() {
this.updateTotal();
+ this.updateToggle();
this.cache.set('cart', this.cartItems);
}
clearCart() {
+ for (let [id, item] of this.cartItems.entries()) {
+ this.removeFromCart(id);
+ }
this.cartItems.clear();
- window.removeChildren(this.table);
+ Array.from(this.ui.cart.table.children).forEach(child => {
+ if (child.tagName === 'TBODY') {
+ child.remove();
+ }
+ });
this.saveCart();
}
@@ -109,22 +140,87 @@
/*****************************************************************
* ELEMENT SETUP
*****************************************************************/
+ defineTemplates() {
+ // const checkout = this;
+ this.T.define('mainCartItem', {
+ refs: {
+ header: '.header th'
+ },
+ setup({el, refs, manyRefs, data}) {
+ refs.header.textContent = data.label;
+ el.dataset.label = data.label;
+ el.dataset.id = data.parentId;
+ }
+ });
+ this.T.define('childCartItem', {
+ refs: {
+ quantity: '.quantity',
+ label: 'label',
+ price: 'span.price',
+ button: '[data-remove-from-cart]'
+ },
+ manyRefs: {},
+ setup({el, refs, manyRefs, data}) {
+ [
+ refs.quantity.dataset.id,
+ refs.quantity.dataset.price,
+ refs.quantity.dataset.catalogId,
+ refs.label.textContent,
+ refs.price.textContent,
+ refs.button.dataset.id
+ ] = [
+ data.id,
+ data.price,
+ data.catalogId || '',
+ data.name,
+ window.formatPrice(data.price),
+ data.id
+ ];
+ }
+ });
+ this.T.define('emptyCart');
+ }
initElements() {
- this.toggle = document.querySelector('.toggle-cart');
+ this.restoreElement = false;
+ this.selectors = {
+ toggle: {
+ btn: '.toggle-cart',
+ count: '.toggle-cart .count',
+ icon: '.toggle-cart .icon',
+ },
+ panels: {
+ checkout: {
+ toggle: 'button[data-tab="checkout"]',
+ form: '#cart form'
+ }
+ },
+ cart: {
+ wrap: '#cart .cart-items',
+ empty: '#cart .cart-items .empty',
+ table: '#cart .cart-items table',
+ body: '#cart .cart-items tbody',
+ total: '#cart .cart-total',
+ foot: {
+ foot: '#cart tfoot',
+ total: '#cart tfoot .total',
+ tax: '#cart tfoot .tax',
+ },
+ grandTotal: '#cart .total span',
+ tax: '#cart .cart-total .tax span',
+ }
+ };
+ this.ui = window.uiFromSelectors(this.selectors);
+ this.ui.cart.items = new Map();
+
if (!this.isOpen) {
- this.toggle.disabled = true;
- this.toggle.title = 'Currently closed for online ordering';
+ this.ui.toggle.btn.disabled = true;
+ this.ui.toggle.btn.title = 'Currently closed for online ordering';
}
- this.checkoutPanel = this.checkout.querySelector('button[data-tab="checkout"]');
- this.itemsList = this.checkout.querySelector('.cart-items');
- this.table = this.checkout.querySelector('.cart-items tbody');
- this.total = this.checkout.querySelector('.cart-total');
- this.totalTax = this.total.querySelector('.tax span');
- this.grandTotal = this.total.querySelector('.total span');
+
this.checkoutForm = this.checkout.querySelector('form');
- this.tabs = new window.jvbTabs(this.checkoutForm, {
+ this.tabs = window.jvbTabs.registerTab(this.checkoutForm, {
updateURL: false
});
}
@@ -137,6 +233,7 @@
this.checkoutForm.addEventListener('submit', (e) => this.handleFormSubmit(e));
document.addEventListener('click', this.clickHandler);
document.addEventListener('change', this.changeHandler);
+ // document.addEventListener('keydown', this.keyHandler);
}
/*****************************************************************
@@ -147,14 +244,15 @@
if (window.targetCheck(e, 'button') && window.targetCheck(e, 'div.quantity')) {
let quantity = window.targetCheck(e, 'div.quantity');
this.handleNumberClick(e, quantity);
- } else if (window.targetCheck(e, '[data-add-to-cart]')) {
+ } else if (window.targetCheck(e, '[data-add-to-cart]') && window.targetCheck(e, 'button')) {
let add = window.targetCheck(e, '[data-add-to-cart]');
this.handleAddToCart(add);
- } else if (window.targetCheck(e, '[data-remove-from-cart]')) {
+ } else if (window.targetCheck(e, '[data-remove-from-cart]') ) {
let remove = window.targetCheck(e, '[data-remove-from-cart]');
this.handleRemoveFromCart(remove);
} else if (window.targetCheck(e, '[data-clear-cart]')) {
this.clearCart();
+ document.querySelector('.restored')?.remove();
} else if (window.targetCheck(e, '[data-dismiss]')) {
window.targetCheck(e, '[data-dismiss]').closest('.restored')?.remove();
}
@@ -164,12 +262,7 @@
let input = window.targetCheck(e, '.quantity-input');
if (input) {
let item = e.target.closest('.quantity');
- let value = input.value;
-
- if (window.targetCheck(e, '.cart-items')) {
- let main = document.querySelector(`.menu-section [data-id="${item.dataset.id}"] input`);
- if (main) main.value = input.value;
- }
+ let value = e.target.value;
if (value > 0) {
this.handleAddToCart(item);
@@ -181,6 +274,10 @@
handleEscape(e) {
if (e.key === 'Escape') {
+ if (this.restoreElement) {
+ this.restoreElement.classList.remove('show');
+ this.restoreElement = false;
+ }
this.stepMultiplier = 1;
} else if (e.ctrlKey && e.shiftKey) {
this.stepMultiplier = Math.max(parseInt(this.stepMultiplier) * 100, 1000);
@@ -196,38 +293,62 @@
handleAddToCart(item) {
let id = item.dataset.id;
let price = parseFloat(item.dataset.price);
- let quantity = parseInt(item.querySelector('.quantity-input')?.value) ?? 1;
+ let quantity = parseInt(item.querySelector('.quantity input')?.value) ?? 1;
let total = parseFloat(price * quantity);
this.createItemElement(item);
- this.cartItems.set(id, {
+ let parent = this.determineParent(item);
+
+ let stored = {};
+ if (this.cartItems.has(id)) {
+ stored = this.cartItems.get(id);
+ }
+ let newValue = {
post_id: id,
- name: item.dataset.name,
+ name: stored.name ?? item.dataset.name,
+ label: stored.label ?? parent.dataset.label ??'',
+ parentId: stored.parentId ?? parent.dataset.id ??'',
price: price,
quantity: quantity,
total: total,
- catalog_id: item.dataset.catalogId || '',
- });
+ catalog_id: stored.catalogId ?? item.dataset.catalogId ?? '',
+ };
+ this.cartItems.set(id, newValue);
this.saveCart();
}
handleRemoveFromCart(item) {
+ console.log(item);
if (confirm('This will remove this item from the cart. Continue?')) {
- if (!item.querySelector('[data-id]')) {
- item = item.closest('.item')?.querySelector('.quantity.field');
+ let id = item.dataset.id??item.id??false;
+ if (!id) {
+ console.error('No ID found..');
}
- let id = item.dataset.id;
- this.cartItems.delete(id);
- this.table.querySelector(`[data-id="${id}"]`)?.closest('tr').remove();
-
- let input = document.querySelector(`[data-id="${id}"] input`);
- if (input) input.value = 0;
+ this.removeFromCart(id);
this.maybeAddEmptyState();
this.saveCart();
}
}
+ removeFromCart(id) {
+ let parent = this.findParentItem(id);
+ if (parent) {
+ let el = parent.items.get(id);
+ el.el.remove();
+ parent.items.delete(id);
+ if (el.reference){
+ el.reference.value = 0;
+ }
+ if (parent.items.size === 0) {
+ console.log('Removing parent item');
+ parent.el.remove();
+ this.ui.cart.items.delete(parent.id);
+ }
+ }
+
+ this.cartItems.delete(id);
+ }
handleNumberClick(e, container) {
e.preventDefault();
@@ -236,11 +357,15 @@
else if (e.target.closest('.decrease')) change -= 1;
if (change !== 0) {
- let step = parseInt(container.dataset.step);
let input = container.querySelector('input');
+ let step = parseInt(input.step);
let value = (input.value === '') ? 0 : parseInt(input.value);
+ if (value === 0 && change < 0) return;
- input.value = value + (step * change * this.stepMultiplier);
+ let increaseBy = step * change * this.stepMultiplier;
+
+ let newValue = value + increaseBy;
+ input.value = Math.max(newValue, 0);
input.dispatchEvent(new Event('change', { bubbles: true }));
this.handleNumberLimits(container);
}
@@ -254,10 +379,12 @@
let decrease = container.querySelector('.decrease');
let value = parseInt(input.value);
- if (value <= min) {
+ if (min !== undefined && value <= min) {
+ console.log('Resetting to minimum value');
input.value = min;
decrease.disabled = true;
- } else if (value >= max) {
+ } else if (max !== undefined && value >= max) {
+ console.log('Resetting to maximum value');
input.value = max;
increase.disabled = true;
} else {
@@ -271,100 +398,163 @@
*****************************************************************/
createItemElement(item) {
- let element = this.itemsList.querySelector(`[data-id="${item.dataset.id}"]`);
- let add = false;
- let price = item.dataset.price;
- let quantity = item.querySelector('[name="quantity"]')?.value ?? 1;
+ let element = this.maybeCreateCartItem(item);
- if (!element) {
- add = true;
- element = window.getTemplate('cartItem');
- let field = element.querySelector('.quantity');
- [
- field.dataset.id,
- element.querySelector('label').textContent,
- element.querySelector('.price').textContent,
- field.dataset.price,
- field.dataset.catalogId,
- ] = [
- item.dataset.id,
- item.dataset.name,
- window.formatPrice(price),
- price,
- item.dataset.catalogId || '',
- ];
- } else {
- element = element.closest('tr');
- }
+ let quantity = item.tagName === undefined ?
+ item.quantity :
+ item.querySelector('input').value ?? 1;
+ let price = item.tagName === undefined ?
+ item.price :
+ item.dataset.price;
- [
- element.querySelector('[name="quantity"]').value,
- element.querySelector('.total').textContent,
- ] = [
- quantity,
- window.formatPrice(quantity * price),
- ];
-
- if (add) {
- element.classList.add('adding');
- this.table.append(element);
- setTimeout(() => element.classList.remove('adding'), 500);
+ element.quantity.value = quantity;
+ element.total.textContent = window.formatPrice(quantity * price);
+ if (element.reference) {
+ element.reference.value = quantity;
}
}
+ maybeCreateCartItem(item) {
+ let normalized = {};
+ if (item.tagName === undefined) {
+ //We're getting it from the cached cart
+ normalized = {
+ id: String(item.post_id),
+ name: item.name,
+ price: item.price,
+ catalogId: item.catalog_id || '',
+ quantity: item.quantity,
+ label: item.label,
+ parentId: String(item.parentId)
+ };
+ } else if (item.closest('#cart')) {
+ // quantity field clicked from inside the cart itself — item already exists
+ let id = item.dataset.id;
+ let parent = this.findParentItem(id);
+ if (parent) {
+ return parent.items.get(id);
+ }
+ // fallback: shouldn't normally happen, but avoid crashing
+ console.warn('Cart quantity changed but no existing row found for id:', id);
+ }else {
+ //We're getting it direct from the quantity field
+ normalized = {
+ id: item.dataset.id,
+ name: item.dataset.name,
+ price: item.dataset.price,
+ catalogId: item.dataset.catalogId || '',
+ }
+ let parent = this.determineParent(item);
+ normalized.parentId = parent.dataset.id??parent.id;
+ normalized.label = parent.dataset.label??parent.label;
+ }
+
+ if (!this.ui.cart.items.has(normalized.parentId)) {
+ let parentEl = this.T.create('mainCartItem', normalized);
+ this.ui.cart.items.set(normalized.parentId, {
+ el: parentEl,
+ id: normalized.parentId,
+ label: normalized.label,
+ items: new Map()
+ });
+ this.ui.cart.table.insertBefore(parentEl, this.ui.cart.foot.foot);
+ }
+
+ let parent = this.ui.cart.items.get(normalized.parentId);
+ if (!parent.items.has(normalized.id)) {
+ let el = this.T.create('childCartItem', normalized);
+ parent.items.set(normalized.id, {
+ el: el,
+ quantity: el.querySelector('[name="quantity"]'),
+ total: el.querySelector('.total'),
+ reference: document.querySelector(`.menu-items [data-id="${normalized.id}"] input`),
+ });
+ parent.el.append(el);
+ }
+
+ return parent.items.get(normalized.id);
+ }
+ determineParent(item) {
+ if (item.tagName !== undefined) {
+ return this.parentFromHTML(item);
+ }
+ }
+ parentFromHTML(item) {
+ let parent = item.closest('.menu-item');
+ if (parent) return parent;
+ let id = item.dataset.id;
+ return this.findParentItem(id).el??false;
+ }
+ findParentItem(searchId) {
+ let parentItem = false;
+ for (let [id, data] of this.ui.cart.items.entries()) {
+ if (data.items.has(searchId)) {
+ parentItem = this.ui.cart.items.get(id);
+ }
+ }
+ return parentItem;
+ }
+
/*****************************************************************
* CART STATE
*****************************************************************/
-
+ toggleButton(open = false)
+ {
+ this.ui.toggle.icon.hidden = open;
+ }
maybeAddEmptyState() {
- let empty = this.itemsList.querySelector('.empty');
- if (empty) empty.remove();
+ this.toggleButton(true);
+ this.findEmpty();
if (this.cartItems.size === 0) {
- this.checkoutPanel.disabled = true;
- this.checkoutPanel.title = 'Add some things to your cart first!';
- let emptyEl = window.getTemplate('emptyCart');
- this.itemsList.append(emptyEl);
- this.table.closest('table').hidden = true;
- this.total.hidden = true;
+ this.ui.panels.checkout.toggle.disabled = true;
+ this.ui.panels.checkout.toggle.title = 'Add some things to your cart first!';
+ this.ui.cart.empty.hidden = false;
+ this.ui.cart.table.hidden = true;
+ this.ui.cart.total.hidden = true;
this.a11y.announce('Nothing in Cart');
} else {
- this.checkoutPanel.disabled = false;
- this.table.closest('table').hidden = false;
- this.total.hidden = false;
- this.checkoutPanel.title = 'Checkout';
+ this.ui.cart.empty.hidden = true;
+ this.ui.panels.checkout.toggle.disabled = false;
+ this.ui.cart.table.hidden = false;
+ this.ui.cart.total.hidden = false;
+ this.ui.panels.checkout.toggle.title = 'Checkout';
}
}
+ findEmpty() {
+ if (!this.ui.cart.empty) {
+ this.ui.cart.empty = this.T.create('emptyCart');
+ this.ui.cart.wrap.append(this.ui.cart.empty);
+ }
+ }
notifyRestoredCart() {
let restored = window.getTemplate('restoredCart');
- this.checkout.querySelector('.tab-content[data-tab=cartItems]').insertBefore(restored, this.itemsList);
+ document.body.append(restored);
+ restored.classList.toggle('show');
+ this.restoreElement = restored;
this.cartItems.forEach(item => {
- let element = window.getTemplate('cartItem');
- let field = element.querySelector('.quantity');
- [
- field.dataset.id,
- element.querySelector('label').textContent,
- element.querySelector('.price').textContent,
- field.dataset.price,
- field.dataset.catalogId,
- element.querySelector('[name="quantity"]').value,
- element.querySelector('.total').textContent,
- ] = [
- item.post_id,
- item.name,
- window.formatPrice(item.price),
- item.price,
- item.catalog_id || '',
- item.quantity,
- window.formatPrice(item.quantity * item.price),
- ];
- this.table.append(element);
+ this.createItemElement(item);
});
this.updateTotal();
}
+ updateToggle() {
+ if (this.cartItems.size > 0 && this.ui.toggle.btn.classList.contains('empty')) {
+ this.ui.toggle.btn.classList.remove('empty');
+ }
+ if (this.cartItems.size === 0 && !this.ui.toggle.btn.classList.contains('empty')) {
+ this.ui.toggle.btn.classList.add('empty');
+ }
+ let total = 0;
+ for (let [id, item] of this.cartItems.entries()) {
+ total += item.quantity;
+ }
+
+ this.ui.toggle.count.textContent = this.cartItems.size === 0 ? '' : total;
+ }
+
/*****************************************************************
* TOTALS
*****************************************************************/
@@ -373,11 +563,13 @@
let total = 0;
this.cartItems.forEach(item => total += item.total);
let tax = total * 0.05;
- window.eraseText(this.totalTax);
- window.eraseText(this.grandTotal);
- window.typeText(this.totalTax, window.formatPrice(tax));
- window.typeText(this.grandTotal, window.formatPrice(total + tax));
- this.totalTax.classList.remove('typeText');
+ window.eraseText(this.ui.cart.tax);
+ window.eraseText(this.ui.cart.foot.tax);
+ window.eraseText(this.ui.cart.foot.total);
+ window.typeText(this.ui.cart.foot.tax, window.formatPrice(tax));
+ window.typeText(this.ui.cart.grandTotal, window.formatPrice(total + tax));
+ window.typeText(this.ui.cart.foot.total, window.formatPrice(total + tax));
+ this.ui.cart.tax.classList.remove('typeText');
}
/*****************************************************************
--
Gitblit v1.10.0