mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-03 08:37:53 +08:00
♻️ Refactor Google Pay preview button
Mainly, separate the preview code into three main parts: - Configuration - Button - ButtonManager Remove dependency from DOM structure
This commit is contained in:
parent
d1a93a1b55
commit
9342086f33
3 changed files with 329 additions and 6 deletions
|
@ -1,7 +1,327 @@
|
|||
import {loadCustomScript} from "@paypal/paypal-js";
|
||||
import GooglepayButton from "./GooglepayButton";
|
||||
import merge from "deepmerge";
|
||||
import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
|
||||
|
||||
/**
|
||||
* Button manager instance; we usually only need a single instance of this object.
|
||||
*
|
||||
* @see buttonManager()
|
||||
*/
|
||||
let managerInstance = null;
|
||||
|
||||
/**
|
||||
* Default attributes for new buttons.
|
||||
*/
|
||||
const defaultAttributes = {
|
||||
button: {
|
||||
style: {
|
||||
type: 'pay',
|
||||
color: 'black',
|
||||
language: 'en'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Accessor that creates and returns a single PreviewButtonManager instance.
|
||||
*/
|
||||
const buttonManager = () => {
|
||||
if (!managerInstance) {
|
||||
managerInstance = new PreviewButtonManager({
|
||||
// Internal, for logging.
|
||||
name: 'GooglePay',
|
||||
// WooCommerce configuration object.
|
||||
buttonConfig: window.wc_ppcp_googlepay_admin,
|
||||
// Internal widgetBuilder instance.
|
||||
widgetBuilder,
|
||||
// Default button styles.
|
||||
defaultAttributes,
|
||||
// Returns an async function that fetches the button configuration.
|
||||
cbFetchConfig: () => widgetBuilder?.paypal?.Googlepay()?.config,
|
||||
// Returns the CSS selector to render the given button.
|
||||
cbInitButton: (button) => {
|
||||
button.selector = `${button.wrapper}GooglePay`
|
||||
button.customClasses = 'ppcp-button-googlepay'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return managerInstance;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new object which contains a copy of all public properties of the
|
||||
* input object.
|
||||
*/
|
||||
const cloneObject = obj => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
/**
|
||||
* Convenience function to attach an event handler to the document node.
|
||||
* The callback handler will not receive the first argument (event), as this
|
||||
* argument is not used by our module.
|
||||
*/
|
||||
const onDocumentEvent = (name, handler) => jQuery(document).on(name, (ev, ...args) => handler(...args));
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* A single GooglePay preview button instance.
|
||||
*/
|
||||
class PreviewButton {
|
||||
constructor({wrapperSelector, configResponse, defaultAttributes, cbInitButton}) {
|
||||
// Do not clone this object. It's generated by an API and might contain methods.
|
||||
this.configResponse = configResponse;
|
||||
|
||||
this.defaultAttributes = cloneObject(defaultAttributes);
|
||||
this.buttonConfig = {};
|
||||
this.ppcpConfig = {};
|
||||
|
||||
this.wrapper = wrapperSelector;
|
||||
this.selector = wrapperSelector + 'Button';
|
||||
this.customClasses = '';
|
||||
|
||||
cbInitButton(this);
|
||||
|
||||
this.domWrapper = jQuery(this.selector);
|
||||
this.payButton = null;
|
||||
}
|
||||
|
||||
createNewWrapper() {
|
||||
const previewId = this.selector.replace('#', '')
|
||||
const previewClass = `ppcp-button-apm ${this.customClasses}`;
|
||||
|
||||
return jQuery(`<div id="${previewId}" class="${previewClass}">`)
|
||||
}
|
||||
|
||||
config(buttonConfig, ppcpConfig) {
|
||||
if (ppcpConfig) {
|
||||
this.ppcpConfig = cloneObject(ppcpConfig);
|
||||
}
|
||||
|
||||
if (buttonConfig) {
|
||||
this.buttonConfig = merge(this.defaultAttributes, buttonConfig)
|
||||
this.buttonConfig.button.wrapper = this.selector
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.remove();
|
||||
|
||||
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 = new GooglepayButton(
|
||||
'preview',
|
||||
null,
|
||||
this.buttonConfig,
|
||||
this.ppcpConfig,
|
||||
);
|
||||
|
||||
this.payButton.init(this.configResponse);
|
||||
return this;
|
||||
}
|
||||
|
||||
remove() {
|
||||
// The payButton has no remove/cleanup function.
|
||||
this.payButton = null;
|
||||
|
||||
if (this.domWrapper) {
|
||||
this.domWrapper.remove();
|
||||
this.domWrapper = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Manages all GooglePay preview buttons on this page.
|
||||
*/
|
||||
class PreviewButtonManager {
|
||||
constructor({
|
||||
name,
|
||||
buttonConfig,
|
||||
widgetBuilder,
|
||||
defaultAttributes,
|
||||
cbFetchConfig,
|
||||
cbInitButton
|
||||
}) {
|
||||
this.name = name;
|
||||
this.buttonConfig = buttonConfig;
|
||||
this.widgetBuilder = widgetBuilder;
|
||||
this.defaultAttributes = defaultAttributes;
|
||||
this.cbInitButton = cbInitButton;
|
||||
this.cbFetchConfig = cbFetchConfig;
|
||||
|
||||
this.state = 'enabled'
|
||||
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 createButton().
|
||||
this.bootstrapping = this.bootstrapping.then(() => this.bootstrap());
|
||||
|
||||
this.registerEventListeners();
|
||||
}
|
||||
|
||||
registerEventListeners() {
|
||||
onDocumentEvent('ppcp_paypal_render_preview', this.createButton.bind(this));
|
||||
onDocumentEvent('DOMContentLoaded', () => this.bootstrapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output an error message to the console, with a module-specific prefix.
|
||||
*/
|
||||
error(message, ...args) {
|
||||
console.error(`${this.name} ${message}`, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load dependencies and bootstrap the module.
|
||||
* Returns a Promise that resolves once all dependencies were loaded and the module can be
|
||||
* used without limitation.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async bootstrap() {
|
||||
if (!this.buttonConfig || !this.widgetBuilder) {
|
||||
this.error('Button could not be configured.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the custom SDK script.
|
||||
const customScriptPromise = loadCustomScript({url: this.buttonConfig.sdk_url});
|
||||
|
||||
// Wait until PayPal is ready.
|
||||
const paypalPromise = new Promise(resolve => {
|
||||
if (this.widgetBuilder.paypal) {
|
||||
resolve();
|
||||
} else {
|
||||
onDocumentEvent('ppcp-paypal-loaded', resolve);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all([customScriptPromise, paypalPromise]);
|
||||
|
||||
const fetchConfig = this.cbFetchConfig()
|
||||
if (!fetchConfig) {
|
||||
this.error('Button could not be initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
this.configResponse = await fetchConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
|
||||
*/
|
||||
createButton(ppcpConfig) {
|
||||
if (!ppcpConfig.button.wrapper) {
|
||||
this.error('Button did not provide a selector', ppcpConfig)
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapper = ppcpConfig.button.wrapper;
|
||||
|
||||
const createButtonInst = () => {
|
||||
if (!this.buttons[wrapper]) {
|
||||
this.buttons[wrapper] = new PreviewButton({
|
||||
wrapperSelector: wrapper,
|
||||
configResponse: this.configResponse,
|
||||
defaultAttributes: this.defaultAttributes,
|
||||
cbInitButton: this.cbInitButton
|
||||
});
|
||||
}
|
||||
|
||||
this.buttons[wrapper].config(
|
||||
this.buttonConfig,
|
||||
ppcpConfig
|
||||
).render()
|
||||
}
|
||||
|
||||
if (this.bootstrapping) {
|
||||
this.bootstrapping.then(createButtonInst);
|
||||
} else {
|
||||
createButtonInst();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the button configuration, and re-renders all buttons.
|
||||
*/
|
||||
updateConfig(newConfig) {
|
||||
if (!newConfig || 'object' !== typeof newConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buttonConfig = merge(this.buttonConfig, newConfig)
|
||||
|
||||
Object.values(this.buttons).forEach(button => button.config(this.buttonConfig))
|
||||
this.renderButtons()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refreshes all buttons using the latest buttonConfig.
|
||||
*/
|
||||
renderButtons() {
|
||||
if ('enabled' === this.state) {
|
||||
Object.values(this.buttons).forEach(button => button.render())
|
||||
} else {
|
||||
Object.values(this.buttons).forEach(button => button.remove())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables this payment method, which re-creates or refreshes all buttons.
|
||||
*/
|
||||
enable() {
|
||||
if ('enabled' === this.state) {
|
||||
return;
|
||||
}
|
||||
this.state = 'enabled';
|
||||
this.renderButtons()
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables this payment method, effectively removing all preview buttons.
|
||||
*/
|
||||
disable() {
|
||||
if ('disabled' === this.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = 'disabled';
|
||||
this.renderButtons()
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the preview button manager.
|
||||
buttonManager().enable()
|
||||
|
||||
// todo - Expose button manager for testing. Remove this!
|
||||
window.gpay = buttonManager()
|
||||
|
||||
|
||||
/**
|
||||
|
||||
(function ({
|
||||
buttonConfig,
|
||||
jQuery
|
||||
|
@ -47,11 +367,6 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi
|
|||
}, 100);
|
||||
});
|
||||
|
||||
/**
|
||||
* Decides, whether to display the Google Pay preview button.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
const shouldDisplayPreviewButton = function() {
|
||||
// TODO - original condition, which is wrong.
|
||||
return jQuery('#ppcp-googlepay_button_enabled').is(':checked');
|
||||
|
@ -154,3 +469,5 @@ import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/Wi
|
|||
buttonConfig: window.wc_ppcp_googlepay_admin,
|
||||
jQuery: window.jQuery
|
||||
});
|
||||
|
||||
// */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue