From 180afaad89ba28b34b17d2afab45fef3e61629f3 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Fri, 7 Jun 2024 12:19:39 +0200
Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Fix=20logic=20in=20preview?=
=?UTF-8?q?=20button=20base=20classes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Promise-based initialization is executed in correct order
- Support to redraw single APM type of button
- Fix the button style
- Simplify the PreviewButton code
---
.../js/modules/Renderer/PreviewButton.js | 37 ++++----
.../modules/Renderer/PreviewButtonManager.js | 94 +++++++++++--------
2 files changed, 73 insertions(+), 58 deletions(-)
diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js
index fcdafd37d..0e4bba6cd 100644
--- a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js
+++ b/modules/ppcp-button/resources/js/modules/Renderer/PreviewButton.js
@@ -50,6 +50,10 @@ class PreviewButton {
this.buttonConfig.button.wrapper = this.selector
}
+ if (this.ppcpConfig && this.buttonConfig) {
+ this.buttonConfig.button.style = this.ppcpConfig.button.style;
+ }
+
return this;
}
@@ -68,38 +72,29 @@ class PreviewButton {
* Will always create a new button in the DOM.
*/
render() {
- this.remove();
+ if (!this.domWrapper) {
+ if (!this.buttonConfig?.button?.wrapper) {
+ console.error('Skip render, button is not configured yet');
+ return;
+ }
+ this.domWrapper = this.createNewWrapper();
- if (!this.buttonConfig?.button?.wrapper) {
- console.error('Skip render, button is not configured yet');
- return;
+ this.domWrapper.insertAfter(this.ppcpConfig.button.wrapper)
+ } else {
+ this.domWrapper.empty().show();
}
this.isVisible = true;
- const newDomWrapper = this.createNewWrapper();
-
- if (this.domWrapper?.length) {
- this.domWrapper.replaceWith(newDomWrapper);
- } else {
- jQuery(this.ppcpConfig.button.wrapper).after(newDomWrapper);
- }
- this.domWrapper = newDomWrapper;
-
- this.payButton = this.createButton();
+ this.createButton()
}
remove() {
this.isVisible = false;
- // The current payButtons have no remove/cleanup function.
- this.payButton = null;
-
- if (this.domWrapper?.remove) {
- this.domWrapper.remove();
+ if (this.domWrapper) {
+ this.domWrapper.hide().empty();
}
-
- this.domWrapper = null;
}
}
diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js b/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js
index 739e14314..f59e1252b 100644
--- a/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js
+++ b/modules/ppcp-button/resources/js/modules/Renderer/PreviewButtonManager.js
@@ -5,9 +5,23 @@ import merge from "deepmerge";
* Manages all PreviewButton instances of a certain payment method on the page.
*/
class PreviewButtonManager {
- constructor({buttonConfig, widgetBuilder, defaultAttributes}) {
+ /**
+ * Resolves the promise.
+ * Used by `this.boostrap()` to process enqueued initialization logic.
+ */
+ #onInitResolver;
+
+ /**
+ * A deferred Promise that is resolved once the page is ready.
+ * Deferred init logic can be added by using `this.#onInit.then(...)`
+ *
+ * @param {Promise|null}
+ */
+ #onInit;
+
+ constructor({methodName, buttonConfig, widgetBuilder, defaultAttributes}) {
// Define the payment method name in the derived class.
- this.methodName = 'UNDEFINED';
+ this.methodName = methodName;
this.buttonConfig = buttonConfig;
this.widgetBuilder = widgetBuilder;
@@ -17,11 +31,9 @@ class PreviewButtonManager {
this.buttons = {};
this.configResponse = null;
- // Empty promise that resolves instantly when called.
- this.bootstrapping = Promise.resolve();
-
- // Add the bootstrap logic to the Promise chain. More `then`s are added by addButton().
- this.bootstrapping = this.bootstrapping.then(() => this.bootstrap());
+ this.#onInit = new Promise(resolve => {
+ this.#onInitResolver = resolve;
+ });
this.registerEventListeners();
}
@@ -49,8 +61,13 @@ class PreviewButtonManager {
}
registerEventListeners() {
- jQuery(document).on('ppcp_paypal_render_preview', (ev, ppcpConfig) => this.addButton(ppcpConfig));
- jQuery(document).on('DOMContentLoaded', () => this.bootstrapping);
+ jQuery(document).on('DOMContentLoaded', this.bootstrap.bind(this));
+
+ // General event that all APM buttons react to.
+ jQuery(document).on('ppcp_paypal_render_preview', this.renderPreview.bind(this));
+
+ // Specific event to only (re)render the current APM button type.
+ jQuery(document).on(`ppcp_paypal_render_preview_${this.methodName}`, this.renderPreview.bind(this));
}
/**
@@ -88,20 +105,41 @@ class PreviewButtonManager {
await Promise.all([customScriptPromise, paypalPromise]);
this.configResponse = await this.fetchConfig();
+
+ await this.#onInitResolver()
+
+ this.#onInit = null;
+ }
+
+ /**
+ * Event handler, fires on `ppcp_paypal_render_preview`
+ *
+ * @param ev - Ignored
+ * @param ppcpConfig - The button settings for the preview.
+ */
+ renderPreview(ev, ppcpConfig) {
+ const id = ppcpConfig.button.wrapper
+
+ if (!id) {
+ this.error('Button did not provide a wrapper ID', ppcpConfig)
+ return;
+ }
+
+ if (!this.buttons[id]) {
+ this.addButton(id, ppcpConfig);
+ } else {
+ this.buttons[id].config({
+ buttonConfig: this.buttonConfig,
+ ppcpConfig
+ }).render()
+ }
}
/**
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
*/
- addButton(ppcpConfig) {
- if (!ppcpConfig.button.wrapper) {
- this.error('Button did not provide a wrapper ID', ppcpConfig)
- return;
- }
-
+ addButton(id, ppcpConfig) {
const createOrUpdateButton = () => {
- const id = ppcpConfig.button.wrapper;
-
if (!this.buttons[id]) {
this.buttons[id] = this.createButtonInst(id);
}
@@ -112,31 +150,13 @@ class PreviewButtonManager {
}).render()
}
- if (this.bootstrapping) {
- this.bootstrapping.then(createOrUpdateButton);
+ if (this.#onInit) {
+ this.#onInit.then(createOrUpdateButton);
} else {
createOrUpdateButton();
}
}
- /**
- * Changes the button configuration and re-renders all buttons.
- *
- * @return {this} Reference to self, for chaining.
- */
- updateConfig(newConfig) {
- if (!newConfig || 'object' !== typeof newConfig) {
- return this;
- }
-
- this.buttonConfig = merge(this.buttonConfig, newConfig)
-
- Object.values(this.buttons).forEach(button => button.config({buttonConfig: this.buttonConfig}))
- this.renderButtons();
-
- return this;
- }
-
/**
* Refreshes all buttons using the latest buttonConfig.