diff --git a/addons/payment/static/src/js/payment_button.js b/addons/payment/static/src/js/payment_button.js new file mode 100644 index 00000000000..055526cf345 --- /dev/null +++ b/addons/payment/static/src/js/payment_button.js @@ -0,0 +1,81 @@ +/** @odoo-module **/ + +import publicWidget from '@web/legacy/js/public/public_widget'; +import { Component } from "@odoo/owl"; + +publicWidget.registry.PaymentButton = publicWidget.Widget.extend({ + selector: 'button[name="o_payment_submit_button"]', + + async start() { + await this._super(...arguments); + this.paymentButton = this.el; + this.iconClass = this.paymentButton.dataset.iconClass; + this._enable(); + Component.env.bus.addEventListener('enablePaymentButton', this._enable.bind(this)); + Component.env.bus.addEventListener('disablePaymentButton',this._disable.bind(this)); + Component.env.bus.addEventListener('hidePaymentButton', this._hide.bind(this)); + Component.env.bus.addEventListener('showPaymentButton', this._show.bind(this)); + }, + + /** + * Check if the payment button can be enabled and do it if so. + * + * @private + * @return {void} + */ + _enable() { + if (this._canSubmit()) { + this.paymentButton.disabled = false; + } + }, + + /** + * Check whether the payment form can be submitted, i.e. whether exactly one payment option is + * selected. + * + * For a module to add a condition on the submission of the form, it must override this method + * and return whether both this method's condition and the override method's condition are met. + * + * @private + * @return {boolean} Whether the form can be submitted. + */ + _canSubmit() { + const paymentForm = document.querySelector('#o_payment_form'); + if (!paymentForm) { // Payment form is not present. + return true; // Ignore the check. + } + return document.querySelectorAll('input[name="o_payment_radio"]:checked').length === 1; + }, + + /** + * Disable the payment button. + * + * @private + * @return {void} + */ + _disable() { + this.paymentButton.disabled = true; + }, + + /** + * Hide the payment button. + * + * @private + * @return {void} + */ + _hide() { + this.paymentButton.classList.add('d-none'); + }, + + /** + * Show the payment button. + * + * @private + * @return {void} + */ + _show() { + this.paymentButton.classList.remove('d-none'); + }, + +}); +export default publicWidget.registry.PaymentButton; diff --git a/addons/payment/static/src/js/payment_form.js b/addons/payment/static/src/js/payment_form.js index d5829fc37a4..0cde3ce9c5a 100644 --- a/addons/payment/static/src/js/payment_form.js +++ b/addons/payment/static/src/js/payment_form.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { Component } from '@odoo/owl'; import publicWidget from '@web/legacy/js/public/public_widget'; import { browser } from '@web/core/browser/browser'; import { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog'; @@ -168,9 +169,7 @@ publicWidget.registry.PaymentForm = publicWidget.Widget.extend({ * @return {void} */ _enableButton(unblockUI = true) { - if (this._canSubmit()) { - this._getSubmitButton().removeAttribute('disabled'); - } + Component.env.bus.trigger('enablePaymentButton'); if (unblockUI) { this.call('ui', 'unblock'); } @@ -184,7 +183,7 @@ publicWidget.registry.PaymentForm = publicWidget.Widget.extend({ * @return {void} */ _disableButton(blockUI = false) { - this._getSubmitButton().setAttribute('disabled', true); + Component.env.bus.trigger('disablePaymentButton'); if (blockUI) { this.call('ui', 'block'); } @@ -202,7 +201,7 @@ publicWidget.registry.PaymentForm = publicWidget.Widget.extend({ tokenizeContainer?.classList.remove('d-none'); // Show the submit button. - this._getSubmitButton().classList.remove('d-none'); + Component.env.bus.trigger('showPaymentButton'); }, /** @@ -221,7 +220,7 @@ publicWidget.registry.PaymentForm = publicWidget.Widget.extend({ tokenizeContainer?.classList.add('d-none'); // Hide the submit button. - this._getSubmitButton().classList.add('d-none'); + Component.env.bus.trigger('hidePaymentButton'); }, /** @@ -313,20 +312,6 @@ publicWidget.registry.PaymentForm = publicWidget.Widget.extend({ // #=== PAYMENT FLOW ===# - /** - * Check whether the payment form can be submitted, i.e. whether exactly one payment option is - * selected. - * - * For a module to add a condition on the submission of the form, it must override this method - * and return whether both this method's condition and the override method's condition are met. - * - * @private - * @return {boolean} Whether the form can be submitted. - */ - _canSubmit() { - return this.el.querySelectorAll('input[name="o_payment_radio"]:checked').length === 1; - }, - /** * Set the payment flow for the selected payment option. * @@ -535,19 +520,6 @@ publicWidget.registry.PaymentForm = publicWidget.Widget.extend({ return inlineFormContainer?.querySelector('[name="o_payment_inline_form"]'); }, - /** - * Find and return the submit button. - * - * The button is searched in the whole document, rather than only in the current form, to allow - * modules to place it outside the payment form (e.g., eCommerce). - * - * @private - * @return {Element} The submit button. - */ - _getSubmitButton() { - return document.querySelector('[name="o_payment_submit_button"]'); - }, - /** * Determine and return the payment flow of the selected payment option. * diff --git a/addons/website_sale/__manifest__.py b/addons/website_sale/__manifest__.py index b641ad81034..b533df479e5 100644 --- a/addons/website_sale/__manifest__.py +++ b/addons/website_sale/__manifest__.py @@ -61,7 +61,10 @@ 'website_sale/static/src/scss/website_sale_delivery.scss', 'website/static/lib/multirange/multirange_custom.scss', 'sale/static/src/scss/sale_portal.scss', + 'website_sale/static/src/js/payment_button.js', + 'website_sale/static/src/js/payment_form.js', 'website_sale/static/src/js/sale_variant_mixin.js', + 'website_sale/static/src/js/terms_and_conditions_checkbox.js', 'website_sale/static/src/js/variant_mixin.js', 'website_sale/static/src/js/website_sale.js', 'website_sale/static/src/xml/website_sale.xml', @@ -69,7 +72,6 @@ 'website_sale/static/src/js/website_sale_price_range_option.js', 'website_sale/static/src/js/website_sale_utils.js', 'website_sale/static/src/xml/website_sale_utils.xml', - 'website_sale/static/src/js/website_sale_payment.js', 'website_sale/static/src/js/website_sale_recently_viewed.js', 'website_sale/static/src/js/website_sale_tracking.js', 'website/static/lib/multirange/multirange_custom.js', diff --git a/addons/website_sale/static/src/js/payment_button.js b/addons/website_sale/static/src/js/payment_button.js new file mode 100644 index 00000000000..3cc0da7e06a --- /dev/null +++ b/addons/website_sale/static/src/js/payment_button.js @@ -0,0 +1,69 @@ +/** @odoo-module **/ + +import paymentButton from '@payment/js/payment_button'; + +paymentButton.include({ + + /** + * Verify that the payment button is ready to be enabled. + * + * The conditions are that: + * - a delivery carrier is selected and ready (the price is computed) if deliveries are enabled; + * - the "Terms and Conditions" checkbox is ticked if it is present. + * + * @override from @payment/js/payment_button + * @return {boolean} + */ + _canSubmit() { + return this._super(...arguments) && this._isCarrierReady() && this._isTCCheckboxReady(); + }, + + /** + * Check if the delivery carrier is selected and if its price is computed. + * + * @private + * @return {boolean} + */ + _isCarrierReady() { + const carriers = document.querySelectorAll('.o_delivery_carrier_select'); + if (carriers.length === 0) { // No carrier is available. + return true; // Ignore the check. + } + + const checkedCarriers = document.querySelectorAll('input[name="delivery_type"]:checked'); + if (checkedCarriers.length === 0) { // No carrier is selected. + return false; // Nothing else to check. + } + const carriersContainer = checkedCarriers[0].closest('.o_delivery_carrier_select'); + const isPickUpPointRequired = carriersContainer.querySelector('.o_show_pickup_locations'); + if (isPickUpPointRequired) { + const address = carriersContainer.querySelector( + '.o_order_location_address' + ).innerText; + const isPickUp = carriersContainer.lastChild.previousSibling.children; + if ( + isPickUp.length > 1 && (address === '' || isPickUp[0].classList.contains('d-none')) + ) { // A pickup point is required but not selected + return false; + } + } + + return true; + }, + + /** + * Check if the "Terms and Conditions" checkbox is ticked, if present. + * + * @private + * @return {boolean} + */ + _isTCCheckboxReady() { + const checkbox = document.querySelector('#website_sale_tc_checkbox'); + if (!checkbox) { // The checkbox is not present. + return true; // Ignore the check. + } + + return checkbox.checked; + }, + +}); diff --git a/addons/website_sale/static/src/js/payment_form.js b/addons/website_sale/static/src/js/payment_form.js new file mode 100644 index 00000000000..4cc29f366b0 --- /dev/null +++ b/addons/website_sale/static/src/js/payment_form.js @@ -0,0 +1,17 @@ +/** @odoo-module **/ + +import PaymentForm from '@payment/js/payment_form'; + +PaymentForm.include({ + + /** + * Create an event listener for the payment button located outside the payment form. + * @override + */ + async start() { + const submitButton = document.querySelector('[name="o_payment_submit_button"]'); + submitButton.addEventListener('click', ev => this._submitForm(ev)); + return await this._super(...arguments); + } + +}); diff --git a/addons/website_sale/static/src/js/terms_and_conditions_checkbox.js b/addons/website_sale/static/src/js/terms_and_conditions_checkbox.js new file mode 100644 index 00000000000..2bcce3d1ba4 --- /dev/null +++ b/addons/website_sale/static/src/js/terms_and_conditions_checkbox.js @@ -0,0 +1,34 @@ +/** @odoo-module **/ + +import { Component } from '@odoo/owl'; +import publicWidget from '@web/legacy/js/public/public_widget'; + +publicWidget.registry.TermsAndConditionsCheckbox = publicWidget.Widget.extend({ + selector: 'div[name="website_sale_terms_and_conditions_checkbox"]', + events: { + 'change #website_sale_tc_checkbox': '_onClickTCCheckbox', + }, + + async start() { + this.checkbox = this.el.querySelector('#website_sale_tc_checkbox'); + return this._super(...arguments); + }, + + /** + * Enable/disabled the payment button when the "Terms and Conditions" checkbox is + * checked/unchecked. + * + * @private + * @return {void} + */ + _onClickTCCheckbox() { + if (this.checkbox.checked) { + Component.env.bus.trigger('enablePaymentButton'); + } else { + Component.env.bus.trigger('disablePaymentButton'); + } + }, + +}); + +export default publicWidget.registry.TermsAndConditionsCheckbox; diff --git a/addons/website_sale/static/src/js/website_sale_delivery.js b/addons/website_sale/static/src/js/website_sale_delivery.js index af9b1f66722..ab740e7ace6 100644 --- a/addons/website_sale/static/src/js/website_sale_delivery.js +++ b/addons/website_sale/static/src/js/website_sale_delivery.js @@ -192,9 +192,14 @@ publicWidget.registry.websiteSaleDelivery = publicWidget.Widget.extend({ } }, + /** + * Disable the payment button. + * + * @private + * @return {void} + */ _disablePayButton: function (){ - var payButton = document.querySelector('button[name="o_payment_submit_button"]'); - payButton? payButton.disabled = true : null; + Component.env.bus.trigger('disablePaymentButton'); }, _disablePayButtonNoPickupPoint : function (ev){ @@ -247,57 +252,16 @@ publicWidget.registry.websiteSaleDelivery = publicWidget.Widget.extend({ this._enableButton(result.status); }, /** - * Check if the submit button can be enabled. - * If it is the case, trigger the `_enableButton` from payment + * Enable the payment button if the rate_shipment request succeeded. * * @private + * @param {boolean} status - The status of the rate_shipment request. + * @return {void} */ - _enableButton: function(status){ - if (!this._isPayable(status)) { - return; + _enableButton(status){ + if (status) { + Component.env.bus.trigger('enablePaymentButton'); } - Component.env.bus.trigger('enableButton'); - }, - /** - * @private - * @param {boolean} status : the status of the rate_shipment request - * @return {boolean} - */ - // FYI we don't cover the case where the payement method is not selected because it already throws an error - _isPayable: function(status=false){ - // abort if no paybutton - var payButton = document.querySelector('button[name="o_payment_submit_button"]'); - if (!payButton) { - return false; - } - // abort if the rating failed - if (!status){ - return false; - } - const carriers = Array.from(document.querySelectorAll('.o_delivery_carrier_select')) - let carrierChecked = null; - carriers.forEach((carrier) => { - if (carrier.querySelector('input').checked){ - carrierChecked = carrier; - } - }) - //abort if no carrier is selected - if (!carrierChecked){ - return false; - } - - // if the carrier does not need a pickup point - const isPickUpPointNeeded = carrierChecked.querySelector('.o_show_pickup_locations') - if (!isPickUpPointNeeded){ - return true; - } - - const address = carrierChecked.querySelector('.o_order_location_address').innerText - const isPickUp = carrierChecked.lastChild.previousSibling.children; - if (isPickUp.length > 1 && (address == "" || isPickUp[0].classList.contains("d-none"))) { - return false; - } - return true; }, _isPickupLocationSelected: function (ev) { @@ -381,11 +345,14 @@ publicWidget.registry.websiteSaleDelivery = publicWidget.Widget.extend({ * @param {Event} ev */ _onCarrierClick: async function (ev) { - this._disablePayButton(); - const radio = ev.currentTarget.closest('.o_delivery_carrier_select').querySelector('input[type="radio"]'); - if (radio.checked && !this._shouldDisplayPickupLocations(ev)) { + const radio = ev.currentTarget.closest('.o_delivery_carrier_select').querySelector( + 'input[type="radio"]' + ); + if (radio.checked) { return; } + + this._disablePayButton(); this._showLoading(radio); radio.checked = true; await this._onClickShowLocations(ev); diff --git a/addons/website_sale/static/src/js/website_sale_payment.js b/addons/website_sale/static/src/js/website_sale_payment.js deleted file mode 100644 index 77835e10b06..00000000000 --- a/addons/website_sale/static/src/js/website_sale_payment.js +++ /dev/null @@ -1,127 +0,0 @@ -/** @odoo-module **/ - -import { debounce } from '@web/core/utils/timing'; -import publicWidget from '@web/legacy/js/public/public_widget'; - -import PaymentForm from '@payment/js/payment_form'; - -const websiteSalePaymentMixin = { - - // #=== WIDGET LIFECYCLE ===# - - /** - * @override - */ - init() { - this._onClickTCCheckbox = debounce(this._onClickTCCheckbox, 100, true); - this._super(...arguments); - }, - - prepareInputs() { - this.$checkbox = $(document.getElementById('website_sale_tc_checkbox')); - this.$submitButton = $(document.querySelector('[name="o_payment_submit_button"]')); - this._adaptConfirmButton(); - }, - - /** - * Update the data on the submit button with the status of the Terms and Conditions input. - * - * @private - * @return {void} - */ - _adaptConfirmButton() { - if (this.$checkbox.length > 0) { - const disabledReasons = this.$submitButton.data('disabled_reasons') || {}; - disabledReasons.tc = !this.$checkbox.prop('checked'); - this.$submitButton.data('disabled_reasons', disabledReasons); - } - }, - -}; - -PaymentForm.include(Object.assign({}, websiteSalePaymentMixin, { - - // #=== WIDGET LIFECYCLE ===# - - /** - * @override - */ - async start() { - this.prepareInputs(); - if (this.$checkbox.length > 0) { // The checkbox is outside the payment form. - this.$checkbox[0].addEventListener('change', () => this._onClickTCCheckbox()); - } - if (this.$submitButton.length > 0) { // The button is outside the payment form. - this.$submitButton[0].addEventListener('click', ev => this._submitForm(ev)); - } - return await this._super(...arguments); - }, - - // #=== EVENT HANDLERS ===# - - /** - * Enable the submit button if it all conditions are met. - * - * @private - * @return {void} - */ - _onClickTCCheckbox() { - this._disableButton(); - this._adaptConfirmButton(); - this._enableButton(); - }, - - // #=== PAYMENT FLOW ===# - - /** - * Verify that the Terms and Condition checkbox is checked. - * - * @override method from @payment/js/payment_form - * @private - * @return {boolean} Whether the form can be submitted. - */ - _canSubmit() { - const disabledReasonFound = Object.values( - this.$submitButton.data('disabled_reasons') || {} - ).includes(true); - return !disabledReasonFound && this._super(); - }, - -})); - -publicWidget.registry.WebsiteSalePayment = publicWidget.Widget.extend( - Object.assign({}, websiteSalePaymentMixin, { - selector: 'div[name="o_website_sale_free_cart"]', - events: { - 'change #website_sale_tc_checkbox': '_onClickTCCheckbox', - }, - - // #=== WIDGET LIFECYCLE ===# - - /** - * @override - */ - async start() { - this.prepareInputs(); - this._onClickTCCheckbox(); - return await this._super(...arguments); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Enable the submit button if it all conditions are met. - * - * @private - * @return {void} - */ - _onClickTCCheckbox() { - this._adaptConfirmButton(); - const disabledReasonFound = Object.values( - this.$submitButton.data('disabled_reasons') || {} - ).includes(true); - this.$submitButton.prop('disabled', disabledReasonFound); - }, - })); diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index d0c0029485a..cad30f3403e 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -1735,6 +1735,7 @@ - _cta_classes: CSS classes to append on the primary navigation button; default `None`. - _form_send_navigation: Whether the primary button serves as a submit button for a form; default `None`. + - hide_payment_button: Whether the payment button should be hidden; default: False. -->