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;
}
}