PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
← All changes | includes/widgets/Form_Builder/script.js +1570 -0 51.1.2 → 51.1.87 View file →
@@ -1,0 +1,1570 @@
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);