PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.2.7
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.2.7
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
merchant / assets / js / modules / quick-view / quick-view.js

quick-view.js in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.2.7, at assets/js/modules/quick-view/quick-view.js

330 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 'use strict';
2
3 var merchant = merchant || {};
4 merchant.modules = merchant.modules || {};
5 (function ($) {
6 merchant.modules.quickView = {
7 init: function init() {
8 var self = this;
9 var $modals = $('.merchant-quick-view-modal');
10 if (!$modals.length) {
11 return;
12 }
13 $modals.each(function () {
14 var $modal = $(this);
15 var $closeButton = $modal.find('.merchant-quick-view-close-button');
16 var $inner = $modal.find('.merchant-quick-view-inner');
17 var $content = $modal.find('.merchant-quick-view-content');
18 var $overlay = $modal.find('.merchant-quick-view-overlay');
19 var isOpen = false;
20 $(document).on('click', '.merchant-quick-view-button', function (e) {
21 e.preventDefault();
22 $content.empty();
23 $modal.addClass('merchant-show');
24 $modal.addClass('merchant-loading');
25 isOpen = true;
26 $.post(window.merchant.setting.ajax_url, {
27 action: 'merchant_quick_view_content',
28 nonce: window.merchant.setting.nonce,
29 product_id: $(this).data('product-id')
30 }, function (response) {
31 if (response.success && isOpen) {
32 $content.html(response.data);
33 $inner.addClass('merchant-show');
34 $modal.removeClass('merchant-loading');
35 var $gallery = $content.find('.woocommerce-product-gallery');
36 wc_single_product_params.zoom_enabled = window.merchant.setting.quick_view_zoom;
37 if (typeof $.fn.wc_product_gallery === 'function' && $gallery.length) {
38 $gallery.trigger('wc-product-gallery-before-init', [$gallery.get(0), wc_single_product_params]);
39
40 // Force-enable the gallery slider in case the theme/plugin has removed it using `remove_theme_support( 'wc-product-gallery-slider' )`
41 wc_single_product_params['flexslider_enabled'] = '1';
42 $gallery.wc_product_gallery(wc_single_product_params);
43 $gallery.trigger('wc-product-gallery-after-init', [$gallery.get(0), wc_single_product_params]);
44 }
45 var $variations = $content.find('.variations_form');
46 if (typeof $.fn.wc_variation_form === 'function' && $variations.length) {
47 $variations.each(function () {
48 $(this).wc_variation_form();
49 });
50 }
51 if (window.botiga && window.botiga.productSwatch) {
52 window.botiga.productSwatch.init();
53 }
54 if (window.botiga && window.botiga.qtyButton) {
55 window.botiga.qtyButton.init('quick-view');
56 }
57
58 // Initialize AJAX add to cart if enabled.
59 if (window.merchant.setting.ajax_add_to_cart) {
60 self.initAjaxAddToCart($content, $modal);
61 }
62 } else {
63 $content.html(response.data);
64 $inner.addClass('merchant-show');
65 $modal.removeClass('merchant-loading');
66 }
67 window.dispatchEvent(new Event('merchant.quickview.ajax.loaded'));
68 }).fail(function (xhr, textStatus) {
69 $content.html(textStatus);
70 $inner.addClass('merchant-show');
71 $modal.removeClass('merchant-loading');
72 });
73 });
74 $overlay.on('click', function (e) {
75 e.preventDefault();
76 $closeButton.trigger('click');
77 });
78 $closeButton.on('click', function (e) {
79 e.preventDefault();
80 isOpen = false;
81 $modal.removeClass('merchant-show');
82 $inner.removeClass('merchant-show');
83 });
84 });
85 },
86 /**
87 * Initialize AJAX add to cart handler.
88 *
89 * @param {jQuery} $content The modal content container.
90 * @param {jQuery} $modal The modal container.
91 */
92 initAjaxAddToCart: function initAjaxAddToCart($content, $modal) {
93 var self = this;
94
95 // Remove any existing handlers to prevent duplicates.
96 $content.off('submit', 'form.cart');
97
98 // Handle add to cart form submission.
99 $content.on('submit', 'form.cart', function (e) {
100 // Access the native submitter.
101 var submitter = e.originalEvent ? e.originalEvent.submitter : null;
102 var $submitter = $(submitter);
103 var isAddToCart = false;
104
105 // 1. If triggered by Enter key (no submitter), assume Add to Cart (standard behavior)
106 // Note: You can add extra checks here for document.activeElement if you want to be extra safe for "Enter on Buy Now",
107 // but typically Enter in input fields implies the default action (Add to Cart).
108 if (!submitter) {
109 isAddToCart = true;
110 }
111 // 2. Check if submitter is explicitly the "Add to Cart" button
112 // WooCommerce standard Add to Cart usually has name="add-to-cart" or class="single_add_to_cart_button"
113 else if ($submitter.attr('name') === 'add-to-cart' || $submitter.hasClass('single_add_to_cart_button')) {
114 isAddToCart = true;
115 }
116
117 // If it's NOT an Add to Cart action (e.g. it's a Buy Now button), allow default submission
118 if (!isAddToCart) {
119 return;
120 }
121 e.preventDefault();
122 e.stopImmediatePropagation(); // Prevent other handlers from firing.
123
124 var $form = $(this);
125 var $button = $form.find('button[type="submit"], input[type="submit"]');
126 var $addToCartButton = $form.find('.single_add_to_cart_button');
127
128 // Use add to cart button if available, otherwise use first submit button.
129 if (!$addToCartButton.length) {
130 $addToCartButton = $button.first();
131 }
132
133 // Prevent duplicate submissions.
134 if ($addToCartButton.hasClass('loading') || $addToCartButton.prop('disabled')) {
135 return false;
136 }
137
138 // Get form data.
139 var formData = $form.serializeArray();
140 var data = {
141 action: 'merchant_quick_view_add_to_cart',
142 nonce: window.merchant.setting.nonce
143 };
144
145 // Check if this is a grouped product (has quantity fields with array notation like quantity[14]).
146 var isGroupedProduct = false;
147 $.each(formData, function (i, field) {
148 if (field.name && field.name.indexOf('quantity[') === 0) {
149 isGroupedProduct = true;
150 return false; // Break the loop.
151 }
152 });
153
154 // Convert form data to object, but handle quantity specially.
155 $.each(formData, function (i, field) {
156 // Skip standalone quantity field - we'll handle it separately for non-grouped products.
157 if (field.name === 'quantity') {
158 return;
159 }
160 // Skip add-to-cart field - we'll handle product ID separately.
161 if (field.name === 'add-to-cart') {
162 return;
163 }
164 // For grouped products, quantity[ID] fields are added as-is.
165 // Variation attributes are sent as-is (attribute_pa_color, etc.).
166 data[field.name] = field.value;
167 });
168
169 // Get product ID - try multiple sources (most reliable first).
170 // 1. Extract from product wrapper div ID (format: product-{id}) - this is always present in the modal.
171 var $productWrapper = $form.closest('[id^="product-"]');
172 if ($productWrapper.length) {
173 var productIdMatch = $productWrapper.attr('id').match(/product-(\d+)/);
174 if (productIdMatch && productIdMatch[1]) {
175 data.product_id = productIdMatch[1];
176 }
177 }
178
179 // 2. Try to get it directly from the form input.
180 if (!data.product_id) {
181 var addToCartInput = $form.find('input[name="add-to-cart"]');
182 if (addToCartInput.length && addToCartInput.val()) {
183 data.product_id = addToCartInput.val();
184 }
185 }
186
187 // Ensure product_id is set, otherwise we can't proceed.
188 if (!data.product_id) {
189 console.error('Quick View: Could not determine product ID');
190 return false;
191 }
192
193 // Get variation ID if it's a variable product.
194 if ($form.find('.variations_form').length) {
195 var variationId = $form.find('input[name="variation_id"]').val();
196 if (variationId) {
197 data.variation_id = parseInt(variationId, 10);
198 }
199 }
200
201 // Get quantity and ensure it's a number (not a string).
202 // Only add standalone quantity for non-grouped products.
203 // Grouped products use quantity[product_id] format which is already in the data object.
204 if (!isGroupedProduct) {
205 delete data.quantity; // Remove if it was included in serialized data.
206 var quantityInput = $form.find('input[name="quantity"]');
207 var quantity = quantityInput.length ? parseInt(quantityInput.val(), 10) : 1;
208 if (isNaN(quantity) || quantity < 1) {
209 quantity = 1;
210 }
211 data.quantity = quantity;
212 } else {
213 // For grouped products, ensure no standalone quantity field exists.
214 delete data.quantity;
215 }
216
217 // Show loading state.
218 var originalText = $addToCartButton.html();
219 var originalDisabled = $addToCartButton.prop('disabled');
220 $addToCartButton.addClass('loading').prop('disabled', true);
221 if ($addToCartButton.is('button')) {
222 $addToCartButton.data('original-text', originalText);
223 $addToCartButton.html('<span class="spinner"></span>');
224 }
225
226 // Send AJAX request.
227 $.ajax({
228 type: 'POST',
229 url: window.merchant.setting.ajax_url,
230 data: data,
231 success: function success(response) {
232 self.handleAddToCartSuccess(response, $addToCartButton, originalText, originalDisabled, $content, $modal);
233 },
234 error: function error(xhr, textStatus, errorThrown) {
235 self.handleAddToCartError(xhr, textStatus, errorThrown, $addToCartButton, originalText, originalDisabled, $content);
236 }
237 });
238 return false;
239 });
240 },
241 /**
242 * Handle successful add to cart response.
243 *
244 * @param {Object} response AJAX response.
245 * @param {jQuery} $button The add to cart button.
246 * @param {string} originalText Original button text.
247 * @param {boolean} originalDisabled Original disabled state.
248 * @param {jQuery} $content The modal content container.
249 * @param {jQuery} $modal The modal container.
250 */
251 handleAddToCartSuccess: function handleAddToCartSuccess(response, $button, originalText, originalDisabled, $content, $modal) {
252 // Remove loading state.
253 $button.removeClass('loading').prop('disabled', originalDisabled);
254 if ($button.is('button') && $button.data('original-text')) {
255 $button.html($button.data('original-text'));
256 }
257 if (response.success && response.data) {
258 // Update cart fragments.
259 if (response.data.fragments) {
260 $.each(response.data.fragments, function (selector, html) {
261 $(selector).replaceWith(html);
262 });
263 }
264
265 // Trigger WooCommerce events.
266 $(document.body).trigger('added_to_cart', [response.data.fragments, response.data.cart_hash, $button, 'quick-view']);
267 $(document.body).trigger('wc_fragment_refresh');
268
269 // Show success message.
270 if (response.data.message) {
271 // Remove existing notices.
272 $content.find('.merchant-quick-view-inner .woocommerce-error, .woocommerce-message, .woocommerce-info').remove();
273 // trigger event for third party integration
274 $(document).trigger('merchant_quick_view_add_to_cart_success', [response.data.message, $content]);
275 }
276 } else {
277 // Handle unexpected response.
278 this.handleAddToCartError(null, 'error', 'Unexpected response format', $button, originalText, originalDisabled, $content);
279 }
280 },
281 /**
282 * Handle add to cart error response.
283 *
284 * @param {Object} xhr XMLHttpRequest object.
285 * @param {string} textStatus Status text.
286 * @param {string} errorThrown Error thrown.
287 * @param {jQuery} $button The add to cart button.
288 * @param {string} originalText Original button text.
289 * @param {boolean} originalDisabled Original disabled state.
290 * @param {jQuery} $content The modal content container.
291 */
292 handleAddToCartError: function handleAddToCartError(xhr, textStatus, errorThrown, $button, originalText, originalDisabled, $content) {
293 // Remove loading state.
294 $button.removeClass('loading').prop('disabled', originalDisabled);
295 if ($button.is('button') && $button.data('original-text')) {
296 $button.html($button.data('original-text'));
297 }
298
299 // Get error message.
300 var errorMessage = '';
301 if (xhr && xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
302 errorMessage = xhr.responseJSON.data.message;
303 } else if (xhr && xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.notices) {
304 // Extract error from notices.
305 var notices = xhr.responseJSON.data.notices;
306 if (notices.error && notices.error.length > 0) {
307 errorMessage = notices.error[0].notice;
308 }
309 } else {
310 errorMessage = errorThrown || textStatus || 'An error occurred while adding the product to cart.';
311 }
312
313 // Remove existing notices.
314 $content.find('.woocommerce-error, .woocommerce-message, .woocommerce-info').remove();
315
316 // Add error message.
317 var $error = $('<div class="woocommerce-error" role="alert"></div>').text(errorMessage);
318 $content.find('form.cart').before($error);
319
320 // Scroll to error if needed.
321 $error[0].scrollIntoView({
322 behavior: 'smooth',
323 block: 'nearest'
324 });
325 }
326 };
327 $(document).ready(function () {
328 merchant.modules.quickView.init();
329 });
330 })(jQuery);