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.