PageSourceSearch

https://www.ifpapinball.com/wp/wp-content/plugins/wp-express-checkout/assets/js/public.js?ver=2.5.2

js ifpapinball.com collected 2026-09-24 08:50:28 UTC 25,953 bytes, 704 lines download raw bytes

1var ppecHandler = function( data ) {
2	this.data = data;
3	this.actions = {};
4
5	const parent = this;
6
7	// TODO: Need to convert this to vanilla js
8	this.processPayment = function( payment, action ) {
9		jQuery.post( ppecFrontVars.ajaxUrl, {
10				'action': action,
11				wp_ppdg_payment: payment,
12				data: parent.data,
13				nonce: parent.data.nonce
14			} )
15			.done( function( data ) {
16				parent.completePayment( data );
17			} );
18	};
19
20	this.completePayment = function( data ) {
21		let ret = true;
22		let dlgTitle = ppecFrontVars.str.paymentCompleted;
23		let dlgMsg = ppecFrontVars.str.redirectMsg;
24		let redirect_url;
25		if ( data.redirect_url ) {
26			redirect_url = data.redirect_url;
27		} else {
28			dlgTitle = ppecFrontVars.str.errorOccurred;
29			dlgMsg = data;
30			ret = false;
31		}
32		document.querySelector( '.wp-ppec-overlay[data-ppec-button-id="' + parent.data.id + '"]' ).style.display = 'none';
33
34		const dialogMsgParagraph = document.createElement('p');
35		dialogMsgParagraph.id = 'wp-ppdg-dialog-msg';
36		dialogMsgParagraph.textContent = dlgMsg;
37		const dialog = document.createElement('div');
38		dialog.id = 'wp-ppdg-dialog-message';
39		dialog.title = dlgTitle;
40		dialog.appendChild(dialogMsgParagraph);
41
42		parent.scCont?.querySelector('.wp-ppec-button-container')?.insertAdjacentElement('afterend', dialog);
43
44		// Fade in dialog
45		dialog.style.display = 'none';
46		dialog.style.opacity = 0;
47		dialog.style.transition = 'opacity 0.5s';
48		dialog.style.display = 'block';
49		setTimeout(() => {
50			dialog.style.opacity = 1;
51		}, 10);
52
53		if ( redirect_url ) {
54			location.href = redirect_url;
55		}
56		return ret;
57	};
58
59	this.ValidatorTos = function( input ) {
60		return input.checked !== true ? ppecFrontVars.str.acceptTos : '';
61	};
62
63	this.ValidatorBilling = function( input ) {
64		let val;
65		if (input.type === 'checkbox'){
66			val = input.checked;
67		} else {
68			val = Boolean(input.value.trim())
69		}
70
71		return parent.isElementVisible(input) && !val ? ppecFrontVars.str.required : null;
72	};
73
74	this.isValidTotal = function() {
75		parent.calcTotal();
76		return !!parent.data.total;
77	};
78
79	this.ValidatorQuantity = function( input ) {
80		const val_orig = input.value;
81		const val = parseInt( val_orig );
82		let error = false;
83		if ( isNaN( val ) || ( val_orig % 1 !== 0 ) || val <= 0 ) {
84			error = ppecFrontVars.str.enterQuantity;
85		} else if ( parent.data.stock_enabled && val > parent.data.stock_items ) {
86			error = ppecFrontVars.str.stockErr.replace( '%d', parent.data.stock_items );
87		} else {
88			parent.data.quantity = val;
89		}
90		return error;
91	};
92
93	this.ValidatorAmount = function( input ) {
94		const val_orig = input.value;
95		const val = parseFloat( val_orig );
96		const min_val = input.getAttribute( 'min' );
97		let error = false;
98		const errMsg = ppecFrontVars.str.enterAmount;
99		if ( !isNaN( val ) && min_val <= val ) {
100			parent.data.orig_price = val;
101			parent.data.price = parent.applyVariations( val );
102		} else {
103			error = errMsg;
104		}
105		return error;
106	};
107
108	// if ( this.data.btnStyle.layout === 'horizontal' ) {
109	// 	this.data.btnStyle.tagline = false;
110	// }
111	//
112	// this.clientVars = {};
113	//
114	// this.clientVars[ this.data.env ] = this.data.client_id;
115
116	this.inputs = [];
117
118	this.validateInput = function( input, validator ) {
119		if ( !input ) {
120			return false;
121		}
122
123		this.inputs.push( [ input, validator ] );
124
125		input.addEventListener('change', function(e) {
126			parent.displayInputError( this, validator );
127			parent.validateOrder();
128		})
129	};
130
131	this.validateOrder = function() {
132		let isValid = true;
133
134		document.querySelectorAll('#wpec_billing_' + parent.data.id + ', #place-order-' + parent.data.id ).forEach(el => {
135			parent.toggleVisibility(el, 'block', !parent.isValidTotal());
136		})
137
138		parent.toggleVisibility(
139			document.getElementById( parent.data.id ),
140			'block',
141			!parent.isElementVisible(document.getElementById( 'place-order-' + parent.data.id ))
142		);
143
144		parent.inputs.forEach( function( inputArr ) {
145			const input = inputArr[ 0 ];
146			const validator = inputArr[ 1 ];
147			if ( validator( input ) ) {
148				isValid = false;
149			}
150		} );
151
152		if ( !parent.isValidTotal() ) {
153			isValid = false;
154		}
155
156		// TODO: this may not needed any more.
157		// if ( isValid ) {
158		// 	parent.actions?.enable();
159		// } else {
160		// 	parent.actions?.disable();
161		// }
162
163		parent.updateAllAmounts();
164
165		// Dispatch a custom event for addons to listen on 'validateOrder' function run.
166		document.dispatchEvent(new CustomEvent('wpec_validate_order', {
167			detail: {
168				ppecHandler: parent,
169				isValid: isValid,
170			}
171		}))
172		jQuery( document ).trigger( 'wpec_validate_order', [ parent ] ); // TODO: Need to remove this jquery version after all addon code is updated to the vanilla version.
173
174		return isValid;
175	};
176
177	this.displayInputError = function( input, validator ) {
178		const error = validator( input );
179		const errMsgCont = input.type === 'checkbox' || input.type === 'number' ? input.parentNode.parentNode.querySelector( '.wp-ppec-form-error-msg' ) : input.parentNode.querySelector( '.wp-ppec-form-error-msg' );
180
181		if (error && error.length){
182			input.classList.add('hasError');
183			errMsgCont.style.display = 'block';
184			errMsgCont.innerText = error;
185		}else {
186			input.classList.remove('hasError');
187			errMsgCont.style.display = 'none';
188			errMsgCont.innerText = '';
189		}
190	};
191
192	this.displayErrors = function() {
193		parent.inputs.forEach( function( input ) {
194			parent.displayInputError( input[ 0 ], input[ 1 ] );
195		} );
196	};
197
198	/**
199	 * @deprecated Use `getScCont()` instead.
200	 */
201	this.scCont = document.querySelector( '.wp-ppec-shortcode-container[data-ppec-button-id="' + parent.data.id + '"]' );
202
203	this.getScCont = function(){
204		return document.querySelector( '.wp-ppec-shortcode-container[data-ppec-button-id="' + parent.data.id + '"]' );
205	}
206
207	// TODO: Need to convert this to vanilla js
208	this.formatMoney = function( n ) {
209		var decimalPlaces = isNaN(decimalPlaces = Math.abs(parent.data.dec_num)) ? 2 : parent.data.dec_num;
210		var decimalSeparator = parent.data.dec_sep || ".";
211		var thousandSeparator = parent.data.thousand_sep || ",";
212		var sign = n < 0 ? "-" : "";
213		var integerPart = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(decimalPlaces)));
214		var integerPartLength = integerPart.length;
215		var remainderLength = integerPartLength > 3 ? integerPartLength % 3 : 0;
216
217		var formattedIntegerPart = sign +
218			(remainderLength ? integerPart.substr(0, remainderLength) + thousandSeparator : "") +
219			integerPart.substr(remainderLength).replace(/(\d{3})(?=\d)/g, "$1" + thousandSeparator) +
220			(decimalPlaces ? decimalSeparator + Math.abs(n - integerPart).toFixed(decimalPlaces).slice(2) : "");
221
222		var formats = {
223			left: '{symbol}{price}',
224			left_space: '{symbol} {price}',
225			right: '{price}{symbol}',
226			right_space: '{price} {symbol}'
227		};
228
229		var formattedPrice = formats[parent.data.curr_pos]
230			.replace('{symbol}', parent.data.currency_symbol)
231			.replace('{price}', formattedIntegerPart);
232
233		return formattedPrice;
234	};
235
236	this.getPriceContainer = function() {
237		// Here, we're using 'querySelectorAll' instead of 'querySelector', because there is more than one element.
238		return document.querySelectorAll( '.' + parent.scCont.getAttribute( 'data-price-class' ) );
239	};
240
241	this.getProductItemContainer = function() {
242		// Get the product item container using the price container, so that we don't select other product item container when more than one same product shortcode in a page.
243		const priceContainer = document.querySelector( '.' + parent.scCont.getAttribute( 'data-price-class' ) );
244
245		// Selector of the container may vary across different templates. Select the one it can find.
246
247		// For template 1,3
248		let productItemContainer = parent.closestParent(priceContainer, '.wpec-product-item-' + parent.data.product_id );
249
250		// For template 2
251		if (!productItemContainer){
252			productItemContainer = parent.closestParent(priceContainer, '.wpec-post-item-' + parent.data.product_id );
253		}
254
255		// For url payment form
256		if (!productItemContainer){
257			productItemContainer = parent.closestParent(priceContainer, '.wpec-modal-product-' + parent.data.product_id );
258		}
259
260		return productItemContainer;
261	};
262
263	this.updateAllAmounts = function() {
264		parent.calcTotal();
265
266		const productItemContainer = parent.getProductItemContainer();
267
268		if ( productItemContainer ){
269			productItemContainer.querySelectorAll( '.wpec-price-amount' ).forEach(el => el.textContent = parent.formatMoney( parent.data.price ))
270
271			if ( parent.data.quantity > 1 ) {
272				productItemContainer.querySelectorAll( '.wpec-quantity' ).forEach(el => el.style.display = 'inline')
273				productItemContainer.querySelectorAll( '.wpec-quantity-val' ).forEach(el => el.textContent = parent.data.quantity)
274			} else {
275				productItemContainer.querySelectorAll( '.wpec-quantity' ).forEach(el => el.style.display = 'none');
276			}
277
278			const total = productItemContainer.querySelectorAll( '.wpec_tot_current_price' );
279			const tot_new = productItemContainer.querySelectorAll( '.wpec_tot_new_price' );
280			const price_new = productItemContainer.querySelectorAll( '.wpec-new-price-amount' );
281
282			if ( typeof parent.data.discountAmount !== "undefined" ) {
283				price_new.forEach(el => el.textContent = parent.formatMoney( parent.data.newPrice ) );
284				tot_new.forEach(el => el.textContent = parent.formatMoney( parent.data.total ) );
285				total.forEach(el => el.textContent = parent.formatMoney( parent.data.subtotal ));
286			} else if ( total.length > 0 ) {
287				total.forEach(el => el.textContent = parent.formatMoney( parent.data.total ));
288			}
289
290			// Update the total shipping cost.
291			const shipping_new = productItemContainer.querySelectorAll( '.wpec_price_shipping_amount' );
292			shipping_new.forEach(el => el.textContent = parent.formatMoney(parent.getTotalShippingCost()));
293
294			// Update the total tax cost.
295			const tax_val = productItemContainer.querySelectorAll( '.wpec-tax-val' );
296			tax_val.forEach(el => el.textContent = parent.formatMoney( parent.data.tax_amount * parent.data.quantity ) );
297		}
298
299		// TODO: Need to convert this to vanilla js
300		jQuery( document ).trigger( 'wpec_after_update_all_amounts', [ parent ] );
301	};
302
303	this.calcTotal = function() {
304		let itemSubt = parseFloat( parent.data.price );
305		let quantity = parseInt( parent.data.quantity );
306		let tAmount = itemSubt * quantity;
307		//We need to round to 2 decimal places to make sure that the API call will not fail.
308		let roundedTotal = tAmount.toFixed(2);//round to 2 decimal places
309		let roundedTotalAsNumber = parseFloat(roundedTotal);//convert to number
310		tAmount = roundedTotalAsNumber;
311		let subtotal = tAmount;
312
313		parent.data.newPrice = itemSubt;
314
315		if ( typeof parent.data.discount !== "undefined" ) {
316			let discountAmount = 0;
317			if ( parent.data.discountType === 'perc' ) {
318				discountAmount = parent.PHP_round( tAmount * parent.data.discount / 100, parent.data.dec_num );
319			} else {
320				discountAmount = data.discount;
321			}
322			if ( discountAmount > tAmount ) {
323				discountAmount = tAmount;
324			}
325			tAmount = tAmount - discountAmount;
326			parent.data.discountAmount = parent.PHP_round( discountAmount, parent.data.dec_num );
327			parent.data.newPrice = parent.PHP_round( itemSubt - discountAmount / quantity, parent.data.dec_num );
328		}
329
330		if ( parent.data.tax ) {
331			let tax = parent.PHP_round( tAmount / quantity * parent.data.tax / 100, parent.data.dec_num );
332			parent.data.tax_amount = tax;
333			tAmount += tax * parent.data.quantity;
334			subtotal += parent.PHP_round( subtotal / quantity * parent.data.tax / 100, parent.data.dec_num ) * parent.data.quantity;
335		}
336
337		const totalShippingCost = parent.getTotalShippingCost();
338		if ( totalShippingCost ) {
339			tAmount += parseFloat( totalShippingCost );			
340			subtotal += parseFloat( totalShippingCost );
341		}
342		parent.data.total = parent.PHP_round( tAmount, parent.data.dec_num );
343		parent.data.subtotal = parent.PHP_round( subtotal, parent.data.dec_num );
344	};
345
346	this.PHP_round = function( num, dec ) {
347		return Math.round( num * Math.pow( 10, dec ) ) / Math.pow( 10, dec );
348	};
349
350	this.applyVariations = function( amount ) {
351		let grpId;
352		if ( parent.data.variations.applied ) {
353			for ( grpId = 0; grpId < parent.data.variations.applied.length; ++grpId ) {
354				let variation = parent.data.variations[ grpId ];
355				amount = amount + parseFloat( variation.prices[ parent.data.variations.applied[ grpId ] ] );
356			}
357		}
358		return parent.PHP_round( amount, parent.data.dec_num );
359	};
360
361	/**
362	 * Calculates the total shipping cost.
363	 * It calculates the per quantity shipping cost along with the base shipping cost of that product.
364	 * 
365	 * @returns {float} The total shipping cost in float of two decimal places. 
366	 */
367	this.getTotalShippingCost = function() {
368		let total = 0;
369		const quantity = parent.data.quantity ? parseInt(parent.data.quantity) : 1;
370		const baseShipping = parent.data.shipping ? parseFloat( parent.data.shipping ) : 0;
371		const shippingPerQuantity = parent.data.shipping_per_quantity ? parseFloat( parent.data.shipping_per_quantity ) : 0;
372	
373		total = baseShipping + (shippingPerQuantity * quantity);
374		// Round to 2 decimal places.
375		total = parseFloat(total.toFixed(2))
376		return total;
377	}
378
379	this.toggleVisibility = function (element, displayVisibilityType = 'block', condition = null) {
380		if (!element){
381			return;
382		}
383
384		// if no condition provided, then the condition will be based on whether the element is visible or not.
385		if (condition === null) {
386			// No specific condition provided.
387			// The content will only visible/invisible if its currently invisible/visible respectively.
388			condition = !parent.isElementVisible(element);
389		}
390
391		// Show / hide the element based on condition.
392		if (condition === true) {
393			element.style.display = displayVisibilityType;
394		} else {
395			element.style.display = 'none';
396		}
397	}
398
399	this.isElementVisible = function (element) {
400		// Check if element exists in the DOM and is an Element node
401		if (!(element instanceof Element)) {
402			return false;
403		}
404
405		// Check if the element has a non-zero bounding box (displayed and has size)
406		const style = getComputedStyle(element);
407		const hasVisibleDisplay = style.display !== 'none';
408		const hasVisibleVisibility = style.visibility !== 'hidden';
409		const hasNonZeroSize = element.offsetWidth > 0 && element.offsetHeight > 0;
410
411		return hasVisibleDisplay && hasVisibleVisibility && hasNonZeroSize;
412	}
413
414	this.closestParent = function (element, selector) {
415		// Traverse up the DOM tree
416		while (element) {
417			// Check if the current element matches the selector
418			if (element.matches(selector)) {
419				return element;
420			}
421			// Move to the parent element
422			element = element.parentElement;
423		}
424		// Return null if no matching parent is found
425		return null;
426	}
427
428	this.construct = function(){
429		parent.validateInput( document.getElementById( 'wpec-tos-' + parent.data.id ), parent.ValidatorTos );
430		parent.validateInput( document.querySelector( '#wp-ppec-custom-quantity[data-ppec-button-id="' + parent.data.id + '"]' ), parent.ValidatorQuantity );
431		parent.validateInput( document.querySelector( '#wp-ppec-custom-amount[data-ppec-button-id="' + parent.data.id + '"]' ), parent.ValidatorAmount );
432		document.querySelectorAll( '#wpec_billing_' + parent.data.id + ' .wpec_required' ).forEach( function(element) {
433			parent.validateInput( element, parent.ValidatorBilling );
434		} );
435		parent.data.orig_price = parseFloat( parent.data.price );
436		parent.scCont.querySelectorAll( 'select.wpec-product-variations-select, input.wpec-product-variations-select-radio' )?.forEach(function (item){
437			item.addEventListener('change', function (e){
438				const grpId = e.currentTarget.getAttribute( 'data-wpec-variations-group-id' );
439				const varId = e.currentTarget.value;
440				if ( Object.getOwnPropertyNames( parent.data.variations ).length !== 0 ) {
441					if ( !parent.data.variations.applied ) {
442						parent.data.variations.applied = [];
443					}
444					parent.data.variations.applied[ grpId ] = varId;
445					parent.data.price = parent.applyVariations( parent.data.orig_price );
446					parent.validateOrder();
447				}
448			})
449		});
450		parent.scCont.querySelectorAll( 'select.wpec-product-variations-select, input.wpec-product-variations-select-radio:checked' )?.forEach(el => {
451			// Dispatch change event to trigger any 'change' event listeners on this element
452			el.dispatchEvent( new Event('change') );
453		})
454
455		parent.scCont.querySelector( '.wpec_product_shipping_enable' )?.addEventListener( 'change', function() {
456			parent.toggleVisibility(parent.scCont.querySelector( '.wpec_shipping_address_container' ), 'inherit');
457			parent.scCont.querySelector( '.wpec_address_wrap' )?.classList.toggle('shipping_enabled');
458		} );
459
460		document.getElementById('place-order-' + parent.data.id)?.addEventListener('click', () => {
461			const hasInputErrors = this.checkAndShowInputErrors();
462			if (hasInputErrors){
463				return;
464			}
465
466			if ( !parent.data.total ) {
467				const same_shipping_billing_address_enabled = parent.scCont.querySelector( '.wpec_product_shipping_enable' )?.checked;
468				const billing_address = {
469					address_line_1: document.getElementById( 'wpec_billing_address-' + parent.data.id )?.value,
470					admin_area_1: document.getElementById( 'wpec_billing_state-' + parent.data.id )?.value,
471					admin_area_2: document.getElementById( 'wpec_billing_city-' + parent.data.id )?.value,
472					postal_code: document.getElementById( 'wpec_billing_postal_code-' + parent.data.id )?.value,
473					country_code: document.getElementById( 'wpec_billing_country-' + parent.data.id )?.value,
474				};
475				const shipping_address = same_shipping_billing_address_enabled ? billing_address : {
476					address_line_1: document.getElementById( 'wpec_shipping_address-' + parent.data.id )?.value,
477					admin_area_1: document.getElementById( 'wpec_shipping_state-' + parent.data.id )?.value,
478					admin_area_2: document.getElementById( 'wpec_shipping_city-' + parent.data.id )?.value,
479					postal_code: document.getElementById( 'wpec_shipping_postal_code-' + parent.data.id )?.value,
480					country_code: document.getElementById( 'wpec_shipping_country-' + parent.data.id )?.value,
481				};
482
483				const paymentData = {
484					payer: {
485						name: {
486							given_name: document.getElementById( 'wpec_billing_first_name-' + parent.data.id )?.value,
487							surname: document.getElementById( 'wpec_billing_last_name-' + parent.data.id )?.value,
488						},
489						email_address: document.getElementById( 'wpec_billing_email-' + parent.data.id )?.value,
490						address: billing_address,
491						shipping_address: shipping_address,
492					}
493				}
494
495				document.dispatchEvent(new CustomEvent('wpec_process_free_payment', {
496					detail: {
497						paymentData,
498						data: parent.data,
499					}
500				}));
501
502				parent.processPayment(paymentData, 'wpec_process_empty_payment' );
503			}
504		});
505
506		document.getElementById( 'wpec-redeem-coupon-btn-' + parent.data.id )?.addEventListener('click', async function (e) {
507			e.preventDefault();
508			const wpecCouponBtn = e.currentTarget;
509			const wpecCouponInputField = document.getElementById('wpec-coupon-field-' + parent.data.id)
510			const couponCode = wpecCouponInputField?.value;
511			if (couponCode.trim() === '') {
512				return false;
513			}
514			const wpecCouponSpinner = wpecCouponBtn.querySelector('svg');
515			wpecCouponBtn.setAttribute('disabled', true);
516			wpecCouponSpinner.style.display = 'inline';
517
518			const ajaxData = new URLSearchParams({
519				'action': 'wpec_check_coupon',
520				'product_id': parent.data.product_id,
521				'coupon_code': couponCode,
522				'curr': parent.data.currency,
523				/*'amount': parent.data.item_price,
524				'tax': parent.data.tax,
525				'shipping': parent.data.shipping*/
526			});
527			try {
528				const response = await fetch(ppecFrontVars.ajaxUrl, {
529					method: "post",
530					headers: {
531						'Content-Type': 'application/x-www-form-urlencoded'
532					},
533					body: ajaxData
534				});
535
536				const response_data = await response.json();
537
538				if (response_data.success) {
539					parent.data.discount = response_data.discount;
540					parent.data.discountType = response_data.discountType;
541					parent.data.couponCode = response_data.code;
542
543					document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = '<span class="wpec_coupon_code">' + response_data.discountStr + '</span><button class="wpec_coupon_apply_btn" id="wpec-remove-coupon-' + parent.data.id + '" title="' + ppecFrontVars.str.strRemoveCoupon + '">' + ppecFrontVars.str.strRemove + '</button>'
544					document.getElementById('wpec-redeem-coupon-btn-' + parent.data.id).style.display = 'none';
545					document.getElementById('wpec-coupon-field-' + parent.data.id).style.display = 'none';
546
547					const totalContainers = parent.getPriceContainer();
548					// Note: there is more than one price container in a single product shortcode.
549					totalContainers.forEach(function (totalCont){
550						let totCurr;
551						let totNew;
552						if (totalCont.querySelector('.wpec_price_full_total')) {
553							totCurr = totalCont.querySelector('span.wpec_tot_current_price')
554							totCurr?.classList.add('wpec_line_through');
555							totNew = totalCont.querySelector('span.wpec_tot_new_price');
556						} else {
557							totCurr = totalCont.querySelector('span.wpec_price_amount')
558							totCurr?.classList.add('wpec_line_through');
559							totNew = totalCont.querySelector('span.wpec_new_price_amount');
560						}
561
562						const priceCurr = totalCont.querySelector('span.wpec-price-amount')
563						priceCurr?.classList.add('wpec_line_through');
564
565						const priceNew = totalCont.querySelector('span.wpec-new-price-amount');
566						parent.validateOrder();
567
568						document.getElementById('wpec-remove-coupon-' + parent.data.id)?.addEventListener('click', function (e) {
569							e.preventDefault();
570							document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = '';
571							document.getElementById('wpec-coupon-field-' + parent.data.id).value = '';
572							document.getElementById('wpec-coupon-field-' + parent.data.id).style.display = 'block';
573							document.getElementById('wpec-redeem-coupon-btn-' + parent.data.id).style.display = 'block';
574							totCurr?.classList.remove('wpec_line_through');
575							priceCurr?.classList.remove('wpec_line_through');
576							if (totNew) {
577								totNew.innerHTML = '';
578							}
579							if (priceNew) {
580								priceNew.innerHTML = '';
581							}
582							delete parent.data.discount;
583							delete parent.data.discountType;
584							delete parent.data.couponCode;
585							delete parent.data.discountAmount;
586							delete parent.data.newPrice;
587							parent.validateOrder();
588						});
589					})
590				} else {
591					document.getElementById('wpec-coupon-info-' + parent.data.id).innerHTML = response_data.msg
592				}
593				wpecCouponSpinner.style.display = 'none';
594				wpecCouponBtn.removeAttribute('disabled');
595
596			} catch (err) {
597				alert(err.message);
598			}
599		})
600
601		const couponField = document.getElementById( 'wpec-coupon-field-' + parent.data.id );
602		couponField?.addEventListener( 'keydown', function( keyboardEvent ) {
603			if ( keyboardEvent.code === 13 ) {
604				keyboardEvent.preventDefault();
605				document.getElementById( 'wpec-redeem-coupon-btn-' + parent.data.id ).click();
606				return false;
607			}
608		} );
609
610		parent.validateOrder();
611
612		if ( parent.data.errors ) {
613			const errorEl = document.createElement('div');
614			errorEl.className = 'wp-ppec-form-error-msg';
615			errorEl.innerHTML = parent.data.errors;
616
617			parent.getScCont().appendChild( errorEl );
618			errorEl.style.display = 'block';
619		}
620
621		document.querySelectorAll( '.wpec-button-placeholder' ).forEach( function (el) {
622			el.remove()
623		})
624
625	}
626	this.construct(); // TODO: Need to move it to class constructor.
627
628
629	this.checkAndShowInputErrors = () => {
630		let result = false;
631
632		const wpecPlaceOrderButtonSection = document.getElementById("place-order-" + parent.data.id);
633		if (wpecPlaceOrderButtonSection) {
634			wpecPlaceOrderButtonSection.querySelector("button>svg").style.display = 'inline';
635			wpecPlaceOrderButtonSection.querySelector("button").setAttribute("disabled", true);
636		}
637		parent.displayErrors();
638		// Get first error input if there is any.
639		const errInput = parent.getScCont().querySelector('.hasError');
640		if (errInput) {
641			errInput.focus();
642			errInput.dispatchEvent(new Event('change'));
643
644			if (wpecPlaceOrderButtonSection) {
645				wpecPlaceOrderButtonSection.querySelector("button>svg").style.display = 'none';
646				wpecPlaceOrderButtonSection.querySelector("button").removeAttribute("disabled");
647			}
648
649			result = true;
650		}
651
652		return result;
653	}
654};
655
656const wpecModal = function( $ ) {
657	let openModalId = false;
658
659	document.querySelectorAll( '.wpec-modal-open, .wpec-modal-overlay, .wpec-modal-close' ).forEach(function (el){
660		el.addEventListener( 'click', function( e ) {
661			e.preventDefault();
662			toggleModal( this.getAttribute( 'data-wpec-modal' ) );
663		} )
664	});
665
666	document.onkeydown = function( evt ) {
667		evt = evt || window.event;
668		var isEscape = false;
669		if ( "key" in evt ) {
670			isEscape = ( evt.key === "Escape" || evt.key === "Esc" );
671		} else {
672			isEscape = ( evt.keyCode === 27 );
673		}
674		if ( isEscape && openModalId ) {
675			toggleModal();
676		}
677	};
678
679	function toggleModal( modalId ) {
680		modalId = modalId ? modalId : openModalId;
681		openModalId = modalId;
682		var modal = document.getElementById( modalId );
683		modal.classList.toggle( 'wpec-opacity-0' );
684		modal.classList.toggle( 'wpec-pointer-events-none' );
685	}
686
687	document.querySelectorAll('.wpec-custom-number-input button').forEach(button => {
688		button.addEventListener('click', function() {
689			const increment = this.getAttribute('data-action') === 'increment' ? 1 : -1;
690			const customQuantityInput = this.parentElement.querySelector('input');
691			const step = customQuantityInput.getAttribute('step') ? Number(customQuantityInput.getAttribute('step')) : 1;
692			customQuantityInput.value = Number(customQuantityInput.value) + increment * step;
693			customQuantityInput.dispatchEvent(new Event('change'));
694		});
695	});
696
697	// TODO: Need to convert this to vanilla js
698	//shortcode_wpec_show_all_products
699	$("#wpec-sort-by").change(function(){
700		$("#wpec-sort-by-form").submit();
701	});
702};
703
704jQuery( wpecModal );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.