PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.83
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 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Form_Builder / script.js

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

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