♻️ Fix logic in preview button base classes

- Promise-based initialization is executed in correct order
- Support to redraw single APM type of button
- Fix the button style
- Simplify the PreviewButton code
This commit is contained in:
Philipp Stracker 2024-06-07 12:19:39 +02:00
parent 202f308850
commit 180afaad89
No known key found for this signature in database
2 changed files with 73 additions and 58 deletions

View file

@ -50,6 +50,10 @@ class PreviewButton {
this.buttonConfig.button.wrapper = this.selector this.buttonConfig.button.wrapper = this.selector
} }
if (this.ppcpConfig && this.buttonConfig) {
this.buttonConfig.button.style = this.ppcpConfig.button.style;
}
return this; return this;
} }
@ -68,38 +72,29 @@ class PreviewButton {
* Will always create a new button in the DOM. * Will always create a new button in the DOM.
*/ */
render() { 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) { this.domWrapper.insertAfter(this.ppcpConfig.button.wrapper)
console.error('Skip render, button is not configured yet'); } else {
return; this.domWrapper.empty().show();
} }
this.isVisible = true; this.isVisible = true;
const newDomWrapper = this.createNewWrapper(); this.createButton()
if (this.domWrapper?.length) {
this.domWrapper.replaceWith(newDomWrapper);
} else {
jQuery(this.ppcpConfig.button.wrapper).after(newDomWrapper);
}
this.domWrapper = newDomWrapper;
this.payButton = this.createButton();
} }
remove() { remove() {
this.isVisible = false; this.isVisible = false;
// The current payButtons have no remove/cleanup function. if (this.domWrapper) {
this.payButton = null; this.domWrapper.hide().empty();
if (this.domWrapper?.remove) {
this.domWrapper.remove();
} }
this.domWrapper = null;
} }
} }

View file

@ -5,9 +5,23 @@ import merge from "deepmerge";
* Manages all PreviewButton instances of a certain payment method on the page. * Manages all PreviewButton instances of a certain payment method on the page.
*/ */
class PreviewButtonManager { 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<void>|null}
*/
#onInit;
constructor({methodName, buttonConfig, widgetBuilder, defaultAttributes}) {
// Define the payment method name in the derived class. // Define the payment method name in the derived class.
this.methodName = 'UNDEFINED'; this.methodName = methodName;
this.buttonConfig = buttonConfig; this.buttonConfig = buttonConfig;
this.widgetBuilder = widgetBuilder; this.widgetBuilder = widgetBuilder;
@ -17,11 +31,9 @@ class PreviewButtonManager {
this.buttons = {}; this.buttons = {};
this.configResponse = null; this.configResponse = null;
// Empty promise that resolves instantly when called. this.#onInit = new Promise(resolve => {
this.bootstrapping = Promise.resolve(); this.#onInitResolver = resolve;
});
// Add the bootstrap logic to the Promise chain. More `then`s are added by addButton().
this.bootstrapping = this.bootstrapping.then(() => this.bootstrap());
this.registerEventListeners(); this.registerEventListeners();
} }
@ -49,8 +61,13 @@ class PreviewButtonManager {
} }
registerEventListeners() { registerEventListeners() {
jQuery(document).on('ppcp_paypal_render_preview', (ev, ppcpConfig) => this.addButton(ppcpConfig)); jQuery(document).on('DOMContentLoaded', this.bootstrap.bind(this));
jQuery(document).on('DOMContentLoaded', () => this.bootstrapping);
// 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]); await Promise.all([customScriptPromise, paypalPromise]);
this.configResponse = await this.fetchConfig(); 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. * Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
*/ */
addButton(ppcpConfig) { addButton(id, ppcpConfig) {
if (!ppcpConfig.button.wrapper) {
this.error('Button did not provide a wrapper ID', ppcpConfig)
return;
}
const createOrUpdateButton = () => { const createOrUpdateButton = () => {
const id = ppcpConfig.button.wrapper;
if (!this.buttons[id]) { if (!this.buttons[id]) {
this.buttons[id] = this.createButtonInst(id); this.buttons[id] = this.createButtonInst(id);
} }
@ -112,31 +150,13 @@ class PreviewButtonManager {
}).render() }).render()
} }
if (this.bootstrapping) { if (this.#onInit) {
this.bootstrapping.then(createOrUpdateButton); this.#onInit.then(createOrUpdateButton);
} else { } else {
createOrUpdateButton(); 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. * Refreshes all buttons using the latest buttonConfig.