From 0fed872c1309df4d257d041c9c001382f9bdc7cc Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 25 Jul 2023 16:33:56 +0100 Subject: [PATCH] Add ability in WidgetBuilder to have multiple buttons rendered per wrapper. --- .../SingleProductActionHandler.js | 9 +- .../ContextBootstrap/SingleProductBootstap.js | 9 +- .../resources/js/modules/Renderer/Renderer.js | 16 ++-- .../js/modules/Renderer/WidgetBuilder.js | 85 +++++++++++++++++-- 4 files changed, 104 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js index 1e5f54ea4..abc07c594 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js @@ -40,8 +40,7 @@ class SingleProductActionHandler { }).then((res)=>{ return res.json(); }).then(() => { - const id = document.querySelector('[name="add-to-cart"]').value; - const products = [new Product(id, 1, null)]; + const products = this.getSubscriptionProducts(); fetch(this.config.ajax.change_cart.endpoint, { method: 'POST', @@ -71,6 +70,12 @@ class SingleProductActionHandler { } } + getSubscriptionProducts() + { + const id = document.querySelector('[name="add-to-cart"]').value; + return [new Product(id, 1, null)]; + } + configuration() { return { diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index f09ef7fda..611e9e3e8 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -163,6 +163,13 @@ class SingleProductBootstap { this.errorHandler, ); + const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions + && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled; + + const products = hasSubscriptions + ? actionHandler.getSubscriptionProducts() + : actionHandler.getProducts(); + (new SimulateCart( this.gateway.ajax.simulate_cart.endpoint, this.gateway.ajax.simulate_cart.nonce, @@ -198,7 +205,7 @@ class SingleProductBootstap { this.handleButtonStatus(false); - }, actionHandler.getProducts()); + }, products); } } diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 629c1abc9..1fd2e22e3 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -75,7 +75,7 @@ class Renderer { renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) { if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) { // Try to render registered buttons again in case they were removed from the DOM by an external source. - widgetBuilder.renderButtons(wrapper); + widgetBuilder.renderButtons([wrapper, fundingSource]); return; } @@ -99,14 +99,20 @@ class Renderer { jQuery(document) .off(this.reloadEventName, wrapper) - .on(this.reloadEventName, wrapper, (event, settingsOverride = {}) => { + .on(this.reloadEventName, wrapper, (event, settingsOverride = {}, triggeredFundingSource) => { + + // Only accept events from the matching funding source + if (fundingSource && triggeredFundingSource && (triggeredFundingSource !== fundingSource)) { + return; + } + const settings = merge(this.defaultSettings, settingsOverride); let scriptOptions = keysToCamelCase(settings.url_params); scriptOptions = merge(scriptOptions, settings.script_attributes); loadScript(scriptOptions).then((paypal) => { widgetBuilder.setPaypal(paypal); - widgetBuilder.registerButtons(wrapper, buttonsOptions()); + widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions()); widgetBuilder.renderAll(); }); }); @@ -114,8 +120,8 @@ class Renderer { this.renderedSources.add(wrapper + (fundingSource ?? '')); if (typeof paypal !== 'undefined' && typeof paypal.Buttons !== 'undefined') { - widgetBuilder.registerButtons(wrapper, buttonsOptions()); - widgetBuilder.renderButtons(wrapper); + widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions()); + widgetBuilder.renderButtons([wrapper, fundingSource]); } } diff --git a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js index ed45b35b4..cc5077d40 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js @@ -1,4 +1,7 @@ - +/** + * Handles the registration and rendering of PayPal widgets: Buttons and Messages. + * To have several Buttons per wrapper, an array should be provided, ex: [wrapper, fundingSource]. + */ class WidgetBuilder { constructor() { @@ -19,14 +22,18 @@ class WidgetBuilder { } registerButtons(wrapper, options) { - this.buttons.set(wrapper, { + wrapper = this.sanitizeWrapper(wrapper); + + this.buttons.set(this.toKey(wrapper), { wrapper: wrapper, - options: options + options: options, }); } renderButtons(wrapper) { - if (!this.buttons.has(wrapper)) { + wrapper = this.sanitizeWrapper(wrapper); + + if (!this.buttons.has(this.toKey(wrapper))) { return; } @@ -34,14 +41,21 @@ class WidgetBuilder { return; } - const entry = this.buttons.get(wrapper); + const entry = this.buttons.get(this.toKey(wrapper)); const btn = this.paypal.Buttons(entry.options); if (!btn.isEligible()) { + this.buttons.delete(this.toKey(wrapper)); return; } - btn.render(entry.wrapper); + let target = this.buildWrapperTarget(wrapper); + + if (!target) { + return; + } + + btn.render(target); } renderAllButtons() { @@ -84,7 +98,64 @@ class WidgetBuilder { } hasRendered(wrapper) { - return document.querySelector(wrapper).hasChildNodes(); + let selector = wrapper; + + if (Array.isArray(wrapper)) { + selector = wrapper[0]; + for (const item of wrapper.slice(1)) { + selector += ' .item-' + item; + } + } + + const element = document.querySelector(selector); + return element && element.hasChildNodes(); + } + + sanitizeWrapper(wrapper) { + if (Array.isArray(wrapper)) { + wrapper = wrapper.filter(item => !!item); + if (wrapper.length === 1) { + wrapper = wrapper[0]; + } + } + return wrapper; + } + + buildWrapperTarget(wrapper) { + let target = wrapper; + + if (Array.isArray(wrapper)) { + const $wrapper = jQuery(wrapper[0]); + + if (!$wrapper.length) { + return; + } + + const itemClass = 'item-' + wrapper[1]; + + // Check if the parent element exists and it doesn't already have the div with the class + let $item = $wrapper.find('.' + itemClass); + + if (!$item.length) { + $item = jQuery(`
`); + $wrapper.append($item); + } + + target = $item.get(0); + } + + if (!jQuery(target).length) { + return null; + } + + return target; + } + + toKey(wrapper) { + if (Array.isArray(wrapper)) { + return JSON.stringify(wrapper); + } + return wrapper; } }