diff --git a/modules/ppcp-button/resources/css/gateway.scss b/modules/ppcp-button/resources/css/gateway.scss index 0a97127ab..b563fcab9 100644 --- a/modules/ppcp-button/resources/css/gateway.scss +++ b/modules/ppcp-button/resources/css/gateway.scss @@ -1,3 +1,9 @@ #place_order.ppcp-hidden { display: none !important; } + +.ppcp-disabled { + cursor: not-allowed; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); +} diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index e93232e2d..024e3d25e 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -20,6 +20,7 @@ import FormSaver from './modules/Helper/FormSaver'; import FormValidator from "./modules/Helper/FormValidator"; import {loadPaypalScript} from "./modules/Helper/ScriptLoading"; import buttonModuleWatcher from "./modules/ButtonModuleWatcher"; +import MessagesBootstrap from "./modules/ContextBootstrap/MessagesBootstap"; // TODO: could be a good idea to have a separate spinner for each gateway, // but I think we care mainly about the script loading, so one spinner should be enough. @@ -165,7 +166,6 @@ const bootstrap = () => { const singleProductBootstrap = new SingleProductBootstap( PayPalCommerceGateway, renderer, - messageRenderer, errorHandler, ); @@ -177,7 +177,6 @@ const bootstrap = () => { const cartBootstrap = new CartBootstrap( PayPalCommerceGateway, renderer, - messageRenderer, errorHandler, ); @@ -189,7 +188,6 @@ const bootstrap = () => { const checkoutBootstap = new CheckoutBootstap( PayPalCommerceGateway, renderer, - messageRenderer, spinner, errorHandler, ); @@ -210,6 +208,11 @@ const bootstrap = () => { buttonModuleWatcher.registerContextBootstrap('pay-now', payNowBootstrap); } + const messagesBootstrap = new MessagesBootstrap( + PayPalCommerceGateway, + messageRenderer, + ); + messagesBootstrap.init(); }; document.addEventListener( diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js index 1deaafd54..c40e03ce1 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js @@ -2,12 +2,10 @@ import CartActionHandler from '../ActionHandler/CartActionHandler'; import BootstrapHelper from "../Helper/BootstrapHelper"; class CartBootstrap { - constructor(gateway, renderer, messages, errorHandler) { + constructor(gateway, renderer, errorHandler) { this.gateway = gateway; this.renderer = renderer; - this.messages = messages; this.errorHandler = errorHandler; - this.lastAmount = this.gateway.messages.amount; this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { this.handleButtonStatus(); @@ -15,16 +13,16 @@ class CartBootstrap { } init() { - if (!this.shouldRender()) { - return; - } - - this.render(); - this.handleButtonStatus(); - - jQuery(document.body).on('updated_cart_totals updated_checkout', () => { + if (this.shouldRender()) { this.render(); this.handleButtonStatus(); + } + + jQuery(document.body).on('updated_cart_totals updated_checkout', () => { + if (this.shouldRender()) { + this.render(); + this.handleButtonStatus(); + } fetch( this.gateway.ajax.cart_script_params.endpoint, @@ -49,16 +47,19 @@ class CartBootstrap { } // handle button status - if (result.data.button || result.data.messages) { - this.gateway.button = result.data.button; - this.gateway.messages = result.data.messages; + const newData = {}; + if (result.data.button) { + newData.button = result.data.button; + } + if (result.data.messages) { + newData.messages = result.data.messages; + } + if (newData) { + BootstrapHelper.updateScriptData(this, newData); this.handleButtonStatus(); } - if (this.lastAmount !== result.data.amount) { - this.lastAmount = result.data.amount; - this.messages.renderWithAmount(this.lastAmount); - } + jQuery(document.body).trigger('ppcp_cart_total_updated', [result.data.amount]); }); }); } @@ -76,6 +77,10 @@ class CartBootstrap { } render() { + if (!this.shouldRender()) { + return; + } + const actionHandler = new CartActionHandler( PayPalCommerceGateway, this.errorHandler, @@ -99,7 +104,7 @@ class CartBootstrap { actionHandler.configuration() ); - this.messages.renderWithAmount(this.lastAmount); + jQuery(document.body).trigger('ppcp_cart_rendered'); } } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index eb9aa3885..27f9ca882 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -8,13 +8,11 @@ import { import BootstrapHelper from "../Helper/BootstrapHelper"; class CheckoutBootstap { - constructor(gateway, renderer, messages, spinner, errorHandler) { + constructor(gateway, renderer, spinner, errorHandler) { this.gateway = gateway; this.renderer = renderer; - this.messages = messages; this.spinner = spinner; this.errorHandler = errorHandler; - this.lastAmount = this.gateway.messages.amount; this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR; @@ -37,7 +35,7 @@ class CheckoutBootstap { this.render() this.handleButtonStatus(); - if (this.shouldRenderMessages()) { // currently we need amount only for Pay Later + if (this.shouldShowMessages() && document.querySelector(this.gateway.messages.wrapper)) { // currently we need amount only for Pay Later fetch( this.gateway.ajax.cart_script_params.endpoint, { @@ -51,10 +49,7 @@ class CheckoutBootstap { return; } - if (this.lastAmount !== result.data.amount) { - this.lastAmount = result.data.amount; - this.updateUi(); - } + jQuery(document.body).trigger('ppcp_checkout_total_updated', [result.data.amount]); }); } }); @@ -69,6 +64,12 @@ class CheckoutBootstap { }) }); + jQuery(document).on('ppcp_should_show_messages', (e, data) => { + if (!this.shouldShowMessages()) { + data.result = false; + } + }); + this.updateUi(); } @@ -138,16 +139,11 @@ class CheckoutBootstap { setVisibleByClass(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, 'ppcp-hidden'); setVisible('.ppcp-vaulted-paypal-details', isPaypal); setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); - setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial); setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) { setVisible(wrapper, gatewayId === currentPaymentMethod); } - if (this.shouldRenderMessages()) { - this.messages.renderWithAmount(this.lastAmount); - } - if (isCard) { if (isSavedCard) { this.disableCreditCardFields(); @@ -155,12 +151,13 @@ class CheckoutBootstap { this.enableCreditCardFields(); } } + + jQuery(document.body).trigger('ppcp_checkout_rendered'); } - shouldRenderMessages() { + shouldShowMessages() { return getCurrentPaymentMethod() === PaymentMethods.PAYPAL - && !PayPalCommerceGateway.is_free_trial_cart - && this.messages.shouldRender(); + && !PayPalCommerceGateway.is_free_trial_cart; } disableCreditCardFields() { diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js new file mode 100644 index 000000000..fe1c49438 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js @@ -0,0 +1,55 @@ +import {setVisible} from "../Helper/Hiding"; + +class MessagesBootstrap { + constructor(gateway, messageRenderer) { + this.gateway = gateway; + this.renderer = messageRenderer; + this.lastAmount = this.gateway.messages.amount; + } + + init() { + jQuery(document.body).on('ppcp_cart_rendered ppcp_checkout_rendered', () => { + this.render(); + }); + jQuery(document.body).on('ppcp_script_data_changed', (e, data) => { + this.gateway = data; + + this.render(); + }); + jQuery(document.body).on('ppcp_cart_total_updated ppcp_checkout_total_updated ppcp_product_total_updated', (e, amount) => { + if (this.lastAmount !== amount) { + this.lastAmount = amount; + + this.render(); + } + }); + + this.render(); + } + + shouldShow() { + if (this.gateway.messages.is_hidden === true) { + return false; + } + + const eventData = {result: true} + jQuery(document.body).trigger('ppcp_should_show_messages', [eventData]); + return eventData.result; + } + + shouldRender() { + return this.shouldShow() && this.renderer.shouldRender(); + } + + render() { + setVisible(this.gateway.messages.wrapper, this.shouldShow()); + + if (!this.shouldRender()) { + return; + } + + this.renderer.renderWithAmount(this.lastAmount); + } +} + +export default MessagesBootstrap; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index f6633d50f..5ff7f2a80 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -6,12 +6,12 @@ import {loadPaypalJsScript} from "../Helper/ScriptLoading"; import {getPlanIdFromVariation} from "../Helper/Subscriptions" import SimulateCart from "../Helper/SimulateCart"; import {strRemoveWord, strAddWord, throttle} from "../Helper/Utils"; +import merge from "deepmerge"; class SingleProductBootstap { - constructor(gateway, renderer, messages, errorHandler) { + constructor(gateway, renderer, errorHandler) { this.gateway = gateway; this.renderer = renderer; - this.messages = messages; this.errorHandler = errorHandler; this.mutationObserver = new MutationObserver(this.handleChange.bind(this)); this.formSelector = 'form.cart'; @@ -36,7 +36,6 @@ class SingleProductBootstap { if (!this.shouldRender()) { this.renderer.disableSmartButtons(this.gateway.button.wrapper); hide(this.gateway.button.wrapper, this.formSelector); - hide(this.gateway.messages.wrapper); return; } @@ -44,7 +43,6 @@ class SingleProductBootstap { this.renderer.enableSmartButtons(this.gateway.button.wrapper); show(this.gateway.button.wrapper); - show(this.gateway.messages.wrapper); this.handleButtonStatus(); } @@ -78,6 +76,12 @@ class SingleProductBootstap { .observe(addToCartButton, { attributes : true }); } + jQuery(document).on('ppcp_should_show_messages', (e, data) => { + if (!this.shouldRender()) { + data.result = false; + } + }); + if (!this.shouldRender()) { return; } @@ -232,7 +236,18 @@ class SingleProductBootstap { this.gateway.ajax.simulate_cart.nonce, )).simulate((data) => { - this.messages.renderWithAmount(data.total); + jQuery(document.body).trigger('ppcp_product_total_updated', [data.total]); + + let newData = {}; + if (typeof data.button.is_disabled === 'boolean') { + newData = merge(newData, {button: {is_disabled: data.button.is_disabled}}); + } + if (typeof data.messages.is_hidden === 'boolean') { + newData = merge(newData, {messages: {is_hidden: data.messages.is_hidden}}); + } + if (newData) { + BootstrapHelper.updateScriptData(this, newData); + } if ( this.gateway.single_product_buttons_enabled !== '1' ) { return; @@ -260,13 +275,6 @@ class SingleProductBootstap { jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons'); } - if (typeof data.button.is_disabled === 'boolean') { - this.gateway.button.is_disabled = data.button.is_disabled; - } - if (typeof data.messages.is_hidden === 'boolean') { - this.gateway.messages.is_hidden = data.messages.is_hidden; - } - this.handleButtonStatus(false); }, products); diff --git a/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js b/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js index 8ba3d0e09..41eb62e25 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js +++ b/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js @@ -1,5 +1,5 @@ -import {disable, enable} from "./ButtonDisabler"; -import {hide, show} from "./Hiding"; +import {disable, enable, isDisabled} from "./ButtonDisabler"; +import merge from "deepmerge"; /** * Common Bootstrap methods to avoid code repetition. @@ -9,31 +9,21 @@ export default class BootstrapHelper { static handleButtonStatus(bs, options) { options = options || {}; options.wrapper = options.wrapper || bs.gateway.button.wrapper; - options.messagesWrapper = options.messagesWrapper || bs.gateway.messages.wrapper; - options.skipMessages = options.skipMessages || false; - // Handle messages hide / show - if (this.shouldShowMessages(bs, options)) { - show(options.messagesWrapper); - } else { - hide(options.messagesWrapper); - } + const wasDisabled = isDisabled(options.wrapper); + const shouldEnable = bs.shouldEnable(); // Handle enable / disable - if (bs.shouldEnable()) { + if (shouldEnable && wasDisabled) { bs.renderer.enableSmartButtons(options.wrapper); enable(options.wrapper); - - if (!options.skipMessages) { - enable(options.messagesWrapper); - } - } else { + } else if (!shouldEnable && !wasDisabled) { bs.renderer.disableSmartButtons(options.wrapper); disable(options.wrapper, options.formSelector || null); + } - if (!options.skipMessages) { - disable(options.messagesWrapper); - } + if (wasDisabled !== !shouldEnable) { + jQuery(options.wrapper).trigger('ppcp_buttons_enabled_changed', [shouldEnable]); } } @@ -47,12 +37,15 @@ export default class BootstrapHelper { && options.isDisabled !== true; } - static shouldShowMessages(bs, options) { - options = options || {}; - if (typeof options.isMessagesHidden === 'undefined') { - options.isMessagesHidden = bs.gateway.messages.is_hidden; - } + static updateScriptData(bs, newData) { + const newObj = merge(bs.gateway, newData); - return options.isMessagesHidden !== true; + const isChanged = JSON.stringify(bs.gateway) !== JSON.stringify(newObj); + + bs.gateway = newObj; + + if (isChanged) { + jQuery(document.body).trigger('ppcp_script_data_changed', [newObj]); + } } } diff --git a/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js b/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js index b05a4dc70..c4042d079 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js @@ -35,12 +35,8 @@ export const setEnabled = (selectorOrElement, enable, form = null) => { } if (enable) { - jQuery(element).removeClass('ppcp-disabled') - .css({ - 'cursor': '', - '-webkit-filter': '', - 'filter': '', - } ) + jQuery(element) + .removeClass('ppcp-disabled') .off('mouseup') .find('> *') .css('pointer-events', ''); @@ -48,12 +44,8 @@ export const setEnabled = (selectorOrElement, enable, form = null) => { triggerEnabled(selectorOrElement, element); } else { - jQuery(element).addClass('ppcp-disabled') - .css({ - 'cursor': 'not-allowed', - '-webkit-filter': 'grayscale(100%)', - 'filter': 'grayscale(100%)', - }) + jQuery(element) + .addClass('ppcp-disabled') .on('mouseup', function(event) { event.stopImmediatePropagation(); @@ -72,6 +64,16 @@ export const setEnabled = (selectorOrElement, enable, form = null) => { } }; +export const isDisabled = (selectorOrElement) => { + const element = getElement(selectorOrElement); + + if (!element) { + return false; + } + + return jQuery(element).hasClass('ppcp-disabled'); +}; + export const disable = (selectorOrElement, form = null) => { setEnabled(selectorOrElement, false, form); }; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js index afbb55efa..85f1475e6 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js @@ -5,6 +5,7 @@ class MessageRenderer { constructor(config) { this.config = config; this.optionsFingerprint = null; + this.currentNumber = 0; } renderWithAmount(amount) { @@ -18,12 +19,19 @@ class MessageRenderer { style: this.config.style }; + // sometimes the element is destroyed while the options stay the same + if (document.querySelector(this.config.wrapper).getAttribute('data-render-number') !== this.currentNumber.toString()) { + this.optionsFingerprint = null; + } + if (this.optionsEqual(options)) { return; } const newWrapper = document.createElement('div'); newWrapper.setAttribute('id', this.config.wrapper.replace('#', '')); + this.currentNumber++; + newWrapper.setAttribute('data-render-number', this.currentNumber); const oldWrapper = document.querySelector(this.config.wrapper); const sibling = oldWrapper.nextSibling; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 1fd2e22e3..c40fcc384 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -165,14 +165,22 @@ class Renderer { if (!this.buttonsOptions[wrapper]) { return; } - this.buttonsOptions[wrapper].actions.disable(); + try { + this.buttonsOptions[wrapper].actions.disable(); + } catch (err) { + console.log('Failed to disable buttons: ' + err); + } } enableSmartButtons(wrapper) { if (!this.buttonsOptions[wrapper]) { return; } - this.buttonsOptions[wrapper].actions.enable(); + try { + this.buttonsOptions[wrapper].actions.enable(); + } catch (err) { + console.log('Failed to enable buttons: ' + err); + } } } diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 6d88e7ec6..342e2c366 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -591,14 +591,12 @@ class SmartButton implements SmartButtonInterface { ); } - if ( in_array( $this->context(), array( 'pay-now', 'checkout' ), true ) ) { - wp_enqueue_style( - 'gateway', - untrailingslashit( $this->module_url ) . '/assets/css/gateway.css', - array(), - $this->version - ); - } + wp_enqueue_style( + 'gateway', + untrailingslashit( $this->module_url ) . '/assets/css/gateway.css', + array(), + $this->version + ); wp_enqueue_script( 'ppcp-smart-button', @@ -1093,9 +1091,7 @@ class SmartButton implements SmartButtonInterface { $enable_funding = array( 'venmo' ); - if ( $this->is_pay_later_button_enabled_for_location( $context ) || - $this->is_pay_later_messaging_enabled_for_location( $context ) - ) { + if ( $this->is_pay_later_button_enabled_for_location( $context ) ) { $enable_funding[] = 'paylater'; } else { $disable_funding[] = 'paylater';