* Add paypal script reloading

* Add button reloading in cart and product page
* Add checking filters asynchronously
This commit is contained in:
Pedro Silva 2023-07-20 08:02:15 +01:00
parent e207a11b7e
commit 4e50d1d6ba
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
16 changed files with 626 additions and 529 deletions

View file

@ -194,9 +194,6 @@ const bootstrap = () => {
payNowBootstrap.init();
}
if (context !== 'checkout') {
messageRenderer.render();
}
};
document.addEventListener(
'DOMContentLoaded',

View file

@ -98,43 +98,38 @@ class SingleProductActionHandler {
}
}
getProducts()
{
if ( this.isBookingProduct() ) {
const id = document.querySelector('[name="add-to-cart"]').value;
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"))];
} else if ( this.isGroupedProduct() ) {
const products = [];
this.formElement.querySelectorAll('input[type="number"]').forEach((element) => {
if (! element.value) {
return;
}
const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/);
if (elementName.length !== 2) {
return;
}
const id = parseInt(elementName[1]);
const quantity = parseInt(element.value);
products.push(new Product(id, quantity, null));
})
return products;
} else {
const id = document.querySelector('[name="add-to-cart"]').value;
const qty = document.querySelector('[name="quantity"]').value;
const variations = this.variations();
return [new Product(id, qty, variations)];
}
}
createOrder()
{
this.cartHelper = null;
let getProducts = (() => {
if ( this.isBookingProduct() ) {
return () => {
const id = document.querySelector('[name="add-to-cart"]').value;
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"))];
}
} else if ( this.isGroupedProduct() ) {
return () => {
const products = [];
this.formElement.querySelectorAll('input[type="number"]').forEach((element) => {
if (! element.value) {
return;
}
const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/);
if (elementName.length !== 2) {
return;
}
const id = parseInt(elementName[1]);
const quantity = parseInt(element.value);
products.push(new Product(id, quantity, null));
})
return products;
}
} else {
return () => {
const id = document.querySelector('[name="add-to-cart"]').value;
const qty = document.querySelector('[name="quantity"]').value;
const variations = this.variations();
return [new Product(id, qty, variations)];
}
}
})();
return (data, actions) => {
this.errorHandler.clear();
@ -170,7 +165,7 @@ class SingleProductActionHandler {
});
};
return this.updateCart.update(onResolve, getProducts());
return this.updateCart.update(onResolve, this.getProducts());
};
}

View file

