import $ from "jquery"; import prestashop from "prestashop"; import debounce from "./components/debounce"; prestashop.cart = prestashop.cart || {}; prestashop.cart.active_inputs = null; const spinnerSelector = 'input[name="product-quantity-spin"]'; let hasError = false; let isUpdateOperation = false; let errorMsg = ""; const CheckUpdateQuantityOperations = { switchErrorStat: () => { /** * if errorMsg is not empty or if notifications are shown, we have error to display * if hasError is true, quantity was not updated : we don't disable checkout button */ const $checkoutBtn = $(prestashop.themeSelectors.checkout.btn); if ( $(prestashop.themeSelectors.notifications.dangerAlert).length || (errorMsg !== "" && !hasError) ) { $checkoutBtn.addClass("disabled"); $checkoutBtn.css({ 'background-color': 'grey', 'color': 'white', }); } if (errorMsg !== "") { const strError = ` `; $(prestashop.themeSelectors.notifications.container).html(strError); errorMsg = ""; isUpdateOperation = false; if (hasError) { // if hasError is true, quantity was not updated : allow checkout $checkoutBtn.removeClass("disabled"); } } else if (!hasError && isUpdateOperation) { hasError = false; isUpdateOperation = false; $(prestashop.themeSelectors.notifications.container).html(""); $checkoutBtn.removeClass("disabled"); $checkoutBtn.css({ 'background-color': '#172554', 'color': 'white', }); } }, checkUpdateOperation: (resp) => { /** * resp.hasError can be not defined but resp.errors not empty: quantity is updated but order cannot be placed * when resp.hasError=true, quantity is not updated */ const { hasError: hasErrorOccurred, errors: errorData } = resp; hasError = hasErrorOccurred ?? false; const errors = errorData ?? ""; // 1.7.2.x returns errors as string, 1.7.3.x returns array if (errors instanceof Array) { errorMsg = errors.join(" "); } else { errorMsg = errors; } isUpdateOperation = true; }, }; /** * Attach Bootstrap TouchSpin event handlers */ function createSpin() { $.each($(spinnerSelector), (index, spinner) => { $(spinner).TouchSpin({ min: parseInt($(spinner).attr("min"), 10), max: 1000000, }); }); $(prestashop.themeSelectors.touchspin).off("touchstart.touchspin"); CheckUpdateQuantityOperations.switchErrorStat(); } const preventCustomModalOpen = (event) => { if (window.shouldPreventModal) { event.preventDefault(); return false; } return true; }; $(function () { const productLineInCartSelector = prestashop.themeSelectors.cart.productLineQty; const promises = []; prestashop.on("updatedCart", () => { window.shouldPreventModal = false; $(prestashop.themeSelectors.product.customizationModal).on( "show.bs.modal", (modalEvent) => { preventCustomModalOpen(modalEvent); }, ); createSpin(); }); createSpin(); const $body = $("body"); function isTouchSpin(namespace) { return namespace === "on.startupspin" || namespace === "on.startdownspin"; } function shouldIncreaseProductQuantity(namespace) { return namespace === "on.startupspin"; } function findCartLineProductQuantityInput($target) { const $input = $target .parents(prestashop.themeSelectors.cart.touchspin) .find(productLineInCartSelector); if ($input.is(":focus")) { return null; } return $input; } function camelize(subject) { const actionTypeParts = subject.split("-"); let i; let part; let camelizedSubject = ""; for (i = 0; i < actionTypeParts.length; i += 1) { part = actionTypeParts[i]; if (i !== 0) { part = part.substring(0, 1).toUpperCase() + part.substring(1); } camelizedSubject += part; } return camelizedSubject; } function parseCartAction($target, namespace) { if (!isTouchSpin(namespace)) { return { url: $target.attr("href"), type: camelize($target.data("link-action")), }; } const $input = findCartLineProductQuantityInput($target); if (!$input) { return false; } let cartAction = {}; if (shouldIncreaseProductQuantity(namespace)) { cartAction = { url: $input.data("up-url"), type: "increaseProductQuantity", }; } else { cartAction = { url: $input.data("down-url"), type: "decreaseProductQuantity", }; } return cartAction; } const abortPreviousRequests = () => { let promise; while (promises.length > 0) { promise = promises.pop(); promise.abort(); } }; const getTouchSpinInput = ($button) => $($button.parents(prestashop.themeSelectors.cart.touchspin).find("input")); $(prestashop.themeSelectors.product.customizationModal).on( "show.bs.modal", (modalEvent) => { preventCustomModalOpen(modalEvent); }, ); const handleCartAction = (event) => { abortPreviousRequests(); window.shouldPreventModal = true; event.preventDefault(); const $target = $(event.currentTarget); const { dataset } = event.currentTarget; const cartAction = parseCartAction($target, event.namespace); const requestData = { ajax: "1", action: "update", }; if (!cartAction) { return; } $.ajax({ url: cartAction.url, method: "POST", data: requestData, dataType: "json", beforeSend(jqXHR) { promises.push(jqXHR); }, }) .then((resp) => { const $quantityInput = getTouchSpinInput($target); CheckUpdateQuantityOperations.checkUpdateOperation(resp); $quantityInput.val(resp.quantity); // Refresh cart preview prestashop.emit("updateCart", { reason: dataset, resp, }); }) .fail((resp) => { prestashop.emit("handleError", { eventType: "updateProductInCart", resp, cartAction: cartAction.type, }); }); }; $body.on("click", prestashop.themeSelectors.cart.actions, handleCartAction); function sendUpdateQuantityInCartRequest( updateQuantityInCartUrl, requestData, $target, ) { abortPreviousRequests(); window.shouldPreventModal = true; return $.ajax({ url: updateQuantityInCartUrl, method: "POST", data: requestData, dataType: "json", beforeSend(jqXHR) { promises.push(jqXHR); }, }) .then((resp) => { CheckUpdateQuantityOperations.checkUpdateOperation(resp); $target.val(resp.quantity); const dataset = $target && $target.dataset ? $target.dataset : resp; // Refresh cart preview prestashop.emit("updateCart", { reason: dataset, resp, }); }) .fail((resp) => { prestashop.emit("handleError", { eventType: "updateProductQuantityInCart", resp, }); }); } function getQuantityChangeType($quantity) { return $quantity > 0 ? "up" : "down"; } function getRequestData(quantity) { return { ajax: "1", qty: Math.abs(quantity), action: "update", op: getQuantityChangeType(quantity), }; } function updateProductQuantityInCart(event) { const $target = $(event.currentTarget); const updateQuantityInCartUrl = $target.data("update-url"); const baseValue = $target.attr("value"); // There should be a valid product quantity in cart const targetValue = $target.val(); /* eslint-disable */ if ( targetValue != parseInt(targetValue, 10) || targetValue < 0 || isNaN(targetValue) ) { window.shouldPreventModal = false; $target.val(baseValue); return; } /* eslint-enable */ // There should be a new product quantity in cart const qty = targetValue - baseValue; if (qty === 0) { return; } if (targetValue === "0") { $target .closest(".product-line-actions") .find('[data-link-action="delete-from-cart"]') .click(); } else { $target.attr("value", targetValue); sendUpdateQuantityInCartRequest( updateQuantityInCartUrl, getRequestData(qty), $target, ); } } $body.on( "touchspin.on.stopspin", spinnerSelector, debounce(updateProductQuantityInCart), ); $body.on("focusout keyup", productLineInCartSelector, (event) => { if (event.type === "keyup") { if (event.keyCode === 13) { isUpdateOperation = true; updateProductQuantityInCart(event); } return false; } if (!isUpdateOperation) { updateProductQuantityInCart(event); } return false; }); const $timeoutEffect = 400; $body.on( "hidden.bs.collapse", prestashop.themeSelectors.cart.promoCode, () => { $(prestashop.themeSelectors.cart.displayPromo).show($timeoutEffect); }, ); $body.on("click", prestashop.themeSelectors.cart.promoCodeButton, (event) => { event.preventDefault(); $(prestashop.themeSelectors.cart.promoCode).collapse("toggle"); }); $body.on("click", prestashop.themeSelectors.cart.displayPromo, (event) => { $(event.currentTarget).hide($timeoutEffect); }); $body.on("click", prestashop.themeSelectors.cart.discountCode, (event) => { event.stopPropagation(); const $code = $(event.currentTarget); const $discountInput = $(prestashop.themeSelectors.cart.discountName); $discountInput.val($code.text()); // Show promo code field $(prestashop.themeSelectors.cart.promoCode).collapse("show"); $(prestashop.themeSelectors.cart.displayPromo).hide($timeoutEffect); return false; }); }); $(document).ready(() => { const e = $("body"); e.off("submit", '[data-link-action="add-voucher"]'), e.on("submit", '[data-link-action="add-voucher"]', (e) => { const promoSubmitBtn = $("#promo-code-form button"); e.preventDefault(); const n = $(e.currentTarget), o = n.attr("action"); 0 === n.find("[name=action]").length && n.append($("", { type: "hidden", name: "ajax", value: 1 })), 0 === n.find("[name=action]").length && n.append( $("", { type: "hidden", name: "action", value: "update" }), ), promoSubmitBtn.text("CHECKING"); promoSubmitBtn.prop("disable", true); $.post(o, n.serialize(), null, "json") .then((n) => { n.hasError ? $(".js-error").show().find(".js-error-text").text(n.errors[0]) : prestashop.emit("updateCart", { reason: e.target.dataset, resp: n, }); promoSubmitBtn.text("ADD"); promoSubmitBtn.prop("disable", false); }) .fail((e) => { prestashop.emit("handleError", { eventType: "updateCart", resp: e, }); promoSubmitBtn.text("ADD"); promoSubmitBtn.prop("disable", false); }); }); });