PageSourceSearch

https://www.balharbourshops.com/wp-content/themes/balharbourshops-…/product-display.js?v=1790910009&ver=6.9.9

js balharbourshops.com collected 2026-10-02 03:01:23 UTC 12,753 bytes, 416 lines download raw bytes

1(function ($) {
2	'use strict';
3
4	let hasUserInteracted = false;
5
6	function triggerCartButtonEvents() {
7		const element = document.querySelector('.wps-btn-cart');
8		if (element) {
9			// Dispatch mouse events for desktop
10			element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
11			element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
12			element.dispatchEvent(new MouseEvent('click', { bubbles: true }));
13
14			// Dispatch touch events for mobile devices
15			if ('ontouchstart' in window) {
16				element.dispatchEvent(new TouchEvent('touchstart', { bubbles: true, cancelable: true }));
17				element.dispatchEvent(new TouchEvent('touchend', { bubbles: true, cancelable: true }));
18			}
19		}
20	}
21
22	function handleFirstUserInteraction() {
23		if (!hasUserInteracted) {
24			hasUserInteracted = true;
25			setTimeout(() => {
26				triggerCartButtonEvents();
27			}, 300);
28
29			// Remove event listeners after first interaction
30			document.removeEventListener('click', handleFirstUserInteraction, true);
31			document.removeEventListener('touchstart', handleFirstUserInteraction, true);
32			document.removeEventListener('keydown', handleFirstUserInteraction, true);
33			document.removeEventListener('scroll', handleFirstUserInteraction, true);
34		}
35	}
36
37	const ProductDisplay = {
38		instances: {},
39
40		init: function () {
41			const self = this;
42
43			wp.hooks.addAction('on.itemsLoad', 'shopwp', function (payload, settings) {
44				handleFirstUserInteraction();
45			});
46
47			wp.hooks.addAction('on.afterPayloadUpdate', 'shopwp', function (itemsState) {
48				self.handleProductData(itemsState);
49			});
50		},
51
52		handleProductData: function (itemsState) {
53			const self = this;
54
55			if (!itemsState || !itemsState.edges || itemsState.edges.length === 0) {
56				return;
57			}
58
59			const product = itemsState.edges[0].node;
60
61			$('.bhs-product-display').each(function () {
62				const $container = $(this);
63				const uniqueId = $container.data('unique-id');
64				const config = window.productDisplayConfigs[uniqueId];
65
66				if (!config) return;
67
68				self.renderProduct($container, uniqueId, product, config);
69			});
70		},
71
72		renderProduct: function ($container, uniqueId, product, config) {
73			const self = this;
74
75			if (!self.instances[uniqueId]) {
76				self.instances[uniqueId] = {
77					allVariants: [],
78					selectedOption: null,
79					basePrice: 0
80				};
81			}
82
83			const instance = self.instances[uniqueId];
84
85			if (product.totalInventory <= 2 && product.totalInventory > 0) {
86				$container.find('.product__inventory').html(
87					'<span class="product__inventory--only">Hurry, only ' + product.totalInventory + ' left</span>'
88				);
89			}
90
91			if (config.showDescription && !config.customDescription) {
92				self.renderDescription($container, uniqueId, product.descriptionHtml);
93			}
94
95			if (config.showAvailability && !config.hideInStock) {
96				self.renderAvailability($container, uniqueId, product.availableForSale);
97			}
98
99			if (config.showTitle && !config.customTitle) {
100				if (product.vendor) {
101					$container.find('.product-single__title').html(product.vendor);
102				}
103				if (product.title) {
104					$container.find('.product-title-vendor-style').html(product.title);
105				}
106			}
107
108			if (config.showPrice && product.priceRange) {
109				self.renderPrice($container, product.priceRange);
110				instance.basePrice = product.priceRange.maxVariantPrice.amount;
111			}
112
113			if (!product.availableForSale) {
114				return;
115			}
116
117			const selectors = {};
118			let salePrice = 0;
119
120			product.variants.edges.forEach(function (variant) {
121				if (variant.node.availableForSale) {
122					instance.allVariants.push(variant.node);
123
124					if (variant.node.compareAtPriceV2 && variant.node.compareAtPriceV2.amount) {
125						if (salePrice > variant.node.compareAtPriceV2.amount || salePrice == 0) {
126							salePrice = variant.node.compareAtPriceV2.amount;
127						}
128					}
129
130					variant.node.selectedOptions.forEach(function (option) {
131						if (selectors[option.name]) {
132							if (!selectors[option.name].values.includes(option.value)) {
133								selectors[option.name].values.push(option.value);
134							}
135						} else {
136							selectors[option.name] = {
137								values: [option.value]
138							};
139						}
140					});
141				}
142			});
143
144			if (salePrice > 0) {
145				const formattedSalePrice = self.formatMoney(salePrice);
146				$container.find('.product__price').append(
147					'<span class="price-item sale-price-item">$' + formattedSalePrice + '</span>'
148				);
149				$container.find('.product__price').append(
150					'<span class="price__badge price__badge--sale"><span>Sale</span></span>'
151				);
152			}
153
154			if (config.showBuyButton) {
155				self.renderBuyForm($container, uniqueId, selectors, config.buttonText, config);
156			}
157		},
158
159		renderDescription: function ($container, uniqueId, descriptionHtml) {
160			if (!descriptionHtml) return;
161
162			const arrayDescription = descriptionHtml.split(/<h4[^>]*>|<\/h4>/).filter(String);
163
164			if (arrayDescription.length > 1) {
165				let tabs = $('<div class="station-tabs"></div>');
166				let count = 0;
167
168				arrayDescription.forEach(function (desc, i) {
169					if (i % 2 == 0) {
170						const actual = count;
171						const link = $('<a href="#tab-' + count + '" class="station-tabs-link"><span>' + desc + '</span></a>');
172						link.click(function (e) {
173							e.preventDefault();
174							ProductDisplay.setActiveTab($container, actual);
175						});
176						tabs.append(link);
177					} else {
178						tabs.append(
179							'<div class="station-tabs-content" id="st-tab-' + count + '">' +
180							'<div class="station-tabs-content-inner">' + desc + '</div></div>'
181						);
182						count++;
183					}
184				});
185
186				$container.find('#' + uniqueId + '_description').html(tabs);
187				this.setActiveTab($container, 0);
188			} else {
189				$container.find('#' + uniqueId + '_description').html(descriptionHtml);
190			}
191		},
192
193		setActiveTab: function ($container, index) {
194			$container.find('.station-tabs-link').removeClass('active');
195			$container.find('.station-tabs-link').eq(index).addClass('active');
196			$container.find('.station-tabs-content').hide();
197			$container.find('.station-tabs-content').eq(index).show();
198		},
199
200		renderAvailability: function ($container, uniqueId, availableForSale) {
201			const html = availableForSale
202				? '<span class="product-availability in-stock">In Stock</span>'
203				: '<span class="product-availability out-stock">Sold out</span>';
204			$container.find('#' + uniqueId + '_availability').html(html);
205		},
206
207		renderPrice: function ($container, priceRange) {
208			if (priceRange && priceRange.maxVariantPrice) {
209				const price = this.formatMoney(priceRange.maxVariantPrice.amount);
210				$container.find('.product__price').html(
211					'<span class="price-item">$' + price + '</span>'
212				);
213			}
214		},
215
216		renderBuyForm: function ($container, uniqueId, selectors, buttonText, config) {
217			const self = this;
218			const $buyContainer = $container.find('#' + uniqueId + '_buy');
219			const $form = $('<form method="post" class="form-product"></form>');
220
221			$container.find('.product__variant').html('');
222
223			// Ocultar variaciones si está configurado
224			const hideVariations = config.hideVariations || false;
225
226			if (selectors && Object.keys(selectors).length > 0 && !hideVariations) {
227				const entries = Object.entries(selectors).reverse();
228				const $controlsGroup = $('<div class="controls-group"></div>');
229
230				entries.forEach(function (sel) {
231					const options = '<option>' + sel[1].values.join('</option><option>') + '</option>';
232					const $controlOption = $('<div class="shop-option"></div>');
233					$controlOption.append('<label>' + sel[0] + '</label>');
234
235					const $selector = $('<select data-name="' + sel[0] + '" class="light-style" name="shop-' + sel[0] + '">' + options + '</select>');
236					$selector.change(function () {
237						self.onChangeOption($container, uniqueId);
238					});
239
240					$controlOption.append($selector);
241					$controlsGroup.append($controlOption);
242				});
243
244				$form.append($controlsGroup);
245			}
246
247			$form.append(
248				'<div class="control-holder">' +
249				'<div class="custom-form"><input type="number" name="quantity" value="1" min="1" class="quantity-input"></div>' +
250				'<button type="submit" class="btn btn-secondary" id="shop-add-to-cart-' + uniqueId + '">' +
251				'<span>' + buttonText + '</span>' +
252				'</button>' +
253				'</div>'
254			);
255
256			$buyContainer.html($form);
257
258			if (typeof jcf !== 'undefined') {
259				jcf.replace($form.find('input'));
260				jcf.replace($form.find('select'));
261			}
262
263			// Preseleccionar variante si está configurado
264			if (config.hideVariations && config.preselectedVariant) {
265				self.preselectVariant($container, uniqueId, config.preselectedVariant);
266			} else {
267				self.onChangeOption($container, uniqueId);
268			}
269
270			$form.submit(function (e) {
271				e.preventDefault();
272				self.addToCart($container, uniqueId);
273			});
274		},
275
276		onChangeOption: function ($container, uniqueId) {
277			const instance = this.instances[uniqueId];
278			instance.selectedOption = null;
279
280			const $button = $container.find('#shop-add-to-cart-' + uniqueId);
281			$button.prop('disabled', true);
282			$button.find('span').html('Sold out');
283
284			$container.find('.product__price').html(
285				'<span class="price-item">$' + this.formatMoney(instance.basePrice) + '</span>'
286			);
287
288			const selected = {};
289			$container.find('.controls-group select').each(function () {
290				const name = $(this).data('name');
291				const value = $(this).val();
292				selected[name] = value;
293			});
294
295			if (instance.allVariants.length == 1) {
296				instance.selectedOption = instance.allVariants[0];
297			} else {
298				instance.allVariants.forEach(function (variant) {
299					let equals = 0;
300					variant.selectedOptions.forEach(function (option) {
301						if (selected[option.name] && selected[option.name] == option.value) {
302							equals++;
303						}
304					});
305					if (equals == variant.selectedOptions.length) {
306						instance.selectedOption = variant;
307						return;
308					}
309				});
310			}
311
312			if (instance.selectedOption) {
313				$button.prop('disabled', false);
314				$button.find('span').html(window.productDisplayConfigs[uniqueId].buttonText);
315
316				if (instance.selectedOption.priceV2) {
317					const price = this.formatMoney(instance.selectedOption.priceV2.amount);
318					$container.find('.product__price').html(
319						'<span class="price-item">$' + price + '</span>'
320					);
321				}
322
323				if (instance.selectedOption.sku) {
324					$container.find('.product__variant-sku').html('SKU: ' + instance.selectedOption.sku);
325				}
326			}
327		},
328
329		addToCart: function ($container, uniqueId) {
330			const instance = this.instances[uniqueId];
331
332			if (!instance.selectedOption) {
333				return;
334			}
335
336			const quantity = parseInt($container.find('input[name="quantity"]').val()) || 1;
337
338			if (quantity > 0) {
339				const $button = $container.find('#shop-add-to-cart-' + uniqueId);
340				$button.prop('disabled', true);
341				$button.find('span').html('Adding...');
342
343				const params = {
344					lines: [
345						{
346							quantity: quantity,
347							merchandiseId: instance.selectedOption.id,
348						},
349					],
350					extras: {
351						openCartAfterAdding: true,
352					},
353				};
354
355				window.dataLayer = window.dataLayer || [];
356				window.dataLayer.push({
357					event: "add_to_cart",
358					element_id: "#shop-add-to-cart-" + uniqueId,
359					element_type: "add_to_cart",
360					params: params,
361				});
362
363				if (typeof wp !== 'undefined' && wp.hooks) {
364					wp.hooks.doAction('do.addToCart', params);
365				}
366
367				triggerCartButtonEvents();
368
369				setTimeout(function () {
370					$button.prop('disabled', false);
371					$button.find('span').html(window.productDisplayConfigs[uniqueId].buttonText);
372				}, 1000);
373			}
374		},
375
376		preselectVariant: function ($container, uniqueId, variantIndex) {
377			const instance = this.instances[uniqueId];
378			const index = parseInt(variantIndex) - 1;
379
380			if (instance.allVariants && instance.allVariants[index]) {
381				instance.selectedOption = instance.allVariants[index];
382
383				const $button = $container.find('#shop-add-to-cart-' + uniqueId);
384				$button.prop('disabled', false);
385				$button.find('span').html(window.productDisplayConfigs[uniqueId].buttonText);
386
387				if (instance.selectedOption.priceV2) {
388					const price = this.formatMoney(instance.selectedOption.priceV2.amount);
389					$container.find('.product__price').html(
390						'<span class="price-item">$' + price + '</span>'
391					);
392				}
393
394				if (instance.selectedOption.sku) {
395					$container.find('.product__variant-sku').html('SKU: ' + instance.selectedOption.sku);
396				}
397			} else {
398				if (instance.allVariants && instance.allVariants.length > 0) {
399					instance.selectedOption = instance.allVariants[0];
400					this.onChangeOption($container, uniqueId);
401				}
402			}
403		},
404
405		formatMoney: function (amount) {
406			return parseFloat(amount).toFixed(2);
407		}
408	};
409
410	$(document).ready(function () {
411		if (typeof wp !== 'undefined' && wp.hooks) {
412			ProductDisplay.init();
413		}
414	});
415
416})(jQuery);

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.