@ -40,11 +40,21 @@ class CartBootstrap {
return;
}
// handle script reload
const newParams = result.data.url_params;
const reloadRequired = this.gateway.url_params.intent !== newParams.intent;
const reloadRequired = JSON.stringify(this.gateway.url_params) !== JSON.stringify(newParams);
// TODO: should reload the script instead
setVisible(this.gateway.button.wrapper, !reloadRequired)
if (reloadRequired) {
this.gateway.url_params = newParams;
jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons', this.gateway);
}
// handle button status
if ( result.data.button ) {
this.gateway.button = result.data.button;
}
this.handleButtonStatus();
if (this.lastAmount !== result.data.amount) {
this.lastAmount = result.data.amount;

View file

@ -2,6 +2,8 @@ import UpdateCart from "../Helper/UpdateCart";
import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
import {hide, show} from "../Helper/Hiding";
import BootstrapHelper from "../Helper/BootstrapHelper";
import SimulateCart from "../Helper/SimulateCart";
import {strRemoveWord, strAddWord} from "../Helper/Utils";
class SingleProductBootstap {
constructor(gateway, renderer, messages, errorHandler) {
@ -38,10 +40,60 @@ class SingleProductBootstap {
this.handleButtonStatus();
}
handleButtonStatus() {
handleButtonStatus(simulateCart = true) {
BootstrapHelper.handleButtonStatus(this, {
formSelector: this.formSelector
});
if (simulateCart) {
//------
const actionHandler = new SingleProductActionHandler(
null,
null,
this.form(),
this.errorHandler,
);
(new SimulateCart(
this.gateway.ajax.simulate_cart.endpoint,
this.gateway.ajax.simulate_cart.nonce,
)).simulate((data) => {
this.messages.renderWithAmount(data.total);
let enableFunding = this.gateway.url_params['enable-funding'];
let disableFunding = this.gateway.url_params['disable-funding'];
for (const [fundingSource, funding] of Object.entries(data.funding)) {
if (funding.enabled === true) {
enableFunding = strAddWord(enableFunding, fundingSource);
disableFunding = strRemoveWord(disableFunding, fundingSource);
} else if (funding.enabled === false) {
enableFunding = strRemoveWord(enableFunding, fundingSource);
disableFunding = strAddWord(disableFunding, fundingSource);
}
}
if (
(enableFunding !== this.gateway.url_params['enable-funding']) ||
(disableFunding !== this.gateway.url_params['disable-funding'])
) {
this.gateway.url_params['enable-funding'] = enableFunding;
this.gateway.url_params['disable-funding'] = disableFunding;
jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons');
}
if (typeof data.button.is_disabled === 'boolean') {
this.gateway.button.is_disabled = data.button.is_disabled;
}
this.handleButtonStatus(false);
}, actionHandler.getProducts());
//------
}
}
init() {
@ -53,12 +105,6 @@ class SingleProductBootstap {
form.addEventListener('change', () => {
this.handleChange();
setTimeout(() => { // Wait for the DOM to be fully updated
// For the moment renderWithAmount should only be done here to prevent undesired side effects due to priceAmount()
// not being correctly formatted in some cases, can be moved to handleButtonStatus() once this issue is fixed
this.messages.renderWithAmount(this.priceAmount());
}, 100);
});
this.mutationObserver.observe(form, { childList: true, subtree: true });

View file

@ -0,0 +1,48 @@
class SimulateCart {
constructor(endpoint, nonce)
{
this.endpoint = endpoint;
this.nonce = nonce;
}
/**
*
* @param onResolve
* @param {Product[]} products
* @returns {Promise<unknown>}
*/
simulate(onResolve, products)
{
return new Promise((resolve, reject) => {
fetch(
this.endpoint,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.nonce,
products,
})
}
).then(
(result) => {
return result.json();
}
).then((result) => {
if (! result.success) {
reject(result.data);
return;
}
const resolved = onResolve(result.data);
resolve(resolved);
})
});
}
}
export default SimulateCart;

View file

@ -0,0 +1,41 @@
export const toCamelCase = (str) => {
return str.replace(/([-_]\w)/g, function(match) {
return match[1].toUpperCase();
});
}
export const keysToCamelCase = (obj) => {
let output = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
output[toCamelCase(key)] = obj[key];
}
}
return output;
}
export const strAddWord = (str, word, separator = ',') => {
let arr = str.split(separator);
if (!arr.includes(word)) {
arr.push(word);
}
return arr.join(separator);
};
export const strRemoveWord = (str, word, separator = ',') => {
let arr = str.split(separator);
let index = arr.indexOf(word);
if (index !== -1) {
arr.splice(index, 1);
}
return arr.join(separator);
};
const Utils = {
toCamelCase,
keysToCamelCase,
strAddWord,
strRemoveWord
};
export default Utils;

View file

@ -44,7 +44,7 @@ class MessageRenderer {
shouldRender() {
if (typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) {
if (typeof paypal === 'undefined' || typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) {
return false;
}
if (! document.querySelector(this.config.wrapper)) {

View file

@ -1,4 +1,6 @@
import merge from "deepmerge";
import {loadScript} from "@paypal/paypal-js";
import {keysToCamelCase} from "../Helper/Utils";
class Renderer {
constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) {
@ -10,6 +12,8 @@ class Renderer {
this.buttonsOptions = {};
this.onButtonsInitListeners = {};
this.activeButtons = {};
this.renderedSources = new Set();
}
@ -68,32 +72,61 @@ class Renderer {
}
renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) {
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) || 'undefined' === typeof paypal.Buttons ) {
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) {
return;
}
console.log('rendering', wrapper);
if (fundingSource) {
contextConfig.fundingSource = fundingSource;
}
const btn = paypal.Buttons({
style,
...contextConfig,
onClick: this.onSmartButtonClick,
onInit: (data, actions) => {
if (this.onSmartButtonsInit) {
this.onSmartButtonsInit(data, actions);
}
this.handleOnButtonsInit(wrapper, data, actions);
},
});
if (!btn.isEligible()) {
return;
const buttonsOptions = () => {
return {
style,
...contextConfig,
onClick: this.onSmartButtonClick,
onInit: (data, actions) => {
if (this.onSmartButtonsInit) {
this.onSmartButtonsInit(data, actions);
}
this.handleOnButtonsInit(wrapper, data, actions);
},
}
}
btn.render(wrapper);
const buildButtons = (paypal) => {
const btn = paypal.Buttons(buttonsOptions());
this.activeButtons[wrapper] = btn;
if (!btn.isEligible()) {
return;
}
btn.render(wrapper);
}
jQuery(wrapper).off('ppcp-reload-buttons');
jQuery(wrapper).on('ppcp-reload-buttons', (event, settingsOverride = {}) => {
const settings = merge(this.defaultSettings, settingsOverride);
const scriptOptions = keysToCamelCase(settings.url_params);
// if (this.activeButtons[wrapper]) {
// this.activeButtons[wrapper].close();
// }
loadScript(scriptOptions).then((paypal) => {
buildButtons(paypal);
});
});
this.renderedSources.add(wrapper + fundingSource ?? '');
if (typeof paypal !== 'undefined' && typeof paypal.Buttons !== 'undefined') {
buildButtons(paypal);
}
}
isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) {
@ -101,9 +134,10 @@ class Renderer {
// this will reduce the risk of breaking with different themes/plugins
// and on the cart page (where we also do not need to render separately), which may fully reload this part of the page.
// Ideally we should also find a way to detect such full reloads and remove the corresponding keys from the set.
if (!hasEnabledSeparateGateways) {
return document.querySelector(wrapper).hasChildNodes();
}
// if (!hasEnabledSeparateGateways) {
// return document.querySelector(wrapper).hasChildNodes();
// }
return this.renderedSources.has(wrapper + fundingSource ?? '');
}