From 9c14e1dc1a28b347d893d5a394a5c38cedebe503 Mon Sep 17 00:00:00 2001 From: snt Date: Thu, 5 Mar 2026 18:57:10 +0100 Subject: [PATCH] [FIX] website_sale_aplicoop: ensure add-to-cart on infinite scroll --- .../static/src/js/website_sale.js | 133 ++++++++++-------- 1 file changed, 73 insertions(+), 60 deletions(-) diff --git a/website_sale_aplicoop/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js index b88bbee..3f76c3c 100644 --- a/website_sale_aplicoop/static/src/js/website_sale.js +++ b/website_sale_aplicoop/static/src/js/website_sale.js @@ -694,6 +694,70 @@ return; } + // Shared handler for add-to-cart to reuse across grid/document listeners + var handleAddToCart = function (e) { + var cartBtn = e.target.closest(".add-to-cart-btn"); + if (!cartBtn) return; + + e.preventDefault(); + var form = cartBtn.closest(".add-to-cart-form"); + if (!form) return; + + 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; + + // Block add-to-cart if product is flagged out of stock (from template) + var isOutOfStock = + (form.getAttribute("data-out-of-stock") || "false") === "true" || + (cartBtn.getAttribute("data-out-of-stock") || "false") === "true"; + + // Fallback guards in case cached markup drops the data attribute + if (!isOutOfStock) { + var btnTitle = (cartBtn.getAttribute("title") || "").toLowerCase(); + var btnAria = (cartBtn.getAttribute("aria-label") || "").toLowerCase(); + var iconEl = cartBtn.querySelector("i"); + var hasBanIcon = iconEl && iconEl.classList.contains("fa-ban"); + + if ( + hasBanIcon || + btnTitle.includes("out of stock") || + btnTitle.includes("sin stock") || + btnAria.includes("out of stock") || + btnAria.includes("sin stock") + ) { + isOutOfStock = true; + } + } + if (isOutOfStock) { + var labels = self._getLabels(); + self._showNotification( + labels.out_of_stock || "Product is out of stock", + "warning" + ); + return; + } + + console.log("Adding:", { + productId: productId, + productName: productName, + productPrice: productPrice, + quantity: quantity, + }); + + if (quantity > 0) { + self._addToCart(productId, productName, productPrice, quantity); + } else { + var labels2 = self._getLabels(); + self._showNotification( + labels2.invalid_quantity || "Please enter a valid quantity", + "warning" + ); + } + }; + // First, adjust quantity steps for all existing inputs var unitInputs = document.querySelectorAll(".product-qty"); console.log("=== ADJUSTING QUANTITY STEPS (from data-quantity-step) ==="); @@ -779,67 +843,16 @@ } }); - // Add to cart button (via event delegation) - productsGrid.addEventListener("click", function (e) { - var cartBtn = e.target.closest(".add-to-cart-btn"); - if (!cartBtn) return; + // Add to cart button (via event delegation on grid) + productsGrid.addEventListener("click", handleAddToCart); - 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; - - // Block add-to-cart if product is flagged out of stock (from template) - var isOutOfStock = - (form.getAttribute("data-out-of-stock") || "false") === "true" || - (cartBtn.getAttribute("data-out-of-stock") || "false") === "true"; - - // Fallback guards in case cached markup drops the data attribute - if (!isOutOfStock) { - var btnTitle = (cartBtn.getAttribute("title") || "").toLowerCase(); - var btnAria = (cartBtn.getAttribute("aria-label") || "").toLowerCase(); - var iconEl = cartBtn.querySelector("i"); - var hasBanIcon = iconEl && iconEl.classList.contains("fa-ban"); - - if ( - hasBanIcon || - btnTitle.includes("out of stock") || - btnTitle.includes("sin stock") || - btnAria.includes("out of stock") || - btnAria.includes("sin stock") - ) { - isOutOfStock = true; - } - } - if (isOutOfStock) { - var labels = self._getLabels(); - self._showNotification( - labels.out_of_stock || "Product is out of stock", - "warning" - ); - return; - } - - 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" - ); - } - }); + // Also attach a document-level delegation as fallback for dynamically + // inserted products (infinite scroll) in case the grid listener is lost + // after DOM replacement. + if (!this._docCartListenerAttached) { + document.addEventListener("click", handleAddToCart, true); + this._docCartListenerAttached = true; + } }, _addToCart: function (productId, productName, productPrice, quantity) {