PluginProbe
Tiered Pricing Table for WooCommerce / 7.1.5
Tiered Pricing Table for WooCommerce v7.1.5
8.0.2 7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 2.3.3 All 91 releases
tier-pricing-table / src / Addons / RequestAQuote / assets / js / quote-form.js

quote-form.js in Tiered Pricing Table for WooCommerce 7.1.5, at src/Addons/RequestAQuote/assets/js/quote-form.js

269 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(document).ready(function ($) {
2
3 if (window.tptQuoteFormConfig === undefined || !window.tptQuoteFormConfig.forms) {
4 return;
5 }
6
7 /**
8 * Represents a single Request a Quote modal form.
9 *
10 * @param {jQuery} $modal
11 * @param {int} productId
12 * @param {object} config
13 */
14 const RequestQuoteForm = function ($modal, productId, config) {
15 this.$modal = $modal;
16 this.productId = productId;
17 this.config = config;
18 this.state = {};
19 this.previousActiveElement = null;
20
21 this.init = function () {
22 this.bindEvents();
23 };
24
25 this.handleKeyDown = (e) => {
26 if (e.key === 'Escape' && this.isOpen()) {
27 this.close();
28 }
29 };
30
31 this.bindEvents = function () {
32 this.$modal.on('click', '.tpt-quote-modal-close', this.close.bind(this));
33
34 let isMouseDownOnOverlay = false;
35
36 this.$modal.on('mousedown', (e) => {
37 isMouseDownOnOverlay = (e.target === this.$modal[0]);
38 });
39
40 this.$modal.on('mouseup', (e) => {
41 if (isMouseDownOnOverlay && e.target === this.$modal[0]) {
42 this.close();
43 }
44 isMouseDownOnOverlay = false;
45 });
46
47 this.$modal.find('form').on('submit', this.submit.bind(this));
48 };
49
50 this.open = function () {
51 this.previousActiveElement = document.activeElement;
52 this.renderState();
53 this.$modal.show();
54
55 // Focus first input field for accessibility
56 setTimeout(() => {
57 this.$modal.find('input:visible, textarea:visible').not('[type="hidden"], .tpt-quote-modal-close').first().focus();
58 }, 50);
59
60 $(document).on('keydown', this.handleKeyDown);
61 };
62
63 this.close = function () {
64 this.$modal.hide();
65 $(document).off('keydown', this.handleKeyDown);
66
67 // Restore focus
68 if (this.previousActiveElement) {
69 this.previousActiveElement.focus();
70 }
71 };
72
73 this.updateState = function (newState) {
74 this.state = { ...this.state, ...newState };
75 if (this.isOpen()) {
76 this.renderState();
77 }
78 };
79
80 this.isOpen = function () {
81 return this.$modal.is(':visible');
82 };
83
84 this.renderState = function () {
85 const $fieldsContainer = this.$modal.find('.tpt-quote-fields-container');
86
87 if (this.state.quantity) {
88 const $qtyInputs = $fieldsContainer.find('input.tpt-quote-sync-quantity');
89 if ($qtyInputs.length) {
90 $qtyInputs.val(this.state.quantity);
91 }
92 }
93
94 const currentId = (this.state.variationId && this.state.variationId > 0) ? this.state.variationId : this.productId;
95 this.$modal.find('.tpt-quote-product-id').val(currentId);
96
97 if (this.state.priceHtml) {
98 this.$modal.find('.tpt-quote-product-info .price').html(this.state.priceHtml);
99 }
100 };
101
102 this.submit = function (e) {
103 e.preventDefault();
104 const $form = $(e.currentTarget);
105
106 const submitData = (token) => {
107 let formData = new FormData($form[0]);
108 if (token) {
109 formData.append('g-recaptcha-response', token);
110 }
111
112 const $submitBtn = $form.find('button[type="submit"]');
113 const originalBtnText = $submitBtn.text();
114
115 $.ajax({
116 url: window.tptQuoteFormConfig.restUrl,
117 method: 'POST',
118 data: formData,
119 processData: false,
120 contentType: false,
121 beforeSend: (xhr) => {
122 xhr.setRequestHeader('X-WP-Nonce', window.tptQuoteFormConfig.nonce);
123 const submittingText = window.tptQuoteFormConfig.i18n?.defaultSubmittingText || 'Submitting...';
124 $submitBtn.prop('disabled', true).text(submittingText);
125 this.$modal.find('.tpt-quote-form-message').empty();
126 },
127 success: (response) => {
128 const successAction = this.config.success_action || 'message';
129 if (successAction === 'redirect' && this.config.success_redirect_url) {
130 window.location.href = this.config.success_redirect_url;
131 } else {
132 const currentUrl = new URL(window.location.href);
133 currentUrl.searchParams.set('tier_pricing_table_quote_success', '1');
134 currentUrl.searchParams.set('form_id', this.config.id);
135 window.location.href = currentUrl.toString();
136 }
137 },
138 error: (xhr) => {
139 const msg = xhr.responseJSON?.message || 'An error occurred.';
140 this.$modal.find('.tpt-quote-form-message').html(`<div style="color: red; margin-bottom: 10px;">${msg}</div>`);
141 },
142 complete: () => {
143 $submitBtn.prop('disabled', false).text(originalBtnText);
144 }
145 });
146 };
147
148 if (this.config?.recaptcha_site_key && this.config?.recaptcha_secret_key && typeof grecaptcha !== 'undefined') {
149 grecaptcha.ready(() => {
150 grecaptcha.execute(this.config.recaptcha_site_key, { action: 'submit' }).then((token) => {
151 submitData(token);
152 });
153 });
154 } else {
155 submitData(null);
156 }
157 };
158 };
159
160 /**
161 * Manages all Request Quote forms on the page.
162 */
163 const RequestQuoteManager = function () {
164 this.forms = {};
165 this.autoOpened = false;
166
167 this.init = function () {
168 this.initializeForms();
169 this.bindGlobalEvents();
170 };
171
172 this.initializeForms = function () {
173 $('.tpt-quote-modal').each((index, el) => {
174 const $modal = $(el);
175 const productId = $modal.find('.tpt-quote-product-id').val();
176 const formId = $modal.find('.tpt-quote-form-id').val();
177
178 const config = window.tptQuoteFormConfig.forms.find(f => f.id == formId);
179 if (!config || !productId) return;
180
181 const quoteForm = new RequestQuoteForm($modal, productId, config);
182 quoteForm.init();
183
184 this.forms[productId] = quoteForm;
185 });
186 };
187
188 this.bindGlobalEvents = function () {
189 $(document).on('click', '.tpt-request-quote-trigger', (e) => {
190 e.preventDefault();
191 const productId = $(e.currentTarget).data('product-id');
192
193 if (this.forms[productId]) {
194 this.forms[productId].open();
195 }
196 });
197
198 $(document).on('tiered_price_update', (event, data) => {
199 if (!data || !data.__instance || !data.__instance.formatting) return;
200
201 const parentId = data.parentId;
202 const variationId = data.productId !== data.parentId ? data.productId : null;
203 const priceHtml = data.__instance.formatting.formatPrice(data.price);
204
205 const stateUpdate = {
206 variationId: variationId,
207 parentId: parentId,
208 quantity: data.quantity,
209 priceHtml: priceHtml
210 };
211
212 if (parentId && this.forms[parentId]) {
213 this.forms[parentId].updateState(stateUpdate);
214 if (data.quantity) {
215 this.handleAutoOpen(parseInt(data.quantity, 10), parentId);
216 }
217 }
218
219 if (variationId && this.forms[variationId]) {
220 this.forms[variationId].updateState(stateUpdate);
221 if (data.quantity) {
222 this.handleAutoOpen(parseInt(data.quantity, 10), variationId);
223 }
224 }
225 });
226 };
227
228 this.handleAutoOpen = function (qty, productId) {
229 if (this.autoOpened || isNaN(qty)) return;
230
231 let $trigger;
232 if (productId) {
233 $trigger = $(`.tpt-request-quote-trigger[data-product-id="${productId}"]`).first();
234 } else {
235 $trigger = $('.tpt-request-quote-trigger').first();
236 }
237
238 if ($trigger.length) {
239 const targetProductId = $trigger.data('product-id');
240 const form = this.forms[targetProductId];
241
242 if (form) {
243 const triggerAttr = $trigger.attr('data-auto-open-quantity');
244 let autoOpenQty = null;
245 if (triggerAttr !== undefined) {
246 // empty string means deactivated, otherwise parse it
247 autoOpenQty = triggerAttr === "" ? null : triggerAttr;
248 } else {
249 // fallback if attribute doesn't exist
250 autoOpenQty = form.config?.auto_open_quantity || null;
251 }
252
253 if (autoOpenQty) {
254 const threshold = parseInt(autoOpenQty, 10);
255 if (!isNaN(threshold) && threshold > 0 && qty >= threshold) {
256 this.autoOpened = true;
257 form.open();
258 }
259 }
260 }
261 }
262 };
263 };
264
265 // Initialize Manager
266 const manager = new RequestQuoteManager();
267 manager.init();
268 });
269