[FIX] website_sale_aplicoop: prevent grid destruction on event listener attachment

The _attachEventListeners() function was cloning the products-grid element
without its children (cloneNode(false)) to remove duplicate event listeners.
This destroyed all loaded products every time the function was called.

Solution: Use a flag (_delegationListenersAttached) to prevent adding
duplicate event listeners instead of cloning and replacing the grid node.

This fixes the issue where products would disappear ~1-2 seconds after
page load.
This commit is contained in:
snt 2026-02-18 16:53:27 +01:00
parent b15e9bc977
commit b07b7dc671
6 changed files with 521 additions and 218 deletions

View file

@ -7,25 +7,105 @@
console.log("[INFINITE_SCROLL] Script loaded!");
// Visual indicator for debugging
if (typeof document !== "undefined") {
try {
var debugDiv = document.createElement("div");
debugDiv.innerHTML = "[INFINITE_SCROLL LOADED]";
debugDiv.style.position = "fixed";
debugDiv.style.top = "0";
debugDiv.style.right = "0";
debugDiv.style.backgroundColor = "#00ff00";
debugDiv.style.color = "#000";
debugDiv.style.padding = "5px 10px";
debugDiv.style.fontSize = "12px";
debugDiv.style.zIndex = "99999";
debugDiv.id = "infinite-scroll-debug";
document.body.appendChild(debugDiv);
} catch (e) {
console.error("[INFINITE_SCROLL] Error adding debug div:", e);
// DEBUG: Add MutationObserver to detect WHO is clearing the products grid
(function () {
var setupGridObserver = function () {
var grid = document.getElementById("products-grid");
if (!grid) {
console.log("[MUTATION_DEBUG] products-grid not found yet, will retry...");
setTimeout(setupGridObserver, 100);
return;
}
console.log("[MUTATION_DEBUG] 🔍 Setting up MutationObserver on products-grid");
console.log("[MUTATION_DEBUG] Initial child count:", grid.children.length);
console.log("[MUTATION_DEBUG] Grid innerHTML length:", grid.innerHTML.length);
// Watch the grid itself for child changes
var gridObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === "childList") {
if (mutation.removedNodes.length > 0) {
console.log("[MUTATION_DEBUG] ⚠️⚠️⚠️ PRODUCTS REMOVED FROM GRID!");
console.log(
"[MUTATION_DEBUG] Removed nodes count:",
mutation.removedNodes.length
);
console.log("[MUTATION_DEBUG] Stack trace:");
console.trace();
}
if (mutation.addedNodes.length > 0) {
console.log("[MUTATION_DEBUG] Products added:", mutation.addedNodes.length);
}
}
});
});
gridObserver.observe(grid, { childList: true, subtree: false });
// ALSO watch the parent for the grid element itself being replaced/removed
var parent = grid.parentElement;
if (parent) {
console.log(
"[MUTATION_DEBUG] 🔍 Also watching parent element:",
parent.tagName,
parent.className
);
var parentObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === "childList") {
mutation.removedNodes.forEach(function (node) {
if (
node.id === "products-grid" ||
(node.querySelector && node.querySelector("#products-grid"))
) {
console.log(
"[MUTATION_DEBUG] ⚠️⚠️⚠️ PRODUCTS-GRID ELEMENT ITSELF WAS REMOVED!"
);
console.log("[MUTATION_DEBUG] Stack trace:");
console.trace();
}
});
}
});
});
parentObserver.observe(parent, { childList: true, subtree: true });
}
// Poll to detect innerHTML being cleared (as backup)
var lastChildCount = grid.children.length;
setInterval(function () {
var currentGrid = document.getElementById("products-grid");
if (!currentGrid) {
console.log("[MUTATION_DEBUG] ⚠️⚠️⚠️ GRID ELEMENT NO LONGER EXISTS!");
console.trace();
return;
}
var currentChildCount = currentGrid.children.length;
if (currentChildCount !== lastChildCount) {
console.log(
"[MUTATION_DEBUG] 📊 Child count changed: " +
lastChildCount +
" → " +
currentChildCount
);
if (currentChildCount === 0 && lastChildCount > 0) {
console.log("[MUTATION_DEBUG] ⚠️⚠️⚠️ GRID WAS EMPTIED!");
console.trace();
}
lastChildCount = currentChildCount;
}
}, 100);
console.log("[MUTATION_DEBUG] ✅ Observers attached (grid + parent + polling)");
};
// Start observing as soon as possible
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", setupGridObserver);
} else {
setupGridObserver();
}
}
})();
(function () {
"use strict";
@ -45,10 +125,16 @@ if (typeof document !== "undefined") {
config: {},
init: function () {
console.log("[INFINITE_SCROLL] 🔧 init() called");
// Get configuration from page data
var configEl = document.getElementById("eskaera-config");
console.log("[INFINITE_SCROLL] eskaera-config element:", configEl);
if (!configEl) {
console.log("[INFINITE_SCROLL] No eskaera-config found, lazy loading disabled");
console.error(
"[INFINITE_SCROLL] ❌ No eskaera-config found, lazy loading disabled"
);
return;
}
@ -58,15 +144,33 @@ if (typeof document !== "undefined") {
this.perPage = parseInt(configEl.getAttribute("data-per-page")) || 20;
this.currentPage = parseInt(configEl.getAttribute("data-current-page")) || 1;
console.log("[INFINITE_SCROLL] Config loaded:", {
orderId: this.orderId,
searchQuery: this.searchQuery,
category: this.category,
perPage: this.perPage,
currentPage: this.currentPage,
});
// Check if there are more products to load from data attribute
var hasNextAttr = configEl.getAttribute("data-has-next");
this.hasMore = hasNextAttr === "true" || hasNextAttr === "True";
console.log(
"[INFINITE_SCROLL] hasMore=" +
this.hasMore +
" (data-has-next=" +
hasNextAttr +
")"
);
if (!this.hasMore) {
console.log(
"[INFINITE_SCROLL] No more products to load (has_next=" + hasNextAttr + ")"
"[INFINITE_SCROLL] ⚠️ No more pages available, but keeping initialized for filter handling (has_next=" +
hasNextAttr +
")"
);
return;
// Don't return - we need to stay initialized so realtime_search can call resetWithFilters()
}
console.log("[INFINITE_SCROLL] Initialized with:", {
@ -77,36 +181,50 @@ if (typeof document !== "undefined") {
currentPage: this.currentPage,
});
this.attachScrollListener();
// Also keep the button listener as fallback
this.attachFallbackButtonListener();
// Only attach scroll listener if there are more pages to load
if (this.hasMore) {
this.attachScrollListener();
this.attachFallbackButtonListener();
} else {
console.log("[INFINITE_SCROLL] Skipping scroll listener (no more pages)");
}
},
attachScrollListener: function () {
var self = this;
var scrollThreshold = 0.8; // Load when 80% scrolled
var scrollThreshold = 300; // Load when within 300px of the bottom of the grid
window.addEventListener("scroll", function () {
if (self.isLoading || !self.hasMore) {
return;
}
var scrollHeight = document.documentElement.scrollHeight;
var scrollTop = window.scrollY;
var clientHeight = window.innerHeight;
var scrollPercent = (scrollTop + clientHeight) / scrollHeight;
var grid = document.getElementById("products-grid");
if (!grid) {
return;
}
if (scrollPercent >= scrollThreshold) {
// Calculate distance from bottom of grid to bottom of viewport
var gridRect = grid.getBoundingClientRect();
var gridBottom = gridRect.bottom;
var viewportBottom = window.innerHeight;
var distanceFromBottom = gridBottom - viewportBottom;
// Load more if we're within threshold pixels of the grid bottom
if (distanceFromBottom <= scrollThreshold && distanceFromBottom > 0) {
console.log(
"[INFINITE_SCROLL] Scroll threshold reached, loading next page"
"[INFINITE_SCROLL] Near grid bottom (distance: " +
Math.round(distanceFromBottom) +
"px), loading next page"
);
self.loadNextPage();
}
});
console.log(
"[INFINITE_SCROLL] Scroll listener attached (threshold:",
scrollThreshold * 100 + "%)"
"[INFINITE_SCROLL] Scroll listener attached (threshold: " +
scrollThreshold +
"px from grid bottom)"
);
},
@ -134,20 +252,53 @@ if (typeof document !== "undefined") {
/**
* Reset infinite scroll to page 1 with new filters and reload products.
* Called by realtime_search when filters change.
*
* WARNING: This clears the grid! Only call when filters actually change.
*/
console.log(
"[INFINITE_SCROLL] Resetting with filters: search=" +
"[INFINITE_SCROLL] ⚠️⚠️⚠️ resetWithFilters CALLED - search=" +
searchQuery +
" category=" +
categoryId
);
console.trace("[INFINITE_SCROLL] ⚠️⚠️⚠️ WHO CALLED resetWithFilters? Call stack:");
this.searchQuery = searchQuery || "";
this.category = categoryId || "0";
// Normalize values: empty string to "", null to "0" for category
var newSearchQuery = (searchQuery || "").trim();
var newCategory = (categoryId || "").trim() || "0";
// CHECK IF VALUES ACTUALLY CHANGED before clearing grid!
if (newSearchQuery === this.searchQuery && newCategory === this.category) {
console.log(
"[INFINITE_SCROLL] ✅ NO CHANGE - Skipping reset (values are identical)"
);
return; // Don't clear grid if nothing changed!
}
console.log(
"[INFINITE_SCROLL] 🔥 VALUES CHANGED - Old: search=" +
this.searchQuery +
" category=" +
this.category +
" → New: search=" +
newSearchQuery +
" category=" +
newCategory
);
this.searchQuery = newSearchQuery;
this.category = newCategory;
this.currentPage = 0; // Set to 0 so loadNextPage() increments to 1
this.isLoading = false;
this.hasMore = true;
console.log(
"[INFINITE_SCROLL] After normalization: search=" +
this.searchQuery +
" category=" +
this.category
);
// Update the config element data attributes for consistency
var configEl = document.getElementById("eskaera-config");
if (configEl) {
@ -155,26 +306,58 @@ if (typeof document !== "undefined") {
configEl.setAttribute("data-category", this.category);
configEl.setAttribute("data-current-page", "1");
configEl.setAttribute("data-has-next", "true");
console.log("[INFINITE_SCROLL] Updated eskaera-config attributes");
}
// Clear the grid and reload from page 1
var grid = document.getElementById("products-grid");
if (grid) {
console.log("[INFINITE_SCROLL] 🗑️ CLEARING GRID NOW!");
grid.innerHTML = "";
console.log("[INFINITE_SCROLL] Grid cleared");
}
// Load first page with new filters
console.log("[INFINITE_SCROLL] Calling loadNextPage()...");
this.loadNextPage();
},
loadNextPage: function () {
console.log(
"[INFINITE_SCROLL] 🚀 loadNextPage() CALLED - currentPage=" +
this.currentPage +
" isLoading=" +
this.isLoading +
" hasMore=" +
this.hasMore
);
if (this.isLoading || !this.hasMore) {
console.log("[INFINITE_SCROLL] ❌ ABORTING - already loading or no more pages");
return;
}
var self = this;
this.isLoading = true;
this.currentPage += 1;
// Only increment if we're not loading first page (currentPage will be 0 after reset)
if (this.currentPage === 0) {
console.log(
"[INFINITE_SCROLL] ✅ Incrementing from 0 to 1 (first page after reset)"
);
this.currentPage = 1;
} else {
console.log(
"[INFINITE_SCROLL] ✅ Incrementing page " +
this.currentPage +
" → " +
(this.currentPage + 1)
);
this.currentPage += 1;
}
console.log(
"[INFINITE_SCROLL] Loading page",
"[INFINITE_SCROLL] 📡 About to fetch page",
this.currentPage,
"for order",
this.orderId

View file

@ -135,6 +135,9 @@
_attachEventListeners: function () {
var self = this;
// Flag to prevent filtering during initialization
self.isInitializing = true;
// Initialize available tags from DOM
self._initializeAvailableTags();
@ -142,8 +145,48 @@
self.originalTagColors = {}; // Maps tag ID to original color
// Store last values at instance level so polling can access them
self.lastSearchValue = "";
self.lastCategoryValue = "";
// Initialize to current values to avoid triggering reset on first poll
self.lastSearchValue = self.searchInput.value.trim();
self.lastCategoryValue = self.categorySelect.value;
console.log(
"[realtimeSearch] Initial values stored - search:",
JSON.stringify(self.lastSearchValue),
"category:",
JSON.stringify(self.lastCategoryValue)
);
// Clear search button - DISABLED FOR NOW
// TODO: Re-implement without causing initialization issues
/*
self.clearSearchBtn = document.getElementById("clear-search-btn");
if (self.clearSearchBtn) {
// Show/hide button based on input content (passive, no filtering)
self.searchInput.addEventListener("input", function () {
if (self.searchInput.value.trim().length > 0) {
self.clearSearchBtn.style.display = "block";
} else {
self.clearSearchBtn.style.display = "none";
}
});
// Clear search when button clicked
self.clearSearchBtn.addEventListener("click", function (e) {
e.preventDefault();
console.log("[realtimeSearch] Clear search button clicked");
self.searchInput.value = "";
self.clearSearchBtn.style.display = "none";
self.searchInput.focus();
// Trigger input event to update search results
self.searchInput.dispatchEvent(new Event("input", { bubbles: true }));
});
// Initial check
if (self.searchInput.value.trim().length > 0) {
self.clearSearchBtn.style.display = "block";
}
}
*/
// Prevent form submission completely
var form = self.searchInput.closest("form");
@ -169,6 +212,11 @@
// Search input: listen to 'input' for real-time filtering
self.searchInput.addEventListener("input", function (e) {
try {
// Skip filtering during initialization
if (self.isInitializing) {
console.log("[realtimeSearch] INPUT event during init - skipping filter");
return;
}
console.log('[realtimeSearch] INPUT event - value: "' + e.target.value + '"');
self._filterProducts();
} catch (error) {
@ -179,6 +227,11 @@
// Also keep 'keyup' for extra compatibility
self.searchInput.addEventListener("keyup", function (e) {
try {
// Skip filtering during initialization
if (self.isInitializing) {
console.log("[realtimeSearch] KEYUP event during init - skipping filter");
return;
}
console.log('[realtimeSearch] KEYUP event - value: "' + e.target.value + '"');
self._filterProducts();
} catch (error) {
@ -189,6 +242,11 @@
// Category select
self.categorySelect.addEventListener("change", function (e) {
try {
// Skip filtering during initialization
if (self.isInitializing) {
console.log("[realtimeSearch] CHANGE event during init - skipping filter");
return;
}
console.log(
'[realtimeSearch] CHANGE event - selected: "' + e.target.value + '"'
);
@ -315,7 +373,10 @@
});
// Filter products (independent of search/category state)
self._filterProducts();
// Skip during initialization
if (!self.isInitializing) {
self._filterProducts();
}
});
});
@ -328,6 +389,11 @@
var pollingCounter = 0;
var pollInterval = setInterval(function () {
try {
// Skip polling during initialization to avoid clearing products
if (self.isInitializing) {
return;
}
pollingCounter++;
// Try multiple ways to get the search value
@ -418,10 +484,13 @@
);
} else {
// Fallback: filter locally (but this only filters loaded products)
console.log(
"[realtimeSearch] infiniteScroll not available, filtering locally only"
);
self._filterProducts();
// Skip during initialization
if (!self.isInitializing) {
console.log(
"[realtimeSearch] infiniteScroll not available, filtering locally only"
);
self._filterProducts();
}
}
}
} catch (error) {
@ -432,6 +501,10 @@
console.log("[realtimeSearch] ✅ Polling interval started with ID:", pollInterval);
console.log("[realtimeSearch] Event listeners attached with polling fallback");
// Initialization complete - allow filtering now
self.isInitializing = false;
console.log("[realtimeSearch] ✅ Initialization complete - filtering enabled");
},
_initializeAvailableTags: function () {

View file

@ -45,6 +45,10 @@
console.log("Initializing cart for order:", this.orderId);
// Attach event listeners FIRST (doesn't depend on translations)
this._attachEventListeners();
console.log("[groupOrderShop] Event listeners attached");
// Wait for i18nManager to load translations from server
i18nManager
.init()
@ -52,8 +56,6 @@
console.log("[groupOrderShop] Translations loaded from server");
self.labels = i18nManager.getAll();
// Initialize event listeners and state after translations are ready
self._attachEventListeners();
self._loadCart();
self._checkConfirmationMessage();
self._initializeTooltips();
@ -603,12 +605,70 @@
_attachEventListeners: function () {
var self = this;
// Helper function to round decimals correctly
function roundDecimal(value, decimals) {
var factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
}
// ============ ATTACH CHECKOUT BUTTONS (ALWAYS, on any page) ============
// These buttons exist on checkout page, not on cart pages
if (!this._cartCheckoutListenersAttached) {
console.log("[_attachEventListeners] Attaching checkout button listeners...");
// Button to save as draft (in checkout page)
var saveBtn = document.getElementById("save-order-btn");
console.log("[_attachEventListeners] save-order-btn found:", !!saveBtn);
if (saveBtn) {
saveBtn.addEventListener("click", function (e) {
console.log("[CLICK] save-order-btn clicked");
e.preventDefault();
self._saveOrderDraft();
});
}
// Confirm order button (in checkout page)
var confirmBtn = document.getElementById("confirm-order-btn");
console.log("[_attachEventListeners] confirm-order-btn found:", !!confirmBtn);
if (confirmBtn) {
confirmBtn.addEventListener("click", function (e) {
console.log("[CLICK] confirm-order-btn clicked");
e.preventDefault();
self._confirmOrder();
});
}
// Button to reload from draft (in My Cart header - cart pages)
var reloadCartBtn = document.getElementById("reload-cart-btn");
console.log("[_attachEventListeners] reload-cart-btn found:", !!reloadCartBtn);
if (reloadCartBtn) {
reloadCartBtn.addEventListener("click", function (e) {
console.log("[CLICK] reload-cart-btn clicked");
e.preventDefault();
self._loadDraftCart();
});
}
this._cartCheckoutListenersAttached = true;
console.log("[_attachEventListeners] Checkout listeners attached (one-time)");
}
// ============ LAZY LOADING: Load More Button ============
this._attachLoadMoreListener();
// Adjust quantity step based on UoM category
// Categories without decimals (per unit): "Unit", "Units", etc.
// Categories with decimals: "Weight", "Volume", "Length", etc.
// ============ USE EVENT DELEGATION FOR QUANTITY & CART BUTTONS ============
// This way, new products loaded via AJAX will automatically have listeners
var productsGrid = document.getElementById("products-grid");
if (!productsGrid) {
console.log("[_attachEventListeners] No products-grid found (checkout page?)");
return;
}
// First, adjust quantity steps for all existing inputs
var unitInputs = document.querySelectorAll(".product-qty");
console.log("=== ADJUSTING QUANTITY STEPS ===");
console.log("Found " + unitInputs.length + " quantity inputs");
@ -638,141 +698,108 @@
}
console.log("=== END ADJUSTING QUANTITY STEPS ===");
// Botones + y - para aumentar/disminuir cantidad
// Helper function to round decimals correctly
function roundDecimal(value, decimals) {
var factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
// IMPORTANT: Do NOT clone the grid node - this destroys all products!
// Instead, use a flag to prevent adding duplicate event listeners
if (productsGrid._delegationListenersAttached) {
console.log(
"[_attachEventListeners] Grid delegation listeners already attached, skipping"
);
return;
}
productsGrid._delegationListenersAttached = true;
console.log("[_attachEventListeners] Attaching grid delegation listeners (one-time)");
// Remove old listeners by cloning elements (to avoid duplication)
var decreaseButtons = document.querySelectorAll(".qty-decrease");
for (var k = 0; k < decreaseButtons.length; k++) {
var newBtn = decreaseButtons[k].cloneNode(true);
decreaseButtons[k].parentNode.replaceChild(newBtn, decreaseButtons[k]);
}
// Quantity decrease button (via event delegation)
productsGrid.addEventListener("click", function (e) {
var decreaseBtn = e.target.closest(".qty-decrease");
if (!decreaseBtn) return;
var increaseButtons = document.querySelectorAll(".qty-increase");
for (var k = 0; k < increaseButtons.length; k++) {
var newBtn = increaseButtons[k].cloneNode(true);
increaseButtons[k].parentNode.replaceChild(newBtn, increaseButtons[k]);
}
e.preventDefault();
var productId = decreaseBtn.getAttribute("data-product-id");
var input = document.getElementById("qty_" + productId);
if (!input) return;
// Ahora asignar nuevos listeners
decreaseButtons = document.querySelectorAll(".qty-decrease");
for (var k = 0; k < decreaseButtons.length; k++) {
decreaseButtons[k].addEventListener("click", function (e) {
e.preventDefault();
var productId = this.getAttribute("data-product-id");
var input = document.getElementById("qty_" + productId);
if (!input) return;
var step = parseFloat(input.step) || 1;
var currentValue = parseFloat(input.value) || 0;
var min = parseFloat(input.min) || 0;
var newValue = Math.max(min, roundDecimal(currentValue - step, 1));
var step = parseFloat(input.step) || 1;
var currentValue = parseFloat(input.value) || 0;
var min = parseFloat(input.min) || 0;
var newValue = Math.max(min, roundDecimal(currentValue - step, 1));
// Si es unidad, mostrar como entero
if (input.dataset.isUnit === "true") {
input.value = Math.floor(newValue);
} else {
input.value = newValue;
}
});
// Si es unidad, mostrar como entero
if (input.dataset.isUnit === "true") {
input.value = Math.floor(newValue);
} else {
input.value = newValue;
}
// Quantity increase button (via event delegation)
productsGrid.addEventListener("click", function (e) {
var increaseBtn = e.target.closest(".qty-increase");
if (!increaseBtn) return;
e.preventDefault();
var productId = increaseBtn.getAttribute("data-product-id");
var input = document.getElementById("qty_" + productId);
if (!input) return;
var step = parseFloat(input.step) || 1;
var currentValue = parseFloat(input.value) || 0;
var newValue = roundDecimal(currentValue + step, 1);
// Si es unidad, mostrar como entero
if (input.dataset.isUnit === "true") {
input.value = Math.floor(newValue);
} else {
input.value = newValue;
}
});
// Add to cart button (via event delegation)
productsGrid.addEventListener("click", function (e) {
var cartBtn = e.target.closest(".add-to-cart-btn");
if (!cartBtn) return;
e.preventDefault();
var form = cartBtn.closest(".add-to-cart-form");
var productId = form.getAttribute("data-product-id");
var productName = form.getAttribute("data-product-name") || "Product";
var productPrice = parseFloat(form.getAttribute("data-product-price")) || 0;
var quantityInput = form.querySelector(".product-qty");
var quantity = quantityInput ? parseFloat(quantityInput.value) : 1;
console.log("Adding:", {
productId: productId,
productName: productName,
productPrice: productPrice,
quantity: quantity,
});
}
increaseButtons = document.querySelectorAll(".qty-increase");
for (var k = 0; k < increaseButtons.length; k++) {
increaseButtons[k].addEventListener("click", function (e) {
e.preventDefault();
var productId = this.getAttribute("data-product-id");
var input = document.getElementById("qty_" + productId);
if (!input) return;
var step = parseFloat(input.step) || 1;
var currentValue = parseFloat(input.value) || 0;
var newValue = roundDecimal(currentValue + step, 1);
// Si es unidad, mostrar como entero
if (input.dataset.isUnit === "true") {
input.value = Math.floor(newValue);
} else {
input.value = newValue;
}
});
}
// Botones de agregar al carrito
var buttons = document.querySelectorAll(".add-to-cart-btn");
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", function (e) {
e.preventDefault();
var form = this.closest(".add-to-cart-form");
var productId = form.getAttribute("data-product-id");
var productName = form.getAttribute("data-product-name") || "Product";
var productPrice = parseFloat(form.getAttribute("data-product-price")) || 0;
var quantityInput = form.querySelector(".product-qty");
var quantity = quantityInput ? parseFloat(quantityInput.value) : 1;
console.log("Adding:", {
productId: productId,
productName: productName,
productPrice: productPrice,
quantity: quantity,
});
if (quantity > 0) {
self._addToCart(productId, productName, productPrice, quantity);
} else {
var labels = self._getLabels();
self._showNotification(
labels.invalid_quantity || "Please enter a valid quantity",
"warning"
);
}
});
}
if (quantity > 0) {
self._addToCart(productId, productName, productPrice, quantity);
} else {
var labels = self._getLabels();
self._showNotification(
labels.invalid_quantity || "Please enter a valid quantity",
"warning"
);
}
});
// Button to save cart as draft (in My Cart header)
var savCartBtn = document.getElementById("save-cart-btn");
if (savCartBtn) {
// Remove old listeners by cloning
var savCartBtnNew = savCartBtn.cloneNode(true);
savCartBtn.parentNode.replaceChild(savCartBtnNew, savCartBtn);
savCartBtnNew.addEventListener("click", function (e) {
e.preventDefault();
self._saveCartAsDraft();
});
}
// Only attach ONCE
if (!this._cartCheckoutListenersAttached) {
console.log("[_attachEventListeners] Attempting to attach checkout listeners...");
// Button to reload from draft (in My Cart header)
var reloadCartBtn = document.getElementById("reload-cart-btn");
if (reloadCartBtn) {
// Remove old listeners by cloning
var reloadCartBtnNew = reloadCartBtn.cloneNode(true);
reloadCartBtn.parentNode.replaceChild(reloadCartBtnNew, reloadCartBtn);
reloadCartBtnNew.addEventListener("click", function (e) {
e.preventDefault();
self._loadDraftCart();
});
}
var savCartBtn = document.getElementById("save-cart-btn");
console.log("[_attachEventListeners] save-cart-btn found:", !!savCartBtn);
// Button to save as draft
var saveBtn = document.getElementById("save-order-btn");
if (saveBtn) {
saveBtn.addEventListener("click", function (e) {
e.preventDefault();
self._saveOrderDraft();
});
}
// Confirm order button
var confirmBtn = document.getElementById("confirm-order-btn");
if (confirmBtn) {
confirmBtn.addEventListener("click", function (e) {
e.preventDefault();
self._confirmOrder();
});
if (savCartBtn) {
savCartBtn.addEventListener("click", function (e) {
console.log("[CLICK] save-cart-btn clicked");
e.preventDefault();
self._saveCartAsDraft();
});
}
}
},
@ -1475,7 +1502,7 @@
},
_saveOrderDraft: function () {
console.log("Saving order as draft:", this.orderId);
console.log("[_saveOrderDraft] Starting - this.orderId:", this.orderId);
var self = this;
var items = [];
@ -1979,13 +2006,10 @@
console.log("cart-items-container found:", !!cartContainer);
console.log("confirm-order-btn found:", !!confirmBtn);
if (cartContainer || confirmBtn) {
console.log("Calling init()");
var result = window.groupOrderShop.init();
console.log("init() result:", result);
} else {
console.warn("No elements found to initialize cart");
}
// Always initialize - it handles both cart pages and checkout pages
console.log("Calling init()");
var result = window.groupOrderShop.init();
console.log("init() result:", result);
});
// Handle confirm order buttons in portal (My Orders page)