PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.14
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.14
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
king-addons / includes / widgets / Pricing_Calculator / script.js

script.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.14, at includes/widgets/Pricing_Calculator/script.js

1,064 lines 54.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Pricing Calculator
3 * JavaScript functionality for the Pricing Calculator widget
4 */
5 (function($) {
6 "use strict";
7
8 /**
9 * Initialize the pricing calculator
10 */
11 var initPricingCalculator = function() {
12 $('.king-pricing-calculator').each(function() {
13 var $calculator = $(this);
14 var calculatorId = $calculator.attr('id');
15
16 if ($calculator.data('initialized')) {
17 return;
18 }
19
20 $calculator.data('initialized', true);
21
22 // Calculator settings
23 var basePrice = parseFloat($calculator.data('base-price')) || 0;
24 var decimalPlaces = parseInt($calculator.data('decimal-places')) || 2;
25 var thousandSeparator = $calculator.data('thousand-separator') || ',';
26 var decimalSeparator = $calculator.data('decimal-separator') || '.';
27 var pricePrefix = $calculator.data('price-prefix') || '';
28 var priceSuffix = $calculator.data('price-suffix') || '';
29
30 // Live calculation mode
31 var isLiveMode = $calculator.hasClass('king-pricing-calculator--live');
32
33 // Cache selectors
34 var $fields = $calculator.find('.king-pricing-calculator__field');
35 var $totalPrice = $calculator.find('.king-pricing-calculator__total-price');
36 var $summaryItems = $calculator.find('.king-pricing-calculator__summary-items');
37 var $calculateButton = $calculator.find('.king-pricing-calculator__calculate-button');
38
39 // Debug info - log initial calculator settings
40 console.log('Calculator initialized with base price:', basePrice);
41
42 // Get ajaxurl from localized script if it exists, fallback to global
43 var ajaxUrl = (typeof king_addons_calculator_vars !== 'undefined') ? king_addons_calculator_vars.ajaxurl : (typeof ajaxurl !== 'undefined' ? ajaxurl : '');
44 // Get nonces from localized script
45 var addToCartNonce = (typeof king_addons_calculator_vars !== 'undefined') ? king_addons_calculator_vars.add_to_cart_nonce : '';
46 var sendEmailQuoteNonce = (typeof king_addons_calculator_vars !== 'undefined') ? king_addons_calculator_vars.send_email_quote_nonce : '';
47
48 // Debounce function for performance
49 var debounce = function(func, wait) {
50 var timeout;
51 return function() {
52 var context = this, args = arguments;
53 clearTimeout(timeout);
54 timeout = setTimeout(function() {
55 func.apply(context, args);
56 }, wait);
57 };
58 };
59
60 // Initialize field values and events
61 initFields($fields);
62
63 // Add accessibility attributes
64 addAccessibilityAttributes();
65
66 // Initial calculation
67 calculatePrice();
68
69 // Calculate button click event
70 if ($calculateButton.length && !isLiveMode) {
71 $calculateButton.on('click', calculatePrice);
72 }
73
74 // Initialize Pro feature handlers if Pro is active
75 if ($calculator.hasClass('king-pricing-calculator--pro')) {
76 initProFeatures($calculator);
77 }
78
79 /**
80 * Add ARIA attributes for accessibility
81 */
82 function addAccessibilityAttributes() {
83 // Range sliders
84 $calculator.find('input[type="range"]').each(function() {
85 var $range = $(this);
86 var fieldLabel = $range.closest('.king-pricing-calculator__field').find('.king-pricing-calculator__field-label').text();
87
88 $range.attr({
89 'role': 'slider',
90 'aria-label': fieldLabel,
91 'aria-valuemin': $range.attr('min'),
92 'aria-valuemax': $range.attr('max'),
93 'aria-valuenow': $range.val()
94 });
95
96 // Update aria values on change
97 $range.on('input change', function() {
98 $range.attr('aria-valuenow', $range.val());
99 });
100 });
101 }
102
103 /**
104 * Initialize calculator fields with events
105 */
106 function initFields($fields) {
107 $fields.each(function() {
108 var $field = $(this);
109 var fieldType = $field.data('field-type');
110
111 // Debug info - log field attributes
112 console.log('Field:', fieldType, 'Price Type:', $field.data('price-type'), 'Price:', $field.data('price'));
113
114 // Check if custom formula is used (Pro feature)
115 if ($field.data('price-type') === 'custom') {
116 // Show a one-time console message
117 if (!window.customFormulaMessageShown) {
118 console.info('Custom formula calculation requires Pro version.');
119 window.customFormulaMessageShown = true;
120 }
121 }
122
123 switch (fieldType) {
124 case 'number':
125 var $input = $field.find('input[type="number"]');
126
127 // Ensure default value is set
128 if ($input.val() === '') {
129 $input.val($input.attr('min') || 0);
130 }
131
132 // Debounce input for better performance
133 $input.on('input change', debounce(function() {
134 // Validate input
135 var value = parseFloat($input.val());
136 var min = parseFloat($input.attr('min'));
137 var max = parseFloat($input.attr('max'));
138
139 if (isNaN(value)) {
140 $input.val(min || 0);
141 value = parseFloat($input.val());
142 }
143
144 if (!isNaN(min) && value < min) {
145 $input.val(min);
146 } else if (!isNaN(max) && value > max) {
147 $input.val(max);
148 }
149
150 if (isLiveMode) {
151 calculatePrice();
152 }
153 }, 300));
154 break;
155
156 case 'range':
157 var $range = $field.find('input[type="range"]');
158 var $value = $field.find('.king-pricing-calculator__range-value');
159
160 // Ensure default value is set
161 if ($range.val() === '') {
162 $range.val($range.attr('min') || 0);
163 $value.text($range.val());
164 }
165
166 // Set initial range progress
167 updateRangeProgress($range);
168
169 // Use both input and change events without debounce for immediate feedback
170 $range.on('input', function() {
171 $value.text($range.val());
172
173 // Update range progress visual
174 updateRangeProgress($range);
175
176 // Always calculate on range change regardless of live mode
177 calculatePrice();
178 });
179 break;
180
181 case 'select':
182 var $select = $field.find('select');
183
184 // Ensure the first option is selected by default
185 if (!$select.find('option:selected').length) {
186 $select.find('option:first').prop('selected', true);
187 }
188
189 $select.on('change', function() {
190 if (isLiveMode) {
191 calculatePrice();
192 }
193 });
194 break;
195
196 case 'radio':
197 var $radios = $field.find('input[type="radio"]');
198
199 // Ensure at least one radio is checked
200 if (!$radios.filter(':checked').length) {
201 $radios.first().prop('checked', true);
202 }
203
204 $radios.on('change', function() {
205 if (isLiveMode) {
206 calculatePrice();
207 }
208 });
209 break;
210
211 case 'checkbox':
212 case 'switch':
213 var $checkbox = $field.find('input[type="checkbox"]');
214
215 $checkbox.on('change', function() {
216 if (isLiveMode) {
217 calculatePrice();
218 }
219 });
220 break;
221 }
222 });
223 }
224
225 /**
226 * Calculate the total price based on all field values
227 */
228 function calculatePrice() {
229 try {
230 // Start with base price
231 var total = basePrice;
232 var summaryHtml = '';
233
234 // Debug info
235 console.log('Starting calculation with base price:', total);
236
237 // Advanced Formula will be applied after default calculation
238
239 // First handle additions
240 $fields.each(function() {
241 var $field = $(this);
242 var fieldType = $field.data('field-type');
243 var priceType = $field.data('price-type');
244 var fieldLabel = $field.find('.king-pricing-calculator__field-label').text();
245 var fieldValue = 0;
246 var fieldPrice = 0;
247 var subTotal = 0;
248
249 if (priceType !== 'add') {
250 return; // Skip non-addition fields on first pass
251 }
252
253 try {
254 switch (fieldType) {
255 case 'number':
256 var $input = $field.find('input[type="number"]');
257 fieldValue = parseFloat($input.val());
258 fieldPrice = parseFloat($field.data('price'));
259
260 if (!isNaN(fieldValue) && !isNaN(fieldPrice)) {
261 subTotal = fieldValue * fieldPrice;
262 total += subTotal;
263 }
264 break;
265
266 case 'range':
267 var $range = $field.find('input[type="range"]');
268 fieldValue = parseFloat($range.val());
269 fieldPrice = parseFloat($field.data('price'));
270
271 if (!isNaN(fieldValue) && !isNaN(fieldPrice)) {
272 subTotal = fieldValue * fieldPrice;
273 total += subTotal;
274 }
275 break;
276
277 case 'select':
278 var $select = $field.find('select');
279 var $option = $select.find('option:selected');
280
281 fieldValue = $option.text();
282 fieldPrice = parseFloat($option.data('price'));
283
284 if (!isNaN(fieldPrice)) {
285 subTotal = fieldPrice;
286 total += subTotal;
287 }
288 break;
289
290 case 'radio':
291 var $radio = $field.find('input[type="radio"]:checked');
292
293 if ($radio.length) {
294 fieldValue = $radio.siblings('label').text();
295 fieldPrice = parseFloat($radio.data('price'));
296
297 if (!isNaN(fieldPrice)) {
298 subTotal = fieldPrice;
299 total += subTotal;
300 }
301 }
302 break;
303
304 case 'checkbox':
305 case 'switch':
306 var $checkbox = $field.find('input[type="checkbox"]');
307
308 if ($checkbox.is(':checked')) {
309 fieldValue = 'Yes';
310 fieldPrice = parseFloat($field.data('price'));
311
312 if (!isNaN(fieldPrice)) {
313 subTotal = fieldPrice;
314 total += subTotal;
315 }
316 } else {
317 fieldValue = 'No';
318 }
319 break;
320 }
321
322 // Add to summary if there's a value and subtotal
323 if (fieldValue !== '' && fieldValue !== 0 && subTotal !== 0) {
324 summaryHtml += '<div class="king-pricing-calculator__summary-item">' +
325 '<span class="king-pricing-calculator__summary-label">' + fieldLabel + ': ' + fieldValue + '</span>' +
326 '<span class="king-pricing-calculator__summary-value">' + formatPrice(subTotal) + '</span>' +
327 '</div>';
328 }
329
330 // Debug info
331 console.log('Field:', fieldLabel, 'Type:', fieldType, 'Value:', fieldValue, 'Price:', fieldPrice, 'Subtotal:', subTotal);
332
333 } catch (fieldError) {
334 console.error('Error processing field for addition:', fieldError);
335 }
336 });
337
338 // Debug info
339 console.log('After additions, total is:', total);
340
341 // Then handle multiplications
342 $fields.each(function() {
343 var $field = $(this);
344 var fieldType = $field.data('field-type');
345 var priceType = $field.data('price-type');
346 var fieldLabel = $field.find('.king-pricing-calculator__field-label').text();
347 var fieldValue = 0;
348 var fieldPrice = 0;
349 var beforeMult = total;
350
351 if (priceType !== 'multiply') {
352 return; // Skip non-multiplication fields on second pass
353 }
354
355 try {
356 switch (fieldType) {
357 case 'number':
358 var $input = $field.find('input[type="number"]');
359 fieldValue = parseFloat($input.val());
360
361 if (!isNaN(fieldValue) && fieldValue !== 0) {
362 beforeMult = total;
363 total *= fieldValue;
364
365 summaryHtml += '<div class="king-pricing-calculator__summary-item">' +
366 '<span class="king-pricing-calculator__summary-label">' + fieldLabel + ': ' + fieldValue + '</span>' +
367 '<span class="king-pricing-calculator__summary-value">' + formatPrice(total - beforeMult) + '</span>' +
368 '</div>';
369 }
370 break;
371
372 case 'range':
373 var $range = $field.find('input[type="range"]');
374 fieldValue = parseFloat($range.val());
375
376 if (!isNaN(fieldValue) && fieldValue !== 0) {
377 beforeMult = total;
378 total *= fieldValue;
379
380 summaryHtml += '<div class="king-pricing-calculator__summary-item">' +
381 '<span class="king-pricing-calculator__summary-label">' + fieldLabel + ': ' + fieldValue + '</span>' +
382 '<span class="king-pricing-calculator__summary-value">' + formatPrice(total - beforeMult) + '</span>' +
383 '</div>';
384 }
385 break;
386
387 case 'select':
388 var $select = $field.find('select');
389 var $option = $select.find('option:selected');
390
391 fieldValue = $option.text();
392 fieldPrice = parseFloat($option.data('price'));
393
394 if (!isNaN(fieldPrice) && fieldPrice !== 0) {
395 beforeMult = total;
396 total *= fieldPrice;
397
398 summaryHtml += '<div class="king-pricing-calculator__summary-item">' +
399 '<span class="king-pricing-calculator__summary-label">' + fieldLabel + ': ' + fieldValue + '</span>' +
400 '<span class="king-pricing-calculator__summary-value">' + formatPrice(total - beforeMult) + '</span>' +
401 '</div>';
402 }
403 break;
404
405 case 'radio':
406 var $radio = $field.find('input[type="radio"]:checked');
407
408 if ($radio.length) {
409 fieldValue = $radio.siblings('label').text();
410 fieldPrice = parseFloat($radio.data('price'));
411
412 if (!isNaN(fieldPrice) && fieldPrice !== 0) {
413 beforeMult = total;
414 total *= fieldPrice;
415
416 summaryHtml += '<div class="king-pricing-calculator__summary-item">' +
417 '<span class="king-pricing-calculator__summary-label">' + fieldLabel + ': ' + fieldValue + '</span>' +
418 '<span class="king-pricing-calculator__summary-value">' + formatPrice(total - beforeMult) + '</span>' +
419 '</div>';
420 }
421 }
422 break;
423
424 case 'checkbox':
425 case 'switch':
426 var $checkbox = $field.find('input[type="checkbox"]');
427
428 if ($checkbox.is(':checked')) {
429 fieldValue = 'Yes';
430 fieldPrice = parseFloat($field.data('price'));
431
432 if (!isNaN(fieldPrice) && fieldPrice !== 0) {
433 beforeMult = total;
434 total *= fieldPrice;
435
436 summaryHtml += '<div class="king-pricing-calculator__summary-item">' +
437 '<span class="king-pricing-calculator__summary-label">' + fieldLabel + ': ' + fieldValue + '</span>' +
438 '<span class="king-pricing-calculator__summary-value">' + formatPrice(total - beforeMult) + '</span>' +
439 '</div>';
440 }
441 }
442 break;
443 }
444
445 // Debug info
446 console.log('Field (multiply):', fieldLabel, 'Type:', fieldType, 'Value:', fieldValue, 'BeforeMult:', beforeMult, 'After:', total);
447
448 } catch (fieldError) {
449 console.error('Error processing field for multiplication:', fieldError);
450 }
451 });
452
453 // Then handle custom formula (fallback to addition in free version)
454 $fields.each(function() {
455 var $field = $(this);
456 var fieldType = $field.data('field-type');
457 var priceType = $field.data('price-type');
458
459 if (priceType !== 'custom') {
460 return; // Skip non-custom fields
461 }
462
463 try {
464 // In the free version, just treat custom formula as add
465 var fieldLabel = $field.find('.king-pricing-calculator__field-label').text();
466 var fieldValue, fieldPrice, subTotal = 0;
467
468 switch (fieldType) {
469 case 'number':
470 case 'range':
471 var $input = fieldType === 'number' ?
472 $field.find('input[type="number"]') :
473 $field.find('input[type="range"]');
474
475 fieldValue = parseFloat($input.val());
476 fieldPrice = parseFloat($field.data('price'));
477
478 if (!isNaN(fieldValue) && !isNaN(fieldPrice)) {
479 // Fallback to addition in free version
480 subTotal = fieldValue * fieldPrice;
481 total += subTotal;
482
483 summaryHtml += '<div class="king-pricing-calculator__summary-item">' +
484 '<span class="king-pricing-calculator__summary-label">' +
485 fieldLabel + ': ' + fieldValue +
486 ' <small>(Pro formula fallback)</small>' +
487 '</span>' +
488 '<span class="king-pricing-calculator__summary-value">' + formatPrice(subTotal) + '</span>' +
489 '</div>';
490 }
491 break;
492
493 case 'select':
494 case 'radio':
495 case 'checkbox':
496 case 'switch':
497 // Handle similar to add, with pro notice
498 // Implementation follows similar pattern to the addition case
499 break;
500 }
501
502 console.log('Custom formula field (free fallback):', fieldLabel, 'Added:', subTotal);
503
504 } catch (fieldError) {
505 console.error('Error processing custom formula field:', fieldError);
506 }
507 });
508
509 // Advanced Formula override (Pro) after default calculation loops
510 if ($calculator.hasClass('king-pricing-calculator--advanced-formula')) {
511 var formulaType = $calculator.data('formulaType') || 'standard';
512 // Map fields for formula execution
513 var fieldsMap = {};
514 var aliasMap = {}; // Create a user-friendly alias map using custom field IDs
515 $fields.each(function() {
516 var $field = $(this);
517 var id = $field.data('field-id');
518 var type = $field.data('field-type');
519 var value;
520 if (type === 'checkbox' || type === 'switch') {
521 value = $field.find('input[type="checkbox"]').is(':checked');
522 } else if (type === 'radio') {
523 value = $field.find('input[type="radio"]:checked').val();
524 } else {
525 var v = $field.find('input, select').val();
526 var num = parseFloat(v);
527 value = isNaN(num) ? v : num;
528 }
529 fieldsMap[id] = value;
530
531 // If this is a custom field ID (starting with 'king-calc-'), create an alias
532 if (id.indexOf('king-calc-') === 0) {
533 var customId = id.replace('king-calc-', '');
534 aliasMap[customId] = value;
535 }
536 });
537
538 // For debugging custom formulas
539 console.log('Available field IDs:', fieldsMap);
540 console.log('User-friendly field aliases:', aliasMap);
541
542 // Apply chosen formula on the aggregated total
543 var newTotal = total;
544 if (formulaType === 'exponential') {
545 var expBase = parseFloat($calculator.data('exponentialBase')) || 1;
546 newTotal = Math.pow(total, expBase);
547 } else if (formulaType === 'logarithmic') {
548 var logBase = parseFloat($calculator.data('logarithmicBase')) || Math.E;
549 // Avoid log(0) or log of negative
550 newTotal = total > 0 ? Math.log(total) / Math.log(logBase) : 0;
551 } else if (formulaType === 'custom') {
552 var customCode = $calculator.data('customFormula') || '';
553 try {
554 // Pass both the full field map and user-friendly aliases
555 var fn = new Function('fields', 'basePrice', 'aliases', customCode);
556 newTotal = fn(fieldsMap, basePrice, aliasMap);
557 } catch (e) {
558 console.error('Custom formula error:', e);
559 }
560 }
561 // Override total with formula result
562 total = (typeof newTotal === 'number' && !isNaN(newTotal)) ? newTotal : total;
563 }
564
565 // Final check to prevent NaN
566 if (isNaN(total)) {
567 console.error('Total calculation resulted in NaN');
568 total = basePrice;
569 }
570
571 // Debug info
572 console.log('Final total:', total);
573
574 // Update the total price
575 $totalPrice.text(formatPrice(total));
576
577 // Update the summary items
578 $summaryItems.html(summaryHtml);
579 } catch(e) {
580 console.error('Calculation error:', e);
581 $totalPrice.text(formatPrice(basePrice));
582 }
583 }
584
585 /**
586 * Format a price number with proper separators
587 */
588 function formatPrice(number) {
589 if (isNaN(number)) {
590 console.error('Attempting to format NaN as price');
591 number = 0;
592 }
593 var parts = number.toFixed(decimalPlaces).toString().split('.');
594 parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator);
595 return pricePrefix + parts.join(decimalSeparator) + priceSuffix;
596 }
597
598 /**
599 * Update the visual appearance of range slider progress
600 */
601 function updateRangeProgress($range) {
602 var min = parseFloat($range.attr('min')) || 0;
603 var max = parseFloat($range.attr('max')) || 100;
604 var value = parseFloat($range.val()) || 0;
605 var progress = ((value - min) / (max - min)) * 100;
606
607 // Set CSS variable for the progress
608 $range.css('--range-progress', progress + '%');
609 }
610 });
611 };
612
613 /**
614 * Initialize Pro feature handlers for Pricing Calculator
615 * @param jQuery $calculator
616 */
617 function initProFeatures($calculator) {
618 var calculatorId = $calculator.attr('id');
619 var $summaryItems = $calculator.find('.king-pricing-calculator__summary-items');
620 var $totalPrice = $calculator.find('.king-pricing-calculator__total-price');
621
622 // Use localized nonces if available, fallback to data attributes
623 var addToCartNonce = addToCartNonce || $calculator.data('addToCartNonce');
624 var sendEmailNonce = sendEmailQuoteNonce || $calculator.data('sendEmailNonce');
625 var recipientEmail = $calculator.data('recipientEmail');
626 var emailSubject = $calculator.data('emailSubject');
627
628 // Try to get conditional rules from data attribute, accounting for possible casing issues
629 var conditionalRules;
630 try {
631 // First try direct data attribute access
632 conditionalRules = $calculator.data('conditionalRules');
633
634 // If that doesn't work, try to get the raw attribute and parse it
635 if (!conditionalRules && $calculator.attr('data-conditional-rules')) {
636 console.log('Found data-conditional-rules attribute, trying to parse:', $calculator.attr('data-conditional-rules'));
637 conditionalRules = JSON.parse($calculator.attr('data-conditional-rules'));
638 }
639
640 console.log('Conditional rules loaded:', conditionalRules);
641 } catch (e) {
642 console.error('Error parsing conditional rules:', e);
643 }
644
645 // Add to Cart Integration
646 $calculator.find('.king-pricing-calculator-pro__add-to-cart-button').on('click', function() {
647 var price = parseFloat($totalPrice.text().replace(/[^0-9\.\-]/g, '')) || 0;
648 var productId = $(this).data('productId') || '';
649 var includeDetails = $(this).data('includeDetails') === 'yes';
650 var productType = productId ? 'specific' : 'dynamic';
651 var data = {
652 action: 'king_addons_add_to_cart',
653 nonce: addToCartNonce,
654 price: price,
655 product_type: productType,
656 product_id: productId,
657 quantity: 1,
658 use_as_budget: includeDetails
659 };
660 $.post(ajaxUrl, data, function(response) {
661 if (response.success) {
662 alert(response.data.message);
663 if (response.data.fragments) {
664 $.each(response.data.fragments, function(key, html) {
665 $(key).replaceWith(html);
666 });
667 }
668 } else {
669 alert(response.data.message || 'Error adding to cart.');
670 }
671 });
672 });
673
674 // Email Quote Option
675 var $emailButton = $calculator.find('.king-pricing-calculator-pro__email-button');
676 var $emailForm = $calculator.find('.king-pricing-calculator-pro__email-form');
677 $emailButton.on('click', function() {
678 $emailForm.toggle();
679 });
680 $emailForm.find('.king-pricing-calculator-pro__cancel-email-button').on('click', function() {
681 $emailForm.hide();
682 });
683 $emailForm.find('.king-pricing-calculator-pro__send-email-button').on('click', function() {
684 var name = $emailForm.find('#quote_name').val();
685 var email = $emailForm.find('#quote_email').val();
686 var message = $emailForm.find('#quote_message').val();
687 var quoteHtml = $summaryItems.html();
688 var totalHtml = '<div class="king-pricing-calculator__summary-item"><span class="king-pricing-calculator__summary-label">Total</span><span class="king-pricing-calculator__summary-value">' + $totalPrice.text() + '</span></div>';
689 var data = {
690 action: 'king_addons_send_email_quote',
691 nonce: sendEmailNonce,
692 name: name,
693 email: email,
694 message: message,
695 quote_data: quoteHtml + totalHtml,
696 recipient: recipientEmail,
697 subject: emailSubject
698 };
699 $.post(ajaxUrl, data, function(response) {
700 if (response.success) {
701 alert(response.data.message);
702 $emailForm.hide();
703 } else {
704 alert(response.data.message || 'Error sending quote.');
705 }
706 });
707 });
708
709 // Generate PDF Quote (print to PDF)
710 $calculator.find('.king-pricing-calculator-pro__pdf-button').on('click', function() {
711 // Create a more styled printable version with better formatting
712 var calculatorTitle = $calculator.find('.king-pricing-calculator__title').text() || document.title;
713 var calculatorDesc = $calculator.find('.king-pricing-calculator__description').text() || '';
714 var logo = $calculator.data('companyLogo') || '';
715 var logoHtml = logo ? '<img src="' + logo + '" alt="Company Logo" style="max-width: 200px; margin-bottom: 20px;">' : '';
716
717 // Get current date in a nice format
718 var today = new Date();
719 var date = today.toLocaleDateString();
720
721 // Create a well-formatted HTML document for printing
722 var content = '<html><head>' +
723 '<title>' + calculatorTitle + ' - Quote</title>' +
724 '<style>' +
725 'body { font-family: Arial, sans-serif; padding: 30px; max-width: 800px; margin: 0 auto; color: #333; }' +
726 '.quote-header { margin-bottom: 30px; border-bottom: 1px solid #eee; padding-bottom: 20px; }' +
727 '.quote-title { font-size: 24px; font-weight: bold; margin-bottom: 10px; color: #2c3e50; }' +
728 '.quote-date { font-size: 14px; color: #7f8c8d; margin-bottom: 15px; }' +
729 '.quote-description { margin-bottom: 20px; }' +
730 '.quote-items { margin-bottom: 30px; }' +
731 '.quote-item { padding: 10px 0; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; }' +
732 '.quote-item:last-child { border-bottom: none; }' +
733 '.quote-label { font-weight: normal; }' +
734 '.quote-value { font-weight: bold; }' +
735 '.quote-total { padding: 15px 0; border-top: 2px solid #2c3e50; display: flex; justify-content: space-between; font-weight: bold; }' +
736 '.quote-total .quote-label { font-size: 18px; }' +
737 '.quote-total .quote-value { font-size: 18px; color: #2c3e50; }' +
738 '</style>' +
739 '</head><body>' +
740 '<div class="quote-header">' +
741 logoHtml +
742 '<div class="quote-title">' + calculatorTitle + '</div>' +
743 '<div class="quote-date">Date: ' + date + '</div>' +
744 (calculatorDesc ? '<div class="quote-description">' + calculatorDesc + '</div>' : '') +
745 '</div>' +
746 '<div class="quote-items">';
747
748 // Add all line items
749 $summaryItems.find('.king-pricing-calculator__summary-item').each(function() {
750 var label = $(this).find('.king-pricing-calculator__summary-label').text();
751 var value = $(this).find('.king-pricing-calculator__summary-value').text();
752 content += '<div class="quote-item">' +
753 '<span class="quote-label">' + label + '</span>' +
754 '<span class="quote-value">' + value + '</span>' +
755 '</div>';
756 });
757
758 // Add the total
759 content += '</div>' +
760 '<div class="quote-total">' +
761 '<span class="quote-label">Total</span>' +
762 '<span class="quote-value">' + $totalPrice.text() + '</span>' +
763 '</div>' +
764 '</body></html>';
765
766 // Open a new window, write the content, and trigger print
767 var win = window.open('', '_blank');
768 win.document.write(content);
769 win.document.close();
770
771 // Slight delay to ensure content is loaded before printing
772 setTimeout(function() {
773 win.focus();
774 win.print();
775 // Don't close automatically to allow users to save as PDF
776 // win.close();
777 }, 250);
778 });
779
780 // Save/Load Calculations using localStorage
781 var storageKey = calculatorId + '_data';
782
783 $calculator.find('.king-pricing-calculator-pro__save-button').on('click', function() {
784 try {
785 var fieldsData = {};
786 var hasValues = false;
787
788 // Collect all field values
789 $calculator.find('.king-pricing-calculator__field').each(function() {
790 var $field = $(this);
791 var id = $field.data('field-id');
792 var type = $field.data('field-type');
793 var val;
794
795 if (type === 'checkbox' || type === 'switch') {
796 val = $field.find('input[type="checkbox"]').prop('checked');
797 } else if (type === 'radio') {
798 val = $field.find('input[type="radio"]:checked').val();
799 } else {
800 val = $field.find('input, select').val();
801 }
802
803 if (val !== undefined && val !== null && val !== '') {
804 hasValues = true;
805 }
806
807 fieldsData[id] = val;
808 });
809
810 // Add metadata
811 fieldsData._meta = {
812 date: new Date().toISOString(),
813 total: $totalPrice.text(),
814 calculatorId: calculatorId
815 };
816
817 if (!hasValues) {
818 alert('No values to save. Please fill in the calculator fields first.');
819 return;
820 }
821
822 // Save to localStorage
823 localStorage.setItem(storageKey, JSON.stringify(fieldsData));
824 alert('Calculation saved successfully.');
825 } catch (error) {
826 console.error('Error saving calculation:', error);
827 alert('There was an error saving your calculation. Please try again.');
828 }
829 });
830
831 $calculator.find('.king-pricing-calculator-pro__load-button').on('click', function() {
832 try {
833 var saved = localStorage.getItem(storageKey);
834 if (!saved) {
835 alert('No saved calculation found for this calculator.');
836 return;
837 }
838
839 var fieldsData = JSON.parse(saved);
840 var foundFields = false;
841
842 // Check if metadata matches this calculator
843 if (fieldsData._meta && fieldsData._meta.calculatorId && fieldsData._meta.calculatorId !== calculatorId) {
844 alert('The saved calculation is for a different calculator. Unable to load.');
845 return;
846 }
847
848 // Apply the saved values to fields
849 $.each(fieldsData, function(id, val) {
850 // Skip metadata
851 if (id === '_meta') return;
852
853 var $field = $calculator.find('[data-field-id="' + id + '"]');
854 if ($field.length) {
855 foundFields = true;
856 var type = $field.data('field-type');
857
858 if (type === 'checkbox' || type === 'switch') {
859 $field.find('input[type="checkbox"]').prop('checked', val);
860 } else if (type === 'radio') {
861 $field.find('input[type="radio"]').filter('[value="' + val + '"]').prop('checked', true);
862 } else {
863 $field.find('input, select').val(val).trigger('change');
864 }
865 }
866 });
867
868 if (!foundFields) {
869 alert('No saved values could be applied to this calculator.');
870 return;
871 }
872
873 // Recalculate with the loaded values
874 calculatePrice();
875
876 // Show success message with saved date if available
877 var savedDate = fieldsData._meta && fieldsData._meta.date ?
878 new Date(fieldsData._meta.date).toLocaleString() : '';
879
880 if (savedDate) {
881 alert('Calculation loaded successfully. (Saved on: ' + savedDate + ')');
882 } else {
883 alert('Calculation loaded successfully.');
884 }
885 } catch (error) {
886 console.error('Error loading calculation:', error);
887 alert('There was an error loading your calculation. The saved data may be corrupted.');
888 }
889 });
890
891 // Conditional Logic
892 if (conditionalRules) {
893 console.log('Conditional rules found:', conditionalRules);
894 function evaluateCondition(val, operator, expected) {
895 try {
896 // Convert values for proper comparison
897 var compVal = val;
898 var compExpected = expected;
899
900 // For numeric comparisons, explicitly convert to numbers
901 if (operator === 'greater' || operator === 'less') {
902 compVal = parseFloat(val);
903 compExpected = parseFloat(expected);
904
905 // Check if we have valid numbers
906 if (isNaN(compVal) || isNaN(compExpected)) {
907 console.warn('Invalid numeric comparison with NaN:', val, operator, expected);
908 return false;
909 }
910 }
911
912 // Special handling for boolean values
913 if (operator === 'is_checked' || operator === 'is_not_checked') {
914 // These operators don't use the expected value, they check the val directly
915 return operator === 'is_checked' ? compVal === true : compVal === false;
916 }
917
918 // Log the comparison
919 console.log('Comparing:', compVal, operator, compExpected);
920
921 // Perform the comparison
922 switch (operator) {
923 case 'equal': return compVal == compExpected;
924 case 'not_equal': return compVal != compExpected;
925 case 'greater': return compVal > compExpected;
926 case 'less': return compVal < compExpected;
927 case 'contains': return ('' + compVal).indexOf(compExpected) !== -1;
928 default: return false;
929 }
930 } catch (e) {
931 console.error('Error evaluating condition:', e);
932 return false;
933 }
934 }
935 $.each(conditionalRules, function(_, rule) {
936 console.log('Processing rule:', rule);
937
938 // Improve field selection to handle both custom IDs and auto-generated IDs
939 var $ifField, $targetField;
940
941 // Try to find the "if" field, first by exact ID, then by custom ID
942 $ifField = $calculator.find('[data-field-id="' + rule.if_field + '"]');
943 if (!$ifField.length) {
944 // Try finding a field with a custom ID (king-calc-*)
945 $ifField = $calculator.find('[data-field-id="king-calc-' + rule.if_field + '"]');
946 }
947
948 // Try to find the target field, first by exact ID, then by custom ID
949 $targetField = $calculator.find('[data-field-id="' + rule.target_field + '"]');
950 if (!$targetField.length) {
951 // Try finding a field with a custom ID (king-calc-*)
952 $targetField = $calculator.find('[data-field-id="king-calc-' + rule.target_field + '"]');
953 }
954
955 console.log('Condition field found:', $ifField.length > 0, 'Target field found:', $targetField.length > 0);
956
957 // Log actual field IDs to help debug
958 if ($ifField.length > 0) {
959 console.log('If field ID:', $ifField.data('field-id'));
960 }
961 if ($targetField.length > 0) {
962 console.log('Target field ID:', $targetField.data('field-id'));
963 }
964
965 // If either field is not found, skip this rule
966 if (!$ifField.length || !$targetField.length) {
967 console.warn('Could not find one or both fields for this rule, skipping');
968 return true; // Continue to next rule
969 }
970
971 function applyRule() {
972 var val;
973 var type = $ifField.data('field-type');
974
975 // Log the field type to debug
976 console.log('Evaluating field type:', type);
977
978 if (type === 'checkbox' || type === 'switch') {
979 val = $ifField.find('input[type="checkbox"]').prop('checked');
980 } else if (type === 'radio') {
981 val = $ifField.find('input[type="radio"]:checked').val();
982 } else {
983 val = $ifField.find('input, select').val();
984 }
985
986 // Log the actual field value
987 console.log('Field value:', val, 'comparing with:', rule.value, 'using operator:', rule.operator);
988
989 var result = evaluateCondition(val, rule.operator, rule.value);
990 console.log('Condition evaluation result:', result);
991
992 // Apply the action based on the result
993 try {
994 switch (rule.action) {
995 case 'show':
996 // Use CSS display property to properly show/hide
997 if (result) {
998 $targetField.css('display', ''); // Use default display
999 } else {
1000 $targetField.css('display', 'none');
1001 }
1002 break;
1003 case 'hide':
1004 // Use CSS display property to properly show/hide (inverse of show)
1005 if (!result) {
1006 $targetField.css('display', ''); // Use default display
1007 } else {
1008 $targetField.css('display', 'none');
1009 }
1010 break;
1011 case 'enable':
1012 $targetField.find('input, select').prop('disabled', !result);
1013 if (!result) {
1014 $targetField.addClass('king-pricing-calculator__field--disabled');
1015 } else {
1016 $targetField.removeClass('king-pricing-calculator__field--disabled');
1017 }
1018 break;
1019 case 'disable':
1020 $targetField.find('input, select').prop('disabled', result);
1021 if (result) {
1022 $targetField.addClass('king-pricing-calculator__field--disabled');
1023 } else {
1024 $targetField.removeClass('king-pricing-calculator__field--disabled');
1025 }
1026 break;
1027 case 'set_value':
1028 if (result) {
1029 var $input = $targetField.find('input, select');
1030 $input.val(rule.set_value).trigger('change');
1031 console.log('Set value to:', rule.set_value);
1032 }
1033 break;
1034 }
1035 } catch (e) {
1036 console.error('Error applying action:', e);
1037 }
1038
1039 // Log the action taken
1040 console.log('Applied action:', rule.action, 'with result:', result);
1041 }
1042 $ifField.on('change input', applyRule);
1043
1044 // Also execute immediately once to set initial state
1045 applyRule();
1046 });
1047 }
1048 }
1049
1050 // Initialize on document ready
1051 $(document).ready(function() {
1052 initPricingCalculator();
1053 });
1054
1055 // Initialize after Elementor frontend init
1056 $(window).on('elementor/frontend/init', function() {
1057 if (typeof elementorFrontend !== 'undefined') {
1058 elementorFrontend.hooks.addAction('frontend/element_ready/king-addons-pricing-calculator.default', function() {
1059 initPricingCalculator();
1060 });
1061 }
1062 });
1063
1064 })(jQuery);