/** * NOTICE OF LICENSE * * This source file is subject to the Software License Agreement * that is bundled with this package in the file LICENSE.txt. * * @author Peter Sliacky (Zelarg) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ // debug_js_controller property is set in front.tpl // checkoutPaymentParser could be set from other (payment) modules, so let's do not reset it here if ('undefined' === typeof checkoutPaymentParser) { var checkoutPaymentParser = {}; } var checkoutShippingParser = {}; var tcIsMobileView = false; // we will allways start with false, our markup from server is desktop optimized var tcMobileViewThreshold = 991; var tc_confirmOrderValidations = {}; var tc_updatePaymentWithShipping = true; // markup added to .inner-area of checkout blocks on updateHtml, while waiting for ajax response var tc_loaderHtml = '\
\
\
\
\
\
\
'; // 'dirty' flag to mark checkout form prepared to be payment-confirmed, without submitting data again. var paymentConfirmationPrepared = false; var paymentLoaderMaxTime = 3000; $(document).ready(function () { if (debug_js_controller) { console.info('front.js loaded!'); } $('body').addClass('document-ready'); initBlocksSelectors(); getShippingAndPaymentBlocks(); //getCartSummary(); setAddressFieldsCountryCSS(); // Remove default checkout handlers (e.g. country change) $('body').off('change', '.js-country'); // Set handlers // Check if email was registered $('body').on('change', '#thecheckout-account [name=email]', function (e) { checkEmail($(this)); }); $('body').on('change', '.checkout-block input.-error, .checkout-block select.-error', function () { $(this).removeClass('-error').addClass('-former-error'); checkAndHideGlobalError(); }); $('body').on('change', '#js-delivery input', function () { // if this is called by programatic 'trigger' to satisfy Mondial and similar shipping method, // do not call ajax request (selectDeliveryOption), only let Mondial's JS to pass // let's see where are we called from - see the potentially generated Error's stack trace: var errObj = new Error(); if ('undefined' !== typeof errObj.stack && errObj.stack.match(/at updateShippingBlock/)) { if (debug_js_controller) { console.info('#js-delivery input, change event called from updateShippingBlock() => SKIPPING'); } } else { selectDeliveryOption($('#js-delivery')); // delivery form object as parameter } }); $('body').on('change', '.js-gift-checkbox', function () { toggleGiftMessage(); }); $('body').on('blur', '#gift_message', function () { selectDeliveryOption($('#js-delivery')); }); $('body').on('blur', '#delivery_message', function () { setDeliveryMessage(); }); $('body').on('click', '[data-link-action=x-delete-from-cart]', function () { deleteFromCart($(this).data()); return false; }); $('body').on('click', '[data-link-action=x-sign-in]', function () { signIn(); return false; }); $('body').on('click', '[data-link-action=x-forced-email-continue]', function () { var enteredEmail = $('[name=forced-email]').val(); if (!tc_helper_validateEmail(enteredEmail)) { $('.error-enter-email').show(); } else { // refreshing of shipping/payment blocks is done in emailCheck, if dummycontainers still exist //$('#thecheckout-account [name=email]').val(enteredEmail).trigger('change'); checkEmail($('[name=forced-email]'), function (jsonData) { if (jsonData.hasErrors) { if ('undefined' !== jsonData.errors && 'undefined' !== jsonData.errors['email']) { jsonData.errors['forced-email'] = jsonData.errors['email']; } blockSel = '.overlay-email'; printContextErrors(blockSel, jsonData.errors, undefined, true); } else { $('body').removeClass('force-email-overlay'); $('#thecheckout-account [name=email]').val(enteredEmail).trigger('change'); } }); } return false; }); // Trigger above defined routine also on Enter keypress $('body').on('keyup', '[name=forced-email]', function (event) { $('.error-enter-email').hide(); if (event.key !== "Enter") return; // Use `.key` instead. $('[data-link-action=x-forced-email-continue]').trigger('click'); event.preventDefault(); }); $('body').on('click', '[data-link-action=x-confirm-order]', function () { confirmOrder($(this)); return false; }); $('body').on('click', '[data-link-action=x-save-account-overlay]', function () { confirmOrder($(this)); return false; }); $('body').on('click', '[data-link-action=x-add-voucher]', function () { addVoucher(); return false; }); $('body').on('click', '[data-link-action=x-remove-voucher]', function () { removeVoucher($(this).data()); return false; }); $('body').on('change', '[data-link-action=x-create-account]', function () { if ($(this).prop('checked')) { $('#thecheckout-account .form-group.password').slideDown('fast', function () { $(this).removeClass('hidden') }); } else { $('#thecheckout-account .form-group.password').slideUp('fast'); } return false; }); $('body').on('change', '[data-link-action=x-ship-to-different-address]', function () { if ($('#thecheckout-address-delivery').is(':visible')) { $(this).prop('checked', false); $('#thecheckout-address-delivery').hide(10, function () { //modifyAccountAndAddress($('#thecheckout-address-invoice [name=id_country]')); modifyAddressSelection('delivery'); }); } else { $(this).prop('checked', true); $('#thecheckout-address-delivery').show(10, function () { //modifyAccountAndAddress($('#thecheckout-address-delivery [name=id_country]')); modifyAddressSelection('delivery'); }); } return false; }); $('body').on('change', '[data-link-action=x-bill-to-different-address]', function () { if ($('#thecheckout-address-invoice').is(':visible')) { $(this).prop('checked', false); $('#thecheckout-address-invoice').hide(10, function () { modifyAddressSelection('invoice'); //modifyAccountAndAddress($('#thecheckout-address-delivery [name=id_country]')); }); } else { $(this).prop('checked', true); $('#thecheckout-address-invoice').show(10, function () { modifyAddressSelection('invoice'); //modifyAccountAndAddress($('#thecheckout-address-invoice [name=id_country]')); }); } return false; }); $('body').on('change', '[data-link-action=x-invoice-addresses]', function () { modifyAddressSelection('invoice'); return false; }); $('body').on('change', '[data-link-action=x-delivery-addresses]', function () { modifyAddressSelection('delivery'); return false; }); $('body').on('change', '[data-link-action=x-i-am-business]', function () { var businessFieldsSelector = '#thecheckout-address-invoice .form-group.business-field'; var businessDisabledFieldsSelector = '#thecheckout-address-invoice .form-group.business-disabled-field'; if ($(this).prop('checked')) { $(businessFieldsSelector).not('.hidden').show(); $('.business-fields-separator').css('display', 'block'); $(businessDisabledFieldsSelector).hide(); } else { $(businessFieldsSelector + ', .business-fields-separator').not('.need-dni').hide(); $(businessDisabledFieldsSelector).not('.hidden').show(); } if ($(businessFieldsSelector + ' .live').length) { modifyAccountAndAddress($(businessFieldsSelector + ' .live').first()); } if ($('#dni-placeholder').length && $('.business-field.dni').length) { swapElements($('#dni-placeholder'), $('.business-field.dni')); } return false; }); $('body').on('click', '[data-link-action=toggle-password-visibility]', function () { var input = $(this).closest('label').find('input'); if (input.attr("type") == "password") { input.attr("type", "text"); } else { input.attr("type", "password"); } return false; }); $('body').on('click', '[data-link-action=x-add-new-address]', function () { $(this).parent('.customer-addresses').find('.addresses-selection') .removeClass('hidden') .find('select').val(-1).trigger('change'); $(this).hide(); return false; }); var quantityInputFieldTimeout; $('body').on('input', '[data-link-action=x-update-cart-quantity]', function () { if (quantityInputFieldTimeout) { clearTimeout(quantityInputFieldTimeout); } var el = $(this); var timeout = (1 == $(this).data('no-wait')) ? 0 : 500; quantityInputFieldTimeout = setTimeout(function () { updateQuantityFromInput(el); }, timeout); return false; }); $('body').on('click', '[data-link-action=x-update-cart-quantity-up]', function () { var inputEl = $(this).parent().find('[data-link-action=x-update-cart-quantity]'); inputEl.val(parseInt(inputEl.val()) + 1).data('no-wait', 1).trigger('input'); return false; }); $('body').on('click', '[data-link-action=x-update-cart-quantity-down]', function () { var inputEl = $(this).parent().find('[data-link-action=x-update-cart-quantity]'); if (parseInt(inputEl.attr('min')) < parseInt(inputEl.val())) inputEl.val(parseInt(inputEl.val()) - 1).data('no-wait', 1).trigger('input'); return false; }); // Remove errors from checkboxes on their modification $('body').on('change', '.form-group.checkbox input[type=checkbox], [data-link-action=x-create-account]', function () { $(this).closest('.form-group').find('.field.error-msg').remove(); checkAndHideGlobalError(); modifyCheckboxOption($(this)); }); $('body').on('change', 'input[id^=conditions_to_approve]', function () { $(this).closest('.terms-and-conditions').find('.error-msg').hide(); checkAndHideGlobalError(); modifyCheckboxOption($(this)); }); $('body').on('change', 'input[name=id_gender]', function () { $(this).closest('.form-group').find('.field.error-msg').remove(); checkAndHideGlobalError(); modifyRadioOption($(this)); }); $('body').on('click', '[data-link-action=x-offer-login]', function (event) { return openLoginForm(); }); $('body').on('click', '.error-msg #sign-in-link', function (event) { event.preventDefault(); return openLoginForm(); }); // On *any* modification, hide binary payment and let user save again $('body').on('change', 'input', function () { payment.hideBinary(); setConfirmationDirty(); }); // triggering 'change' events earlier then on focusOut var tc_fieldChangeObserverTimeout = {}; var tc_inputTriggerChangeTimeoutMillis = 1500; $('body').on('input', '.checkout-block .text input', function () { $self = $(this); clearTimeout(tc_fieldChangeObserverTimeout[$self.attr('name')]); tc_fieldChangeObserverTimeout[$self.attr('name')] = setTimeout(function () { $self.trigger('change') }, tc_inputTriggerChangeTimeoutMillis); }); $('body').on('change', '.checkout-block .text input', function () { $self = $(this); clearTimeout(tc_fieldChangeObserverTimeout[$self.attr('name')]); }); $('body').on('blur', '[name=address1]', function () { // put space before last number in address: var field_value = $(this).val(); $(this).val(field_value.replace(/\s*(\d+)$/, " \$1").replace(/^(\d+(,|st|nd|rd|th)?)\s*/, "\$1 ").trim()); // Check if number is present in address, if not, add 'missing-street-number' class on parent element var pattern = /\d/; if (!field_value.match(pattern)) { $(this).closest('.form-group').addClass('missing-street-number'); } else { $(this).closest('.form-group').removeClass('missing-street-number'); } }); $('body').on('change', '[name=firstname], [name=lastname], [name=address1], [name=city]', function () { $(this).val($(this).val().toCapitalize()); // In firstname and lastname, as preventive measure, replace dots that are not followed // by spaces, with dot+space, so that customer_firstname and customer_lastname validation passes properly. if ($(this).attr('name').match(/.*?tname/)) { $(this).val(jQuery.trim($(this).val().replace(/\.\s*/g, '. '))); } }); $('body').on('change', '[name=postcode], [name=vat_number]', function () { var t_fieldVal = jQuery.trim($(this).val().toUpperCase()); // remove spaces for vat_number and for postcode only when enabled in settings if ('postcode' !== $(this).attr('name') || config_postcode_remove_spaces) { t_fieldVal = t_fieldVal.replace(/\s|\./g, ''); } $(this).val(t_fieldVal); }); $('body').on('change', '.address-fields .js-country', function () { setAddressFieldsCountryCSS(); }); var liveFieldTimeout; // On these fields modification, address shall be stored and carriers / payments reloaded // Register it at the end, so that the other fields-modifications take place earlier $('body').on('change', '.live', function () { // FIX for autofill, which triggers modifyAccount multiple times in short span of time // First, let's wait a moment and execute only last call if (liveFieldTimeout) { clearTimeout(liveFieldTimeout); } var el = $(this); // In certain cases, make full page reload // This will be on rare occasions, so we can allow a fixed timeout here setTimeout(function () { if ('id_country' === el.attr('name') && installedModules['mondialrelay']) { location.reload(true); } }, 2000); var timeout = 20; liveFieldTimeout = setTimeout(function () { modifyAccountAndAddress(el); }, timeout); return false; }); handleWindowResize($(this)); // Init - (to switch to mobile, if we're on small screen) $(window).on('resize', function () { handleWindowResize($(this)); }); // Show password "red_eye" iconds to switch between password and text field $('[data-link-action="toggle-password-visibility"]').removeClass('hidden'); $(document).ajaxError(function myErrorHandler(event, xhr, ajaxOptions, thrownError) { console.info("Ajax error \n\nDetails:\nError thrown: " + thrownError + "\n" + 'event: '); console.info(event); console.info("\n" + 'xhr: '); console.info(xhr); console.info("\n" + 'ajaxOptions: '); console.info(ajaxOptions); }); // Modal window on terms and conditions link click $("#main").on("click", ".js-terms a", function (t) { t.preventDefault(); var e = $(t.target).attr("href"); e && (e += "?content_only=1", $.get(e, function (t) { $("#modal").find(".js-modal-content").html($(t).find("[class*=page-cms]:first").contents()) }).fail(function (t) { l.default.emit("handleError", { eventType: "clickTerms", resp: t }) })), $("#modal").modal("show"); }); promoteBusinessFields(); if (config_force_customer_to_choose_country) { tc_confirmOrderValidations['force_customer_to_choose_country'] = function () { if ( $('#thecheckout-shipping .dummy-block-container.disallowed').is(":visible") || $('#thecheckout-payment .dummy-block-container.disallowed').is(":visible") ) { scrollToElement($('.dummy-block-container.disallowed').first()); $('.dummy-block-container.disallowed').css('color', 'red'); return false; } else { return true; } }; } // Register global events on every ajax request and watch out for property 'customPropAffectedBlocks' // in $.ajax settings; and for such property, display loader animation. if (config_blocks_update_loader) { $(document).ajaxSend(function (event, jqxhr, settings) { if ('undefined' !== typeof settings.customPropAffectedBlocks) { // attach loader to element specified by selector 'customPropAffectedBlocks' // removed: we don't need clean up, default loader serves very well and once it is removed, we do // standard attach / remove HTML (tc_loaderHTML) //$(settings.customPropAffectedBlocks).find('.inner-area .dummy-block-container .tc-spinner').remove(); // append loader right before update $(settings.customPropAffectedBlocks).find('.inner-area').prepend(tc_loaderHtml); // Attach also loading-remove handler, when (this) ajax is finished jqxhr.always(function() { $(settings.customPropAffectedBlocks).find('.inner-area > .tc-ajax-loading').remove(); }); } }); // $(document).ajaxComplete(function (event, xhr, settings) { // if ('undefined' !== typeof settings.customPropAffectedBlocks) { // // remove loader from element specified by selector 'customPropAffectedBlocks' // $(settings.customPropAffectedBlocks).find('.inner-area > .tc-ajax-loading:first-child').remove(); // } // }); } }); function initBlocksSelectors() { shippingBlockElement = $('#thecheckout-shipping .inner-area'); paymentBlockElement = $('#thecheckout-payment .inner-area .dynamic-content'); cartSummaryBlockElement = $('#thecheckout-cart-summary .inner-area'); invoiceAddressBlockElement = $('#thecheckout-address-invoice .inner-area'); deliveryAddressBlockElement = $('#thecheckout-address-delivery .inner-area'); } function handleWindowResize(win) { if (win.width() <= tcMobileViewThreshold && !tcIsMobileView) { tcIsMobileView = true; // Take out all checkout blocks from their desktop layout and put into new container for mobile sorting $('.checkout-block').each(function () { $(this).appendTo('#tc-container-mobile'); }); } else if (win.width() > tcMobileViewThreshold && tcIsMobileView) { tcIsMobileView = false; // Put .checkout-block containers back to desktop (out of mobile / single column layout) $('.checkout-block').each(function () { $(this).insertAfter('.tc-block-placeholder.' + $(this).attr('id')); }); } } // On init, and on country change, set data-iso-code attribute on address fields, so that we can modify // checkout form (address section) with CSS rules, based on selected country function setAddressFieldsCountryCSS() { $('.address-fields .js-country option:selected').each(function () { $(this).closest('.address-fields').attr('data-iso-code', $(this).data('iso-code')); }) } function openLoginForm() { $('#login-form [name=email]').val($('#thecheckout-account [name=email]').val()); $('.offer-login').addClass('expanded'); $('#login-form').fadeIn(); scrollToElement($('#login-form').closest('.checkout-block')); return false; } function formatErrors(errors, tag) { if ('undefined' === typeof tag) { tag = 'div'; } var errMsg = ""; $.each(errors, function (index, value) { if ("" !== jQuery.trim(value)) { errMsg += "<" + tag + ">"; if ("" !== jQuery.trim(index) && isNaN(index)) { errMsg += index + ': '; } errMsg += value + "\n"; } }); return errMsg; } function checkAndHideGlobalError() { if (0 == $('.field.error-msg:visible').length) { $('#tc-payment-confirmation > .error-msg').hide(); } } function showGlobalError() { $('#tc-payment-confirmation > .error-msg').show(); scrollToError(); } function scrollToError() { scrollToElement($('.error-msg:visible').closest('.form-group')); } function scrollToElement(element) { var scrollOffset = ("undefined" !== typeof globalScrollOffset) ? globalScrollOffset : -100; if (element.length) { var actions = computeScrollIntoView(element.get(0), { behavior: 'smooth', scrollMode: 'if-needed', block: 'center' }); if ("undefined" !== typeof actions[0]) { window.scrollTo({ top: actions[0].top - scrollOffset, behavior: "smooth" }); } } } function showError(element) { $(element).show(); } function hideError(element) { $(element).hide(); checkAndHideGlobalError(); } function removeError(element) { $(element).remove(); checkAndHideGlobalError(); } // Modify checkout option (typically checkbox) and send it to backend to be remembered in session (cookie) function modifyCheckboxOption(element) { $.ajax({ type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=modifyCheckboxOption" + "&name=" + element.attr('name') + "&isChecked=" + element.is(':checked') + "&token=" + static_token, success: function (jsonData) { } }); } // Modify checkout option (typically checkbox) and send it to backend to be remembered in session (cookie) function modifyRadioOption(radioElements) { var elName = radioElements.attr('name'); var checkedElement = $('[name=' + elName + ']:checked'); $.ajax({ type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=modifyRadioOption" + "&name=" + checkedElement.attr('name') + "&checkedValue=" + checkedElement.val() + "&token=" + static_token, success: function (jsonData) { } }); } function printContextErrors(blockSel, errors, triggerElement, dontShowGlobal) { var highlightOnElements = []; if ("undefined" !== typeof triggerElement && !isMainConfirmationButton(triggerElement) && !isSaveAccountOverlayConfirmation(triggerElement)) { highlightOnElements.push(triggerElement.attr('name')); removeError(blockSel + ' [name=' + triggerElement.attr('name') + '] ~ .field.error-msg'); // With country change, re-validate postcode, if it's filled in if ("id_country" === triggerElement.attr('name') && "" != $(blockSel + ' [name=postcode]').val()) { highlightOnElements.push('postcode'); $(blockSel + ' [name=postcode]').removeClass('-error'); removeError(blockSel + ' [name=postcode] ~ .field.error-msg'); } } else { removeError(blockSel + ' .field.error-msg'); $(blockSel + ' .error').removeClass('-error'); } $.each(errors, function (index, value) { if ("" !== jQuery.trim(value) && (0 == highlightOnElements.length || highlightOnElements.indexOf(index) > -1)) { $(blockSel + ' [name=' + index + ']').addClass('-error'); if ($(blockSel + ' [name=' + index + ']').is(':checkbox') || $(blockSel + ' [name=' + index + ']').is(':radio')) { $(blockSel + ' [name=' + index + ']').closest('.form-group').append('
' + value + '
'); } else { $(blockSel + ' [name=' + index + ']').after('
' + value + '
'); } if (0 == highlightOnElements.length && ('undefined' === typeof dontShowGlobal || !dontShowGlobal)) { showGlobalError(); } } }); } function swapElements(el1, el2) { var tempNode = $('
'); el1.after(tempNode); el2.after(el1); tempNode.after(el2); tempNode.remove(); } function promoteBusinessFields() { // Group and put in front the business fields, if "I am a business" checkbox is ticked if (config_show_i_am_business) { // Special treatment of .need-dni, which can be displayed for consumer and business, but on different position if ($('.business-field.dni').length) { $('.business-field.dni').after('
'); } // To save the order of fields, we'd create placeholder and move the placeholder only to business section // After #i_am_business is ticked, placeholder will be replaced by field and field by placeholder $('#thecheckout-address-invoice .form-group.business-field, #dni-placeholder').not('.dni').prependTo($('.business-fields-container')); // If company fields are filled in (and thus #i_am_business ticked), we'll right away swap .need-dni with placeholder if ($('#i_am_business').is(':checked')) { swapElements($('#dni-placeholder'), $('.business-field.dni')); } $('#i_am_business').prop('disabled', false); } } function addVoucher() { // url - implicitly using current $.ajax({ customPropAffectedBlocks: '#thecheckout-shipping, #thecheckout-payment, #thecheckout-cart-summary', type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=addVoucher" + "&addDiscount=1" + "&discount_name=" + $('[name=discount_name]').val() + "&token=" + static_token, success: function (jsonData) { if (jsonData.hasErrors) { var errMsg = formatErrors(jsonData.cartErrors, 'span'); $('.promo-code > .alert-danger > .js-error-text').html(errMsg); $('.promo-code > .alert-danger').slideDown(); } else { updateCheckoutBlocks(jsonData, true, true, tc_updatePaymentWithShipping); } } }); } function removeVoucher(data) { $.ajax({ customPropAffectedBlocks: '#thecheckout-shipping, #thecheckout-payment, #thecheckout-cart-summary', type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=removeVoucher" + "&deleteDiscount=" + data["discountId"] + "&token=" + static_token, success: function (jsonData) { if (jsonData.hasErrors) { var errMsg = formatErrors(jsonData.cartErrors, 'span'); $('.promo-code > .alert-danger > .js-error-text').html(errMsg); $('.promo-code > .alert-danger').slideDown(); } else { updateCheckoutBlocks(jsonData, true, true, tc_updatePaymentWithShipping); } } }); } /* Prepare checkout form, so that once payment methods are loaded in payment block, they can be used immediately */ function prepareConfirmOrder() { } function confirmOrder(confirmButtonEl) { // typically, shipping modules can attach to tc_confirmOrderValidations, their respective // callbacks will be called here and should they not pass, order confirmation will be stopped var validationFailed = false; // clear shipping error before validations $('#thecheckout-shipping .error-msg').hide(); $.each(tc_confirmOrderValidations, function (validationName, validationCallback) { if (!validationCallback()) { if (debug_js_controller) { console.info('validation did not pass for: ' + validationName); } validationFailed = true; } }); if (validationFailed) { showGlobalError(); return; } modifyAccountAndAddress(confirmButtonEl, function (jsonData) { // callback method, called when account/address validation was successful // check selected carrier and payment method (additionally if they have some selection requirements) var selectedDeliveryEl = $('[name^=delivery_option]:checked'); var selectedPaymentEl = $('[name=payment-option]:checked'); var cartSummaryErrorVisible = $('#thecheckout-cart-summary .error-msg:visible').length; if (!selectedDeliveryEl.length && !jsonData.isVirtualCart) { var shippingErrorMsg = $('#thecheckout-shipping > .inner-area > .error-msg'); shippingErrorMsg.show(); scrollToElement(shippingErrorMsg); showGlobalError(); return; } if (!selectedPaymentEl.length && !config_separate_payment) { var paymentErrorMsg = $('#thecheckout-payment > .inner-area .error-msg'); paymentErrorMsg.show(); scrollToElement(paymentErrorMsg); showGlobalError(); return; } if (cartSummaryErrorVisible) { showGlobalError(); return; } // Do we have any unchecked T&C? if ($('input[id^=conditions_to_approve]').not(':checked').length) { $('.terms-and-conditions > .error-msg').show(); showGlobalError(); return; } if (debug_js_controller) { console.info('delivery: ' + selectedDeliveryEl.val()); console.info('payment: ' + selectedPaymentEl.attr('id')); console.info('*VALIDATION OK* Call payment method'); } // Confirmation processing effect showConfirmButtonLoader(confirmButtonEl, true); // should there be an issue in Payment method form, handled by payment module only, rather safely set // timeout to hide loader after few seconds. setTimeout(function () { hideConfirmButtonLoader(confirmButtonEl) }, paymentLoaderMaxTime); if (isMainConfirmationButton(confirmButtonEl)) { if (!config_separate_payment) { payment.confirm(); } else { if (debug_js_controller) { console.info(' ==== REDIRECT TO p3i ==== '); } location.href = insertUrlParam(separate_payment_key); } // Maybe: for some payment modules, call confirmButtonEl.find('button').click(); } else { // binary payment method, just hide save account overlay payment.hideSaveAccountOverlay(); } }); } function updateQuantityFromInput(el) { var data = el.data(); qtyWanted = parseInt(el.val()); qtyChange = qtyWanted - parseInt(data["qtyOrig"]); if (isNaN(qtyWanted) || isNaN(qtyChange)) { return; } data["qtyOrig"] = qtyWanted; // To allow rapid type-in changes in input field, e.g. modifying from single digit to 2-digit number if (qtyWanted < 1 || qtyChange == 0) { return; } el.prop('disabled', true); // AWP module support (also template - cart-detailed-product-line.tpl - modification is necessary!) var awpSpecialInstructions = data.updateUrl.match('special_instructions.*'); var additionalData = (awpSpecialInstructions)?'&'+awpSpecialInstructions:''; // url - implicitly using current $.ajax({ customPropAffectedBlocks: '#thecheckout-shipping, #thecheckout-payment, #thecheckout-cart-summary', type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=updateQuantity" + "&update=1" + "&qty=" + Math.abs(qtyChange) + "&op=" + ((qtyChange > 0) ? "up" : "down") + "&id_product=" + data["idProduct"] + "&id_product_attribute=" + data["idProductAttribute"] + "&id_customization=" + data["idCustomization"] + "&token=" + static_token + additionalData, success: function (jsonData) { // Removed, 5.6.2019: Now errors will go directly to cart-summary.tpl // $('#thecheckout-cart-summary > .error-msg').remove(); // if (jsonData.hasErrors) { // var errMsg = formatErrors(jsonData.cartErrors, 'span'); // $('#thecheckout-cart-summary').prepend('
' + errMsg + '
') // $('#thecheckout-cart-summary > .error-msg').show(); // } updateCheckoutBlocks(jsonData, true, true, tc_updatePaymentWithShipping); } }); } function modifyAddressSelection(addressType) { // Send to server information about expanded/collapsed second address // And additionaly ID of selected address from combobox (for logged-in users) var addressesDropdown = $('[data-link-action=x-' + addressType + '-addresses]'); var newAddressId = 0; if (addressesDropdown.length) { newAddressId = addressesDropdown.val(); } $.ajax({ customPropAffectedBlocks: '#thecheckout-shipping, #thecheckout-payment, #thecheckout-cart-summary, #thecheckout-address-' + addressType, url: insertUrlParam('modifyAddressSelection'), type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=modifyAddressSelection" + "&addressType=" + addressType + "&addressId=" + newAddressId + "&invoiceVisible=" + $('#thecheckout-address-invoice form:visible').length + "&deliveryVisible=" + $('#thecheckout-address-delivery form:visible').length + "&token=" + $('#thecheckout-account [name=token]').val(), success: function (jsonData) { updateAddressBlock(addressType, jsonData.newAddressBlock, jsonData.newAddressSelection); updateCheckoutBlocks(jsonData, true, true, tc_updatePaymentWithShipping); } }); // Returned value - whole address block; simply replace, and also update other blocks - cart, shipping, payment // for non-logged in users, simply call modifyAccountAndAddress // modifyAccountAndAddress($('#thecheckout-address-' + addressType + ' [name=id_country]')); } function showConfirmButtonLoader(buttonEl, showLoadingAnimation) { if (showLoadingAnimation) { buttonEl.addClass('confirm-loading') } buttonEl.prop('disabled', true); if (debug_js_controller) { console.info('[thecheckout] show confirm loader at ' + new Date().getSeconds() + ':' + new Date().getMilliseconds()); } } function hideConfirmButtonLoader(buttonEl) { buttonEl.removeClass('confirm-loading').prop('disabled', false); if (debug_js_controller) { console.info('[thecheckout] hide confirm loader at ' + new Date().getSeconds() + ':' + new Date().getMilliseconds()); } } function isMainConfirmationButton(element) { return ("x-confirm-order" === element.data()["linkAction"]); } function isSaveAccountOverlayConfirmation(element) { return ("x-save-account-overlay" === element.data()["linkAction"]); } function checkEmail(element, callback) { // url - implicitly using current $.ajax({ type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=checkEmail" + "&email=" + encodeURIComponent(element.val()) + "&token=" + $('#thecheckout-account [name=token]').val(), success: function (jsonData) { if (jsonData.hasErrors) { blockSel = '.account-fields'; printContextErrors(blockSel, jsonData.errors, undefined, true); } else { updateAccountToken(jsonData.newToken); updateStaticToken(jsonData.newStaticToken); // if out of some reason, shipping/payment blocks are still disallowed, maybe entering email // would allow them (e.g. if forced-email-overlay was active) if ($('.dummy-block-container.disallowed').length) { getShippingAndPaymentBlocks(); } } // call 'callback' method to let caller know we're ready if ('function' === typeof callback) { callback(jsonData); } } }); } function updateNoticeStatus(status) { if ('undefined' === typeof status) { status = '-'; } // url - implicitly using current $.ajax({ type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=saveNoticeStatus" + "¬iceStatus=" + status + "&token=" + $('#thecheckout-account [name=token]').val(), success: function (jsonData) { if (jsonData.hasErrors) { console.info('notice status update failed'); } else { console.info('notice status update succeeded'); } } }); } function updateAccountToken(token) { if ("undefined" !== typeof token) { $('#thecheckout-account input[type=hidden][name=token]').val(token); } } function updateStaticToken(token) { if ("undefined" !== typeof token) { static_token = token; if ('undefined' !== typeof prestashop) { prestashop.static_token = token; } } } function serializeVisibleFields(formSelector) { return encodeURIComponent($(formSelector).find('input:visible, [type=hidden]').serialize()); } function setConfirmationDirty() { if (debug_js_controller) { console.info('[form-change-flag] confirmation dirty!'); } // Check, if everything 'required' to trigger confirmation is filled in // If yes, then trigger it, but without visual feedback paymentConfirmationPrepared = false; } function setConfirmationPrepared() { if (debug_js_controller) { console.info('[form-change-flag] confirmation prepared!'); } paymentConfirmationPrepared = true; } function modifyAccountAndAddress(triggerElement, callback) { var triggerSection = triggerElement.closest('.checkout-block').attr('id'); // url - implicitly using current if ('prepare_confirmation' == triggerElement.attr('id')) { // after calling modifyAccountAndAddress($('#prepare_confirmation'), setConfirmationPrepared); triggerSection = 'thecheckout-prepare-confirmation'; // Disable (silently) confirmation button $('[data-link-action=x-confirm-order]').prop('disabled', true).css('cursor', 'wait'); } else if (paymentConfirmationPrepared && isMainConfirmationButton(triggerElement)) { // do not repeat Ajax request, if form wasn't modified since last data refresh and just call callback() if ("function" === typeof callback) { callback(); return; } } else if (isSaveAccountOverlayConfirmation(triggerElement)) { showConfirmButtonLoader($('[data-link-action=x-save-account-overlay]'), true); } else { // Add loader (2nd param) only when confirmation button was pressed by user; otherwise, just disable button for a moment showConfirmButtonLoader($('[data-link-action=x-confirm-order]'), isMainConfirmationButton(triggerElement)); } // Extra fields added through hooks, tepmplate updates or JS injections var extraAccountAndAddressFields = $('.account-fields, .address-fields').find('input, select, textarea').not('.orig-field').not('.not-extra-field'); var extraAccountParams = ''; if (extraAccountAndAddressFields.length) { extraAccountAndAddressFields.each(function () { extraAccountParams += '&' + $(this).attr('name') + '=' + encodeURIComponent($(this).val()); }) } // Exceptions for certain modules, that hooks in checkout fields, but need field to be sent separately var extraAccountSeparateFields = $('#thecheckout-account [type=checkbox]').not('[name=optin]').not('[name=create-account]'); if (extraAccountSeparateFields.length) { extraAccountSeparateFields.each(function () { extraAccountParams += '&' + $(this).attr('name') + '=' + encodeURIComponent($(this).val()); }) } $.ajax({ customPropAffectedBlocks: '#thecheckout-shipping, #thecheckout-payment, #thecheckout-cart-summary', url: insertUrlParam('modifyAccountAndAddress'), type: 'POST', cache: false, dataType: "json", data: "modifyAccountAndAddress&ajax_request=1&action=modifyAccountAndAddress&trigger=" + triggerSection + "&account=" + serializeVisibleFields('form.account-fields') + "&invoice=" + encodeURIComponent($('#thecheckout-address-invoice form :visible').serialize()) + "&delivery=" + encodeURIComponent($('#thecheckout-address-delivery form :visible').serialize()) + "&passwordVisible=" + $('#thecheckout-account input[name=password]:visible').length + "&passwordRequired=" + $('#thecheckout-account input[name=create-account]:checked').length + "&invoiceVisible=" + $('#thecheckout-address-invoice form:visible').length + "&deliveryVisible=" + $('#thecheckout-address-delivery form:visible').length + "&token=" + $('#thecheckout-account [name=token]').val() + extraAccountParams, success: function (jsonData) { var noErrors = true; // We can't clean all errors here, e.g. if we're updating delivery address only and have errors in invoice // this would clean also invoice errors (which we don't wont unless we update invoice address too) if ('undefined' !== typeof jsonData.customerSignInArea && 'undefined' !== typeof jsonData.customerSignInArea.staticCustomerInfo) { $('#static-customer-info-container').replaceWith(jsonData.customerSignInArea.staticCustomerInfo); } // Go through account, invoice and delivery errors, show them all if ("undefined" !== typeof jsonData.account && null !== jsonData.account) { blockSel = '.account-fields'; printContextErrors(blockSel, jsonData.account.errors); if (jsonData.account.hasErrors) { if (debug_js_controller) { var errMsg = formatErrors(jsonData.account.errors, triggerElement); console.info('modifyAccountAndAddress: account has errros'); console.info(errMsg); } // account.errors could contain also firstname / lastname errors, in that case, we need to push this to // invoice address error highlight also var customerProps = ['firstname', 'lastname']; var customerProp; for (ci = 0; ci < customerProps.length; ci++) { customerProp = customerProps[ci]; if ('undefined' !== typeof jsonData.account.errors && 'undefined' !== typeof jsonData.invoice && null !== jsonData.invoice && 'undefined' !== typeof jsonData.invoice.errors && '' != jsonData.account.errors[customerProp]) { jsonData.invoice.errors[customerProp] = jsonData.account.errors[customerProp]; } } noErrors = false; } else { // Update token only when customer account ID or password is changed if (debug_js_controller) { console.info('account created, customerId=' + jsonData.account.customerId); console.info('updating token from: ' + $('#thecheckout-account input[type=hidden][name=token]').val() + ', to: ' + jsonData.account.newToken); console.info('isGuest?' + jsonData.account.isGuest); // TODO: if isGuest == false, disable email field; or allow update email in controller? } // Disable email field and hide password when somebody logged in if (!jsonData.account.isGuest) { $('.form-group.password, .form-group.email, #thecheckout-login-form, #create_account').hide(); } if ('undefined' !== typeof jsonData.customerSignInArea.displayNav2) { var userInfoEl = null; if ($('#_desktop_user_info').length) { userInfoEl = $('#_desktop_user_info'); } else if ($('.userinfo-selector.popup-over').length) { userInfoEl = $('.userinfo-selector.popup-over'); } else if ($('#header .user-info').length) { userInfoEl = $('#header .user-info'); } else if ($('.quick_login.dropdown_wrap').length) { userInfoEl = $('.quick_login.dropdown_wrap'); } if (null !== userInfoEl) { userInfoEl.replaceWith(jsonData.customerSignInArea.displayNav2); } } updateAccountToken(jsonData.account.newToken); updateStaticToken(jsonData.account.newStaticToken); } }// End of jsonData.account handling if ("undefined" !== typeof jsonData.invoice && null !== jsonData.invoice) { blockSel = '#thecheckout-address-invoice'; printContextErrors(blockSel, jsonData.invoice.errors, triggerElement); if (jsonData.invoice.hasErrors) { if (debug_js_controller) { var errMsg = formatErrors(jsonData.invoice.errors); console.info('modifyAccountAndAddress: invoice has errros'); console.info(errMsg); } noErrors = false; } } if ("undefined" !== typeof jsonData.delivery && null !== jsonData.delivery) { blockSel = '#thecheckout-address-delivery'; printContextErrors(blockSel, jsonData.delivery.errors, triggerElement); if (jsonData.delivery.hasErrors) { if (debug_js_controller) { var errMsg = formatErrors(jsonData.delivery.errors); console.info('modifyAccountAndAddress: delivery has errros'); console.info(errMsg); } noErrors = false; } } // Handle states and refresh blocks regardless of errors status if ("thecheckout-address-invoice" === triggerSection || "thecheckout-address-delivery" === triggerSection) { var addressType = triggerSection.substring("thecheckout-address-".length); if ('undefined' !== typeof jsonData[addressType].states) { handleStates($('[id=' + triggerSection + '] [name=id_state]'), jsonData[addressType].states); } if ('undefined' !== typeof jsonData[addressType].needZipCode) { handlePostcode($('[id=' + triggerSection + '] [name=postcode]'), jsonData[addressType].needZipCode); } if ('undefined' !== typeof jsonData[addressType].needDni) { handleNeedDni($('[id=' + triggerSection + '] [name=dni]'), jsonData[addressType].needDni); } if ('undefined' !== typeof jsonData[addressType].callPrefix) { handleCallPrefix($('[id=' + triggerSection + '] [name^=phone]'), jsonData[addressType].callPrefix); } } updateCheckoutBlocks(jsonData, true, true, tc_updatePaymentWithShipping); hideConfirmButtonLoader($('[data-link-action=x-confirm-order]')); hideConfirmButtonLoader($('[data-link-action=x-save-account-overlay]')); if ('thecheckout-prepare-confirmation' == triggerSection) { $('[data-link-action=x-confirm-order]').prop('disabled', false).css('cursor', 'pointer'); } if (noErrors && "function" === typeof callback) { callback(jsonData); } } }); } function signedInUpdateForm() { $('[data-link-action=x-sign-in], .forgot-password').hide(); $('.successful-login.hidden').show(); // simply reload the checkout page with new context; take care of cart/checkout redirection, do not display // cart summary again! window.location.reload(); } function signIn() { // recover from (possible) previous login attempts $('#errors-login-form').slideUp(); $('[data-link-action=x-sign-in]').prop('disabled', true).css('cursor', 'wait'); // url - implicitly using current $.ajax({ type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=signIn&" + $('#login-form').serialize() + "&token=" + $('#thecheckout-account [name=token]').val(), success: function (jsonData) { $('[data-link-action=x-sign-in]').prop('disabled', false).css('cursor', 'pointer'); if (jsonData.hasErrors) { var errMsg = formatErrors(jsonData.errors); $('#errors-login-form').html(errMsg).slideDown(); } else { signedInUpdateForm(); } } }); } function deleteFromCart(data) { // AWP module support (also template - cart-detailed-product-line.tpl - modification is necessary!) var additionalData = ''; if ('undefined' !== typeof data.deleteUrl) { var awpSpecialInstructions = data.deleteUrl.match('special_instructions.*'); additionalData = (awpSpecialInstructions)?'&'+awpSpecialInstructions:''; } // url - implicitly using current $.ajax({ customPropAffectedBlocks: '#thecheckout-shipping, #thecheckout-payment, #thecheckout-cart-summary', type: 'POST', cache: false, dataType: "json", data: "&ajax_request=1&action=deleteFromCart" + "&delete=1" + "&id_product=" + data["idProduct"] + "&id_product_attribute=" + data["idProductAttribute"] + "&id_customization=" + data["idCustomization"] + "&token=" + static_token + additionalData, success: function (jsonData) { updateCheckoutBlocks(jsonData, true, true, tc_updatePaymentWithShipping); } }); } // Fill in states to combobox after address change/update function handleStates(selectEl, states) { var oldVal = selectEl.val(); //var shallResetPointer = selectEl.find('option:selected').index() > states.length; selectEl.children('option:not(:first)').remove(); $.each(states, function (i, item) { if ("1" === item.active) { $(selectEl).append($('