[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

@ -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)