PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.81
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.81
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 51.1.45 All 40 releases
king-addons / includes / widgets / Form_Builder / script.js

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

1,566 lines 74.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use strict";
2 (function ($) {
3 $(window).on('elementor/frontend/init', function () {
4 elementorFrontend.hooks.addAction('frontend/element_ready/king-addons-form-builder.default', function ($scope) {
5 elementorFrontend.elementsHandler.addHandler(elementorModules.frontend.handlers.Base.extend({
6 onInit: function onInit() {
7 let $scope = this.$element;
8
9 // console.log("FORM BUILDER");
10
11 var formContent = {};
12
13 var fileUrl = {};
14
15 if ($scope.find('#g-recaptcha-response').length > 0 && $scope.find('#g-recaptcha-response').data('site-key')) {
16 var script = document.createElement('script');
17 script.src = 'https://www.google.com/recaptcha/api.js?render=' + $scope.find('#g-recaptcha-response').data('site-key') + '';
18 document.body.appendChild(script);
19 }
20
21 var currentTab = 0;
22 if (0 < $scope.find('.king-addons-fb-step-tab').length) {
23 // console.log(currentTab);
24 showTab(currentTab);
25
26 $scope.find('.king-addons-fb-step-prev').each(function () {
27 $(this).on('click', function () {
28 nextPrev(-1);
29 });
30 });
31
32 $scope.find(".king-addons-fb-step-next").each(function () {
33 $(this).on('click', function () {
34 nextPrev(1);
35 });
36 });
37 }
38
39 var actions = $scope.find('.king-addons-form-field-type-submit').data('actions');
40
41 initExtraFields($scope);
42 initConditionalLogic($scope);
43 initCalculations($scope);
44 initPartialEntries($scope);
45
46 $scope.find('input[type="file"]').on('change', function (e) {
47 var files = this.files;
48 var thisInput = $(this);
49 var eventType = e.type;
50 handleFileValidityAndUpload(thisInput, files, eventType);
51 });
52
53 $scope.find('input, select, textarea').each(function () {
54 $(this).on('change', function () {
55 var $this = $(this);
56 if ('checkbox' == $this.attr('type')) {
57 var $option = $this.closest('.king-addons-form-field-option');
58 if ($option.hasClass('king-addons-checked')) {
59 $option.removeClass('king-addons-checked');
60 } else {
61 $option.addClass('king-addons-checked');
62 }
63 } else if ('radio' == $this.attr('type')) {
64
65 var name = $this.attr('name');
66 var $group = $('input[type="radio"][name="' + name + '"]');
67
68
69 $group.closest('.king-addons-form-field-option').removeClass('king-addons-checked');
70
71
72 if ($this.is(':checked')) {
73 $this.closest('.king-addons-form-field-option').addClass('king-addons-checked');
74 }
75 }
76 });
77
78 $(this).on('input change keyup', function (e) {
79 if ($(this).closest('.king-addons-select-wrap').length > 0) {
80 $(this).closest('.king-addons-select-wrap').removeClass('king-addons-form-error-wrap');
81 }
82 $(this).removeClass('king-addons-form-error');
83 $(this).closest('.king-addons-field-group').find('.king-addons-submit-error').remove();
84 });
85 });
86
87 $scope.find('.king-addons-button').on('click', function (e) {
88 e.preventDefault();
89
90 var eventType = e.type;
91
92 formContent = {};
93
94
95 let fileUploadPromises = [];
96
97 if (0 < $scope.find('input[type="file"').length) {
98 $scope.find('input[type="file"]').each(function () {
99 var files = this.files;
100 var thisInput = $(this);
101
102 fileUploadPromises.push(handleFileValidityAndUpload(thisInput, files, eventType));
103 });
104
105
106 Promise.all(fileUploadPromises)
107 .then(() => {
108 createFormContent();
109
110
111 if (validateForm()) {
112 $(this).closest('form').trigger('submit');
113 }
114 })
115 .catch((error) => {
116
117 // console.error(error);
118 });
119 } else {
120 createFormContent();
121
122 if (validateForm()) {
123 $(this).closest('form').trigger('submit');
124 }
125 }
126 });
127
128
129 $scope.find('form').on('submit', function (e) {
130
131 e.preventDefault();
132
133 let responsesArray = [];
134 let paymentRedirect = '';
135
136 $scope.find('.king-addons-button>span').addClass('king-addons-loader-hidden');
137 $scope.find('.king-addons-button').find('.king-addons-double-bounce').removeClass('king-addons-loader-hidden');
138
139 $scope.find('.king-addons-submit-error, .king-addons-submit-success, .king-addons-submit-warning').remove();
140
141 function processRecaptcha(callback) {
142 if ($scope.find('#g-recaptcha-response').length > 0 && $scope.find('#g-recaptcha-response').data('site-key')) {
143 grecaptcha.ready(function () {
144 grecaptcha.execute(KingAddonsFormBuilderData.recaptcha_v3_site_key, {action: 'submit'}).then(function (token) {
145
146 $scope.find('#g-recaptcha-response').val(token);
147
148
149 $.ajax({
150 type: 'POST',
151 url: KingAddonsFormBuilderData.ajaxurl,
152 data: {
153 action: 'king_addons_verify_recaptcha',
154 'g-recaptcha-response': token,
155 nonce: KingAddonsFormBuilderData.nonce
156 },
157 success: function (response) {
158 if (!response.success) {
159 // console.log(response);
160 setTimeout(function () {
161 $scope.find('.king-addons-button').find('.king-addons-double-bounce').addClass('king-addons-loader-hidden');
162 $scope.find('.king-addons-button>span').removeClass('king-addons-loader-hidden');
163 $scope.find('form').append('<p class="king-addons-submit-error">' + KingAddonsFormBuilderData.recaptcha_error + '</p>');
164 }, 500);
165 callback(false);
166 } else {
167 // console.log(response);
168 callback(true);
169 }
170 },
171 error: function (error) {
172 // console.log(error);
173 setTimeout(function () {
174 $scope.find('.king-addons-button').find('.king-addons-double-bounce').addClass('king-addons-loader-hidden');
175 $scope.find('.king-addons-button>span').removeClass('king-addons-loader-hidden');
176 $scope.find('form').append('<p class="king-addons-submit-error">' + KingAddonsFormBuilderData.recaptcha_error + '</p>');
177 }, 500);
178 callback(false);
179 }
180 });
181 });
182 });
183 } else {
184 callback(true);
185 }
186 }
187
188
189 processRecaptcha(function (isRecaptchaSuccessful) {
190 if (isRecaptchaSuccessful) {
191
192
193 var actionsObject = {
194 emailPromise: sendEmail,
195 submissionsPromise: createPost,
196 mailchimpPromise: subscribeMailchimp,
197 webhookPromise: sendWebhook,
198 integrationsPromise: sendIntegrations
199 }
200
201 var firstActions = actions.filter(function (action) {
202 return action !== 'payment';
203 });
204
205 Promise.all(
206 firstActions.map((action) => {
207 try {
208 if (actionsObject[action + 'Promise']) {
209 return actionsObject[action + 'Promise']();
210 }
211 } catch (error) {
212 return Promise.reject(error);
213 }
214 })
215 )
216 .then((responses) => {
217 if (!actions.includes('payment')) {
218 return responses;
219 }
220 var created = responses.find(function (response) {
221 return response && response.data && response.data.action === 'king_addons_form_builder_submissions';
222 });
223 var submissionId = created && created.data ? created.data.post_id : 0;
224 return startPayment(submissionId).then(function (payResponse) {
225 return responses.concat([payResponse]);
226 });
227 })
228 .then((responses) => {
229 // console.log(responses);
230
231
232 const createPostResponse = responses.find((response) => response && response.data.action === 'king_addons_form_builder_submissions');
233
234 const postId = createPostResponse ? createPostResponse.data.post_id : null;
235
236
237 var updateMetaPromises = actions.map((action) => {
238 if (action !== 'redirect') {
239 action = 'king_addons_form_builder_' + action;
240
241
242 const response = responses.find((response) => response && response.data.action === action);
243
244
245 const message = response ? response.data.message : '';
246
247 if (response && response.data.status === 'success') {
248 responsesArray.push('success');
249
250 if (postId) {
251 return updateFormActionMeta(postId, action, 'success', message);
252 }
253 } else {
254 responsesArray.push('error');
255
256 if (postId) {
257 return updateFormActionMeta(postId, action, 'error', message);
258 }
259 }
260 }
261 });
262
263 return Promise.all(updateMetaPromises).then(() => {
264 var anySuccess = responsesArray.includes('success');
265 var anyError = responsesArray.includes('error');
266 var paymentConfigured = actions.includes('payment');
267 var paymentOk = responses.some(function (response) {
268 return response && response.data
269 && response.data.action === 'king_addons_form_builder_payment'
270 && response.data.status === 'success';
271 });
272 var paymentFailed = paymentConfigured && !paymentOk;
273
274 function firstActionMessage() {
275 var note = '';
276 responses.forEach(function (response) {
277 if (note || !response || !response.data || !response.data.message) {
278 return;
279 }
280 if (response.data.status === 'error' || response.success === false) {
281 note = String(response.data.message);
282 }
283 });
284 return note || $scope.data('settings').error_message;
285 }
286
287 // A broken Slack key must not hide a sent email. A failed
288 // payment must, or the visitor thinks they have paid.
289 if (anySuccess && !paymentFailed) {
290 var successCopy = $scope.data('settings').success_message;
291 // Provider None (and any payment with no checkout URL)
292 // still answers success; show that copy, not the generic
293 // “Submission successful”, or the visitor thinks they paid.
294 if (paymentConfigured && !paymentRedirect) {
295 responses.forEach(function (response) {
296 if (response && response.data
297 && response.data.action === 'king_addons_form_builder_payment'
298 && response.data.status === 'success'
299 && response.data.message) {
300 successCopy = String(response.data.message);
301 }
302 });
303 }
304 $scope.find('form').append(
305 $('<p class="king-addons-submit-success"></p>').text(successCopy)
306 );
307 $scope.find('form').trigger('king-addons/form/submitted');
308
309 $scope.find('button').attr('disabled', true);
310 $scope.find('button').css('opacity', 0.6);
311
312 if (anyError) {
313 var failedNotes = [];
314 responses.forEach(function (response) {
315 if (response && response.data && response.data.status === 'error' && response.data.message) {
316 failedNotes.push(String(response.data.message));
317 }
318 });
319 if (failedNotes.length) {
320 $scope.find('form').append(
321 $('<p class="king-addons-submit-warning"></p>').text(failedNotes.join(' '))
322 );
323 }
324 }
325 } else {
326 $scope.find('form').append(
327 $('<p class="king-addons-submit-error"></p>').text(firstActionMessage())
328 );
329 }
330 });
331
332 })
333 .catch((error) => {
334
335 // console.error(error);
336 })
337 .then(() => {
338
339 setTimeout(function () {
340
341 $scope.find('.king-addons-button').find('.king-addons-double-bounce').addClass('king-addons-loader-hidden');
342 $scope.find('.king-addons-button>span').removeClass('king-addons-loader-hidden');
343 setTimeout(function () {
344 // Paying comes first: a redirect
345 // set on the form would otherwise
346 // navigate away from the checkout.
347 if (paymentRedirect && !responsesArray.includes('error')) {
348 $(location).prop('href', paymentRedirect);
349 return;
350 }
351
352 if (actions.includes('redirect') && responsesArray.includes('success')) {
353
354 $(location).prop('href', $scope.find('.king-addons-form-field-type-submit').data('redirect-url'))
355 }
356 }, 500);
357 }, 500);
358 })
359 .catch((error) => {
360
361 // console.error(error);
362 });
363 } else {
364
365 return false;
366 }
367 });
368
369 function updateFormActionMeta(postId, actionName, status, message) {
370 return $.ajax({
371 type: 'POST',
372 url: KingAddonsFormBuilderData.ajaxurl,
373 data: {
374 action: 'king_addons_update_form_action_meta',
375 nonce: KingAddonsFormBuilderData.nonce,
376 post_id: postId,
377 action_name: actionName,
378 status: status,
379 message: message
380 },
381 });
382 }
383
384 function deepCopy(obj) {
385 return JSON.parse(JSON.stringify(obj));
386 }
387
388 function sendEmail() {
389 var data = deepCopy(formContent);
390
391 for (let key in data) {
392 if (data[key][0] == 'radio' || data[key][0] == 'checkbox') {
393 if (Array.isArray(data[key][1])) {
394 let trueValues = data[key][1].filter(innerArray => innerArray[1] === true).map(innerArray => innerArray[0]);
395 let trueValuesString = trueValues.join(', ');
396 data[key][1] = trueValuesString;
397 }
398 }
399 }
400
401 return $.ajax({
402 type: 'POST',
403 url: KingAddonsFormBuilderData.ajaxurl,
404 data: {
405 action: 'king_addons_form_builder_email',
406 nonce: KingAddonsFormBuilderData.nonce,
407 form_content: data,
408 ...spamPayload(),
409 },
410 success: function (response) {
411 // console.log(response);
412 if (!response.success) {
413
414
415
416 } else {
417
418
419
420 }
421 },
422 error: function (error) {
423
424
425
426 }
427 });
428 }
429
430 /**
431 * Hand the submission to the Integrations action, which
432 * decides on the server which services to forward it to.
433 *
434 * @return {jqXHR} The request.
435 */
436 function sendIntegrations() {
437 return $.ajax({
438 type: 'POST',
439 url: KingAddonsFormBuilderData.ajaxurl,
440 data: {
441 ...spamPayload(),
442 action: 'king_addons_form_builder_integrations',
443 nonce: KingAddonsFormBuilderData.nonce,
444 form_content: formContent
445 }
446 });
447 }
448
449 /**
450 * Ask the server to open a payment and note where to
451 * send the visitor once everything else has run.
452 *
453 * @return {jqXHR} The request.
454 */
455 function startPayment(submissionId) {
456 return $.ajax({
457 type: 'POST',
458 url: KingAddonsFormBuilderData.ajaxurl,
459 data: {
460 ...spamPayload(),
461 action: 'king_addons_form_builder_payment',
462 nonce: KingAddonsFormBuilderData.nonce,
463 form_content: formContent,
464 king_addons_form_id: $scope.find('input[name="form_id"]').val(),
465 form_id: $scope.find('input[name="form_id"]').val(),
466 form_name: $scope.find('form').attr('name'),
467 form_page: $scope.find('form').attr('page'),
468 form_page_id: $scope.find('form').attr('page_id'),
469 submission_id: submissionId || 0
470 },
471 success: function (response) {
472 if (response && response.success && response.data && response.data.redirect) {
473 paymentRedirect = response.data.redirect;
474 }
475 }
476 });
477 }
478
479 function sendWebhook() {
480 var data = deepCopy(formContent);
481
482 for (let key in data) {
483 if (data[key][0] == 'radio' || data[key][0] == 'checkbox') {
484 if (Array.isArray(data[key][1])) {
485 let trueValues = data[key][1].filter(innerArray => innerArray[1] === true).map(innerArray => innerArray[0]);
486 let trueValuesString = trueValues.join(', ');
487 data[key][1] = trueValuesString;
488 }
489 }
490 }
491
492 return $.ajax({
493 type: 'POST',
494 url: KingAddonsFormBuilderData.ajaxurl,
495 data: {
496 ...spamPayload(),
497 action: 'king_addons_form_builder_webhook',
498 nonce: KingAddonsFormBuilderData.nonce,
499 form_content: data,
500 king_addons_form_id: $scope.find('input[name="form_id"]').val(),
501 form_name: $scope.find('form').attr('name')
502 },
503 success: function (response) {
504 // console.log(response);
505 if (!response.success) {
506
507
508
509 } else {
510
511
512
513 }
514 },
515 error: function (error) {
516 // console.log(error);
517
518
519
520 }
521 });
522 }
523
524 function flattenChoiceFields(data) {
525 var copy = deepCopy(data);
526 for (var key in copy) {
527 if (!copy[key] || (copy[key][0] !== 'radio' && copy[key][0] !== 'checkbox')) {
528 continue;
529 }
530 if (!Array.isArray(copy[key][1])) {
531 continue;
532 }
533 var picked = copy[key][1]
534 .filter(function (row) {
535 return row && row[1] === true;
536 })
537 .map(function (row) {
538 return row[0];
539 });
540 copy[key][1] = picked.join(', ');
541 }
542 return copy;
543 }
544
545 function createPost() {
546
547 var data = {
548 ...spamPayload(),
549 action: 'king_addons_form_builder_submissions',
550 nonce: KingAddonsFormBuilderData.nonce,
551 form_content: flattenChoiceFields(formContent),
552 status: 'publish',
553 form_name: $scope.find('form').attr('name'),
554 form_id: $scope.find('input[name="form_id"]').val(),
555 form_page: $scope.find('form').attr('page'),
556 form_page_id: $scope.find('form').attr('page_id')
557 };
558
559 return $.ajax({
560 type: 'POST',
561 url: KingAddonsFormBuilderData.ajaxurl,
562 data: data,
563 success: function (response) {
564 // console.log(response);
565
566
567
568 },
569 error: function (error) {
570 // console.log(error)
571
572
573
574 }
575 });
576 }
577
578 function subscribeMailchimp() {
579
580 const submitButton = $scope.find('.king-addons-form-field-type-submit');
581 const mailchimpFields = JSON.parse(submitButton.attr('data-mailchimp-fields'));
582
583 let formData = {};
584
585 Object.keys(mailchimpFields).forEach(function (fieldId) {
586 if (fieldId == 'group_id') {
587
588 var fieldValue = Array.isArray(mailchimpFields[fieldId]) ? mailchimpFields[fieldId].join(',') : mailchimpFields[fieldId];
589 } else {
590 var fieldValue = $scope.find('#form-field-' + mailchimpFields[fieldId]).val();
591 }
592 if (fieldValue) {
593 if (fieldId == 'birthday_field') {
594 formData[fieldId] = convertToMailchimpBirthdayFormat(fieldValue);
595 } else {
596 formData[fieldId] = fieldValue;
597 }
598 }
599 });
600
601 return $.ajax({
602 url: KingAddonsFormBuilderData.ajaxurl,
603 method: 'POST',
604 data: {
605 ...spamPayload(),
606 action: 'king_addons_form_builder_mailchimp',
607 nonce: KingAddonsFormBuilderData.nonce,
608 form_data: formData,
609 listId: submitButton.data('list-id')
610
611 },
612 beforeSend: function () {
613 submitButton.prop('disabled', true);
614 },
615 success: function (response) {
616 // console.log(response);
617 if (!response.success) {
618
619
620
621 } else {
622
623
624
625 }
626
627 },
628 error: function (jqXHR, textStatus, errorThrown) {
629 // console.log(errorThrown);
630
631
632
633 },
634 complete: function () {
635 submitButton.prop('disabled', false);
636 }
637 });
638 }
639 });
640
641
642 /**
643 * Wire up the field types that are more than a plain input: range, rating,
644 * signature and acceptance.
645 *
646 * @param {jQuery} $scope Widget scope.
647 */
648 function initExtraFields($scope) {
649 var root = $scope && $scope[0] ? $scope[0] : document;
650
651 // Range: keep the printed value in step with the handle.
652 root.querySelectorAll('.king-addons-form-range').forEach(function (wrap) {
653 var input = wrap.querySelector('input[type="range"]');
654 var output = wrap.querySelector('.king-addons-form-range__value');
655 if (!input) {
656 return;
657 }
658
659 var sync = function () {
660 if (output) {
661 output.textContent = input.value;
662 }
663 };
664
665 input.addEventListener('input', sync);
666 input.addEventListener('change', sync);
667 sync();
668 });
669
670 // Rating: buttons drive a hidden input so the collector sees one value.
671 root.querySelectorAll('[data-ka-rating]').forEach(function (wrap) {
672 if (wrap.dataset.kaRatingReady === '1') {
673 return;
674 }
675 wrap.dataset.kaRatingReady = '1';
676
677 var input = wrap.querySelector('input');
678 var items = wrap.querySelectorAll('.king-addons-form-rating__item');
679
680 var paint = function (upto) {
681 items.forEach(function (item, index) {
682 item.classList.toggle('is-on', index < upto);
683 });
684 };
685
686 items.forEach(function (item) {
687 item.addEventListener('click', function () {
688 var value = parseInt(item.dataset.value || '0', 10);
689 // Clicking the current value again clears it.
690 if (input && String(value) === input.value) {
691 value = 0;
692 }
693 if (input) {
694 input.value = value ? String(value) : '';
695 }
696 paint(value);
697 wrap.classList.remove('king-addons-form-error');
698 });
699
700 item.addEventListener('mouseenter', function () {
701 paint(parseInt(item.dataset.value || '0', 10));
702 });
703 });
704
705 wrap.addEventListener('mouseleave', function () {
706 paint(input && input.value ? parseInt(input.value, 10) : 0);
707 });
708 });
709
710 // Signature: draw on a canvas, store a PNG data URL.
711 root.querySelectorAll('[data-ka-signature]').forEach(function (wrap) {
712 if (wrap.dataset.kaSignatureReady === '1') {
713 return;
714 }
715 wrap.dataset.kaSignatureReady = '1';
716
717 var canvas = wrap.querySelector('canvas');
718 var input = wrap.querySelector('input');
719 var clear = wrap.querySelector('.king-addons-form-signature__clear');
720 if (!canvas || !input) {
721 return;
722 }
723
724 var ctx = canvas.getContext('2d');
725 var drawing = false;
726 var dirty = false;
727
728 var resize = function () {
729 // Re-sizing wipes the canvas, so only do it while it is empty.
730 if (dirty) {
731 return;
732 }
733 var width = wrap.clientWidth || canvas.clientWidth;
734 if (width > 0) {
735 canvas.width = width;
736 }
737 ctx.lineWidth = 2;
738 ctx.lineCap = 'round';
739 ctx.lineJoin = 'round';
740 ctx.strokeStyle = getComputedStyle(canvas).color || '#000';
741 };
742
743 var point = function (event) {
744 var rect = canvas.getBoundingClientRect();
745 var source = event.touches && event.touches[0] ? event.touches[0] : event;
746 return {
747 x: (source.clientX - rect.left) * (canvas.width / rect.width),
748 y: (source.clientY - rect.top) * (canvas.height / rect.height)
749 };
750 };
751
752 var start = function (event) {
753 event.preventDefault();
754 drawing = true;
755 var p = point(event);
756 ctx.beginPath();
757 ctx.moveTo(p.x, p.y);
758 };
759
760 var move = function (event) {
761 if (!drawing) {
762 return;
763 }
764 event.preventDefault();
765 var p = point(event);
766 ctx.lineTo(p.x, p.y);
767 ctx.stroke();
768 dirty = true;
769 };
770
771 var stop = function () {
772 if (!drawing) {
773 return;
774 }
775 drawing = false;
776 if (dirty) {
777 input.value = canvas.toDataURL('image/png');
778 wrap.classList.remove('king-addons-form-error');
779 }
780 };
781
782 resize();
783 window.addEventListener('resize', resize);
784
785 canvas.addEventListener('mousedown', start);
786 canvas.addEventListener('mousemove', move);
787 document.addEventListener('mouseup', stop);
788 canvas.addEventListener('touchstart', start, { passive: false });
789 canvas.addEventListener('touchmove', move, { passive: false });
790 canvas.addEventListener('touchend', stop);
791
792 if (clear) {
793 clear.addEventListener('click', function () {
794 ctx.clearRect(0, 0, canvas.width, canvas.height);
795 input.value = '';
796 dirty = false;
797 resize();
798 });
799 }
800 });
801 }
802
803
804 /**
805 * Show or hide fields according to their conditional logic rules.
806 *
807 * @param {jQuery} $scope Widget scope.
808 */
809 function initConditionalLogic($scope) {
810 var root = $scope && $scope[0] ? $scope[0] : document;
811 var form = root.querySelector('form');
812 var groups = [];
813
814 root.querySelectorAll('[data-ka-cond]').forEach(function (group) {
815 var config;
816 try {
817 config = JSON.parse(group.getAttribute('data-ka-cond'));
818 } catch (e) {
819 return;
820 }
821
822 if (config && config.rules && config.rules.length) {
823 groups.push({ el: group, config: config });
824 }
825 });
826
827 if (!groups.length || !form) {
828 return;
829 }
830
831 /**
832 * Current value of the watched field, as a string.
833 *
834 * @param {string} fieldId Field ID as set in the panel.
835 * @return {string} Value.
836 */
837 function readValue(fieldId) {
838 var byId = form.querySelector('#form-field-' + CSS.escape(fieldId));
839 if (byId) {
840 if (byId.type === 'checkbox' || byId.type === 'radio') {
841 return byId.checked ? (byId.value || '1') : '';
842 }
843 return byId.value || '';
844 }
845
846 // Radio and checkbox groups share a name rather than an id.
847 var named = form.querySelectorAll('[name="form_fields[' + fieldId + ']"], [name="form_fields[' + fieldId + '][]"]');
848 var values = [];
849 Array.prototype.forEach.call(named, function (input) {
850 if (input.type === 'checkbox' || input.type === 'radio') {
851 if (input.checked) {
852 values.push(input.value);
853 }
854 } else if (input.value) {
855 values.push(input.value);
856 }
857 });
858
859 return values.join(', ');
860 }
861
862 /**
863 * Test one rule.
864 *
865 * @param {Object} rule Rule.
866 * @return {boolean} Whether it matches.
867 */
868 function test(rule) {
869 var actual = String(readValue(rule.field));
870 var expected = String(rule.value === undefined ? '' : rule.value);
871
872 switch (rule.op) {
873 case 'is':
874 return actual === expected;
875 case 'is_not':
876 return actual !== expected;
877 case 'contains':
878 return actual.toLowerCase().indexOf(expected.toLowerCase()) !== -1;
879 case 'not_contains':
880 return actual.toLowerCase().indexOf(expected.toLowerCase()) === -1;
881 case 'is_empty':
882 return actual.trim() === '';
883 case 'is_not_empty':
884 return actual.trim() !== '';
885 case 'gt':
886 return parseFloat(actual) > parseFloat(expected);
887 case 'lt':
888 return parseFloat(actual) < parseFloat(expected);
889 }
890
891 return false;
892 }
893
894 /**
895 * Re-evaluate every conditional field.
896 */
897 function apply() {
898 groups.forEach(function (entry) {
899 var results = entry.config.rules.map(test);
900 var matched = 'any' === entry.config.relation
901 ? results.some(Boolean)
902 : results.every(Boolean);
903
904 var visible = 'hide' === entry.config.action ? !matched : matched;
905
906 entry.el.classList.toggle('king-addons-cond-hidden', !visible);
907 if (!visible) {
908 entry.el.querySelectorAll('.king-addons-form-error').forEach(function (el) {
909 el.classList.remove('king-addons-form-error');
910 });
911 entry.el.querySelectorAll('.king-addons-submit-error').forEach(function (el) {
912 el.remove();
913 });
914 }
915 });
916 }
917
918 form.addEventListener('input', apply);
919 form.addEventListener('change', apply);
920 apply();
921 }
922
923
924 /**
925 * Evaluate an arithmetic expression without eval().
926 *
927 * Shunting-yard into RPN, then a stack machine. Anything the tokenizer does
928 * not recognise makes the whole expression fail rather than fall through to
929 * something surprising.
930 *
931 * @param {string} expression Expression with numbers and + - * / ( ).
932 * @return {number|null} Result, or null when the expression is not valid.
933 */
934 function evaluateExpression(expression) {
935 var tokens = String(expression).match(/\d+\.?\d*|[-+*/()]/g);
936 if (!tokens || !tokens.length) {
937 return null;
938 }
939
940 // Reject anything the tokenizer skipped over: letters, symbols, stray
941 // characters. Only then is what is left safe to fold.
942 if (tokens.join('').length !== String(expression).replace(/\s+/g, '').length) {
943 return null;
944 }
945
946 var precedence = { '+': 1, '-': 1, '*': 2, '/': 2 };
947 var output = [];
948 var operators = [];
949 var previous = null;
950
951 for (var i = 0; i < tokens.length; i++) {
952 var token = tokens[i];
953
954 if (/^\d/.test(token)) {
955 output.push(parseFloat(token));
956 } else if ('(' === token) {
957 operators.push(token);
958 } else if (')' === token) {
959 while (operators.length && operators[operators.length - 1] !== '(') {
960 output.push(operators.pop());
961 }
962 if (!operators.length) {
963 return null;
964 }
965 operators.pop();
966 } else {
967 // A minus with nothing usable in front of it is a sign, not an
968 // operator: turn "-5" into "0 - 5".
969 if ('-' === token && (null === previous || '(' === previous || precedence[previous])) {
970 output.push(0);
971 }
972
973 while (
974 operators.length &&
975 operators[operators.length - 1] !== '(' &&
976 precedence[operators[operators.length - 1]] >= precedence[token]
977 ) {
978 output.push(operators.pop());
979 }
980 operators.push(token);
981 }
982
983 previous = token;
984 }
985
986 while (operators.length) {
987 var last = operators.pop();
988 if ('(' === last) {
989 return null;
990 }
991 output.push(last);
992 }
993
994 var stack = [];
995 for (var j = 0; j < output.length; j++) {
996 var item = output[j];
997
998 if ('number' === typeof item) {
999 stack.push(item);
1000 continue;
1001 }
1002
1003 if (stack.length < 2) {
1004 return null;
1005 }
1006
1007 var right = stack.pop();
1008 var left = stack.pop();
1009
1010 switch (item) {
1011 case '+': stack.push(left + right); break;
1012 case '-': stack.push(left - right); break;
1013 case '*': stack.push(left * right); break;
1014 case '/':
1015 if (0 === right) {
1016 return null;
1017 }
1018 stack.push(left / right);
1019 break;
1020 default: return null;
1021 }
1022 }
1023
1024 if (1 !== stack.length || !isFinite(stack[0])) {
1025 return null;
1026 }
1027
1028 return stack[0];
1029 }
1030
1031 /**
1032 * Keep calculation fields in step with the fields they reference.
1033 *
1034 * @param {jQuery} $scope Widget scope.
1035 */
1036 function initCalculations($scope) {
1037 var root = $scope && $scope[0] ? $scope[0] : document;
1038 var form = root.querySelector('form');
1039 var fields = root.querySelectorAll('[data-ka-calc]');
1040
1041 if (!form || !fields.length) {
1042 return;
1043 }
1044
1045 /**
1046 * Numeric value of one referenced field.
1047 *
1048 * @param {string} fieldId Field ID.
1049 * @return {number} Value, 0 when there is nothing usable.
1050 */
1051 function numberOf(fieldId) {
1052 var input = form.querySelector('#form-field-' + CSS.escape(fieldId));
1053 var raw = '';
1054
1055 if (input) {
1056 if (input.type === 'checkbox' || input.type === 'radio') {
1057 raw = input.checked ? input.value : '0';
1058 } else {
1059 raw = input.value;
1060 }
1061 } else {
1062 var checked = form.querySelector('[name="form_fields[' + fieldId + ']"]:checked, [name="form_fields[' + fieldId + '][]"]:checked');
1063 if (checked) {
1064 raw = checked.value;
1065 }
1066 }
1067
1068 var value = parseFloat(String(raw).replace(',', '.'));
1069
1070 return isFinite(value) ? value : 0;
1071 }
1072
1073 function apply() {
1074 Array.prototype.forEach.call(fields, function (wrap) {
1075 var input = wrap.querySelector('input');
1076 var formula = wrap.getAttribute('data-formula') || '';
1077
1078 if (!input || '' === formula.trim()) {
1079 return;
1080 }
1081
1082 var resolved = formula.replace(/\[([^\]]+)\]/g, function (match, fieldId) {
1083 return '(' + numberOf(fieldId.trim()) + ')';
1084 });
1085
1086 var result = evaluateExpression(resolved);
1087
1088 if (null === result) {
1089 input.value = '';
1090 return;
1091 }
1092
1093 var decimals = parseInt(wrap.getAttribute('data-decimals') || '2', 10);
1094 var text = result.toFixed(isFinite(decimals) ? decimals : 2);
1095
1096 input.value = (wrap.getAttribute('data-prefix') || '') + text + (wrap.getAttribute('data-suffix') || '');
1097 });
1098 }
1099
1100 form.addEventListener('input', apply);
1101 form.addEventListener('change', apply);
1102 apply();
1103 }
1104
1105
1106 /**
1107 * Honeypot value and captcha token for this form.
1108 *
1109 * Every submit action is its own request, and each one is
1110 * checked on the server, so the challenge has to travel with
1111 * all of them.
1112 *
1113 * @return {Object} Extra request fields.
1114 */
1115 function spamPayload() {
1116 var extra = {
1117 king_addons_form_id: $scope.find('input[name="form_id"]').val()
1118 };
1119
1120 var honeypot = $scope.find('.king-addons-form-honeypot__input');
1121 if (honeypot.length) {
1122 extra[honeypot.attr('name')] = honeypot.val();
1123 }
1124
1125 var captcha = $scope.find('[data-ka-captcha]');
1126 if (captcha.length) {
1127 var token = captcha.find('[name="cf-turnstile-response"], [name="h-captcha-response"], [name="g-recaptcha-response"]').val();
1128 extra.ka_captcha_token = token || '';
1129 }
1130
1131 return extra;
1132 }
1133
1134
1135 /**
1136 * Save what the visitor has typed, before they submit.
1137 *
1138 * @param {jQuery} $scope Widget scope.
1139 */
1140 function initPartialEntries($scope) {
1141 var submitGroup = $scope.find('.king-addons-form-field-type-submit');
1142
1143 if ('1' !== String(submitGroup.data('partial-entries') || '')) {
1144 return;
1145 }
1146
1147 var form = $scope.find('form');
1148 if (!form.length) {
1149 return;
1150 }
1151
1152 var formId = $scope.find('input[name="form_id"]').val();
1153 var storageKey = 'kaPartial:' + formId;
1154 var entryKey = '';
1155
1156 try {
1157 entryKey = window.sessionStorage.getItem(storageKey) || '';
1158 } catch (e) {
1159 entryKey = '';
1160 }
1161
1162 if (!entryKey) {
1163 // One key per visit, so a returning visitor updates their own row
1164 // instead of starting another.
1165 entryKey = 'ka' + Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
1166 try {
1167 window.sessionStorage.setItem(storageKey, entryKey);
1168 } catch (e) {
1169 // Private browsing: the entry simply will not be reused.
1170 }
1171 }
1172
1173 var timer = null;
1174 var lastSent = '';
1175
1176 /**
1177 * Send the current values, at most once every couple of seconds.
1178 */
1179 function save() {
1180 createFormContent();
1181
1182 var snapshot = JSON.stringify(formContent);
1183 if (snapshot === lastSent || '{}' === snapshot) {
1184 return;
1185 }
1186 lastSent = snapshot;
1187
1188 // Send a copy: formContent is shared with the submit path and is
1189 // rebuilt from scratch there.
1190 var payload = JSON.parse(snapshot);
1191
1192 $.ajax({
1193 type: 'POST',
1194 url: KingAddonsFormBuilderData.ajaxurl,
1195 data: {
1196 action: 'king_addons_form_builder_partial',
1197 nonce: KingAddonsFormBuilderData.nonce,
1198 king_addons_form_id: formId,
1199 form_page_id: form.attr('page_id'),
1200 entry_key: entryKey,
1201 form_content: payload
1202 }
1203 });
1204 }
1205
1206 form.on('input change', function () {
1207 window.clearTimeout(timer);
1208 timer = window.setTimeout(save, 2000);
1209 });
1210
1211 // A finished submission is the record; the draft is no longer wanted.
1212 form.on('king-addons/form/submitted', function () {
1213 try {
1214 window.sessionStorage.removeItem(storageKey);
1215 } catch (e) {
1216 // Nothing to clean up.
1217 }
1218
1219 $.ajax({
1220 type: 'POST',
1221 url: KingAddonsFormBuilderData.ajaxurl,
1222 data: {
1223 action: 'king_addons_form_builder_partial',
1224 nonce: KingAddonsFormBuilderData.nonce,
1225 king_addons_form_id: formId,
1226 form_page_id: form.attr('page_id'),
1227 entry_key: entryKey,
1228 finalise: 1
1229 }
1230 });
1231 });
1232 }
1233
1234 function createFormContent() {
1235 $scope.find('.king-addons-form-field, .king-addons-form-field-type-radio, .king-addons-form-field-type-checkbox, .king-addons-fb-step-input').each(function () {
1236
1237 // A field hidden by conditional logic is not part of
1238 // the submission.
1239 if ($(this).closest('.king-addons-cond-hidden').length) {
1240 return;
1241 }
1242
1243 // prev('label') always returns a jQuery object, so the
1244 // old else branch never ran; and the newer field types
1245 // wrap their input, which puts the label out of reach
1246 // of prev(). Fall back to the field group's own label.
1247 var label = $(this).prev('label').text().trim();
1248 if ('' === label) {
1249 label = $(this).closest('.king-addons-field-group')
1250 .children('.king-addons-form-field-label')
1251 .first().text().trim();
1252 }
1253
1254 if ('textarea' !== $(this).prop('tagName').toLowerCase()) {
1255 if ($(this).hasClass('king-addons-select-wrap')) {
1256 var selectValue = $(this).find('select').val();
1257 if (Array.isArray($(this).find('select').val())) {
1258 selectValue = $(this).find('select').val().join(', ');
1259 } else {
1260 selectValue = $(this).find('select').val();
1261 }
1262 formContent[$(this).find('select').attr('id').replace('-', '_')] = ['select', selectValue, label];
1263 } else if ($(this).hasClass('king-addons-form-field-type-radio') || $(this).hasClass('king-addons-form-field-type-checkbox')) {
1264 var valuesArray = [];
1265 var checkedField = $(this).find('input');
1266 var type;
1267 checkedField.each(function () {
1268 valuesArray.push([$(this).val(), $(this).is(':checked'), $(this).attr('name'), $(this).attr('id')]);
1269 });
1270
1271 if ($(this).hasClass('king-addons-form-field-type-radio')) {
1272 type = 'radio'
1273 } else {
1274 type = 'checkbox';
1275 }
1276
1277 var inputLabel = $(this).find('.king-addons-form-field-label').text().trim();
1278
1279 if (checkedField.length > 0) {
1280 formContent[$(this).find('.king-addons-form-field-option').data('key').replace('-', '_')] = [type, valuesArray, inputLabel];
1281 }
1282 } else if ($(this).attr('type') === 'checkbox' && $(this).closest('.king-addons-field-group').hasClass('king-addons-form-field-type-acceptance')) {
1283 // A lone consent checkbox: val() would report
1284 // its value attribute whether it is ticked or not.
1285 formContent[$(this).attr('id').replace('-', '_')] = ['acceptance', $(this).is(':checked') ? $(this).val() : '', label];
1286 } else if ($(this).hasClass('king-addons-fb-step-input')) {
1287 formContent[$(this).attr('id').replace('-', '_')] = [$(this).attr('type'), '', $(this).val(), label];
1288 } else {
1289 if ($(this).attr('type') == 'file') {
1290 formContent[$(this).attr('id').replace('-', '_')] = [$(this).attr('type'), fileUrl[$(this).attr('id')], label];
1291 } else {
1292 formContent[$(this).attr('id').replace('-', '_')] = [$(this).attr('type'), $(this).val(), label];
1293 }
1294 }
1295 } else {
1296 formContent[$(this).attr('id').replace('-', '_')] = [$(this).prop('tagName').toLowerCase(), $(this).val(), label];
1297 }
1298
1299 });
1300 }
1301
1302 function handleFileValidityAndUpload(thisInput, files, eventType) {
1303 var thisId = thisInput.attr('id');
1304
1305 if (0 < thisInput.closest('.king-addons-field-group').find('.king-addons-submit-error').length) {
1306 thisInput.closest('.king-addons-field-group').find('.king-addons-submit-error').remove();
1307 }
1308
1309
1310 var maxFileSize = thisInput.data('maxfs') ? thisInput.data('maxfs') : 0;
1311 var allowedFileTypes = thisInput.data('allft') ? thisInput.data('allft') : 0;
1312
1313
1314 let uploadPromises = [];
1315
1316 for (let i = 0; i < files.length; i++) {
1317 var fileInput = files[i];
1318
1319
1320 var formDataForFile = new FormData();
1321 formDataForFile.append('action', 'king_addons_upload_file');
1322 formDataForFile.append('uploaded_file', fileInput);
1323 formDataForFile.append('max_file_size', maxFileSize);
1324 formDataForFile.append('allowed_file_types', allowedFileTypes);
1325 formDataForFile.append('triggering_event', eventType);
1326 formDataForFile.append('king_addons_fb_nonce', KingAddonsFormBuilderData.nonce);
1327
1328 if ('click' == eventType) {
1329 if (!fileUrl[thisId]) {
1330 fileUrl[thisId] = [];
1331 }
1332 }
1333
1334
1335 uploadPromises.push(
1336 new Promise((resolve, reject) => {
1337 $.ajax({
1338 url: KingAddonsFormBuilderData.ajaxurl,
1339 type: 'POST',
1340 data: formDataForFile,
1341 processData: false,
1342 contentType: false,
1343 success: function (response) {
1344 if (response.success) {
1345
1346 // console.log(response);
1347 if (eventType == 'click') {
1348 fileUrl[thisId][i] = response.data.url;
1349 }
1350 resolve(response);
1351 } else {
1352 // console.error('Error:', response);
1353 if (response.data) {
1354 if ('filesize' === response.data.cause) {
1355 let maxFileNotice = thisInput.data('maxfs-notice') ? thisInput.data('maxfs-notice') : response.data.message;
1356 thisInput.closest('.king-addons-field-group').append('<p class="king-addons-submit-error">' + maxFileNotice + '</p>');
1357 }
1358
1359 if ('filetype' == response.data.cause) {
1360 thisInput.closest('.king-addons-field-group').append('<p class="king-addons-submit-error">' + response.data.message + '</p>');
1361 }
1362 }
1363
1364 reject(response);
1365 }
1366 },
1367 error: function (error) {
1368 if ('filesize' === error.cause) {
1369 let maxFileNotice = thisInput.data('maxfs-notice') ? thisInput.data('maxfs-notice') : error.message;
1370 thisInput.closest('.king-addons-field-group').append('<p class="king-addons-submit-error">' + maxFileNotice + '</p>');
1371 }
1372
1373 if ('filetype' == error.cause) {
1374 thisInput.closest('.king-addons-field-group').append('<p class="king-addons-submit-error">' + error.message + '</p>');
1375 }
1376 // console.log(error);
1377 reject(error);
1378 },
1379 });
1380 }),
1381 );
1382 }
1383
1384
1385 return Promise.all(uploadPromises);
1386 }
1387
1388 function convertToMailchimpBirthdayFormat(dateString) {
1389 const date = new Date(dateString);
1390 const month = (date.getMonth() + 1).toString().padStart(2, '0');
1391 const day = date.getDate().toString().padStart(2, '0');
1392 return `${month}/${day}`;
1393 }
1394
1395 function showTab(n) {
1396
1397 var $stepTab = $scope.find(".king-addons-fb-step-tab");
1398 $stepTab.eq(n).removeClass('king-addons-fb-step-tab-hidden');
1399
1400 if (n === 0) {
1401 $scope.find(".king-addons-fb-step-prev").hide();
1402 } else {
1403 $scope.find(".king-addons-fb-step-prev").show();
1404 }
1405
1406 fixStepIndicator(n);
1407 }
1408
1409 function nextPrev(n) {
1410
1411 var $stepTab = $scope.find(".king-addons-fb-step-tab");
1412
1413
1414 if (n === 1 && !validateForm()) {
1415 return false;
1416 }
1417
1418 $stepTab.eq(currentTab).addClass('king-addons-fb-step-tab-hidden');
1419
1420 currentTab = currentTab + n;
1421
1422 if (currentTab >= $stepTab.length) {
1423
1424 $scope.find("form").submit();
1425 return false;
1426 }
1427
1428 showTab(currentTab);
1429 }
1430
1431 function validateForm() {
1432 var valid = true;
1433 var $stepTab = $scope.find(".king-addons-fb-step-tab");
1434 if (!($stepTab.length > 0)) {
1435 $stepTab = $scope.find('.king-addons-form-fields-wrap');
1436 currentTab = 0;
1437 }
1438 var $types = ['text', 'email', 'password', 'file', 'url', 'tel', 'number', 'date', 'datetime-local', 'time', 'week', 'month', 'color'];
1439
1440 $stepTab.eq(currentTab).find('input, select, textarea').each(function () {
1441 if ($(this).closest('.king-addons-cond-hidden').length) {
1442 return;
1443 }
1444
1445 const type = $(this).attr('type');
1446
1447 var requiredField = $(this).closest('.king-addons-field-group').find('.king-addons-form-field').attr('required') === 'required' || $(this).closest('.king-addons-field-group').find('.king-addons-form-field-textual').attr('required') === 'required';
1448
1449
1450
1451
1452
1453 if (type !== undefined && $.inArray(type, $types) !== -1 && $(this).val() === '' && requiredField) {
1454
1455 $(this).addClass("king-addons-form-error");
1456
1457 valid = false;
1458 } else if (type === 'radio' || type === 'checkbox') {
1459 let requiredOption = $(this).closest('.king-addons-field-group').find('.king-addons-form-field-option input').attr('required') === 'required';
1460
1461 if (requiredOption && $stepTab.eq(currentTab).find('input[type="' + type + '"]:checked').length === 0) {
1462
1463 $(this).addClass("king-addons-form-error");
1464
1465 valid = false;
1466 }
1467 } else if (requiredField && this.tagName === 'SELECT' && $(this).val().trim() === '') {
1468
1469 $(this).closest('.king-addons-select-wrap').addClass('king-addons-form-error-wrap');
1470
1471 $(this).addClass("king-addons-form-error");
1472
1473 valid = false;
1474 } else if (requiredField && this.tagName === 'TEXTAREA' && $(this).val().trim() === '') {
1475
1476 $(this).addClass("king-addons-form-error");
1477
1478 valid = false;
1479 }
1480 });
1481
1482 // Rating, signature and acceptance carry their value in a
1483 // hidden input or a lone checkbox, so the loop above never
1484 // sees them as required.
1485 $stepTab.eq(currentTab).find('[data-ka-rating][data-ka-required], [data-ka-signature][data-ka-required]').not('.king-addons-cond-hidden *').each(function () {
1486 var holder = $(this).find('input');
1487 if (!holder.length || '' === String(holder.val() || '').trim()) {
1488 $(this).addClass('king-addons-form-error');
1489 valid = false;
1490 } else {
1491 $(this).removeClass('king-addons-form-error');
1492 }
1493 });
1494
1495 $stepTab.eq(currentTab).find('.king-addons-form-field-type-acceptance:not(.king-addons-cond-hidden) input[type="checkbox"][required]').each(function () {
1496 if (!$(this).is(':checked')) {
1497 $(this).addClass('king-addons-form-error');
1498 valid = false;
1499 } else {
1500 $(this).removeClass('king-addons-form-error');
1501 }
1502 });
1503
1504 if (!valid) {
1505 $stepTab.eq(currentTab).find('.king-addons-form-error, .king-addons-form-error-wrap').each(function () {
1506 if (!($(this).closest('.king-addons-field-group').find('.king-addons-submit-error').length > 0)) {
1507 if ($(this).attr('type') == 'file') {
1508 $(this).closest('.king-addons-field-group').append('<p class="king-addons-submit-error">' + KingAddonsFormBuilderData.file_empty + '</p>');
1509 } else if ($(this).is('select') || $(this).attr('type') === 'radio' || $(this).attr('type') === 'checkbox') {
1510 $(this).closest('.king-addons-field-group').append('<p class="king-addons-submit-error">' + KingAddonsFormBuilderData.select_empty + '</p>');
1511 } else {
1512 $(this).closest('.king-addons-field-group').append('<p class="king-addons-submit-error">' + KingAddonsFormBuilderData.input_empty + '</p>');
1513 }
1514 }
1515 });
1516 }
1517
1518 if (valid) {
1519 $scope.find(".king-addons-fb-step").eq(currentTab).addClass("king-addons-fb-step-finish");
1520 } else {
1521 if ($scope.find(".king-addons-fb-step").eq(currentTab).hasClass('king-addons-fb-step-finish')) {
1522 $scope.find(".king-addons-fb-step").eq(currentTab).removeClass('king-addons-fb-step-finish');
1523 }
1524 }
1525
1526 return valid;
1527 }
1528
1529 function fixStepIndicator(n) {
1530
1531 var $step = $scope.find(".king-addons-fb-step");
1532 $step.removeClass("king-addons-fb-step-active");
1533
1534 $step.eq(n).addClass("king-addons-fb-step-active");
1535
1536 if ($scope.find('.king-addons-fb-step-active').hasClass('king-addons-fb-step-finish')) {
1537 $scope.find('.king-addons-fb-step-active').removeClass('king-addons-fb-step-finish');
1538 }
1539
1540 const stepTabs = $scope.find('.king-addons-fb-step-tab');
1541 const progressBarFill = $scope.find('.king-addons-fb-step-progress-fill');
1542
1543 let currentStep = n + 1;
1544
1545 updateProgressBar()
1546
1547 function updateProgressBar() {
1548 const totalSteps = stepTabs.length;
1549 const progressPercentage = (currentStep / totalSteps) * 100;
1550
1551 progressBarFill.css('width', progressPercentage + '%');
1552 setTimeout(function () {
1553 progressBarFill.text(Math.round(progressPercentage) + '%');
1554 }, 500);
1555 }
1556 }
1557
1558
1559
1560 },
1561 }), {
1562 $element: $scope
1563 });
1564 });
1565 });
1566 })(jQuery);