Full Group™ Posted May 15 Share Posted May 15 (edited) Hola, pues tengo el carrito de prestashop, lo estuve modificando un poco, pero necesito que tenga una respuesta inmediata. Suma, resta, muestra cantidad, elimina producto; pero eso, lo hace con un pequeño delay cuando uno apreta varias veces el botón de "+" o "-". ¿Alguna idea? Ya minimizé gran parte del código .js, .css, y .tpl PRESTASHOP 8.2.0 Html: <button type="button" class="close-cart-btn" onclick="window.cartClosedManually=true;document.querySelectorAll('.tvcart-product-list').forEach(el=>el.style.display='none');" style="font-size:xx-large;position:absolute;width:30px;height:30px;top:-35px;right:auto;align-items:center;align-content:center;align-self:center;cursor:pointer;display:flex;z-index:9999;justify-content:center;" aria-label="Cerrar carrito" title="Cerrar">×</button> <a href="{$product.url}"><div class="tvcart-product-list-img"><img class='tvimage-lazy' src="{$product.cover.bySize.cart_default.url}"></div><div class="tvcart-product-content"><div class="tvcart-product-list-quentity"><div class="tvcart-dropdown-title"><span class="product-quentity">{$product.quantity}</span><span class="tvshopping-cart-quentity"> x </span><span class="product-name">{$product.name}</span></div><div class="tvcart-product-remove">{$url='controller=cart&delete='|cat:$product.id_product}<a class="remove-from-cart tvcmsremove-from-cart" rel="nofollow" href="{$product.remove_from_cart_url}" data-link-action="delete-from-cart" data-id-product="{$product.id_product|escape:'javascript'}" data-id-product-attribute="{$product.id_product_attribute|escape:'javascript'}" data-id-customization="{$product.id_customization|escape:'javascript'}" title="{l s='remove from cart' d='Shop.Theme.Actions'}"><i class="material-icons"></i></a></div></div><div class="tvcart-product-list-price"><span class="product-price" data-unit-price="{$product.unit_price|floatval}" data-price-format="{$product.price}">{$product.total}</span><span class="product-price" style="color:#fff;font-size:xx-small;"> {$product.unit_price_full}</span>{if isset($product.unit_price_full)&&!empty($product.unit_price_full)}{if isset($cart)&&isset($cart.products)&&count($cart.products)>0}{assign var="unit_text" value=$product.unit_price_full|regex_replace:'/^[^ ]* /':''}{else}<span class="product-price" style="color:#fff;font-size:xx-small;">{$product.unit_price_full}</span>{/if}{else}{hook h='displayProductPriceBlock' product=$product type="before_price"}<span class="product-price" style="color:#fff;font-size:xx-small;">PRECIO DESDE UNA UNID</span>{/if}{hook h='displayProductPriceBlock' product=$product type="unit_price"}{foreach from=$product.attributes item="property_value" key="property"}<span style="display:none;"><strong>{$property}</strong>: {$property_value}</span><br>{/foreach}<p style="color:#fff;font-weight:700;">{l s='Quantity:' d='Shop.Theme.Checkout'} {$product.cart_quantity}<br></p>{if isset($product.is_gift)&&$product.is_gift}<span class="gift-quantity product-cart-qty-{$product.id_product}-{$product.id_product_attribute}" style="display:none!important;">{$product.quantity}</span>{else}<div class="cart-line" data-id-product="{$product.id_product}" data-id-product-attribute="{$product.id_product_attribute}"><div class="cart-line" data-id-product="{$product.id_product}" data-id-product-attribute="{$product.id_product_attribute}" data-id-customization="{$product.id_customization}"><div class="quantity-wrapper"><button class="qty-btn down" aria-label="Restar cantidad">−</button><input class="js-cart-line-product-quantity" type="text" value="{$product.quantity}" min="{$product.minimal_quantity}" readonly data-product-id="{$product.id_product}" data-product-attribute-id="{$product.id_product_attribute}" data-update-url="{$product.update_quantity_url}" data-up-url="{$product.up_quantity_url}" data-down-url="{$product.down_quantity_url}"><button class="qty-btn up" aria-label="Sumar cantidad">+</button></div></div></div>{/if}<a class="remove-from-cart" rel="nofollow" href="{$product.remove_from_cart_url}" data-link-action="delete-from-cart" data-id-product="{$product.id_product|escape:'javascript'}" data-id-product-attribute="{$product.id_product_attribute|escape:'javascript'}" data-id-customization="{$product.id_customization|escape:'javascript'}"></a>{block name='hook_cart_extra_product_actions'}{hook h='displayCartExtraProductActions' product=$product}{/block}<div class="tvcart-product-list-attribute">{foreach $product.attributes as $prod_attb=>$prod_val}<div class="tvcart-product-attr"><span>{$prod_attb}:</span> {$prod_val}</div>{/foreach}</div>{if $product.customizations|count}<div class="customizations"><ul>{foreach from=$product.customizations item='customization'}<li><span class="product-quantity">{$customization.quantity}</span><a href="{$customization.remove_from_cart_url}" title="{l s='remove from cart' d='Shop.Theme.Actions'}" class="remove-from-cart" rel="nofollow">{l s='Remove' d='Shop.Theme.Actions'}</a><ul>{foreach from=$customization.fields item='field'}<li><span>{$field.label}</span>{if $field.type=='text'}<span>{$field.text nofilter}</span>{else if $field.type=='image'}<img class='tvimage-lazy' src="{$field.image.small.url}">{/if}</li>{/foreach}</ul></li>{/foreach}</ul></div>{/if}</div></a> Style: span.input-group-btn-vertical { width: 12% !important; display: flex !important; flex-direction: column; } input.js-cart-line-product-quantity.form-control { width: 50px !important; } input.js-cart-line-product-quantity { width: 50px !important; height: 30px !important; margin: -5px; } .quantity-wrapper { display: flex; align-items: center; gap: 5px; } .qty-btn { width: 30px; height: 30px; font-size: 18px; cursor: pointer; } @keyframes bounceQty { 0% { transform: scale(1); } 30% { transform: scale(1.2); } 60% { transform: scale(0.95); } 100% { transform: scale(1); } } .qty-animated { animation: bounceQty 0.4s ease; } Script: document.addEventListener("DOMContentLoaded", () => { console.log("🛒 Script de cantidad actualizado y funcionando"); function animateQty(input) { input.classList.add("qty-animated"); setTimeout(() => input.classList.remove("qty-animated"), 400); } // 🔒 Previene que se agregue el event listener más de una vez if (!window.qtyBtnHandlerAdded) { document.body.addEventListener("click", (e) => { const btn = e.target.closest(".qty-btn"); if (!btn) return; const wrapper = btn.closest(".quantity-wrapper"); const input = wrapper.querySelector(".js-cart-line-product-quantity"); if (!input) return; const min = parseInt(input.getAttribute("min")) || 1; const downUrl = input.dataset.downUrl; const upUrl = input.dataset.upUrl; let currentQty = parseInt(input.value) || min; let newQty = currentQty; let targetUrl = ""; if (btn.classList.contains("up")) { newQty = currentQty + 1; targetUrl = upUrl; } else if (btn.classList.contains("down")) { newQty = currentQty - 1; if (newQty < 1) { // Buscar y usar la URL de eliminar producto const removeBtn = wrapper.closest('.tvcart-product-content')?.querySelector('.remove-from-cart'); if (removeBtn && removeBtn.href) { fetch(removeBtn.href, { method: "POST", credentials: "same-origin" }) .then(res => res.json()) .then(data => { prestashop.emit("updateCart", data); reloadCustomCart(); }) .catch(err => console.error("❌ Error al eliminar producto:", err)); } return; } targetUrl = downUrl; } input.value = newQty; animateQty(input); // Actualiza el total del producto (si existe) const totalPriceEl = wrapper.closest('.tvcart-product-content')?.querySelector('.product-price[data-unit-price]'); if (totalPriceEl) { const unitPrice = parseFloat(totalPriceEl.dataset.unitPrice); const priceFormat = totalPriceEl.dataset.priceFormat; const total = (unitPrice * newQty).toFixed(0); const formattedTotal = priceFormat.replace('%s', total.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.')); totalPriceEl.textContent = formattedTotal; } fetch(targetUrl, { method: "POST", credentials: "same-origin" }) .then(res => { const contentType = res.headers.get("Content-Type"); if (contentType && contentType.includes("application/json")) { return res.json(); } else { return res.text(); } }) .then(data => { if (data && typeof data === "object" && data.cart) { prestashop.emit("updateCart", data); } else { console.log("ℹ️ Cambio aplicado sin emitir updateCart (respuesta no JSON)"); } reloadCustomCart(); }) .catch(err => console.error("❌ Error al actualizar cantidad:", err)); }); window.qtyBtnHandlerAdded = true; // Marcamos que ya fue agregado } }); // 🔁 Recarga y actualiza el contenido del carrito function reloadCustomCart() { const cartWrapper = document.querySelector('.tvcart-product-list'); if (cartWrapper) { cartWrapper.style.display = 'block'; } fetch(window.location.href, { method: "GET", headers: { "X-Requested-With": "XMLHttpRequest" } }) .then(res => res.text()) .then(html => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 🔁 Reemplazo del listado de productos del carrito const newCartList = doc.querySelector('.tvcart-product-list'); const currentCartList = document.querySelector('.tvcart-product-list'); if (newCartList && currentCartList) { currentCartList.innerHTML = newCartList.innerHTML; } // 🔁 Reemplazo del resumen del carrito const newSummary = doc.querySelector('.tvcart-summary'); const currentSummary = document.querySelector('.tvcart-summary'); if (newSummary && currentSummary) { currentSummary.innerHTML = newSummary.innerHTML; } // 🔁 Reemplazo del minicart const newMiniCart = doc.querySelector('.blockcart'); const currentMiniCart = document.querySelector('.blockcart'); if (newMiniCart && currentMiniCart) { currentMiniCart.innerHTML = newMiniCart.innerHTML; } // 🔁 Reemplazo de la cantidad de productos en el checkout const newSummaryProducts = doc.querySelector('.cart-summary-products'); const currentSummaryProducts = document.querySelector('.cart-summary-products'); if (newSummaryProducts && currentSummaryProducts) { currentSummaryProducts.innerHTML = newSummaryProducts.innerHTML; } console.log("✅ Carrito, totales, minicart y resumen de checkout actualizados visualmente."); }) .catch(err => console.error("❌ Error al recargar el carrito:", err)); } // 🔔 Siempre mantener el carrito visible al recibir updateCart prestashop.on('updateCart', () => { const cartList = document.querySelector('.tvcart-product-list'); if (cartList && !window.cartClosedManually) { cartList.style.display = 'block'; } }); document.addEventListener('DOMContentLoaded', () => { const toggleBtn = document.querySelector('.tvshopping-cart-price'); if (toggleBtn) { toggleBtn.addEventListener('click', () => { const cartList = document.querySelector('.tvcart-product-list'); if (cartList) { cartList.style.display = 'block'; window.cartClosedManually = false; } }); } }); // Bandera global para saber si el usuario cerró el carrito window.cartClosedManually = false; // Función para abrir el carrito function openCart() { const cartList = document.querySelector('.tvcart-product-list'); if (cartList) { cartList.style.display = 'block'; } window.cartClosedManually = false; } // Función para cerrar el carrito manualmente (con la X) function closeCart() { const cartList = document.querySelector('.tvcart-product-list'); if (cartList) { cartList.style.display = 'none'; } window.cartClosedManually = true; } // Reaplicar comportamiento luego de que el carrito se actualice con AJAX prestashop.on('updateCart', () => { setTimeout(() => { const cartList = document.querySelector('.tvcart-product-list'); const closeButton = document.querySelector('.close-cart-btn'); // Solo mostrar el carrito si el usuario no lo cerró manualmente if (cartList && !window.cartClosedManually) { cartList.style.display = 'block'; } // Reasignar el listener al botón de cerrar if (closeButton) { closeButton.removeEventListener('click', closeCart); // Evitar duplicados closeButton.addEventListener('click', closeCart); } }); // Espera para asegurar que el DOM esté actualizado }); // Evento al cargar la página document.addEventListener('DOMContentLoaded', () => { // Mostrar el carrito al hacer clic en el total del carrito const toggleBtn = document.querySelector('.tvshopping-cart-price'); if (toggleBtn) { toggleBtn.addEventListener('click', () => { openCart(); }); } }); // 🔄 Forzar visibilidad del carrito mientras se detectan cambios (function forceCartVisibleDuringUpdates() { const cartList = document.querySelector('.tvcart-product-list'); if (!cartList) return; // Marcar como "actualizando carrito" const setCartUpdating = (updating) => { cartList.setAttribute('data-updating', updating ? 'true' : 'false'); if (updating && !window.cartClosedManually) { cartList.style.display = 'block'; } }; // Hook de PrestaShop para detectar eventos relacionados al carrito prestashop.on('updateCart', () => { setCartUpdating(true); // Esperamos un momento para "desmarcar" la actualización setTimeout(() => { setCartUpdating(false); if (!window.cartClosedManually) { cartList.style.display = 'block'; // Nos aseguramos de que siga visible } }); // puedes ajustar este delay según rendimiento del sitio }); // Observa si hay cambios visibles en el carrito const observer = new MutationObserver(() => { if (!window.cartClosedManually) { cartList.style.display = 'block'; } }); observer.observe(cartList, { childList: true, subtree: true }); })(); SCRIPT DEL HEADER: //CANTIDADES function updateCartQuantity(productId, productAttributeId, newQty) { const action = newQty === 0 ? 'delete' : 'update'; const requestData = { ajax: true, action: action, id_product: productId, id_product_attribute: productAttributeId, }; if (action === 'update') { requestData.qty = newQty; } $.post(prestashop.urls.pages.cart, requestData) .done(function (resp) { prestashop.emit('updateCart', { reason: { idProduct: productId, idProductAttribute: productAttributeId, quantity: newQty }, resp: resp }); }); } function bindTouchSpinEvents() { $('.js-cart-line-product-quantity').off('change').on('change', function () { const $input = $(this); const newQty = parseInt($input.val()); const $line = $input.closest('[data-id-product]'); const idProduct = $line.data('id-product'); const idProductAttribute = $line.data('id-product-attribute') || 0; if (!isNaN(newQty)) { updateCartQuantity(idProduct, idProductAttribute, newQty); } }); } document.addEventListener('DOMContentLoaded', function () { bindTouchSpinEvents(); prestashop.on('updateCart', function () { bindTouchSpinEvents(); }); }); //ELIMINAR PRODUCTO document.addEventListener('DOMContentLoaded', function () { document.body.addEventListener('click', function (e) { const removeBtn = e.target.closest('.remove-from-cart'); if (removeBtn && removeBtn.dataset.linkAction === 'delete-from-cart') { e.preventDefault(); // Evita la recarga const url = removeBtn.getAttribute('href'); $.post(url, { ajax: true }) .done(function (resp) { prestashop.emit('updateCart', { reason: { linkAction: 'delete-from-cart' }, resp: resp }); }) .fail(function (err) { console.error('Error al eliminar del carrito:', err); }); } }); }); //MOSTRAR BLOQUE CARRITO DE COMPRAS AL DARLE CLIC AL CONTADOR DE ITEMS document.addEventListener('DOMContentLoaded', function () { prestashop.on('updateCart', function () { const blockCart = document.querySelector('.blockcart'); if (blockCart) { blockCart.classList.add('show-cart-panel'); } }); // Evita cerrar el carrito si se hace clic en un botón de cantidad o dentro del bloque del carrito const clickedQtyBtn = event.target.closest('.qty-btn, .quantity-wrapper, .js-cart-line-product-quantity'); if ( blockCart && !blockCart.contains(event.target) && !clickedQtyBtn && // 👈 Añadido para ignorar clicks sobre botones o inputs de cantidad blockCart.classList.contains('show-cart-panel') ) { blockCart.classList.remove('show-cart-panel'); }; }); //ALTERNAR MOSTRAR-OCULTAR BLOQUE DE CARRITO DE COMPRAS AL DARLE CLIC AL CONTADOR DE ITEMS document.addEventListener('DOMContentLoaded', function () { document.body.addEventListener('click', function (e) { const cartTrigger = e.target.closest('.tvshopping-cart-containt-box, .tvshopping-cart-inner, .tvshopping-cart-price'); if (cartTrigger) { const cartList = document.querySelector('.tvcart-product-list'); if (cartList) { const currentDisplay = window.getComputedStyle(cartList).display; cartList.style.display = (currentDisplay === 'block') ? 'block' : 'block'; } } }); }); //ELIMINA UN PRODUCTO DEL CARRITO SI ÉSTE BAJA DE CANTIDAD 1 document.addEventListener('DOMContentLoaded', function () { // 1. Si el usuario baja desde 1 con el botón de restar (↓) document.body.addEventListener('click', function (e) { const downBtn = e.target.closest('.bootstrap-touchspin-down, .touchspin-down'); if (!downBtn) return; const input = downBtn.closest('.cart-line')?.querySelector('.js-cart-line-product-quantity'); if (!input) return; const currentQty = parseInt(input.value); const updateUrl = input.dataset.updateUrl; if (currentQty === 1 && updateUrl) { e.preventDefault(); $.post(updateUrl, { ajax: true, qty: 0 }) .done(function (resp) { prestashop.emit('updateCart', { reason: { idProduct: input.dataset.productId, linkAction: 'delete-from-cart', quantity: 0 }, resp: resp }); }) .fail(function (err) { console.error('Error al eliminar con botón ↓:', err); }); } }); // 2. Si el usuario escribe 0 manualmente document.body.addEventListener('change', function (e) { const input = e.target.closest('.js-cart-line-product-quantity'); if (!input) return; const newQty = parseInt(input.value); const updateUrl = input.dataset.updateUrl; if (newQty <= 0 && updateUrl) { $.post(updateUrl, { ajax: true, qty: 0 }) .done(function (resp) { prestashop.emit('updateCart', { reason: { idProduct: input.dataset.productId, linkAction: 'delete-from-cart', quantity: 0 }, resp: resp }); }) .fail(function (err) { console.error('Error al eliminar al escribir 0:', err); }); } }); }); //OCULTA LA OPCIÓN "CREAR CUENTA" SI EL USUARIO ESTÁ LOGUEADO document.addEventListener('DOMContentLoaded', function () { if (document.body.classList.contains('page-customer-logged-in')) { const registerLink = document.querySelector('.register-link'); if (registerLink) { registerLink.style.display = 'none'; } } }); document.addEventListener('DOMContentLoaded', function () { var isLogged = document.body.classList.contains('page-customer-logged-in'); if (isLogged) { var registerLink = document.querySelector('li.register-link'); if (registerLink) { registerLink.style.display = 'none'; } } }); document.addEventListener('DOMContentLoaded', function () { var isLoggedIn = document.querySelector('.user-info a[href*="my-account"]'); if (isLoggedIn) { var regLink = document.querySelector('.register-link'); if (regLink) regLink.style.display = 'none'; } }); (function () { const CART_SELECTOR = '.tvcart-product-list'; const TOGGLE_BTN_SEL = '.tvshopping-cart-containt-box, .tvshopping-cart-inner, .tvshopping-cart-price'; const CLOSE_BTN_SEL = '.close-cart-btn'; const STORAGE_KEY = 'alishop_cart_closed'; // Devuelve true si estaba marcado como cerrado function isClosed() { return localStorage.getItem(STORAGE_KEY) === 'true'; } function applyState() { const cart = document.querySelector(CART_SELECTOR); if (!cart) { console.warn('Cart selector no encontrado:', CART_SELECTOR); return; } if (isClosed()) { console.log('[Cart] Estado: CERRADO (hide)'); cart.style.display = 'none'; } else { console.log('[Cart] Estado: ABIERTO (show)'); cart.style.display = 'block'; } } function openCart() { console.log('[Cart] openCart()'); localStorage.setItem(STORAGE_KEY, 'false'); applyState(); } function closeCart() { console.log('[Cart] closeCart()'); localStorage.setItem(STORAGE_KEY, 'true'); applyState(); } // Inicializamos al cargar la página document.addEventListener('DOMContentLoaded', function () { console.log('[Cart] DOMContentLoaded → aplicando estado guardado'); applyState(); // Toggle al clic en el ícono de items document.body.addEventListener('click', function (e) { if (e.target.closest(TOGGLE_BTN_SEL)) { e.preventDefault(); console.log('[Cart] Click en TOGGLE_BTN_SEL'); openCart(); } }); // Cerrar al clic en la X document.body.addEventListener('click', function (e) { if (e.target.closest(CLOSE_BTN_SEL)) { e.preventDefault(); console.log('[Cart] Click en CLOSE_BTN_SEL'); closeCart(); } }); // Cada vez que PrestaShop emita updateCart tras AJAX if (window.prestashop) { prestashop.on('updateCart', function () { console.log('[Cart] evento updateCart recibido'); applyState(); }); } else { console.warn('PrestaShop JS no encontrado'); } }); })(); function forceReflow() { document.body.style.display = 'none'; // lectura forzada: void document.body.offsetHeight; document.body.style.display = ''; } window.addEventListener('load', function () { document.querySelector('ul#top-menu').style.display = 'flex'; }); document.addEventListener('DOMContentLoaded', function () { const currentUrl = window.location.href.split('#')[0].split('?')[0]; document.querySelectorAll('a.dropdown-item.d-flex').forEach(function (link) { const linkUrl = link.href.split('#')[0].split('?')[0]; if (linkUrl === currentUrl) { link.classList.add('active'); } }); }); Edited May 15 by Full Group™ (see edit history) Link to comment Share on other sites More sharing options...
ExpertoPrestaShop Posted May 21 Share Posted May 21 Cuando modificas la cantidad de los productos en PS hay una petición ajax por detrás que tiene que verificar, entre otras muchas cosas, si puedes comprar la cantidad de producto que estas seleccionando, si algún cupón de descuento aplica al carrito. Por supuesto que habrá siempre un retardo causado por esperar la respuesta del servidor y luego actualizar mediante JS la interfaz. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now