PageSourceSearch

https://scoutingfryslan.nl/components/com_baforms/assets/js/ba-form.js?2.4.3.4

js scoutingfryslan.nl collected 2026-10-03 01:58:31 UTC 145,827 bytes, 3,635 lines download raw bytes

1/**
2 * @package   BaForms
3 * @author    Balbooa http://www.balbooa.com/
4 * @copyright Copyright @ Balbooa
5 * @license   http://www.gnu.org/licenses/gpl.html GNU/GPL
6 */
7
8var $f = jQuery,
9    formsApp = {
10        loaded: {}
11    };
12
13!function($){
14    class formsModal
15    {
16        constructor(element) {
17            this.element = $(element);
18            this.position = element.dataset.position ? element.dataset.position : '';
19            this.setEvents();
20            if (element.parentNode != document.body) {
21                $('body').append(this.element);
22            }
23        }
24
25        setEvents()
26        {
27            this.element.on('click keyup', (event) => {
28                FormsAccessibilityHelper.processAction(event, () => {
29                    this.hide(event)
30                })
31            })
32            this.element.on('keyup', event => {
33                if (event.key == 'Escape') {
34                    this.hide();
35                }
36            })
37        }
38
39        show() {
40            this.element.css({
41                'opacity': '',
42                'pointer-events': ''
43            });
44            this.setScroll();
45            this.element.addClass('visible-forms-modal');
46            setTimeout(() => {
47                this.element.addClass('motion-start');
48            }, 1)
49            FormsAccessibilityHelper.trapFocus(this.element[0], 100);
50            let scroll = this.element[0].offsetWidth - this.element[0].clientWidth;
51            this.element[0].style.setProperty('--forms-modal-scroll-width', scroll + 'px');
52        }
53
54        setScroll() {
55            document.body.classList.add('forms-modal-position-' + this.position);
56            if (!document.body.classList.contains('forms-modal-opened')) {
57                let scroll = window.innerWidth - document.documentElement.clientWidth;
58                document.body.classList.add('forms-modal-opened');
59                document.body.style.setProperty('--forms-body-scroll-width', scroll + 'px');
60            }
61        }
62
63        hide(event) {
64            if (!event || event.target.dataset.dismiss == 'formsModal') {
65                let $this = this;
66                this.element.removeClass('motion-start')
67                setTimeout(() => {
68                    this.element.removeClass('visible-forms-modal');
69                }, 800)
70                FormsAccessibilityHelper.releaseTrap(this.element[0])
71                setTimeout(function () {
72                    $this.hideScroll();
73                }, 300);
74            }
75        }
76
77        hideScroll() {
78            document.body.classList.remove('forms-modal-position-' + this.position);
79            if (!document.querySelector('.visible-forms-modal')) {
80                document.body.classList.remove('forms-modal-opened');
81                document.body.style.removeProperty('--forms-body-scroll-width');
82            }
83        }
84    }
85    $.fn.formsModal = function(option){
86        return this.each(function() {
87            let data = $(this).data('formsModal');
88            if (!data) {
89                $(this).data('formsModal', (data = new formsModal(this)));
90            }
91            if (typeof option == 'string') {
92                data[option]();
93            } else {
94                data.show();
95            }
96        });
97    }
98    $.fn.formsModal.Constructor = formsModal;
99}(window.$f);
100
101class FormsAccessibilityHelper
102{
103    static focusableSelectors = [
104        'a[href]:not([tabindex="-1"])',
105        'area[href]:not([tabindex="-1"])',
106        'input:not([disabled]):not([tabindex="-1"])',
107        'select:not([disabled]):not([tabindex="-1"])',
108        'textarea:not([disabled]):not([tabindex="-1"])',
109        'button:not([disabled]):not([tabindex="-1"])',
110        'iframe:not([tabindex="-1"])',
111        'object:not([tabindex="-1"])',
112        'embed:not([tabindex="-1"])',
113        '[tabindex]:not([tabindex="-1"])',
114        '[contenteditable]:not([tabindex="-1"])',
115    ];
116
117    constructor(wrapper, timeout = 0)
118    {
119        this.wrapper = wrapper;
120        this.timeout = timeout;
121        this.handleKeydown = this.handleTrappedKeydown.bind(this);
122    }
123
124    getVisibleFocusableElements()
125    {
126        const elements = Array.from(
127            this.wrapper.querySelectorAll(FormsAccessibilityHelper.focusableSelectors.join(','))
128        );
129
130        return elements.filter(element =>
131            element.offsetParent !== null && getComputedStyle(element).visibility !== 'hidden'
132        );
133    }
134
135    trap()
136    {
137        const elements = this.getVisibleFocusableElements();
138        if (elements.length === 0) return;
139
140        this.focusedElement = document.activeElement;
141        this.focusedElement.blur();
142        this.wrapper.setAttribute('tabindex', '-1');
143        this.wrapper.style.outline = 'none';
144        setTimeout(() => {
145            this.wrapper.focus();
146            this.wrapper.addEventListener('keydown', this.handleKeydown);
147        }, this.timeout);
148    }
149
150    handleTrappedKeydown(event)
151    {
152        if (event.key !== 'Tab') {
153            return;
154        }
155        const elements = this.getVisibleFocusableElements();
156        if (elements.length === 0) return;
157
158        const first = elements[0];
159        const last = elements[elements.length - 1];
160        if (event.shiftKey && document.activeElement === first) {
161            event.preventDefault();
162            last.focus();
163        } else if (!event.shiftKey && document.activeElement === last) {
164            event.preventDefault();
165            first.focus();
166        }
167    }
168
169    release()
170    {
171        this.wrapper.removeEventListener('keydown', this.handleKeydown);
172        this.wrapper.removeAttribute('tabindex');
173        this.wrapper.style.outline = '';
174        clearTimeout(this.delay);
175        this.delay = setTimeout(() => {
176            this.focusedElement.focus();
177        }, 300);
178    }
179
180    static trapFocus(wrapper, timeout = 0)
181    {
182        if (!wrapper.accessibility) {
183            wrapper.accessibility = new this(wrapper, timeout);
184        }
185        wrapper.accessibility.trap();
186    }
187
188    static releaseTrap(wrapper)
189    {
190        if (!wrapper.accessibility) return;
191        setTimeout(() => {
192            wrapper.accessibility.release()
193        }, 100);
194    }
195
196    static isClick(event)
197    {
198        return event.type == "click" || event.type == "touchend"
199    }
200
201    static processAction(event, callback, eventsList)
202    {
203        if (eventsList && eventsList.indexOf(event.type) == -1) return;
204        if (this.isClick(event) || (event.type == "keyup" && event.key == 'Enter')) {
205            callback.call();
206        }
207    }
208}
209
210formsApp.renderFormsCalendar = function(input){
211    if (input) {
212        formsApp.calendar.formInput = input;
213    }
214    formsApp.fetch.request(JUri+'index.php?option=com_baforms&task=form.renderFormsCalendar', {
215        start: formsApp.calendar.start,
216        year: formsApp.calendar.year,
217        month: formsApp.calendar.month * 1 + 1
218    }).then(function(text){
219        let data = JSON.parse(text),
220            disable = formsApp.calendar.disable,
221            input = formsApp.calendar.formInput,
222            div = document.createElement('div');
223        div.innerHTML = data.body;
224        for (let i = 0; i < disable.days.length; i++) {
225            div.querySelectorAll('.ba-date-cell[data-day-number="'+disable.days[i]+'"]').forEach((cell) => {
226                cell.classList.add('disabled-date');
227                cell.setAttribute('tabindex', -1);
228            });
229        }
230        for (let i = 0; i < disable.dates.length; i++) {
231            div.querySelectorAll('.ba-date-cell[data-day-date="'+disable.dates[i]+'"]').forEach((cell) => {
232                cell.classList.add('disabled-date');
233                cell.setAttribute('tabindex', -1);
234            })
235        }
236        for (let i = 0; i < disable.range.length; i++) {
237            let minMax = disable.range[i].split(' - ');
238            div.querySelectorAll('.ba-date-cell:not(.disabled-date)').forEach((cell) => {
239                if (cell.dataset.dayDate >= minMax[0] && cell.dataset.dayDate <= minMax[1]) {
240                    cell.classList.add('disabled-date');
241                    cell.setAttribute('tabindex', -1);
242                }
243            });
244        }
245        div.querySelectorAll('.ba-curent-date').forEach(($this) => {
246            let today = formsApp.calendar.querySelector('.ba-forms-today-btn'),
247                action = $this.classList.contains('disabled-date') ? 'add' : 'remove';
248            today.dataset.date = $this.dataset.date;
249            today.dataset.dayDate = $this.dataset.dayDate;
250            today.classList[action]('disabled-date');
251            today.setAttribute('tabindex', (action == 'add' ? -1 : 0));
252        });
253        if (input.rangeBtn && input.dataset.index == 1) {
254            let date = input.rangeBtn.dataset.value;
255            div.querySelectorAll('.ba-date-cell:not(.disabled-date)').forEach((cell) => {
256                if (cell.dataset.dayDate <= date) {
257                    cell.classList.add('disabled-date');
258                    cell.setAttribute('tabindex', -1);
259                }
260            });
261        }
262        formsApp.calendar.querySelector('.ba-forms-calendar-title').textContent = data.title;
263        formsApp.calendar.querySelector('.ba-forms-calendar-header').innerHTML = data.header;
264        formsApp.calendar.querySelector('.ba-forms-calendar-body').innerHTML = div.innerHTML;
265        if (!formsApp.calendar.classList.contains('visible-forms-calendar')) {
266            formsApp.calendar.classList.add('visible-forms-calendar');
267            FormsAccessibilityHelper.trapFocus(formsApp.calendar);
268        }
269    });
270}
271
272formsApp.setFormsCalendarEvents = function(){
273    let $calendar = $f(formsApp.calendar);
274    $calendar.find('.ba-forms-today-btn').on('click keyup', function(event){
275        if (this.classList.contains('disabled-date')) return;
276
277        FormsAccessibilityHelper.processAction(event, () => {
278            formsApp.calendar.formInput.value = this.dataset.date;
279            formsApp.calendar.formInput.dataset.value = this.dataset.dayDate;
280            $f(formsApp.calendar.formInput).trigger('input');
281            formsApp.hideCalendar();
282        });
283    });
284    $calendar.find('i[data-action]').on('click keyup', function(event){
285        FormsAccessibilityHelper.processAction(event, () => {
286            let year = formsApp.calendar.year * 1,
287                month = formsApp.calendar.month * 1;
288            if (this.dataset.action == 'next') {
289                year = (month === 11) ? year + 1 : year;
290                month = (month + 1) % 12;
291            } else if (this.dataset.action == 'prev') {
292                year = (month === 0) ? year - 1 : year;
293                month = (month === 0) ? 11 : month - 1;
294            } else if (this.dataset.action == 'next-year') {
295                year = year + 1;
296            } else if (this.dataset.action == 'prev-year') {
297                year = year - 1;
298            }
299            formsApp.calendar.year = year;
300            formsApp.calendar.month = month;
301            formsApp.renderFormsCalendar();
302        });
303    });
304    $calendar.on('click keyup', '.ba-date-cell', function(event){
305        FormsAccessibilityHelper.processAction(event, () => {
306            if (!(formsApp.calendar.classList.contains('disable-previous-date') && this.classList.contains('ba-previous-date'))
307                && !this.classList.contains('disabled-date')) {
308                formsApp.calendar.formInput.value = this.dataset.date;
309                formsApp.calendar.formInput.dataset.value = this.dataset.dayDate;
310                $f(formsApp.calendar.formInput).trigger('input');
311                formsApp.hideCalendar();
312            }
313        });
314    });
315    $calendar.on('keyup', function(event){
316        if (event.key == 'Escape') {
317            formsApp.hideCalendar();
318        }
319    });
320    $calendar.find('.ba-close-calendar').on('click', function(){
321        formsApp.hideCalendar();
322    });
323}
324
325formsApp.hideCalendar = function(){
326    formsApp.calendar.classList.add('forms-calendar-out');
327    FormsAccessibilityHelper.releaseTrap(formsApp.calendar);
328    setTimeout(function(){
329        formsApp.calendar.classList.remove('visible-forms-calendar');
330        formsApp.calendar.classList.remove('forms-calendar-out');
331    }, 600);
332}
333
334formsApp.createCalendar = function($this){
335    formsApp.calendar = document.createElement('div');
336    formsApp.calendar.className = 'forms-calendar-wrapper';
337    formsApp.calendar.innerHTML = `<div class="ba-forms-calendar">
338            <div class="ba-forms-calendar-title-wrapper">
339                <span class="forms-calendar-btn">
340                    <i class="ba-form-icons ba-icon-chevron-left" tabindex="0" data-action="prev-year"></i>
341                    <span class="ba-tooltip ba-bottom ba-hide-element">
342                        ${formsApp._('YEAR')}
343                    </span>
344                </span>
345                <span class="forms-calendar-btn">
346                    <i class="ba-form-icons ba-icon-chevron-left" tabindex="0" data-action="prev"></i>
347                    <span class="ba-tooltip ba-bottom ba-hide-element">
348                        ${formsApp._('MONTH')}
349                    </span>
350                </span>
351                <span class="ba-forms-calendar-title"></span>
352                <span class="forms-calendar-btn">
353                    <i class="ba-form-icons ba-icon-chevron-right" tabindex="0" data-action="next"></i>
354                    <span class="ba-tooltip ba-bottom ba-hide-element">
355                        ${formsApp._('MONTH')}
356                    </span>
357                </span>
358                <span class="forms-calendar-btn">
359                    <i class="ba-form-icons ba-icon-chevron-right" tabindex="0" data-action="next-year"></i>
360                    <span class="ba-tooltip ba-bottom ba-hide-element">
361                        ${formsApp._('YEAR')}
362                    </span>
363                </span>
364            </div>
365            <div class="ba-forms-calendar-header"></div>
366            <div class="ba-forms-calendar-body"></div>
367            <div class="ba-forms-calendar-footer">
368                <span class="ba-forms-today-btn" tabindex="0">
369                    ${formsApp._('TODAY')}
370                </span>
371            </div>
372        </div>
373        <div class="ba-close-calendar"></div>`
374    document.body.appendChild(formsApp.calendar);
375    formsApp.calendar.year = $this.dataset.year;
376    formsApp.calendar.month = $this.dataset.month;
377    formsApp.setFormsCalendarEvents();
378}
379
380formsApp.hideNotice = function(){
381    this.notification.classList.remove('notification-in');
382    this.notification.classList.add('animation-out');
383}
384
385formsApp.showNotice = function(message, className = ''){
386    if (!this.notification) {
387        this.notification = document.createElement('div');
388        this.notification.id = 'ba-forms-notification';
389        this.notification.innerHTML = '<i class="ba-form-icons ba-icon-close"></i><h4>'+this._('ERROR')+'</h4><p></p>';
390        this.notification.querySelector('.ba-icon-close').addEventListener('click', function(){
391            formsApp.hideNotice();
392        });
393        document.body.append(this.notification);
394    }
395    this.notification.showCallback = function(){};
396    if (this.notification.classList.contains('notification-in')) {
397        this.notification.showCallback = function(){
398            formsApp.notification.showCallback = function(){};
399            formsApp.addNoticeText(message, className);
400        };
401    } else {
402        this.addNoticeText(message, className);
403    }
404}
405
406formsApp.addNoticeText = function(message, className){
407    $f(this.notification).find(' > p').html(message);
408    if (className) {
409        this.notification.classList.add(className);
410    } else {
411        this.notification.classList.remove('ba-alert');
412    }
413    this.notification.classList.remove('animation-out')
414    this.notification.classList.add('notification-in');
415    clearTimeout(this.notification.hideDelay);
416    this.notification.hideDelay = setTimeout(function(){
417        formsApp.hideNotice();
418        setTimeout(function(){
419            if (className) {
420                formsApp.notification.classList.remove(className);
421            }
422            formsApp.notification.showCallback();
423        }, 400);
424    }, 6000);
425}
426
427formsApp.getLocalStorage = function(){
428    let storage = localStorage.getItem('forms-progress'),
429        date = +new Date(),
430        expire = false;
431    if (!storage) {
432        storage = '{}';
433    }
434    formsApp.storage = JSON.parse(storage);
435    for (let ind in formsApp.storage) {
436        let page = formsApp.storage[ind];
437        for (let key in page) {
438            let data = page[key];
439            if (Math.floor((date - data.date) / (1000 * 60 * 60 * 24)) > 30) {
440                expire = true;
441                delete formsApp.storage[ind][key];
442            }
443        }
444    }
445    if (expire) {
446        localStorage.setItem('forms-progress', JSON.stringify(formsApp.storage));
447    }
448}
449
450formsApp.fetch = {
451    getErrorText: function(text){
452        let div = document.createElement('div');
453        div.innerHTML = text;
454        if (div.querySelector('title')) {
455            text = div.querySelector('title').textContent;
456        }
457
458        return text;
459    },
460    request: async function(url, data){
461        let request = await fetch(url, {
462                headers: {
463                    'Content-Type': 'application/x-www-form-urlencoded',
464                },
465                method: 'POST',
466                cache: 'no-cache',
467                body: this.getFormData(data)
468            }),
469            response = null;
470        if (request.ok) {
471            response = await request.text();
472        } else {
473            let utf8Decoder = new TextDecoder("utf-8"),
474                reader = request.body.getReader(),
475                textData = await reader.read(),
476                text = utf8Decoder.decode(textData.value);
477            console.info(this.getErrorText(text));
478        }
479
480        return response;
481    },
482    getFormData: function(data){
483        let formData = new URLSearchParams();
484        formData.append(formsApp.token, '1');
485        if (data) {
486            for (let ind in data) {
487                if (Array.isArray(data[ind])) {
488                    data[ind].forEach(v => {
489                        formData.append(`${ind}[]`, v);
490                    })
491                } else if (typeof data[ind] == 'object') {
492                    for (let i in data[ind]) {
493                        formData.append(`${ind}[${i}]`, data[ind][i]);
494                    }
495                } else {
496                    formData.append(ind, data[ind]);
497                }
498            }
499        }
500
501        return formData;
502    },
503
504}
505
506formsApp.countries = {
507    append: function(){
508        document.querySelectorAll('.ba-form-phone-field .ba-phone-countries-list').forEach(function(ul){
509            if (!ul.querySelector('li')) {
510                ul.innerHTML = formsApp.countries.html;
511            }
512        })
513    },
514    check: function(){
515        if (this.data) {
516            this.append()
517        } else if (document.querySelector('.ba-form-phone-field') && !this.loading) {
518            this.load();
519        }
520    },
521    readCountries: function(){
522        return new Promise(function(resolve) {
523            fetch(JUri+'index.php?option=com_baforms&task=form.readCountries').then(function(response){
524                if (response.ok) {
525                    response.json().then(function(json){
526                        resolve(json);
527                    });
528                } else {
529                    console.info(response)
530                }
531            });
532        });
533    },
534    load: function(){
535        this.loading = true;
536        fetch(JUri+'components/com_baforms/libraries/countries/countries.json').then(function(response){
537            if (!response.ok) {
538                return formsApp.countries.readCountries()
539            } else {
540                return response.json();
541            }
542        }).then(function(json){
543            formsApp.countries.set(json);
544        });
545    },
546    set: function(data){
547        let html = '';
548        for (let ind in data) {
549            const prefix = data[ind].prefix;
550            const title = data[ind].title;
551            const placeholder = data[ind].placeholder.replace(/X/g, '_');
552            html += `<li class="ba-phone-country-item" data-prefix="+${prefix}" tabindex="0"
553                    data-flag="${ind}" data-title="${title}" data-placeholder="${placeholder}">
554                <span class="ba-phone-flag ba-phone-flag-${ind}"></span>
555                <span class="ba-phone-country-title">${title}</span>
556                <span class="ba-phone-country-prefix">+${prefix}</span>
557            </li>`;
558        }
559        this.html = html;
560        this.data = data;
561        this.append();
562    },
563    hide: () => {
564        const wrapper = $f('.visible-countries-list').removeClass('visible-countries-list');
565        const list = wrapper.find('.ba-phone-countries-list-wrapper')[0];
566        if (!list) return;
567
568        FormsAccessibilityHelper.releaseTrap(list);
569    }
570}
571
572formsApp.createForms = function(){
573    FormsSignature.boot();
574    formsApp.refreshToken()
575    formsApp.checkGoogleMaps();
576    formsApp.checkLanguage();
577    formsApp.countries.check();
578    formsApp.getLocalStorage();
579    CaptchaHelper.loadIfExist();
580    $f('.com-baforms-wrapper').each(formsApp.createForm)
581        .closest('.ba-form-lightbox-layout').each(formsApp.renderLightbox);
582}
583
584formsApp.renderLightbox = function(){
585    if (!this.trigger) {
586        this.trigger = JSON.parse(this.dataset.trigger);
587        this.session = JSON.parse(this.dataset.session);
588        $f(this).removeAttr('data-trigger').removeAttr('data-session')
589    }
590    if (!this.session.enable) {
591        formsApp.initLightbox(this);
592    } else if (this.session.enable && this.trigger.type != '') {
593        let flag = true;
594        if (localStorage['formsModal-'+this.dataset.id]) {
595            let date =  new Date().getTime(),
596                expires = new Date(localStorage[this.dataset.id]);
597            expires.getTime();
598            if (date >= expires) {
599                flag = true;
600                localStorage.removeItem(this.dataset.id);
601            } else {
602                flag = false;
603            }
604        }
605        if (flag) {
606            formsApp.initLightbox(this);
607        }
608    }
609}
610
611formsApp.showLightbox = function($this){
612    if ($this.session.enable) {
613        const expiration = new Date();
614        expiration.setDate(expiration.getDate()+$this.session.duration);
615        localStorage.setItem('formsModal-'+$this.dataset.id, expiration);
616    }
617    $f($this).formsModal().find('.ba-form-submit-btn').each(function(){
618        CaptchaHelper.createIfExist(this);
619    });
620}
621
622formsApp.initLightbox = function($this){
623    if ($this.trigger.type == 'time-delay') {
624        setTimeout(function(){
625            formsApp.showLightbox($this);
626        }, $this.trigger.time);
627    } else if ($this.trigger.type == 'scrolling') {
628        formsApp.lightboxScroll($this, $this.trigger.scroll * 1);
629    } else if ($this.trigger.type == 'exit-intent') {
630        $f(document).one('mouseleave', function(){
631            formsApp.showLightbox($this);
632        });
633    } else if ($this.trigger.type == 'bottom-of-page') {
634        formsApp.lightboxScroll($this, 100);
635    }
636}
637
638formsApp.lightboxScroll = function($this, scroll){
639    let top,
640        docHeight,
641        htmlHeight;
642    $f(window).on('scroll.ba-lightbox'+$this.dataset.id+' load.ba-lightbox'+$this.dataset.id, function(){
643        top = $f(window).scrollTop();
644        docHeight = document.documentElement.clientHeight
645        htmlHeight = Math.max(
646            document.body.scrollHeight, document.documentElement.scrollHeight,
647            document.body.offsetHeight, document.documentElement.offsetHeight,
648            document.body.clientHeight, document.documentElement.clientHeight
649        );
650        const x = (docHeight + top) * 100 / htmlHeight;
651        if (x >= scroll || (scroll > 97 && x >= 97)) {
652            $f(window).off('scroll.ba-lightbox'+$this.dataset.id+' load.ba-lightbox'+$this.dataset.id);
653            formsApp.showLightbox($this)
654        }
655    });
656}
657
658formsApp.checkAlert = function(form){
659    form.find('.confirm-email-wrapper').find('input').each(function(){
660        let alert = !this.value.trim(),
661            key = 'THIS_FIELD_REQUIRED';
662        if (this.value) {
663            alert = !(/@/g.test(this.value) && this.value.match(/@/g).length == 1);
664            key = 'ENTER_VALID_VALUE';
665            if (!alert) {
666                let email = this.closest('.ba-form-field-item').querySelector('.ba-input-wrapper input').value.trim();
667                alert = !(email == this.value.trim());
668                key = 'EMAIL_ADDRESSES_NOT_MATCH';
669            }
670        }
671        formsApp.toggleAlertTooltip(alert, this, this.closest('.ba-field-container'), key);
672    });
673    form.find('.ba-form-signature-field').each(function(){
674        if (this.dataset.required) {
675            let alert = this.instance.isEmpty(),
676                container = this.querySelector('.ba-field-container');
677            formsApp.toggleAlertTooltip(alert, container, container, 'THIS_FIELD_REQUIRED');
678        }
679    })
680    form.find('.confirm-password-wrapper').find('input').each(function(){
681        let password = this.closest('.ba-form-field-item').querySelector('.ba-input-wrapper input').value.trim(),
682            alert = this.required && !this.value.trim(),
683            key = 'THIS_FIELD_REQUIRED';
684        if (password) {
685            alert = !(password == this.value.trim());
686            key = 'PASSWORDS_NOT_MATCH';
687        }
688        formsApp.toggleAlertTooltip(alert, this, this.closest('.ba-field-container'), key);
689    });
690    form.find('.ba-form-input-field, .ba-form-address-field').find(' > .ba-input-wrapper').find('input, textarea').each(function(){
691        let alert = this.required && !this.value.trim(),
692            key = 'THIS_FIELD_REQUIRED';
693        if (this.formsInputMask) {
694            alert = this.required && this.value == this.formsInputMask;
695        }
696        if (this.value && this.characters && this.characters.key == 'min') {
697            alert = !(this.value.length >= this.characters.length);
698            key = 'ENTER_VALID_VALUE';
699        } else if (this.value && this.dataset.validation == 'email') {
700            alert = !(/@/g.test(this.value) && this.value.match(/@/g).length == 1);
701            key = 'ENTER_VALID_VALUE';
702        } else if (this.value && this.dataset.validation == 'numbers') {
703            alert = !(/\d+\.{0,1}\d{0,}/.test(this.value));
704            key = 'ENTER_VALID_VALUE';
705        } else if (this.value && this.dataset.validation == 'url') {
706            alert = !(/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i.test(this.value));
707            key = 'ENTER_VALID_VALUE';
708        } else if (!alert && this.value && this.formsInputMask) {
709            alert = this.value != this.formsInputMask && this.value.indexOf('_') != -1;
710            key = 'ENTER_VALID_VALUE';
711        }
712        formsApp.toggleAlertTooltip(alert, this, this.closest('.ba-field-container'), key);
713    });
714    form.find('.ba-form-phone-field input.ba-phone-number-input').each(function(){
715        let alert = this.required && this.value == this.formsInputMask,
716            key = 'THIS_FIELD_REQUIRED';
717        if (!alert && this.value) {
718            alert = this.value != this.formsInputMask && this.value.indexOf('_') != -1;
719            key = 'ENTER_VALID_VALUE';
720        }
721        formsApp.toggleAlertTooltip(alert, this, this.closest('.ba-field-container'), key);
722    });
723    form.find('.ba-form-upload-field').find('input[type="file"]').each(function(){
724        const alert = this.required && this.uploads.count == 0;
725        formsApp.toggleAlertTooltip(alert, this, this.closest('.upload-file-input'), 'THIS_FIELD_REQUIRED');
726    });
727    form.find('.ba-form-field-item').find('.ba-form-rating-group-wrapper').each(function(){
728        if (this.dataset.required) {
729            let alert = false;
730            $f(this).find('input').each(function(){
731                alert = !this.checked;
732                if (this.checked) {
733                    return false;
734                }
735            })
736            formsApp.toggleAlertTooltip(alert, this, this.closest('.ba-form-rating-group-wrapper'), 'THIS_FIELD_REQUIRED');
737        }
738    });
739    form.find('.ba-form-field-item').find('.ba-form-checkbox-group-wrapper').each(function(){
740        if (this.dataset.required) {
741            let alert = false;
742            $f(this).find('input').each(function(){
743                alert = !this.checked;
744                if (this.checked) {
745                    return false;
746                }
747            })
748            formsApp.toggleAlertTooltip(alert, this, this.closest('.ba-form-checkbox-group-wrapper'),'THIS_FIELD_REQUIRED');
749        }
750    });
751    form.find('.ba-form-dropdown-field, .ba-form-select-multiple-field').find('select').each(function(){
752        let alert = this.required && !this.value.trim();
753        formsApp.toggleAlertTooltip(alert, this, this.closest('.ba-field-container'), 'THIS_FIELD_REQUIRED');
754    });
755    form.find('.ba-form-acceptance-field').find('.ba-field-container').each(function(){
756        if (this.dataset.required) {
757            let alert = false;
758            $f(this).find('input').each(function(){
759                alert = !this.checked;
760                if (this.checked) {
761                    return false;
762                }
763            })
764            formsApp.toggleAlertTooltip(alert, this, this, 'THIS_FIELD_REQUIRED');
765        }
766    });
767    form.find('.ba-form-calendar-field input[required]').each(function(){
768        formsApp.toggleAlertTooltip(!this.value.trim(), this, this.closest('.calendar-field-wrapper'), 'THIS_FIELD_REQUIRED');
769    })
770    let tooltip = form.find('.ba-form-field-item .ba-alert-tooltip');
771    if (tooltip.length) {
772        tooltip[0].closest('.ba-form-field-item').scrollIntoView({
773            block: 'center'
774        });
775        return false;
776    }
777}
778
779formsApp.toggleAlertTooltip = function(alert, $this, parent, key){
780    if (alert && !$this.alertTooltip && !$this.closest('.hidden-condition-field')) {
781        $this.alertTooltip = document.createElement('span');
782        $this.alertTooltip.className = 'ba-alert-tooltip';
783        $this.alertTooltip.textContent = formsApp._(key);
784        let str = '.ba-input-wrapper, .confirm-email-wrapper, .confirm-password-wrapper, .forms-recaptcha,'+
785            ' .calendar-field-wrapper, .ba-forms-authorize-field-wrapper';
786        parent.closest(str).classList.add('ba-alert');
787        parent.appendChild($this.alertTooltip);
788    } else if (alert && $this.alertTooltip) {
789        $this.alertTooltip.textContent = formsApp._(key);
790    } else if (!alert && $this.alertTooltip) {
791        formsApp.removeAlertTooltip($this);
792    }
793}
794
795formsApp.removeAlertTooltip = function($this){
796    if ($this.alertTooltip) {
797        $this.alertTooltip.remove();
798        $this.alertTooltip = null;
799        $this.closest('.ba-alert').classList.remove('ba-alert');
800    }
801}
802
803formsApp.checkLanguage = function(){
804    if (!formsApp.language) {
805        fetch(JUri+'index.php?option=com_baforms&task=form.getFormsLanguage').then(function(response){
806            return response.json();
807        }).then(function(json){
808            formsApp.language = json;
809        });
810    }
811}
812
813formsApp._ = function(key){
814    if (formsApp.language && formsApp.language[key]) {
815        return formsApp.language[key];
816    }
817
818    return key;
819}
820
821class FormsSignature
822{
823    static loading = false;
824    static loaded = false;
825    static selector = '.ba-form-signature-field';
826
827    static boot()
828    {
829        if (!document.querySelector(this.selector)) return;
830
831        if (!this.loaded && !this.loading) {
832            this.loadScript();
833        } else if (this.loaded) {
834            this.make();
835        }
836    }
837
838    static loadScript()
839    {
840        this.loading = true;
841        const script = document.createElement('script');
842        script.src = `${JUri}components/com_baforms/assets/js/signature.js`;
843        script.onload = () => {
844            this.loaded = true;
845            this.make();
846        }
847        document.head.append(script);
848    }
849
850    constructor(element)
851    {
852        this.element = element;
853        this.container = this.element.querySelector('.ba-field-container');
854        this.clearButton = this.element.querySelector('.ba-clear-signature-canvas');
855        this.textarea = this.element.querySelector('textarea');
856        this.$textarea = $f(this.textarea);
857        this.signature = this.create();
858        this.setEvents();
859    }
860
861    isEmpty()
862    {
863        return this.signature.isEmpty();
864    }
865
866    create()
867    {
868        const canvas = this.element.querySelector('canvas');
869        canvas.width = canvas.offsetWidth;
870        canvas.height = canvas.offsetHeight;
871
872        return new SignaturePad(canvas, {
873            backgroundColor: canvas.dataset.bg,
874            penColor: canvas.dataset.color
875        });
876    }
877
878    setEvents()
879    {
880        this.signature.addEventListener('beginStroke', this.beginStroke.bind(this));
881        this.signature.addEventListener('endStroke', this.endStroke.bind(this));
882        this.clearButton.addEventListener('click', this.clear.bind(this));
883    }
884
885    beginStroke()
886    {
887        formsApp.removeAlertTooltip(this.container);
888    }
889
890    endStroke()
891    {
892        const str = JSON.stringify({
893            image: this.signature.toDataURL('image/jpeg')
894        });
895        this.updateTextarea(str)
896    }
897
898    clear()
899    {
900        this.signature.clear();
901        this.updateTextarea()
902    }
903
904    updateTextarea(value = '')
905    {
906        this.textarea.value = value;
907        this.$textarea.trigger('input');
908    }
909
910    static make()
911    {
912        document.querySelectorAll(this.selector).forEach(field => {
913            if (field.instance || field.closest('.ba-hidden-form-page')) {
914                return;
915            }
916            field.instance = new this(field);
917        });
918    }
919}
920
921formsApp.checkGoogleMaps = function(){
922    if (loadFormsMap.load && !formsApp.googleMaps && !loadFormsMap.loading) {
923        loadFormsMap.loading = true;
924        let script = document.createElement('script');
925        script.src = JUri+'components/com_baforms/assets/js/mapStyles.js';
926        script.onload = function(){
927            formsApp.mapScript = document.createElement('script');
928            formsApp.mapScript.onload = function(){
929                formsApp.googleMaps = true;
930                $f('.ba-form-map-field .ba-map-wrapper').each(function(){
931                    formsApp.createGoogleMap(this, this.options);
932                });
933                $f('.ba-form-address-field input').each(function(){
934                    this.createAutocomplete();
935                });
936            }
937            formsApp.mapScript.src = 'https://maps.googleapis.com/maps/api/js?libraries=places&key='+loadFormsMap.api_key;
938            document.head.append(formsApp.mapScript);
939        }
940        document.head.append(script);
941    }
942}
943
944formsApp.createGoogleMap = function(div, obj){
945    div.map = new google.maps.Map(div, obj.map);
946    div.map.setOptions({styles: mapStyles[obj.styleType]});
947    if (obj.marker.position) {
948        let object = {
949            position : obj.marker.position,
950            map : div.map
951        }
952        if (obj.marker.icon) {
953            object.icon = JUri+obj.marker.icon;
954        }
955        let marker = new google.maps.Marker(object);
956        if (obj.marker.description) {
957            marker.infoWindow = new google.maps.InfoWindow({
958                content : obj.marker.description
959            });
960            if (obj.marker.infobox == 1) {
961                marker.infoWindow.open(div.map, marker);
962            }
963            marker.addListener('click', function(){
964                this.infoWindow.open(div.map, this);
965            });
966        }
967    }
968}
969
970formsApp.removeTmpAttachment = function(attachment, input, $this){
971    if (attachment.dataset.id) {
972        delete input.uploads[attachment.dataset.id];
973        input.uploads.count--;
974        formsApp.updateFieldsValues($this.fields, input, input.fieldId);
975        formsApp.calculation($this);
976        formsApp.fetch.request(JUri+'index.php?option=com_baforms&task=form.removeTmpAttachment', {
977            id: attachment.dataset.id
978        }).then(() => attachment.remove());
979    }
980}
981
982formsApp.getAttachmentHTML = function(input, type, name, $this){
983    let attachment = document.createElement('div'),
984        str = '';
985    attachment.className = 'ba-form-xhr-attachment';
986    if (type == 'file') {
987        str += '<i class="ba-form-icons ba-icon-attachment"></i>';
988    } else {
989        str += '<span class="attachment-intro-image"></span>';
990    }
991    str += '<span class="forms-attachment-title">'+name;
992    str += '</span><span class="forms-attachment-progress-bar-wrapper"><span class="forms-attachment-progress-bar">';
993    str += '</span></span><i class="ba-form-icons ba-icon-delete" tabindex="0"></i>';
994    attachment.innerHTML = str;
995    $f(attachment).find('.ba-icon-delete').on('click keyup', function(event){
996        FormsAccessibilityHelper.processAction(event, () => {
997            formsApp.removeTmpAttachment(attachment, input, $this);
998        })
999    });
1000
1001    return attachment;
1002}
1003
1004formsApp.uploadAttachmentFile = function(files, input, $this){
1005    if (files.length &&
1006        (input.options.count == '' || (input.options.count != '' && input.uploads.count < input.options.count))) {
1007        let file = files.shift(),
1008            type = input.options.images.indexOf(file.ext) == -1 ? 'file' : 'image',
1009            attachment = formsApp.getAttachmentHTML(input, type, file.name, $this),
1010            xhr = new XMLHttpRequest(),
1011            formData = new FormData();
1012        if (type == 'image') {
1013            let reader = new FileReader();
1014            reader.onloadend = function() {
1015                attachment.querySelector('.attachment-intro-image').style.backgroundImage = 'url('+reader.result+')';
1016            }
1017            reader.readAsDataURL(file);
1018        }
1019
1020        const { token } = formsApp;
1021        formData.append(token, "1");
1022
1023        formData.append('file', file);
1024        formData.append('id', input.options.id);
1025        formData.append('field_id', input.dataset.id);
1026        formData.append('option', 'com_balbooa_forms');
1027        formData.append('task', 'form.uploadAttachmentFile');
1028
1029        xhr.upload.onprogress = function(e) {
1030            const bar = attachment.querySelector('.forms-attachment-progress-bar');
1031            bar.style.width = Math.round(e.loaded / e.total * 100) + "%";
1032        }
1033        xhr.onload = xhr.onerror = function(){
1034            try {
1035                const response = JSON.parse(this.responseText);
1036                if (response.id) {
1037                    input.uploads[response.id] = response;
1038                    attachment.dataset.id = response.id;
1039                    input.uploads.count++;
1040                } else {
1041                    attachment.remove()
1042                }
1043            } catch (error) {
1044                console.info(error)
1045                console.info(this.responseText)
1046            }
1047            formsApp.uploadAttachmentFile(files, input, $this);
1048            setTimeout(function(){
1049                attachment.classList.add('forms-attachment-file-uploaded')
1050            }, 300);
1051        };
1052        input.options[type+'Container'].appendChild(attachment);
1053        xhr.open("POST", JUri+"index.php", true);
1054        xhr.send(formData);
1055    } else {
1056        input.uploading = '';
1057        formsApp.updateFieldsValues($this.fields, input, input.fieldId);
1058        formsApp.calculation($this);
1059    }
1060}
1061
1062formsApp.sendAjaxForm = function(form, $this){
1063    $this.form.find('.ba-form-total-field .ba-cart-total-row .field-price-value').each(function(){
1064        let object = {
1065            products: $f.extend(true, {}, this.calculation.products),
1066            total: this.calculation.total,
1067            resultTotal : this.calculation.resultTotal
1068        }
1069        for (let ind in object.products) {
1070            for (let i in object.products[ind]) {
1071                delete object.products[ind][i].input;
1072            }
1073        }
1074        if (this.calculation.shipping) {
1075            this.calculation.shipping.forEach(function(input){
1076                if (input.checked) {
1077                    object.shipping = {
1078                        price: input.price * 1,
1079                        ind: input.dataset.ind,
1080                        title: input.dataset.title
1081                    }
1082                }
1083            });
1084        }
1085        if (this.calculation.promo) {
1086            object.promo = this.calculation.promo.code;
1087        }
1088        this.calculation.input.value = JSON.stringify(object);
1089    });
1090    let clone = form.cloneNode(true),
1091        payments = ['authorize', 'cloudpayments', 'paypal_sdk'],
1092        submitBtn = form.submitBtn;
1093    clone['submit-btn'].value = submitBtn.options.id;
1094    clone.task.value = 'form.message';
1095    $this.form.find('.upload-file-input .ba-forms-attachment').each(function(){
1096        let obj = $f.extend(true, {}, this.uploads),
1097            str,
1098            name = this.dataset.id;
1099        delete obj.count;
1100        str = JSON.stringify(obj);
1101        clone.querySelector('textarea[name="'+name+'"]').value = str;
1102    });
1103    $this.form.find('select[name]').each(function(){
1104        let cloneOptions = $f(clone).find('select[name="'+this.name+'"] option');
1105        this.querySelectorAll('option').forEach(function(element, ind){
1106            cloneOptions.get(ind).selected = element.selected;
1107        });
1108    });
1109    $f(clone).find('.hidden-condition-field').remove();
1110    if (!(submitBtn.options.onclick == 'payment' && payments.indexOf(submitBtn.options.payment) != -1)) {
1111        submitBtn.classList.add('ba-thank-you-animation-in');
1112    }
1113    if (submitBtn.options.onclick == 'payment' && submitBtn.options.payment == 'stripe') {
1114        formsApp.executeStripePayment(form, clone, $this);
1115    } else if (submitBtn.options.onclick == 'payment' && submitBtn.options.payment == 'paypal_sdk') {
1116        formsApp.executePaypalPayment(form, clone, $this);
1117    } else if (submitBtn.options.onclick == 'payment' && submitBtn.options.payment == 'authorize') {
1118        formsApp.executeAuthorizePayment(form, clone, $this);
1119    } else if (submitBtn.options.onclick == 'payment' && submitBtn.options.payment == 'cloudpayments') {
1120        formsApp.executeCloudPayments(form, clone, $this);
1121    } else if (submitBtn.options.onclick == 'payment') {
1122        const tokenInput = document.createElement('input');
1123        tokenInput.name = formsApp.token;
1124        tokenInput.value = '1';
1125        clone.append(tokenInput);
1126
1127        clone.style.display = 'none';
1128        document.body.append(clone);
1129        clone.submit();
1130    } else {
1131        formsApp.XMLHttpRequestForm(form, clone, $this);
1132    }
1133}
1134
1135formsApp.executeCloudPayments = function(form, clone, $this){
1136    if (!('cp' in window)) {
1137        let script = document.createElement('script');
1138        script.onload = function(){
1139            formsApp.loadCloudPaymentsData(form, clone, $this);
1140        }
1141        script.src = "https://widget.cloudpayments.ru/bundles/cloudpayments";
1142        document.head.append(script);
1143    } else {
1144        let object = {},
1145            title = [],
1146            code = 'RUB',
1147            widget = new cp.CloudPayments(),
1148            invoiceId = +(new Date());
1149        $this.form.find('.ba-form-total-field .ba-cart-total-row .field-price-value').each(function(){
1150            if (!this.closest('.hidden-condition-field')) {
1151                code = this.calculation.code;
1152                object = JSON.parse(this.calculation.input.value);
1153            }
1154        });
1155        for (let ind in object.products) {
1156            for (let i in object.products[ind]) {
1157                title.push(object.products[ind][i].title)
1158            }
1159        }
1160        if (object.shipping) {
1161            title.push(object.shipping.title);
1162        }
1163        const options = {
1164            publicId: formsApp.cloudPayments.public_id,
1165            description: title.join(', '),
1166            amount: object.resultTotal,
1167            invoiceId: invoiceId,
1168            currency: code,
1169            skin: 'modern'
1170        }
1171        if (formsApp.cloudPayments.fiscalization) {
1172            const receipt = {
1173                email: form.querySelector('input[type="email"]')?.value ?? '',
1174                taxationSystem: formsApp.cloudPayments.taxationSystem,
1175                Items: [
1176                    {
1177                        "label": options.description,
1178                        "price": object.resultTotal,
1179                        "quantity": 1.00,
1180                        "amount": object.resultTotal,
1181                        "vat": formsApp.cloudPayments.vat == 'null' ? null : formsApp.cloudPayments.vat,
1182                        "method": formsApp.cloudPayments.method,
1183                        "object": formsApp.cloudPayments.object
1184                    }
1185                ]
1186            }
1187            options.data = {
1188                CloudPayments: {
1189                    CustomerReceipt: receipt,
1190                }
1191            };
1192        }
1193
1194
1195        widget.charge(options, function(){
1196            let input = document.createElement('input');
1197            form.submitBtn.classList.add('ba-thank-you-animation-in');
1198            input.type = 'hidden';
1199            input.name = 'invoiceId';
1200            input.value = invoiceId;
1201            clone.append(input);
1202            formsApp.XMLHttpRequestForm(form, clone, $this);
1203        }, function(){
1204            form.status = '';
1205        });
1206    }
1207}
1208
1209formsApp.showMessagePopup = (btn, html, animation, $this) => {
1210    if (!btn.popup) {
1211        let div = document.createElement('div'),
1212            properties = ['width', 'radius', 'background-color', 'backdrop-color', 'icon-color'],
1213            wrapper = btn.closest('.ba-form-submit-wrapper');
1214        div.innerHTML = '<div class="ba-forms-modal-backdrop" data-dismiss="formsModal"></div>'+
1215            '<div class="ba-forms-modal">'+
1216            '<i class="ba-form-icons ba-icon-close" data-dismiss="formsModal" tabindex="0"></i>'+
1217            '<div class="ba-forms-modal-body"></div>'+
1218            '</div>';
1219        div.className = 'ba-forms-modal-wrapper ba-form-message-popup '+animation;
1220        div.dataset.form = $this.formId;
1221        properties.forEach((property) => {
1222            div.style.setProperty('--submit-popup-'+property, wrapper.style.getPropertyValue('--submit-popup-'+property));
1223        })
1224        btn.popup = $f(div);
1225        document.body.append(div);
1226    }
1227    btn.popup.find('.ba-forms-modal-body').html(html);
1228    setTimeout(() => {
1229        btn.popup.formsModal();
1230        btn.popup.find('.ba-forms-modal').each(function(){
1231            this.style.setProperty('--popup-modal-height', this.offsetHeight+'px');
1232        });
1233    }, 300);
1234}
1235
1236formsApp.executeAuthorizePayment = function(form, clone, $this){
1237    if (!formsApp.authorize) {
1238        let div = document.createElement('div'),
1239            btn;
1240        div.innerHTML = '<div class="ba-forms-modal-backdrop" data-dismiss="formsModal"></div>'+
1241            '<div class="ba-forms-modal">'+
1242            '<div class="ba-forms-modal-header"><span class="ba-forms-modal-title">Authorize.Net</span>'+
1243            '<i class="ba-form-icons ba-icon-close" data-dismiss="formsModal" tabindex="0"></i></div>'+
1244            '<div class="ba-forms-modal-body">'+
1245            '<div class="ba-forms-authorize-field-wrapper">'+
1246            '<input type="text" class="ba-forms-authorize-card-number"><i class="ba-form-icons ba-icon-card"></i></div>'+
1247            '<div class="ba-forms-authorize-field-wrapper">'+
1248            '<input type="text" class="ba-forms-authorize-expiration-date"><i class="ba-form-icons ba-icon-calendar"></i>'+
1249            '</div>'+
1250            '<div class="ba-forms-authorize-field-wrapper">'+
1251            '<input type="text" class="ba-forms-authorize-card-code"><i class="ba-form-icons ba-icon-lock"></i></div>'+
1252            '</div><div class="ba-forms-modal-footer"><span class="ba-forms-authorize-pay-btn">'+
1253            '<span class="ba-forms-authorize-pay">'+formsApp._('PAY')+'</span>'+
1254            '<span class="field-price-currency"></span><span class="field-price-value"></span>'+
1255            '</span></div></div>';
1256        div.className = 'ba-forms-modal-wrapper ba-form-authorize-modal';
1257        btn = div.querySelector('.ba-forms-authorize-pay-btn');
1258        btn.cardNumber = div.querySelector('.ba-forms-authorize-card-number');
1259        btn.expirationDate = div.querySelector('.ba-forms-authorize-expiration-date');
1260        btn.cardCode = div.querySelector('.ba-forms-authorize-card-code');
1261        btn.cardNumber.placeholder = 'Card Number';
1262        btn.expirationDate.placeholder = 'MM / YY';
1263        btn.cardCode.placeholder = 'CVC';
1264        btn.cardNumber.addEventListener('input', function(){
1265            let value = this.value.replace(/\s/g, ''),
1266                match = value.match(/\d{1,16}/);
1267            if (!match) {
1268                this.value = '';
1269            } else {
1270                let str = '',
1271                    j = 1;
1272                for (let i = 0; i < match[0].length; i++) {
1273                    if (j == 5) {
1274                        j = 1;
1275                        str += ' ';
1276                    }
1277                    str += match[0][i];
1278                    j++;
1279                }
1280                if (str != this.value) {
1281                    this.value = str;
1282                }
1283            }
1284            this.filled = this.value.length >= 14;
1285        });
1286        btn.expirationDate.addEventListener('input', function(){
1287            formsApp.executeMask(this, '##/##');
1288            this.filled = !!this.value.match(/\d\d\/\d{2}/);
1289        });
1290        btn.cardCode.addEventListener('input', function(){
1291            let match = this.value.match(/\d{1,4}/);
1292            if (!match) {
1293                this.value = '';
1294            } else if (match[0] != this.value) {
1295                this.value = match[0];
1296            }
1297            this.filled = this.value.length >= 3;
1298        });
1299        btn.cardNumber.addEventListener('focus', function(){
1300            formsApp.removeAlertTooltip(this);
1301        });
1302        btn.expirationDate.addEventListener('focus', function(){
1303            formsApp.removeAlertTooltip(this);
1304        });
1305        btn.cardCode.addEventListener('focus', function(){
1306            formsApp.removeAlertTooltip(this);
1307        });
1308        formsApp.authorize = $f(div);
1309        document.body.append(div);
1310    }
1311    formsApp.authorize.attr('data-form', $this.formId);
1312    let price = symbol = '',
1313        position = false,
1314        total = 0;
1315    $this.form.find('.ba-form-total-field .ba-cart-total-row .field-price-value').each(function(){
1316        if (!this.closest('.hidden-condition-field')) {
1317            total = this.calculation.resultTotal;
1318            price = this.textContent;
1319            symbol = this.calculation.symbol;
1320            position = this.closest('.ba-field-container').classList.contains('right-currency-position');
1321        }
1322    });
1323    formsApp.authorize.find('input').val('').each(function(){
1324        this.filled = false;
1325        formsApp.removeAlertTooltip(this);
1326    });
1327    formsApp.authorize.find('.ba-forms-authorize-pay-btn').each(function(){
1328        this.classList[position ? 'add' : 'remove']('right-currency-position');
1329        this.querySelector('.field-price-currency').textContent = symbol;
1330        this.querySelector('.field-price-value').textContent = price;
1331        this.total = total;
1332    }).off('click').on('click', function(){
1333        let btn = this;
1334        if (!btn.status && btn.cardNumber.filled && btn.expirationDate.filled && btn.cardCode.filled) {
1335            btn.status = 'pending';
1336            btn.classList.add('ba-thank-you-animation-in');
1337            formsApp.fetch.request(JUri+'index.php?option=com_baforms&task=form.payAuthorize', {
1338                id : $this.formId,
1339                cardNumber: btn.cardNumber.value,
1340                expirationDate: btn.expirationDate.value,
1341                cardCode: btn.cardCode.value,
1342                total : total
1343            }).then(function(text){
1344                let obj = JSON.parse(text),
1345                    input = document.createElement('input'),
1346                    flag = obj.transactionResponse && obj.transactionResponse.transId != ''
1347                        && obj.transactionResponse.transId != 0;
1348                setTimeout(function(){
1349                    btn.classList.remove('ba-thank-you-animation-in');
1350                    btn.classList.add('ba-thank-you-animation-out');
1351                    setTimeout(function(){
1352                        btn.classList.remove('ba-thank-you-animation-out');
1353                        if (!flag) {
1354                            msg = '';
1355                            if (obj.transactionResponse) {
1356                                obj.transactionResponse.errors.forEach(function(error){
1357                                    msg += (msg ? ', ' : '')+error.errorText;
1358                                });
1359                            }
1360                            msg = msg ? msg : 'Transaction failed';
1361                            formsApp.showNotice(msg, 'ba-alert');
1362                        }
1363                        setTimeout(function(){
1364                            btn.status = '';
1365                            if (flag) {
1366                                input.type = 'hidden';
1367                                input.name = 'transId';
1368                                input.value = obj.transactionResponse.transId;
1369                                clone.append(input);
1370                                form.submitBtn.options.authorize_return_url = obj.return_url;
1371                                form.submitBtn.classList.add('ba-thank-you-animation-in');
1372                                formsApp.authorize.formsModal('hide');
1373                                formsApp.XMLHttpRequestForm(form, clone, $this);
1374                            }
1375                        }, 600);
1376                    }, 300);
1377                }, 2000);
1378            })
1379        } else if (!btn.status) {
1380            ['cardNumber', 'expirationDate', 'cardCode'].forEach(function(el){
1381                if (!btn[el].filled) {
1382                    let key = btn[el].value ? 'ENTER_VALID_VALUE' : 'THIS_FIELD_REQUIRED';
1383                    formsApp.toggleAlertTooltip(true, btn[el], btn[el].closest('.ba-forms-authorize-field-wrapper'), key);
1384                }
1385            });
1386        }
1387    });
1388    form.status = '';
1389    setTimeout(function(){
1390        formsApp.authorize.formsModal();
1391    }, 100);
1392}
1393
1394formsApp.executePaypalPayment = function(form, clone, $this){
1395    let obj = null;
1396    $this.form.find('.ba-form-total-field .ba-cart-total-row .field-price-value').each(function(){
1397        if (!this.closest('.hidden-condition-field')) {
1398            obj = this.calculation;
1399        }
1400    });
1401    if (!formsApp.paypalData) {
1402        formsApp.loadPaypalData(form, clone, $this);
1403    } else if (!('paypal' in window)) {
1404        let script = document.createElement('script');
1405        script.onload = function(){
1406            formsApp.executePaypalPayment(form, clone, $this);
1407        }
1408        script.src = 'https://www.paypal.com/sdk/js?client-id='+formsApp.paypalData.client_id+'&currency='+obj.code;
1409        document.head.append(script);
1410    } else {
1411        if (!formsApp.paypal) {
1412            let div = document.createElement('div');
1413            div.innerHTML = '<div class="ba-forms-modal-backdrop" data-dismiss="formsModal"></div>'+
1414                '<div class="ba-forms-modal">'+
1415                '<div class="ba-forms-modal-header"><span class="ba-forms-modal-title">PayPal</span>'+
1416                '<i class="ba-form-icons ba-icon-close" data-dismiss="formsModal" tabindex="0"></i></div>'+
1417                '<div class="ba-forms-modal-body">'+
1418                '<div id="paypal-buttons-wrapper"></div>'+
1419                '</div></div>';
1420            div.className = 'ba-forms-modal-wrapper ba-form-paypal-modal';
1421            formsApp.paypal = $f(div);
1422            document.body.append(div);
1423        }
1424        formsApp.paypal.attr('data-form', $this.formId);
1425        formsApp.paypal.find('#paypal-buttons-wrapper')[0].innerHTML = '';
1426        let total = obj.total,
1427            item_total = 0,
1428            discount = obj.promo ? (obj.promo.unit == '%' ? total * obj.promo.discount / 100 : obj.promo.discount) : 0,
1429            tax = obj.taxResult ? (total - discount) * obj.tax / 100 : 0,
1430            shipping = 0,
1431            purchase_units = [{
1432                amount: {
1433                    currency: obj.code,
1434                    breakdown: {
1435                        item_total: {
1436                            currency_code: obj.code,
1437                            value: 0
1438                        }
1439                    },
1440                    value: obj.resultTotal
1441                },
1442                items: [],
1443            }];
1444        for (let i in obj.products) {
1445            for (let ind in obj.products[i]) {
1446                purchase_units[0].items.push({
1447                    name: obj.products[i][ind].title,
1448                    unit_amount: {
1449                        currency_code: obj.code,
1450                        value: formsApp.decimalAdjust('round', obj.products[i][ind].price * 1, -2),
1451                    },
1452                    quantity: obj.products[i][ind].quantity
1453                });
1454                item_total += obj.products[i][ind].total;
1455            }
1456        }
1457        purchase_units[0].amount.breakdown.item_total.value = formsApp.decimalAdjust('round', item_total, -2);
1458        if (tax) {
1459            tax = formsApp.decimalAdjust('round', tax, -2);
1460            purchase_units[0].amount.breakdown.tax_total = {
1461                currency_code: obj.code,
1462                value: tax
1463            }
1464        }
1465        if (discount) {
1466            discount = formsApp.decimalAdjust('round', discount, -2);
1467            purchase_units[0].amount.breakdown.discount = {
1468                currency_code: obj.code,
1469                value: discount
1470            }
1471        }
1472        if (obj.shipping) {
1473            obj.shipping.forEach(function(input){
1474                if (input.checked) {
1475                    shipping = formsApp.decimalAdjust('round', input.price * 1, -2);
1476                    purchase_units[0].amount.breakdown.shipping = {
1477                        currency_code: obj.code,
1478                        value: shipping
1479                    }
1480                }
1481            });
1482        }
1483        purchase_units[0].amount.value = formsApp.decimalAdjust('round', total + tax + shipping - discount, -2);
1484        paypal.Buttons({
1485            createOrder: function (data, actions){
1486                return actions.order.create({
1487                    purchase_units: purchase_units
1488                });
1489            },
1490            onApprove: function(data, actions){
1491                return actions.order.capture().then(function(){
1492                    formsApp.paypal.formsModal('hide');
1493                    form.submitBtn.classList.add('ba-thank-you-animation-in');
1494                    formsApp.XMLHttpRequestForm(form, clone, $this);
1495                });
1496            }
1497        }).render('#paypal-buttons-wrapper').then(function(){
1498            formsApp.paypal.formsModal();
1499        });
1500        form.status = '';
1501    }
1502}
1503
1504formsApp.executeStripePayment = function(form, clone, $this){
1505    if (!('Stripe' in window)) {
1506        let script = document.createElement('script');
1507        script.onload = function(){
1508            formsApp.loadStripeData(form, clone, $this);
1509        }
1510        script.src = "https://js.stripe.com/v3";
1511        document.head.append(script);
1512    } else if (!formsApp.stripeData) {
1513        formsApp.loadStripeData(form, clone, $this);
1514    } else {
1515        let object = '{}',
1516            name = 0;
1517        formsApp.stripe = Stripe(formsApp.stripeData.api_key);
1518        $this.form.find('.ba-form-total-field .ba-cart-total-row .field-price-value').last().each(function(){
1519            if (!this.closest('.hidden-condition-field')) {
1520                name = this.calculation.input.name;
1521                object = this.calculation.input.value;
1522            }
1523        });
1524        formsApp.fetch.request(JUri+'index.php?option=com_baforms&task=form.stripeCharges', {
1525            object : object,
1526            name: name,
1527            id : $this.formId
1528        }).then(function(text){
1529            let obj = JSON.parse(text),
1530                input = document.createElement('input');
1531            if (obj.error) {
1532                console.error(obj.error.message);
1533                form.status = '';
1534                form.submitBtn.classList.remove('ba-thank-you-animation-in');
1535                form.submitBtn.classList.add('ba-thank-you-animation-out');
1536                setTimeout(function(){
1537                    form.submitBtn.classList.remove('ba-thank-you-animation-out');
1538                }, 300);
1539            } else {
1540                formsApp.stripeData.sessionId = obj.id;
1541                input.type = 'hidden';
1542                input.name = 'payment_id';
1543                input.value = obj.payment_intent;
1544                clone.append(input);
1545                formsApp.XMLHttpRequestForm(form, clone, $this);
1546            }
1547        });
1548    }
1549}
1550
1551formsApp.loadPaypalData = function(form, clone, $this){
1552    fetch(JUri+'index.php?option=com_baforms&task=form.getPaypalData').then(function(response){
1553        return response.json();
1554    }).then(function(json){
1555        formsApp.paypalData = json;
1556        formsApp.executePaypalPayment(form, clone, $this);
1557    });
1558}
1559
1560formsApp.loadStripeData = function(form, clone, $this){
1561    fetch(JUri+'index.php?option=com_baforms&task=form.getStripeData').then(function(response){
1562        return response.json();
1563    }).then(function(json){
1564        formsApp.stripeData = json;
1565        formsApp.executeStripePayment(form, clone, $this);
1566    });
1567}
1568
1569formsApp.loadCloudPaymentsData = function(form, clone, $this){
1570    fetch(JUri+'index.php?option=com_baforms&task=form.getCloudPaymentsData').then(function(response){
1571        return response.json();
1572    }).then(function(json){
1573        formsApp.cloudPayments = json;
1574        formsApp.executeCloudPayments(form, clone, $this);
1575    });
1576}
1577
1578formsApp.XMLHttpRequestForm = async function(form, clone, $this){
1579    let btn = form.submitBtn,
1580        xhr = new XMLHttpRequest(),
1581        data = new FormData(clone);
1582
1583    const { token } = formsApp;
1584    data.append(token, "1");
1585
1586    xhr.onload = xhr.onerror = function(){
1587        if (xhr.readyState == 4) {
1588            setTimeout(function(){
1589                btn.classList.remove('ba-thank-you-animation-in');
1590                btn.classList.add('ba-thank-you-animation-out');
1591                setTimeout(function(){
1592                    btn.classList.remove('ba-thank-you-animation-out');
1593                    setTimeout(function(){
1594                        form.status = '';
1595                        if (formsApp.debug || xhr.status != 200) {
1596                            console.info(xhr.responseText);
1597                        }
1598                        let response = btn.options.onclick == 'payment' ? {
1599                            status: true
1600                        } : JSON.parse(xhr.responseText);
1601                        if (!response.status) {
1602                            formsApp.showNotice(response.message, 'ba-alert');
1603                            return;
1604                        }
1605                        formsApp.clearFields($this);
1606                        $this.form.find('.ba-form-page')
1607                            .addClass('ba-hidden-form-page')
1608                            .first()
1609                            .removeClass('ba-hidden-form-page');
1610                        $f(btn).trigger('success-submit');
1611                        if (response.isClosed) {
1612                            $this.form.html('<div class="ba-form-expiration-message">'+response.closedMessage+'</div>')
1613                        }
1614                        if (btn.options.onclick == 'message' && btn.options.messageType == 'advanced') {
1615                            formsApp.showMessagePopup(btn, response.message, btn.options.popupAnimation, $this);
1616                        } else if (btn.options.onclick == 'message') {
1617                            formsApp.showNotice(response.message);
1618                        } else if (btn.options.onclick == 'redirect') {
1619                            window.location.href = response.message;
1620                        } else if (btn.options.payment == 'stripe') {
1621                            formsApp.stripe.redirectToCheckout({
1622                                sessionId: formsApp.stripeData.sessionId
1623                            }).then(function(result) {
1624                                console.info(result)
1625                            });
1626                        } else if (btn.options.payment == 'authorize' && btn.options.authorize_return_url) {
1627                            window.location.href = btn.options.authorize_return_url;
1628                        } else if (btn.options.payment == 'cloudpayments') {
1629                            window.location.href = formsApp.cloudPayments.return_url;
1630                        } else if (btn.options.payment == 'paypal_sdk') {
1631                            window.location.href = formsApp.paypalData.return_url;
1632                        }
1633                    }, 500);
1634                }, 300);
1635            }, 1500);
1636        }
1637    };
1638    xhr.open("POST", clone.action, true);
1639    xhr.send(data);
1640}
1641
1642formsApp.clearFields = function($this){
1643    if (formsApp.storage[$this.formURL] && formsApp.storage[$this.formURL][$this.formId]) {
1644        delete formsApp.storage[$this.formURL][$this.formId];
1645        localStorage.setItem('forms-progress', JSON.stringify(formsApp.storage));
1646    }
1647    let not = '[data-type="submit"],[data-type="map"],[data-type="html"],[data-type="text"],[data-type="image"],'+
1648        '[data-type="headline"]';
1649    $this.form.find('.forms-recaptcha[data-captcha="turnstile"]').each(function(){
1650        CaptchaFactory.get('turnstile').reset(this.id);
1651    })
1652    $this.form.find('.ba-form-field-item').not(not).each(function(){
1653        let input = $f(this).find('[name]');
1654        switch (this.dataset.type) {
1655            case 'signature':
1656                this.instance.clear();
1657                input.value = '';
1658                break;
1659            case 'poll':
1660                let id = 0;
1661                this.querySelectorAll('input').forEach(function(input){
1662                    id = input.name.replace('[]', '');
1663                });
1664                if (id != 0) {
1665                    let item = this.querySelector('.ba-form-checkbox-group-wrapper');
1666                    formsApp.fetch.request(JUri+'index.php?option=com_baforms&task=form.getPollResults', {
1667                        id: id,
1668                        form_id: $this.formId
1669                    }).then(function(text){
1670                        let div = document.createElement('div');
1671                        div.innerHTML = text;
1672                        item.querySelectorAll('.ba-form-checkbox-wrapper').forEach(function(wrapper){
1673                            wrapper.remove();
1674                        });
1675                        div.querySelectorAll('.ba-form-checkbox-wrapper').forEach(function(wrapper){
1676                            item.append(wrapper);
1677                        });
1678                        formsApp.sortPoll(item);
1679                        item.classList.add('ba-poll-results');
1680                        if ($this.form.find('.ba-poll-results').length != 0) {
1681                            setTimeout(function(){
1682                                $this.form.find('.ba-form-submit-btn').remove();
1683                            }, 200);
1684                        }
1685                    });
1686                }
1687                break;
1688            case 'input':
1689            case 'address':
1690            case 'calendar':
1691                input.val('').trigger('input');
1692                if (this.dataset.type == 'calendar') {
1693                    $f(this).find('input').val('');
1694                }
1695                if (this.querySelector('.confirm-email-wrapper')) {
1696                    this.querySelector('.confirm-email-wrapper input').value = '';
1697                }
1698                if (this.querySelector('.confirm-password-wrapper')) {
1699                    this.querySelector('.confirm-password-wrapper input').value = '';
1700                    this.querySelector('.confirm-password-wrapper input').type = 'password';
1701                }
1702                if (this.querySelector('.ba-input-password-icons')) {
1703                    input[0].type = 'password';
1704                }
1705                break;
1706            case 'phone':
1707                let parent = input.closest('.ba-field-container'),
1708                    flag = parent.find('.ba-phone-selected-country').attr('data-default');
1709                parent.find(`li.ba-phone-country-item[data-flag="${flag}"]`).trigger('click');
1710                break;
1711            case 'radio':
1712            case 'checkbox':
1713            case 'rating':
1714                input.prop('checked', false).trigger('change');
1715                break;
1716            case 'select':
1717            case 'selectMultiple':
1718                input.find('option').prop('selected', false);
1719                input.trigger('change');
1720                break;
1721            case 'acceptance':
1722                input[0].checked = false;
1723                break;
1724            case 'slider':
1725                input.val('');
1726                $f(this).find('input[type="range"]').each(function(){
1727                    if ('index' in this.dataset) {
1728                        this.value = this.dataset.index == 0 ? this.min : this.max;
1729                    } else {
1730                        this.value = this.min;
1731                    }
1732                }).trigger('input');
1733                break;
1734            case 'upload':
1735                input.val('');
1736                this.classList.remove('has-uploaded-file');
1737                this.querySelector('.ba-forms-attachment').uploads = {
1738                    count: 0
1739                }
1740                $f(this).find('.ba-forms-xhr-attachment-wrapper').empty();
1741                break;
1742        }
1743    });
1744}
1745
1746class FormProgressHelper {
1747    static #disabledFields = {
1748        submit: true,
1749        map: true,
1750        html: true,
1751        text: true,
1752        image: true,
1753        headline: true,
1754        upload: true
1755    }
1756
1757    static setSavedProgress(formWrapper) {
1758        const { formURL, formId } = formWrapper;
1759        const data = formsApp.storage[formURL]?.[formId] ?? null;
1760        if (!data) {
1761            return;
1762        }
1763
1764        data.fields.forEach(field => {
1765            const input = formWrapper.form.find(`[name="${field.name}"]`);
1766            if (!input.length) {
1767                return;
1768            }
1769
1770            switch (field.type) {
1771                case 'input' :
1772                case 'address':
1773                    if (input[0].formsInputMask) {
1774                        input[0].setSelectionRange(0, 0);
1775                        input[0].setRangeText(field.data.value);
1776                    } else {
1777                        input.val(field.data.value);
1778                    }
1779                    input.trigger('input');
1780                    if ('confirm' in field.data) {
1781                        input.closest('.ba-form-field-item').find('.confirm-email-wrapper input').val(field.data.confirm);
1782                    }
1783                    if ('password' in field.data) {
1784                        input.closest('.ba-form-field-item').find('.confirm-password-wrapper input').val(field.data.password);
1785                    }
1786                    break;
1787                case 'phone':
1788                    let parent = input.closest('.ba-field-container');
1789                    parent.find('.ba-phone-country-item[data-flag="'+field.data.flag+'"]').trigger('click');
1790                    parent.find('.ba-phone-number-input').each(function(){
1791                        this.setSelectionRange(0, 0);
1792                        this.setRangeText(field.data.value);
1793                    }).trigger('input');
1794                    break;
1795                case 'calendar':
1796                    let value = field.data.value.split(' - ');
1797                    input.closest('.ba-field-container').find('input[type="text"]').each(function(i){
1798                        this.value = value[i];
1799                    }).trigger('input');
1800                    break;
1801                case 'radio':
1802                case 'checkbox':
1803                case 'rating':
1804                    input.each(function(){
1805                        if (field.data.checked.indexOf(this.value) != -1) {
1806                            this.checked = true;
1807                            $f(this).trigger('change');
1808                        }
1809                    });
1810                    break;
1811                case 'select':
1812                case 'selectMultiple':
1813                    input.find('option').each(function(){
1814                        if (field.data.selected.indexOf(this.value) != -1) {
1815                            this.selected = true;
1816                            input.trigger('change');
1817                        }
1818                    });
1819                    break;
1820                case 'acceptance':
1821                    if (field.data.selected) {
1822                        input.prop('checked', field.data.selected);
1823                    }
1824                    break;
1825                case 'slider':
1826                    let range = input.closest('.ba-field-container').find('input[type="range"]');
1827                    input.value = field.data.value;
1828                    if (field.data.type == 'range') {
1829                        range.val(field.data.value).trigger('input');
1830                    } else {
1831                        let array = field.data.value.split(' ');
1832                        range.first().val(array[0]).trigger('input');
1833                        range.last().val(array[1]).trigger('input');
1834                    }
1835                    break;
1836            }
1837        })
1838    }
1839
1840    static getData(formWrapper) {
1841        const progress = {
1842            id: formWrapper.formId,
1843            date: +new Date(),
1844            url: formWrapper.formURL,
1845            fields: []
1846        }
1847
1848        Array.from(formWrapper.form.find('.ba-form-field-item')).forEac
1848h(item => {
1849            const { type } = item.dataset;
1850            if (this.#disabledFields[type]) {
1851                return;
1852            }
1853
1854            const input = item.querySelector('[name]');
1855            const field = {
1856                name: input.name,
1857                type: type,
1858                data: {}
1859            }
1860            switch (type) {
1861                case 'input' :
1862                case 'address':
1863                case 'calendar':
1864                    field.data.value = input.value;
1865                    if (input.formsInputMask) {
1866                        field.data.value = '';
1867                        for (let i = 0; i < input.value.length; i++) {
1868                            if (input.formsInputMask[i] == '_') {
1869                                field.data.value += input.value[i];
1870                            }
1871                        }
1872                    }
1873                    if (item.querySelector('.confirm-email-wrapper')) {
1874                        field.data.confirm = item.querySelector('.confirm-email-wrapper input').value;
1875                    }
1876                    if (item.querySelector('.confirm-password-wrapper')) {
1877                        field.data.password = item.querySelector('.confirm-password-wrapper input').value;
1878                    }
1879                    break;
1880                case 'phone':
1881                    const parent = input.closest('.ba-field-container');
1882                    const flag = parent.querySelector('.ba-phone-selected-country .ba-phone-flag');
1883                    const match = flag.className.match(/ba-phone-flag-\w+/);
1884                    const phoneNumber = parent.querySelector('.ba-phone-number-input');
1885                    field.data.value = '';
1886                    field.data.flag = match[0].replace('ba-phone-flag-', '');
1887                    for (let i = 0; i < phoneNumber.value.length; i++) {
1888                        if (phoneNumber.placeholder[i] == '_') {
1889                            field.data.value += phoneNumber.value[i];
1890                        }
1891                    }
1892                    break;
1893                case 'radio':
1894                case 'checkbox':
1895                case 'rating':
1896                    field.data.checked = [];
1897                    item.querySelectorAll('[name]').forEach(checkbox => {
1898                        if (checkbox.checked) {
1899                            field.data.checked.push(checkbox.value)
1900                        }
1901                    })
1902                    break;
1903                case 'select':
1904                case 'selectMultiple':
1905                    field.data.selected = [];
1906                    item.querySelectorAll('select[name] option').forEach(option => {
1907                        if (option.selected) {
1908                            field.data.selected.push(option.value)
1909                        }
1910                    })
1911                    break;
1912                case 'acceptance':
1913                    field.data.selected = input.checked;
1914                    break;
1915                case 'slider':
1916                    field.data.type = item.querySelector('.form-range-wrapper') ? 'range' : 'slider';
1917                    field.data.value = input.value;
1918                    break;
1919            }
1920            progress.fields.push(field);
1921        });
1922
1923        return progress;
1924    }
1925}
1926
1927formsApp.checkAutoNavigation = function($this, field){
1928    if ($this.autoNavigation.enable) {
1929        let page = field.closest('.ba-form-page'),
1930            next = true;
1931        page.querySelectorAll('.ba-form-field-item').forEach(function(el){
1932            let empty = true;
1933            if ($this.autoNavigation.fields.indexOf(el.dataset.type) == -1) {
1934
1935            } else if (el.dataset.type == 'select') {
1936                empty = el.querySelector('select').value == '';
1937            } else {
1938                el.querySelectorAll('input').forEach(function(input){
1939                    if (input.checked) {
1940                        empty = false;
1941                        return false;
1942                    }
1943                });
1944            }
1945            if (empty) {
1946                next = false;
1947                return false;
1948            }
1949        });
1950        if (next) {
1951            $f(page).find('.ba-form-page-break-button[data-action="next"]').trigger('click');
1952        }
1953    }
1954}
1955
1956formsApp.submitData = () => {
1957    const form = formsApp.submitForm.form;
1958    if (form.find('.ba-form-field-item:not(.hidden-condition-field)').find('.ba-alert-tooltip').length == 0) {
1959        form[0].status = 'pending';
1960        formsApp.sendAjaxForm(form[0], formsApp.submitForm);
1961    }
1962    formsApp.submitForm = null;
1963}
1964
1965formsApp.createForm = function(){
1966    if (this.formId) {
1967        return true;
1968    }
1969    let $this = this,
1970        defaultValues = {};
1971    $this.carts = [];
1972    $this.products = {};
1973    $this.fields = {};
1974    $this.calculation = {};
1975    $this.items = {};
1976    $this.autoNavigation = {
1977        enable : false,
1978        fields: ['select', 'radio', 'rating']
1979    };
1980    this.formId = this.querySelector('input[name="form-id"]').value * 1;
1981    formsApp.loaded[this.formId] = this;
1982    this.querySelector('input[name="page-title"]').value = document.querySelector('title').textContent;
1983    this.querySelector('input[name="page-url"]').value = window.location.href;
1984    this.querySelectorAll('input[name="page-id"]').forEach(function(el){
1985        if (el.value == 0 && window.themeData) {
1986            el.value = window.themeData.id;
1987        }
1988    });
1989    this.formURL = window.location.host.replace('www.', '')+window.location.pathname.replace('/index.php', '');
1990    if (this.formURL[this.formURL.length - 1] != '/') {
1991        this.formURL += '/';
1992    }
1993    this.form = $f(this).find('form').on('submit', function(event){
1994        event.preventDefault();
1995    });
1996    this.form.find('.ba-form-submit-btn').each(function(){
1997        this.options = {
1998            id: this.dataset.id,
1999            onclick: this.dataset.onclick,
2000            payment: this.dataset.payment,
2001            popupAnimation: this.dataset.popupAnimation,
2002            messageType: this.dataset.messageType,
2003            productTitle: this.dataset.productTitle,
2004            productPrice: this.dataset.productPrice
2005        }
2006    }).removeAttr('data-payment').removeAttr('data-id').removeAttr('data-message-type')
2007        .removeAttr('data-popup-animation').on('click', async function (event) {
2008        event.preventDefault();
2009        let form = $this.form[0];
2010        if (form.status != 'pending') {
2011            form.submitBtn = this;
2012            formsApp.submitForm = $this;
2013            formsApp.checkAlert($this.form);
2014            const canSubmit = await CaptchaHelper.checkResponse(this);
2015            if (canSubmit) {
2016                formsApp.submitData();
2017            }
2018        }
2019    }).removeAttr('data-onclick').removeAttr('data-message');
2020    this.form.find('.ba-poll-results').each(function(){
2021        formsApp.sortPoll(this);
2022    })
2023    this.form.find('.ba-form-page-break-button').each(function(){
2024        if (this.dataset.action == 'next' && this.dataset.auto) {
2025            $this.autoNavigation.enable = true;
2026        }
2027    }).on('click keyup', function(event){
2028        FormsAccessibilityHelper.processAction(event, () => {
2029            let page = $f(this).closest('.ba-form-page'),
2030                rect = $this.form[0].getBoundingClientRect(),
2031                pageKey = page[0].dataset.pageKey;
2032            if (this.dataset.action == 'next') {
2033                formsApp.checkAlert(page);
2034                if (page.find('.ba-form-field-item').not('.hidden-condition-field').find('.ba-alert-tooltip').length) {
2035                    return false;
2036                }
2037            }
2038            page.addClass('ba-hidden-form-page');
2039            if ($this.pages && $this.pages[pageKey] && this.dataset.action == 'next') {
2040                page = $f($this.pages[pageKey].next).removeClass('ba-hidden-form-page');
2041                page.find('.ba-form-page-break-button[data-action="back"]')[0].prevPage = pageKey;
2042            } else if (this.dataset.action == 'back' && this.prevPage) {
2043                page = $f($this.pages[this.prevPage].prev).removeClass('ba-hidden-form-page');
2044                this.prevPage = null;
2045            } else {
2046                page = page[this.dataset.action == 'next' ? 'next': 'prev']().removeClass('ba-hidden-form-page');
2047            }
2048            if (rect.top < 0) {
2049                $this.form[0].scrollIntoView(true);
2050            }
2051            if (this.dataset.action == 'next') {
2052                page.find('.ba-form-submit-btn').each(function(){
2053                    CaptchaHelper.createIfExist(this);
2054                });
2055                FormsSignature.make();
2056            }
2057        })
2058    });
2059    this.form.find('.upload-file-btn').each(function(){
2060        this.file = this.closest('.upload-file-input').querySelector('input[type="file"]');
2061    }).on('click keyup', function(event){
2062        FormsAccessibilityHelper.processAction(event, () => {
2063            formsApp.removeAlertTooltip(this.file);
2064            if (this.file.uploading != 'pending') {
2065                this.file.click();
2066            }
2067        });
2068    });
2069    this.form.find('.drag-drop-upload-file').on('dragenter dragover', function(event){
2070        event.preventDefault();
2071        event.stopPropagation();
2072        this.classList.add('upload-file-drag-over');
2073        return false;
2074    }).on('dragleave', function(event){
2075        event.preventDefault();
2076        event.stopPropagation();
2077        this.classList.remove('upload-file-drag-over');
2078        return false;
2079    }).on('drop', function(event){
2080        event.preventDefault();
2081        event.stopPropagation();
2082        this.classList.remove('upload-file-drag-over');
2083        var files = event.originalEvent.target.files || event.originalEvent.dataTransfer.files;
2084        $f(this).find('.ba-forms-attachment').each(function(){
2085            this.files = files;
2086        }).trigger('change');
2087    });
2088    this.form.find('input[type="file"].ba-forms-attachment').each(function(){
2089        let name = '.ba-forms-xhr-attachment-wrapper';
2090        this.options = {
2091            id: $this.formId,
2092            size: this.dataset.size * 1000,
2093            types: this.dataset.types.replace(/\s/g, '').split(','),
2094            count: this.multiple ? this.dataset.count : 1,
2095            images: ['gif', 'jpg', 'jpeg', 'png', 'svg', 'webp'],
2096            fileContainer: this.closest('.ba-field-container').querySelector(name+'[data-type="file"]'),
2097            imageContainer: this.closest('.ba-field-container').querySelector(name+'[data-type="image"]')
2098        }
2099        this.uploads = {
2100            count: 0
2101        }
2102    }).on('change', function(){
2103        this.uploading = 'pending';
2104        let files = [].slice.call(this.files),
2105            uploaded = {},
2106            msg = '';
2107        for (let ind in this.uploads) {
2108            if (ind == 'count') {
2109                continue;
2110            }
2111            uploaded[this.uploads[ind].name] = true;
2112        }
2113        if (this.options.count != '' && this.uploads.count == this.options.count * 1) {
2114            msg = 'MAXIMUM_FILES_NUMBER_EXCEEDED';
2115        } else {
2116            for (let i = 0; i < files.length; i++) {
2117                let name = files[i].name.split('.');
2118                files[i].ext = name[name.length - 1].toLowerCase()
2119                if (this.options.size < files[i].size) {
2120                    msg = 'NOT_ALLOWED_FILE_SIZE';
2121                    break;
2122                } else if (this.options.types.indexOf(files[i].ext) == -1) {
2123                    msg = 'NOT_SUPPORTED_FILE';
2124                    break;
2125                } else if (uploaded[files[i].name]) {
2126                    msg = 'FILES_ALREADY_UPLOADED';
2127                    break;
2128                }
2129            }
2130        }
2131        this.value = '';
2132        if (msg != '') {
2133            this.uploading = '';
2134            formsApp.showNotice(formsApp._(msg), 'ba-alert');
2135        } else {
2136            formsApp.uploadAttachmentFile(files, this, $this);
2137        }
2138    });
2139    this.form.find('.ba-form-save-progress-link').on('click', function(){
2140        const progress = FormProgressHelper.getData($this);
2141        if (!formsApp.storage[progress.url]) {
2142            formsApp.storage[progress.url] = {};
2143        }
2144        formsApp.storage[progress.url][progress.id] = progress;
2145        localStorage.setItem('forms-progress', JSON.stringify(formsApp.storage));
2146        formsApp.showNotice(formsApp._('PROGRESS_SAVED_MESSAGE'));
2147    });
2148    this.form.find('.ba-form-map-field .ba-map-wrapper').each(function(){
2149        this.options = {
2150            map: JSON.parse(this.dataset.map),
2151            marker: JSON.parse(this.dataset.marker),
2152            controls: Boolean(this.dataset.controls * 1),
2153            styleType: this.dataset.styleType
2154        }
2155        if (formsApp.googleMaps || (('google' in window) && google.maps)) {
2156            formsApp.createGoogleMap(this, this.options);
2157        }
2158    }).removeAttr('data-map').removeAttr('data-marker').removeAttr('data-controls').removeAttr('data-style-type');
2159    this.form.find('.ba-input-password-icons i').on('click', function(){
2160        let input = this.closest('.ba-field-container').querySelector('input'),
2161            icons = this.closest('.ba-input-password-icons').querySelectorAll('i');
2162        this.style.display = 'hide';
2163        if (this.dataset.action == 'show') {
2164            input.type = 'text';
2165            icons[1].style.display = '';
2166        } else {
2167            input.type = 'password';
2168            icons[0].style.display = '';
2169        }
2170    });
2171    this.form.find('.ba-form-calendar-field input[type="text"]').each(function(){
2172        this.rangeBtn = $f(this).closest('.calendar-range-type').find('input[type="text"]').not(this)[0];
2173        this.hiddenInput = this.closest('.ba-field-container').querySelector('input[type="hidden"]');
2174    });
2175    this.form.find('.ba-form-calendar-field input[type="text"]').on('click keyup', function(event){
2176        FormsAccessibilityHelper.processAction(event, () => {
2177            formsApp.removeAlertTooltip(this);
2178            if (this.rangeBtn && this.dataset.index == 1 && !this.rangeBtn.value) {
2179                this.rangeBtn.click();
2180                return false;
2181            }
2182            if (!formsApp.calendar) {
2183                formsApp.createCalendar(this);
2184            }
2185            formsApp.calendar.disable = {
2186                days: [],
2187                dates: [],
2188                range: []
2189            };
2190            if (this.dataset.disableDays) {
2191                formsApp.calendar.disable.days = this.dataset.disableDays.split(',');
2192                let pos = formsApp.calendar.disable.days.indexOf('0');
2193                if (this.dataset.start == 1 && pos != -1) {
2194                    formsApp.calendar.disable.days[pos] = '7';
2195                }
2196            }
2197            if (this.dataset.disableDates) {
2198                formsApp.calendar.disable.dates = this.dataset.disableDates.split(',');
2199            }
2200            if (this.dataset.disableRangeDates) {
2201                formsApp.calendar.disable.range = this.dataset.disableRangeDates.split(',');
2202            }
2203            if (this.dataset.disablePrevious == 1) {
2204                formsApp.calendar.classList.add('disable-previous-date');
2205            } else {
2206                formsApp.calendar.classList.remove('disable-previous-date');
2207            }
2208            formsApp.calendar.dataset.form = $this.form.attr('data-id');
2209            formsApp.calendar.start = this.dataset.start;
2210            formsApp.calendar.current = {
2211                year: this.dataset.year,
2212                month: this.dataset.month
2213            }
2214            formsApp.renderFormsCalendar(this);
2215        })
2216    }).on('input', function(){
2217        if (this.rangeBtn && this.dataset.index == 0) {
2218            this.rangeBtn.value = '';
2219        }
2220        this.hiddenInput.price = 0;
2221        if (!this.rangeBtn) {
2222            this.hiddenInput.value = this.value;
2223        } else if (this.rangeBtn && this.dataset.index == 0) {
2224            this.hiddenInput.value = this.value+' - '+this.rangeBtn.value;
2225        } else {
2226            this.hiddenInput.value = this.rangeBtn.value+' - '+this.value;
2227            var date1 = new Date(this.rangeBtn.dataset.value),
2228                date2 = new Date(this.dataset.value);
2229            this.hiddenInput.price = Math.ceil(Math.abs(date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))
2230        }
2231        formsApp.updateFieldsValues($this.fields, this.hiddenInput, this.hiddenInput.fieldId);
2232        formsApp.checkConditionLogic($this);
2233        formsApp.calculation($this);
2234    });
2235    this.form.find('.ba-form-address-field input').each(function(){
2236        this.createAutocomplete = function(){
2237            let autocomplete = new google.maps.places.Autocomplete(this),
2238                input = this;
2239            autocomplete.addListener('place_changed', function(){
2240                formsApp.updateFieldsValues($this.fields, input, input.fieldId);
2241                formsApp.checkConditionLogic($this);
2242            });
2243        }
2244        if (formsApp.googleMaps || (('google' in window) && google.maps)) {
2245            this.createAutocomplete();
2246        }
2247    }).on('input', function(){
2248        formsApp.updateFieldsValues($this.fields, this, this.fieldId);
2249        formsApp.checkConditionLogic($this);
2250    }).on('focus', function(){
2251        formsApp.removeAlertTooltip(this);
2252    });
2253    this.form.find('.ba-form-slider-field .form-range-wrapper input').each(function(){
2254        let parent = this.closest('.ba-field-container');
2255        this.linear = parent.querySelector('.ba-form-range-liner');
2256        this.number = parent.querySelector('.form-slider-input-wrapper input[data-type="range"]');
2257        this.input = parent.querySelector('.form-slider-input-wrapper input[type="hidden"][name]');
2258        this.number.range = this;
2259    }).on('input', function(){
2260        var max = this.max * 1,
2261            min = this.min * 1,
2262            value = this.value * 1,
2263            sx = (value - min) * 100 / (max - min);
2264        this.input.value = value;
2265        this.number.value = this.value;
2266        this.linear.style.width = sx+'%';
2267        formsApp.updateFieldsValues($this.fields, this.input, this.input.fieldId);
2268        formsApp.checkConditionLogic($this);
2269        formsApp.calculation($this);
2270    });
2271    this.form.find('.ba-form-slider-field .form-slider-input-wrapper input[data-type="range"]').on('input', function(){
2272        var max = this.range.max * 1,
2273            min = this.range.min * 1,
2274            value = this.value * 1;
2275        if (value > max) {
2276            this.value = value = max;
2277        }
2278        if (value >= min) {
2279            this.range.value = value;
2280            $f(this.range).trigger('input');
2281        }
2282    });
2283    this.form.find('.ba-form-slider-field .form-slider-wrapper input[type="range"]').each(function(){
2284        let parent = this.closest('.ba-field-container');
2285        this.linear = parent.querySelector('.ba-form-range-liner');
2286        this.slides = parent.querySelectorAll('input[type="range"]');
2287        this.numbers = parent.querySelectorAll('.form-slider-input-wrapper input[data-type="slider"]');
2288        this.numbers[0].slides = this.slides;
2289        this.numbers[1].slides = this.slides;
2290        this.input = parent.querySelector('.form-slider-input-wrapper input[type="hidden"][name]');
2291        if (!this.input.values) {
2292            this.input.values = [];
2293        }
2294        this.input.values.push(this.value);
2295    }).on('input', function(){
2296        var max = this.max * 1,
2297            min = this.min * 1,
2298            ind = this.dataset.index * 1,
2299            index = ind == 0 ? 1 : 0,
2300            value = this.value * 1,
2301            value2 = this.slides[index].value * 1,
2302            sx = left = 0;
2303        if (this.slides[0].value * 1 > this.slides[1].value * 1) {
2304            ind = ind == 0 ? 1 : 0;
2305            index = index == 0 ? 1 : 0;
2306        }
2307        this.input.values[index] = value2;
2308        this.numbers[index].value = value2;
2309        this.numbers[ind].value = value;
2310        this.input.values[ind] = value;
2311        this.input.value = this.input.values[0]+' '+this.input.values[1];
2312        sx = (this.input.values[1] * 1 - this.input.values[0] * 1) * 100 / (max - min)
2313        left = (max - this.input.values[1] * 1) * 100 / (max - min);
2314        this.linear.style.width = sx+'%';
2315        this.linear.style.marginLeft = (100 - sx - left)+'%';
2316        formsApp.updateFieldsValues($this.fields, this.input, this.input.fieldId);
2317        formsApp.checkConditionLogic($this);
2318    });
2319    this.form.find('.ba-form-slider-field .form-slider-input-wrapper input[data-type="slider"]').on('input', function(){
2320        let $this = this
2321        clearTimeout(this.delay);
2322        this.delay = setTimeout(function(){
2323            if ($this.value) {
2324                let value = $this.value * 1,
2325                    values = $this.slides[0].input.values,
2326                    index = $this.dataset.index * 1;
2327                if (index == 0 && value > values[1] * 1) {
2328                    index = 1;
2329                    $this.slides[1].numbers[1].focus();
2330                    $this.value = values[1];
2331                    $this.slides[0].input.values[0] = $this.value;
2332                    $f($this.slides[0]).val($this.value);
2333                } else if (index == 1 && value < values[0] * 1) {
2334                    index = 0;
2335                    $this.slides[0].numbers[0].focus();
2336                    $this.value = values[0];
2337                    $this.slides[0].input.values[1] = $this.value;
2338                    $f($this.slides[1]).val($this.value);
2339                }
2340                $this.slides[index].value = value;
2341                $f($this.slides[index]).trigger('input');
2342            }
2343        }, 500);
2344    });
2345    this.form.find('.ba-form-phone-field').on('click', function(){
2346        formsApp.removeAlertTooltip(this.querySelector('input.ba-phone-number-input'));
2347    }).on('keyup', '.ba-phone-countries-list-wrapper', function(event){
2348        if (event.key == 'Escape') {
2349            formsApp.countries.hide();
2350        }
2351    }).on('click keydown', '.ba-phone-selected-country', function(event){
2352        if (event.type != 'click' && !(event.type == 'keydown' && event.key == 'ArrowDown')) return;
2353
2354        event.preventDefault();
2355        let wrapper = this.closest('.ba-phone-countries-wrapper'),
2356            search = wrapper.querySelector('.ba-phone-countries-search');
2357        setTimeout(function(){
2358            wrapper.querySelectorAll('li').forEach(function(li){
2359                li.style.display = '';
2360            });
2361            wrapper.classList.remove('top-countries-list');
2362            wrapper.classList.add('visible-countries-list');
2363            const countriesList = wrapper.querySelector('.ba-phone-countries-list-wrapper');
2364            FormsAccessibilityHelper.trapFocus(countriesList);
2365            const rect = wrapper.querySelector('ul').getBoundingClientRect();
2366            if (window.innerHeight < rect.bottom) {
2367                wrapper.classList.add('top-countries-list');
2368            }
2369            search.value = '';
2370            setTimeout(() => {
2371                search.focus();
2372            }, 50);
2373            $f('body').off('click.selected-country').one('click.selected-country', function(){
2374                formsApp.countries.hide();;
2375            });
2376        }, 100);
2377    }).on('input', '.ba-phone-countries-search', function(){
2378        var li = this.closest('.ba-phone-countries-wrapper').querySelectorAll('li'),
2379            search = this.value.toLowerCase();
2380        clearTimeout(this.delay);
2381        this.delay = setTimeout(function(){
2382            li.forEach(function(el){
2383                let title = el.dataset.title.toLowerCase();
2384                el.style.display = !search || title.indexOf(search) != -1 ? '' : 'none';
2385            });
2386        }, 300);
2387    }).on('click', '.ba-phone-countries-search', function(event){
2388        event.stopPropagation();
2389    }).on('click keyup', '.ba-phone-countries-list li.ba-phone-country-item', function(event){
2390        FormsAccessibilityHelper.processAction(event, () => {
2391            event.stopPropagation();
2392            let wrapper = this.closest('.ba-field-container');
2393            if (!wrapper.phoneInput) {
2394                wrapper.countryFlag = wrapper.querySelector('.ba-phone-selected-country .ba-phone-flag');
2395                wrapper.countryPrefix = wrapper.querySelector('.ba-phone-selected-country .ba-phone-prefix');
2396                wrapper.phoneInput = wrapper.querySelector('input.ba-phone-number-input');
2397            }
2398            wrapper.countryFlag.className = 'ba-phone-flag ba-phone-flag-'+this.dataset.flag;
2399            wrapper.countryPrefix.textContent = this.dataset.prefix;
2400            wrapper.phoneInput.placeholder = this.dataset.placeholder;
2401            wrapper.phoneInput.value = '';
2402            wrapper.phoneInput.formsInputMask = this.dataset.placeholder;
2403            wrapper.phoneInput.dataset.prefix = this.dataset.prefix;
2404            wrapper.phoneInput.dispatchEvent(wrapper.phoneInput.inputEvent);
2405            formsApp.prepareInputMask(wrapper.phoneInput, wrapper.phoneInput.formsInputMask);
2406            formsApp.countries.hide();
2407        })
2408    }).on('keydown', 'input.ba-phone-number-input', function(event){
2409        formsApp.prepareInputMask(this, this.formsInputMask, event);
2410    }).on('input', 'input.ba-phone-number-input', function(){
2411        formsApp.executeInputMask(this, this.formsInputMask);
2412        let value = this.dataset.prefix+' '+this.value,
2413            placeholder = this.dataset.prefix+' '+this.placeholder;
2414        this.hiddenInput.value = this.value && value != placeholder ? value : '';
2415        this.hiddenInput.dispatchEvent(this.inputEvent);
2416    }).find('input.ba-phone-number-input').each(function(){
2417        this.prefix = this.dataset.prefix;
2418        this.formsInputMask = this.placeholder;
2419        formsApp.prepareInputMask(this, this.formsInputMask);
2420        this.hiddenInput = this.closest('.ba-field-container').querySelector('input[type="hidden"]');
2421        this.hiddenInput.addEventListener('input', function(){
2422            formsApp.updateFieldsValues($this.fields, this, this.fieldId);
2423            formsApp.checkConditionLogic($this);
2424            formsApp.calculation($this);
2425        });
2426        this.inputEvent = new Event('input');
2427    });
2428    this.form.find('.ba-form-input-field > .ba-input-wrapper').find('[data-mask]').each(function(){
2429        this.formsInputMask = this.dataset.mask.replace(/#/g, '_');
2430        formsApp.prepareInputMask(this, this.formsInputMask);
2431    }).on('keydown', function(event){
2432        formsApp.prepareInputMask(this, this.formsInputMask, event);
2433    }).on('input', function(event){
2434        formsApp.executeInputMask(this, this.formsInputMask);
2435    });
2436    this.form.find('.characters-wrapper').each(function(){
2437        let input = this.closest('.ba-field-container').querySelector('input, textarea');
2438        input.characters = {
2439            div: this,
2440            key: this.dataset.direction,
2441            length: this.dataset.length
2442        }
2443    });
2444    this.form.find('.ba-form-input-field > .ba-input-wrapper').find('input, textarea').on('input', function(){
2445        if (this.dataset.validation == 'numbers') {
2446            let match = this.value.match(/\d+\.{0,1}\d{0,}/);
2447            if (!match) {
2448                this.value = '';
2449            } else if (match[0] != this.value) {
2450                this.value = match[0];
2451            }
2452        }
2453        if (this.characters) {
2454            let length = this.value.length;
2455            this.characters.div.querySelector('.current-characters').textContent = length;
2456        }
2457        formsApp.updateFieldsValues($this.fields, this, this.fieldId);
2458        formsApp.checkConditionLogic($this);
2459        formsApp.calculation($this);
2460    }).on('focus', function(){
2461        formsApp.removeAlertTooltip(this);
2462    });
2463    this.form.find('.ba-form-signature-field textarea').on('input', function(){
2464        formsApp.updateFieldsValues($this.fields, this, this.fieldId);
2465        formsApp.checkConditionLogic($this);
2466    });
2467    this.form.find('.confirm-email-wrapper, .confirm-password-wrapper').find('input').on('focus', function(){
2468        formsApp.removeAlertTooltip(this);
2469    });
2470    this.form.find('.ba-form-dropdown-field, .ba-form-select-multiple-field').find('select').on('focus', function(){
2471        formsApp.removeAlertTooltip(this);
2472    }).on('change', function(){
2473        $this.fields[this.fieldId] = {
2474            value: '',
2475            price: 0
2476        }
2477        formsApp.updateFieldsValues($this.fields, this, this.fieldId);
2478        formsApp.checkConditionLogic($this);
2479        formsApp.calculation($this);
2480        formsApp.checkAutoNavigation($this, this);
2481        if (this.product) {
2482            formsApp.productChange($this, this.name);
2483        }
2484    });
2485    this.form.find('.ba-form-rating-group-wrapper').on('change', function(){
2486        formsApp.removeAlertTooltip(this);
2487        let input = this.querySelectorAll('input');
2488        $this.fields[input[0].fieldId] = {
2489            value: '',
2490            price: 0
2491        }
2492        this.classList.remove('active');
2493        $f(this).find('label.active').removeClass('active');
2494        for (let i = 0; i < input.length; i++) {
2495            formsApp.updateFieldsValues($this.fields, input[i], input[i].fieldId);
2496            if (input[i].checked) {
2497                this.classList.add('active');
2498                input[i].closest('label').classList.add('active');
2499            }
2500        }
2501        formsApp.checkConditionLogic($this);
2502        formsApp.calculation($this);
2503        formsApp.checkAutoNavigation($this, this);
2504    });
2505    this.form.find('.ba-form-checkbox-group-wrapper').each(function(){
2506        if (this.querySelector('.ba-checkbox-image')) {
2507            this.classList.add('checkbox-image-group-wrapper');
2508        }
2509        let checkbox = this.querySelector('.ba-form-checkbox-wrapper:last-child');
2510        if (checkbox && !checkbox.classList.contains('last-row-checkbox-wrapper')) {
2511            checkbox.classList.add('last-row-checkbox-wrapper')
2512        }
2513    }).on('change', function(){
2514        formsApp.removeAlertTooltip(this);
2515        let input = this.querySelectorAll('input'),
2516            name = product = null,
2517            image = this.classList.contains('checkbox-image-group-wrapper');
2518        $this.fields[input[0].fieldId] = {
2519            value: '',
2520            price: 0
2521        }
2522        if (image) {
2523            $f(this).find('.checked-image-container').removeClass('checked-image-container');
2524        }
2525        for (let i = 0; i < input.length; i++) {
2526            formsApp.updateFieldsValues($this.fields, input[i], input[i].fieldId);
2527            name = input[i].name;
2528            product = input[i].product;
2529            if (input[i].checked) {
2530                input[i].closest('.ba-form-checkbox-wrapper').classList.add('checked-image-container');
2531            }
2532        }
2533        formsApp.checkConditionLogic($this);
2534        formsApp.calculation($this);
2535        formsApp.checkAutoNavigation($this, this);
2536        if (product && name) {
2537            formsApp.productChange($this, name);
2538        }
2539    }).find('.ba-form-checkbox-wrapper').on('click', function(event){
2540        if (!event.target.classList.contains('ba-form-checkbox') && !event.target.closest('.ba-form-checkbox') &&
2541            !event.target.closest('.ba-form-radio')) {
2542            $f(this).find('> .ba-checkbox-wrapper input').each(function(){
2543                if (this.type == 'radio' && !this.checked) {
2544                    this.checked = true;
2545                } else if (this.type == 'checkbox') {
2546                    this.checked = !this.checked;
2547                }
2548            }).trigger('change');
2549        }
2550    });
2551    this.form.find('.ba-form-acceptance-field .ba-field-container').on('change', function(){
2552        formsApp.removeAlertTooltip(this);
2553        let input = this.querySelector('input');
2554        $this.fields[input.fieldId] = {
2555            value: '',
2556            price: 0
2557        }
2558        formsApp.updateFieldsValues($this.fields, input, input.fieldId);
2559        formsApp.checkConditionLogic($this);
2560    });
2561    this.form.find('[data-price]').each(function(){
2562        this.price = this.dataset.price;
2563    }).removeAttr('data-price');
2564    this.form.find('[data-field-id]').each(function(){
2565        this.fieldId = this.dataset.fieldId;
2566        $this.items[this.fieldId] = this.closest('.ba-form-field-item');
2567        if (this.classList.contains('ba-form-submit-btn')) {
2568            return true;
2569        }
2570        if (!$this.fields[this.fieldId]) {
2571            $this.fields[this.fieldId] = {
2572                value: '',
2573                price: 0
2574            }
2575        }
2576        formsApp.updateFieldsValues($this.fields, this, this.fieldId);
2577    }).removeAttr('data-field-id');
2578    this.form.find('[data-default]').each(function(){
2579        let match = this.dataset.default.match(/\[Field ID=\d+\]/) || this.dataset.default.match(/\[Page Title\]/);
2580        match = match || this.dataset.default.match(/\[Page URL\]/);
2581        if (this.dataset.default && match) {
2582            let key = this.fieldId ? this.fieldId : +new Date(),
2583                obj =  {
2584                    item: this,
2585                    value: this.dataset.default
2586                }
2587            defaultValues[key] = obj;
2588        }
2589    });
2590    this.form.find('.ba-form-calculation-field .field-price-value').each(function(){
2591        let name = '.ba-form-calculation-price-wrapper';
2592        this.calculation = $f.extend({}, this.dataset);
2593        this.calculation.result = this;
2594        this.calculation.input = this.closest('.ba-field-container').querySelector('input');
2595        this.calculation.product = this.closest(name).querySelector('input').dataset.product == 'product';
2596        for (ind in this.dataset) {
2597            $f(this).removeAttr('data-'+ind)
2598        }
2599        $this.calculation[this.calculation.input.fieldId] = this.calculation;
2600    });
2601    this.form.find('.ba-form-calculation-field input[type="hidden"]').on('change', function(){
2602        formsApp.updateFieldsValues($this.fields, this, this.fieldId);
2603        formsApp.checkConditionLogic($this);
2604        if (this.product) {
2605            formsApp.productChange($this, this.name);
2606        }
2607    });
2608    this.form.find('.ba-form-total-field .ba-cart-total-row .field-price-value').each(function(){
2609        this.calculation = $f.extend({}, this.dataset);
2610        let calculation = this.calculation,
2611            name = '.ba-cart-subtotal-row .field-price-value';
2612        calculation.subtotal = this.closest('.ba-cart-total-container').querySelector(name);
2613        calculation.result = this;
2614        calculation.input = this.closest('.ba-form-calculation-price-wrapper').querySelector('textarea');
2615        calculation.cart = this.closest('.ba-input-wrapper').querySelector('.ba-form-products-cart');
2616        calculation.products = {};
2617        calculation.total = 0;
2618        calculation.resultTotal = 0;
2619        name = '.ba-cart-discount-row .field-price-value';
2620        calculation.discount = this.closest('.ba-cart-total-container').querySelector(name);
2621        name = '.ba-cart-tax-row .field-price-value';
2622        calculation.taxResult = this.closest('.ba-cart-total-container').querySelector(name);
2623        name = '.ba-cart-shipping-row input[type="radio"]';
2624        calculation.shipping = this.closest('.ba-cart-total-container').querySelectorAll(name);
2625        calculation.shipping.forEach(function(el){
2626            el.addEventListener('change', function(){
2627                formsApp.calculateCartTotal(calculation);
2628            })
2629        });
2630        $this.carts.push(this);
2631        for (ind in this.dataset) {
2632            $f(this).removeAttr('data-'+ind)
2633        }
2634    });
2635    this.form.find('.ba-form-total-field textarea').on('change', function(){
2636        let calc = this.closest('.ba-form-calculation-price-wrapper').querySelector('.field-price-value').calculation;
2637        if ($this.fields[this.fieldId].price != calc.resultTotal) {
2638            $this.fields[this.fieldId] = {
2639                value: calc.resultTotal,
2640                price: calc.resultTotal
2641            }
2642            formsApp.calculation($this, true);
2643        }
2644    });
2645
2646    this.form.find('.ba-cart-promo-code-btn').each(function(){
2647        this.promo = {
2648            input: this.closest('.ba-cart-promo-code-container').querySelector('.ba-cart-promo-code-input'),
2649            total: this.closest('.ba-field-container').querySelector('.ba-cart-total-row .field-price-value'),
2650            discount: this.closest('.ba-field-container').querySelector('.ba-cart-discount-row')
2651        }
2652    }).on('click keyup', function(event){
2653        if (this.status == 'pending') {
2654            return;
2655        }
2656        FormsAccessibilityHelper.processAction(event, () => {
2657            this.status = 'pending';
2658            let btn = this;
2659            formsApp.fetch.request(JUri+'index.php?option=com_baforms&task=form.checkCoupon', {
2660                coupon: btn.promo.input.value.trim(),
2661                id: btn.dataset.name
2662            }).then(function(text){
2663                btn.status = '';
2664                if (!text || !btn.promo.input.value.trim()) {
2665                    formsApp.showNotice(formsApp._('PROMO_CODE_NOT_VALID'), 'ba-alert');
2666                    btn.promo.total.calculation.promo = null;
2667                    btn.promo.discount.style.display = 'none';
2668                } else {
2669                    btn.promo.total.calculation.promo = JSON.parse(text);
2670                    btn.promo.discount.style.display = '';
2671                }
2672                formsApp.calculateCartTotal(btn.promo.total.calculation);
2673            });
2674        })
2675    })
2676    this.form.find('[data-product]').each(function(){
2677        if (this.dataset.product) {
2678            this.product = this.dataset.product;
2679            let name = this.name;
2680            if (!$this.products[name]) {
2681                $this.products[name] = [];
2682            }
2683            if (this.localName == 'select') {
2684                this.querySelectorAll('option').forEach(function(option){
2685                    $this.products[name].push(option);
2686                });
2687            } else {
2688                $this.products[name].push(this);
2689            }
2690        }
2691    }).removeAttr('data-product');
2692    formsApp.updateFieldsDefaultValues($this, defaultValues);
2693    FormProgressHelper.setSavedProgress($this);
2694    formsApp.checkConditionLogic($this);
2695    formsApp.calculation($this);
2696    formsApp.updateCarts($this);
2697}
2698
2699formsApp.productChange = function($this, name){
2700    for (let i = 0; i < $this.carts.length; i++) {
2701        if (!$this.carts[i].calculation.products[name]) {
2702            continue;
2703        }
2704        let calculation = $this.carts[i].calculation;
2705        formsApp.updateProducts($this, calculation, name);
2706    }
2707}
2708
2709formsApp.updateCarts = function($this){
2710    for (let i = 0; i < $this.carts.length; i++) {
2711        let calculation = $this.carts[i].calculation;
2712        for (let name in $this.products) {
2713            if (!calculation.products[name]) {
2714                calculation.products[name] = {};
2715            }
2716            formsApp.updateProducts($this, calculation, name);
2717        }
2718    }
2719}
2720
2721formsApp.updateProducts = function($this, calculation, name){
2722    let products = $this.products[name],
2723        object = {};
2724    for (let i = 0; i < products.length; i++) {
2725        if (products[i].closest('.hidden-condition-field')) {
2726            continue;
2727        }
2728        if (products[i].price != '' &&
2729            (((products[i].type == 'radio' || products[i].type == 'checkbox') && products[i].checked)
2730                || (products[i].localName == 'option' && products[i].selected) || products[i].type == 'hidden')) {
2731            let obj = {
2732                key: products[i].dataset.key ?? '0',
2733                input: products[i],
2734                quantity: 1,
2735                title: products[i].dataset.title ? products[i].dataset.title : products[i].value,
2736                price: products[i].price
2737            }
2738            obj.total = obj.price * obj.quantity;
2739            object[i] = obj;
2740        }
2741    }
2742    for (let ind in calculation.products[name]) {
2743        if (!object[ind] || calculation.products[name][ind].input.type == 'hidden') {
2744            calculation.total -= calculation.products[name][ind].total;
2745            if (calculation.cart) {
2746                calculation.cart.querySelector('.ba-form-product-row[data-name="'+name+'"][data-ind="'+ind+'"]').remove();
2747            }
2748            delete calculation.products[name][ind];
2749        }
2750    }
2751    for (let ind in object) {
2752        if (!calculation.products[name][ind]) {
2753            if (Number.isNaN(object[ind].total)) {
2754                object[ind].total = 0;
2755            }
2756            calculation.total += object[ind].total;
2757            if (calculation.cart) {
2758                let div = formsApp.getProductCartHTML(object[ind], name, ind, calculation);
2759                calculation.cart.append(div);
2760            }
2761            calculation.products[name][ind] = object[ind];
2762        }
2763    }
2764    calculation.total = formsApp.decimalAdjust('round', calculation.total, calculation.decimals * -1);
2765    formsApp.calculateCartTotal(calculation);
2766
2767}
2768
2769formsApp.calculateCartTotal = function(calculation){
2770    let decimals = calculation.decimals,
2771        price = formsApp.renderPrice(String(calculation.total), calculation.thousand, calculation.separator, decimals),
2772        total = calculation.total,
2773        tax = 0,
2774        discount = calculation.promo ? calculation.promo.discount * 1 : 0;
2775    if (calculation.subtotal) {
2776        calculation.subtotal.textContent = price;
2777    }
2778    if (calculation.promo && calculation.promo.unit == '%') {
2779        discount = total * discount / 100;
2780    }
2781    total -= discount;
2782    if (calculation.taxResult) {
2783        tax = total * calculation.tax / 100
2784        price = formsApp.renderPrice(String(tax), calculation.thousand, calculation.separator, decimals)
2785        calculation.taxResult.textContent = price;
2786    }
2787    total += tax;
2788    if (calculation.discount) {
2789        price = formsApp.renderPrice(String(discount), calculation.thousand, calculation.separator, decimals);
2790        calculation.discount.textContent = '-'+price;
2791    }
2792    if (calculation.shipping) {
2793        calculation.shipping.forEach(function(input){
2794            if (input.checked) {
2795                total += input.price * 1;
2796            }
2797        });
2798    }
2799    calculation.resultTotal = total;
2800    price = formsApp.renderPrice(String(total), calculation.thousand, calculation.separator, decimals)
2801    calculation.result.textContent = price;
2802    $f(calculation.input).trigger('change');
2803}
2804
2805formsApp.getProductCartHTML = function(product, name, ind, calculation){
2806    let div = document.createElement('div'),
2807        decimals = calculation.decimals,
2808        price = formsApp.renderPrice(String(product.total), calculation.thousand, calculation.separator, decimals),
2809        image = product.input.closest('.checkbox-image-wrapper');
2810    div.className = 'ba-form-product-row';
2811    div.dataset.name = name;
2812    div.dataset.ind = ind;
2813    div.innerHTML = '<div class="ba-form-product-title-cell">'+product.title+'</div>'+
2814        '<div class="ba-form-product-quantity-cell"><i class="ba-form-icons ba-icon-minus-circle '+
2815        (product.quantity == 1 ? 'ba-disabled' : '')+'" data-action="-"></i>'+
2816        '<input type="number" value="'+product.quantity+'" min="1" step="1">'+
2817        '<i class="ba-form-icons ba-icon-plus-circle" data-action="+"></i></div>'+
2818        '<div class="ba-form-product-total-cell"><div class="ba-form-calculation-price-wrapper">'+
2819        '<span class="field-price-currency">'+calculation.symbol+'</span><span class="field-price-value">'+price+
2820        '</span></div>'+
2821        '</div><div class="ba-form-product-remove-cell"><i class="ba-form-icons ba-icon-delete" tabindex="0"></i></div>';
2822    if (image) {
2823        let img = document.createElement('div');
2824        img.className = 'ba-form-product-image-cell';
2825        img.append(image.querySelector('img').cloneNode());
2826        div.insertBefore(img, div.querySelector('.ba-form-product-title-cell'));
2827    }
2828    let input = div.querySelector('.ba-form-product-quantity-cell input'),
2829        trash = div.querySelector('.ba-form-product-remove-cell i'),
2830        quantityBtns = div.querySelectorAll('.ba-form-product-quantity-cell i[data-action]');
2831    quantityBtns.forEach(function(element){
2832        element.addEventListener('click', function(){
2833            if (this.classList.contains('ba-disabled')) {
2834                return false;
2835            }
2836            if (this.dataset.action == '+') {
2837                input.value = input.value * 1 + 1;
2838            } else {
2839                input.value = input.value * 1 - 1;
2840            }
2841            let event = new Event('input');
2842            input.dispatchEvent(event);
2843        });
2844    });
2845    input.addEventListener('input', function(){
2846        if (this.value != '') {
2847            quantityBtns[0].classList[this.value == 1 ? 'add' : 'remove']('ba-disabled');
2848            let match = this.value.match(/\d+/),
2849                decimals = calculation.decimals;
2850            if (!match || match[0] == 0) {
2851                this.value = 1;
2852            } else if (match[0] != this.value) {
2853                this.value = match[0];
2854            }
2855            if (this.value * 1 > 0) {
2856                calculation.total -= product.total;
2857                product.quantity = this.value * 1;
2858                product.total = product.price * product.quantity;
2859                price = formsApp.renderPrice(String(product.total), calculation.thousand, calculation.separator, decimals);
2860                div.querySelector('.field-price-value').textContent = price;
2861                calculation.total += product.total;
2862                formsApp.calculateCartTotal(calculation);
2863            }
2864        }
2865    });
2866    $f(trash).on('click keyup', function(event){
2867        FormsAccessibilityHelper.processAction(event, () => {
2868            if (product.input.type == 'hidden') {
2869                calculation.total -= product.total;
2870                delete calculation.products[name][ind];
2871                div.remove();
2872                let decimals = calculation.decimals;
2873                price = formsApp.renderPrice(String(calculation.total), calculation.thousand, calculation.separator, decimals);
2874                calculation.result.textContent = price;
2875            } else {
2876                product.input[product.input.localName == 'option' ? 'selected' : 'checked'] = false;
2877                $f(product.input).trigger('change');
2878            }
2879        });
2880    });
2881
2882    return div;
2883}
2884
2885formsApp.decimalAdjust = function(type, value, exp){
2886    if (typeof exp === 'undefined' || +exp === 0) {
2887        return Math[type](value);
2888    }
2889    value = +value;
2890    exp = +exp;
2891    if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
2892        return NaN;
2893    }
2894    value = value.toString().split('e');
2895    value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
2896    value = value.toString().split('e');
2897
2898    return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
2899}
2900
2901formsApp.calculation = function($this, fromCart){
2902    formsApp.calculated = {};
2903    for (let i in $this.calculation) {
2904        if (fromCart && $this.calculation[i].product) {
2905            continue;
2906        }
2907        let calc = $this.calculation[i],
2908            total = formsApp.calculate(calc.formula, $this),
2909            price = String(total);
2910        total = formsApp.decimalAdjust('round', total, calc.decimals * -1);
2911        price = String(total);
2912        calc.input.price = total;
2913        formsApp.calculated[i] = total;
2914        if (total != calc.input.value * 1) {
2915            price = formsApp.renderPrice(price, calc.thousand, calc.separator, calc.decimals);
2916            calc.input.value = total;
2917            calc.result.textContent = price;
2918            $f(calc.input).trigger('change');
2919        }
2920    }
2921}
2922
2923formsApp.calculate = function(formula, $this){
2924    if (formula == '') {
2925        return 0;
2926    }
2927    formula = formula.replace(/\s/g, '').replace(/elseif/g, 'else if');
2928    let matches = formula.match(/\[FieldID=\d+\]/g),
2929        total = 0;
2930    if (matches) {
2931        for (let i = 0; i < matches.length; i++) {
2932            let match = matches[i].match(/\d+/g),
2933                price = $this.fields['baform-'+match[0]] ? $this.fields['baform-'+match[0]].price : 0;
2934            if (!$this.items['baform-'+match[0]] || $this.items['baform-'+match[0]].closest('.hidden-condition-field')) {
2935                price = 0;
2936            } else  if ('baform-'+match[0] in formsApp.calculated) {
2937                price = formsApp.calculated['baform-'+match[0]];
2938            } else if ($this.calculation['baform-'+match[0]]) {
2939                price = formsApp.calculate($this.calculation['baform-'+match[0]].formula, $this)
2940                formsApp.calculated['baform-'+match[0]] = price;
2941            }
2942            formula = formula.replace(matches[i], price);
2943        }
2944    }
2945    const method = 'ZXZhbA==';
2946    try {
2947        total = window[window.atob(method)](formula);
2948    } catch (error) {
2949        console.error(error)
2950    }
2951    if (total == -Infinity || total == Infinity || Number.isNaN(total)) {
2952        total = 0;
2953    }
2954
2955    return total;
2956}
2957
2958formsApp.updateFieldsDefaultValues = function($this, defaultValues){
2959    let object = {};
2960    for (let ind in defaultValues) {
2961        object[ind] = '';
2962        object[ind] = formsApp.updateFieldsDefaultValue($this, defaultValues, defaultValues[ind].value, object);
2963        defaultValues[ind].item.value = object[ind];
2964    }
2965}
2966
2967formsApp.updateFieldsDefaultValue = function($this, defaultValues, value, object){
2968    let matches = value.match(/\[Field ID=\d+\]/g),
2969        title = $this.querySelector('input[name="page-title"]').value,
2970        url = $this.querySelector('input[name="page-url"]').value;
2971    if (matches) {
2972        for (let i = 0; i < matches.length; i++) {
2973            let match = matches[i].match(/\d+/g),
2974                key = 'baform-'+match[0],
2975                val = $this.fields[key].value;
2976            if (key in object) {
2977                val = object[match[0]];
2978            } else if (key in defaultValues) {
2979                object[key] = '';
2980                val = formsApp.updateFieldsDefaultValue($this, defaultValues, defaultValues[key].value, object);
2981                object[key] = val;
2982            }
2983        }
2984    }
2985    value = value.replace(/\[Page Title\]/g, title);
2986    value = value.replace(/\[Page URL\]/g, url);
2987
2988    return value;
2989}
2990
2991formsApp.updateFieldsValues = function(fields, $this, id){
2992    if ($this.type == 'radio' && $this.checked) {
2993        fields[id] = {
2994            value: $this.value,
2995            price: $this.price * 1
2996        }
2997    } else if ($this.type == 'checkbox' && $this.checked) {
2998        fields[id].value += fields[id].value != '' ? ' '+$this.value : $this.value;
2999        fields[id].price += $this.price * 1;
3000    } else if ($this.localName == 'select') {
3001        $f($this).find('option').each(function(){
3002            if (this.selected) {
3003                fields[id].value += fields[id].value != '' ? ' '+this.value : this.value;
3004                fields[id].price += this.price * 1;
3005            }
3006        });
3007    } else if ($this.dataset.calendar) {
3008        fields[id].value = $this.value;
3009        fields[id].price = $this.price * 1;
3010    } else if ($this.type == 'hidden' || $this.type == 'text' || $this.type == 'number'
3011        || $this.localName == 'textarea' || $this.type == 'email') {
3012        fields[id].value = $this.value;
3013        fields[id].price = $this.value.replace(/,/g, '.') * 1;
3014    } else if ($this.type == 'file') {
3015        fields[id].value = '';
3016        fields[id].price = $this.uploads.count;
3017        $this.closest('.ba-form-upload-field').classList[$this.uploads.count > 0 ? 'add' : 'remove']('has-uploaded-file');
3018    }
3019    if (String(fields[id].price) == 'NaN') {
3020        fields[id].price = 0;
3021    }
3022}
3023
3024formsApp.checkConditionLogic = function($this){
3025    $this.pages = {};
3026    $this.querySelectorAll('.hidden-condition-field').forEach((div) => {
3027        div.classList.remove('hidden-condition-field');
3028    });
3029    conditionLogic[$this.formId].forEach(function(el){
3030        if (!el.publish) {
3031            return true;
3032        }
3033        let flags = [],
3034            page = null,
3035            value;
3036        el.when.forEach(function(obj){
3037            if (!obj.field || !obj.state) {
3038                return true;
3039            }
3040            if (!$this.fields['baform-'+obj.field]) {
3041                return true;
3042            }
3043            page = $this.items['baform-'+obj.field].closest('.ba-form-page');
3044            if ($this.items['baform-'+obj.field].classList.contains('hidden-condition-field')
3045                || $this.items['baform-'+obj.field].closest('.hidden-condition-field')) {
3046                flags.push(false);
3047                return true;
3048            }
3049            value = $this.fields['baform-'+obj.field].value;
3050            if ($this.items['baform-'+obj.field].dataset.type == 'checkbox'
3051                || $this.items['baform-'+obj.field].dataset.type == 'selectMultiple') {
3052                value = [];
3053                $this.items['baform-'+obj.field].querySelectorAll('input[name], option').forEach(function(input){
3054                    input.checked || input.selected ? value.push(input.value) : '';
3055                });
3056            }
3057            switch (obj.state) {
3058                case 'equal':
3059                    if (typeof value == 'string') {
3060                        flags.push(value == obj.value);
3061                    } else {
3062                        flags.push(value.indexOf(obj.value) != -1);
3063                    }
3064                    break;
3065                case 'not-equal':
3066                    if (typeof value == 'string') {
3067                        flags.push(value != obj.value);
3068                    } else {
3069                        flags.push(value.indexOf(obj.value) == -1);
3070                    }
3071                    break;
3072                case 'not-empty':
3073                    if (typeof value == 'string') {
3074                        flags.push(value != '');
3075                    } else {
3076                        flags.push(value.length != 0);
3077                    }
3078                    break;
3079                case 'empty':
3080                    if (typeof value == 'string') {
3081                        flags.push(value == '');
3082                    } else {
3083                        flags.push(value.length == 0);
3084                    }
3085                    break;
3086                case 'greater':
3087                    flags.push((!isNaN(value) ? value * 1 : value) > (!isNaN(obj.value) ? obj.value * 1 : obj.value));
3088                    break;
3089                case 'less':
3090                    flags.push((!isNaN(value) ? value * 1 : value) < (!isNaN(obj.value) ? obj.value * 1 : obj.value));
3091                    break;
3092                case 'contain':
3093                    flags.push(value.indexOf(obj.value) != -1);
3094                    break;
3095                case 'not-contain':
3096                    flags.push(value.indexOf(obj.value) == -1);
3097                    break;
3098            }
3099        });
3100        if (!flags.length) {
3101            return true;
3102        }
3103        let method = el.operation == 'OR' ? 'some' : 'every',
3104            flag = flags[method](function(state){return state});
3105        el.do.forEach(function(obj){
3106            if (obj.field && obj.action) {
3107                let item = $this.items['baform-'+obj.field]
3108                    || $this.form[0].querySelector('.ba-form-page[data-page-key="'+obj.field+'"]');
3109                if (!item) {
3110                    return true;
3111                }
3112                if (obj.action == 'show' || obj.action == 'hide') {
3113                    let method = (obj.action == 'show' && flag) || (obj.action == 'hide' && !flag) ? 'remove' : 'add';
3114                    item.classList[method]('hidden-condition-field');
3115                    item.querySelectorAll('input[name], select[name], textarea[name]').forEach(function(itm){
3116                        if (itm.product) {
3117                            formsApp.productChange($this, itm.name);
3118                        }
3119                    });
3120                    if (item.classList.contains('ba-form-submit-field') && method == 'remove') {
3121                        CaptchaHelper.createIfExist(item.querySelector('.ba-form-submit-btn'));
3122                    }
3123                } else if (obj.action == 'move' && flag) {
3124                    $this.pages[page.dataset.pageKey] = {
3125                        prev: page,
3126                        next: item
3127                    }
3128                    let hidden = [];
3129                    $f(page).nextAll('.ba-form-page').each(function(){
3130                        if (this == item) {
3131                            this.classList.remove('hidden-condition-field');
3132                            return false;
3133                        }
3134                        this.classList.add('hidden-condition-field');
3135                        hidden.push(this.dataset.pageKey);
3136                    });
3137                    $this.pages[page.dataset.pageKey].hidden = hidden;
3138                }
3139            }
3140        });
3141        $f(window).trigger('scroll');
3142    });
3143}
3144
3145formsApp.strrev = function(string){
3146    var ret = '', i = 0;
3147    for (i = string.length - 1; i >= 0; i--) {
3148        ret += string[i];
3149    }
3150
3151    return ret;
3152}
3153
3154formsApp.renderPrice = function(value, thousand, separator, decimals){
3155    let delta = value < 0 ? '-' : '',
3156        priceArray = value.replace('-', '').trim().split('.'),
3157        priceThousand = priceArray[0],
3158        priceDecimal = priceArray[1] ? priceArray[1] : '',
3159        price = '';
3160    if (priceThousand.length > 3 && thousand != '') {
3161        for (let i = 0; i < priceThousand.length; i++) {
3162            if (i % 3 == 0 && i != 0) {
3163                price += thousand;
3164            }
3165            price += priceThousand[priceThousand.length - 1 - i];
3166        }
3167        price = formsApp.strrev(price);
3168    } else {
3169        price += priceThousand;
3170    }
3171    if (decimals != 0) {
3172        price += separator;
3173        for (let i = 0; i < decimals; i++) {
3174            price += priceDecimal[i] ? priceDecimal[i] : '0';
3175        }
3176    }
3177
3178    return delta + price;
3179}
3180
3181function formsHCaptchaOnload() {
3182    CaptchaFactory.get('hcaptcha').setCaptcha(hcaptcha);
3183}
3184
3185function formsTurnstileOnload() {
3186    CaptchaFactory.get('turnstile').setCaptcha(turnstile);
3187}
3188
3189class FormsCaptcha {
3190    static captcha = null;
3191    static loaded = false;
3192    static type = '';
3193    static src = '';
3194    static settings = {
3195        site_key: '',
3196        secret_key: ''
3197    }
3198    static data = {};
3199
3200    static setCaptcha(captcha) {
3201        this.captcha = captcha;
3202        this.loaded = true;
3203        this.init();
3204    }
3205
3206    static loadSettings() {
3207        formsApp.fetch.request(JUri + 'index.php?option=com_baforms&task=form.loadCaptchaSettings', {
3208            type: this.type
3209        }).then(text => {
3210            const settings = JSON.parse(text);
3211            this.setSettings(settings);
3212            this.createScript();
3213            document.head.append(this.script);
3214        });
3215    }
3216
3217    static setSettings(settings) {
3218        this.settings = settings;
3219    }
3220
3221    static check(id) {
3222        return app.items[id] && app.items[id].recaptcha == this.type;
3223    }
3224
3225    static createScript() {
3226        this.script = document.createElement('script');
3227        this.script.src = this.src;
3228    }
3229
3230    static load() {
3231        if (this.loaded) {
3232            this.init();
3233        } else if (!this.loaded && !this.loading) {
3234            this.loading = true;
3235            this.loadSettings();
3236        }
3237    }
3238
3239    static init() {
3240        document.querySelectorAll(`.ba-form-submit-btn[data-captcha="${this.type}"]`).forEach(btn => {
3241            this.create(btn);
3242        });
3243    }
3244
3245    static create(btn) {
3246        if (!this.loaded) {
3247            return;
3248        }
3249
3250        const element = btn.closest('.ba-form-submit-field');
3251        if (element.offsetHeight == 0 || element.rendered) {
3252            return;
3253        }
3254
3255        this.set(element);
3256    }
3257
3258
3259    static set(element) {
3260        const div = document.createElement('div');
3261        const parent = element.querySelector('.ba-form-submit-recaptcha-wrapper');
3262        div.id = `forms-recaptcha-${Date.now()}`;
3263        div.className = 'forms-recaptcha';
3264        div.dataset.captcha = this.type;
3265        parent.append(div);
3266        this.render(div);
3267        element.rendered = true;
3268    }
3269
3270    static render(div) {
3271
3272    }
3273
3274    static get(key) {
3275        return this.settings[key];
3276    }
3277
3278    static async getResponse(id, element) {
3279        return new Promise(resolve => {
3280            resolve('');
3281        })
3282    }
3283}
3284
3285class FormsTurnstileCaptcha extends FormsCaptcha {
3286    static src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=formsTurnstileOnload';
3287    static type = 'turnstile';
3288
3289    static async getResponse(id, element) {
3290        return new Promise(resolve => {
3291            resolve(!this.captcha.isExpired(id) ? this.captcha.getResponse(id) : '');
3292        })
3293    }
3294
3295    static render(div) {
3296        FormsCaptcha.data[div.id] = this.captcha.render(div, {
3297            sitekey: this.settings.site_key,
3298            theme: this.settings.theme,
3299            size: this.settings.size,
3300            callback: CaptchaHelper.clearAlerts
3301        });
3302    }
3303
3304    static reset(id) {
3305        const widgetId = FormsCaptcha.data[id];
3306        this.captcha.reset(widgetId);
3307    }
3308}
3309
3310class FormsHCaptcha extends FormsCaptcha {
3311    static src = 'https://js.hcaptcha.com/1/api.js?onload=formsHCaptchaOnload';
3312    static type = 'hcaptcha';
3313
3314    static async getResponse(id, element) {
3315        if (!this.settings.invisible) {
3316            return await this.captcha.getResponse(id);
3317        }
3318
3319        const token = await this.captcha.getResponse();
3320        if (!token) {
3321            await this.captcha.execute(id);
3322        }
3323
3324        return await this.captcha.getResponse();
3325    }
3326
3327    static render(div) {
3328        const size = this.settings.invisible ? 'invisible' : 'normal'
3329        div.dataset.size = size;
3330        FormsCaptcha.data[div.id] = this.captcha.render(div, {
3331            sitekey: this.settings.site_key,
3332            theme: this.settings.theme,
3333            size: size,
3334            callback: CaptchaHelper.clearAlerts
3335        });
3336    }
3337}
3338
3339class FormsGoogleRecaptcha extends FormsCaptcha {
3340    static src = 'https://www.google.com/recaptcha/api.js?render=';
3341    static type = 'grecaptcha';
3342
3343    static createScript() {
3344        super.createScript();
3345        this.script.src = this.src + this.settings.site_key;
3346        this.script.onload = () => {
3347            grecaptcha.ready(function () {
3348                CaptchaFactory.get('grecaptcha').setCaptcha(grecaptcha);
3349            });
3350        }
3351    }
3352
3353    static async getResponse(id, element) {
3354        return new Promise(resolve => {
3355            this.execute().then(token => {
3356                element.innerHTML = `<input type="hidden" name="g-recaptcha-response" value="${token}">`;
3357                resolve(token);
3358            });
3359        })
3360    }
3361
3362    static render(div) {
3363        this.execute()
3364    }
3365
3366    static async execute() {
3367        return await this.captcha.execute(this.settings.site_key, {
3368            action: 'submit'
3369        })
3370    }
3371}
3372
3373class CaptchaFactory {
3374    /** @type {Object<string, typeof FormsCaptcha>} */
3375    static map = {
3376        hcaptcha: FormsHCaptcha,
3377        turnstile: FormsTurnstileCaptcha,
3378        grecaptcha: FormsGoogleRecaptcha
3379    }
3380
3381    /** @param {string} key */
3382    static has(key) {
3383        return key in this.map;
3384    }
3385
3386    /**
3387     * @param {string} key
3388     * @returns {typeof FormsCaptcha}
3389     */
3390    static get(key) {
3391        return this.map[key];
3392    }
3393}
3394
3395class CaptchaHelper {
3396    static getIfExist(element) {
3397        const captcha = element.dataset.captcha;
3398        if (!captcha || !CaptchaFactory.has(captcha)) {
3399            return null;
3400        }
3401
3402        return CaptchaFactory.get(captcha)
3403    }
3404
3405    static loadIfExist() {
3406        document.querySelectorAll('.ba-form-submit-btn').forEach(btn => {
3407            const captcha = this.getIfExist(btn);
3408            if (captcha) {
3409                captcha.load();
3410            }
3411        });
3412    }
3413
3414    static createIfExist(btn) {
3415        const captcha = this.getIfExist(btn);
3416        if (captcha) {
3417            captcha.create(btn);
3418        }
3419    }
3420
3421    static async isValidResponse(element) {
3422        if (!CaptchaFactory.has(element.dataset.captcha)) {
3423            return;
3424        }
3425
3426        const widgetId = FormsCaptcha.data[element.id];
3427        const captcha = CaptchaFactory.get(element.dataset.captcha);
3428        const response = await captcha.getResponse(widgetId, element);
3429        const isValid = response != '';
3430        formsApp.toggleAlertTooltip(!isValid, element, element, 'THIS_FIELD_REQUIRED');
3431
3432        return isValid;
3433    }
3434
3435    static async checkResponse(btn) {
3436        let isValid = true;
3437        const recaptcha = btn.closest('.ba-form-submit-wrapper').querySelector('.forms-recaptcha');
3438        if (recaptcha) {
3439            isValid = await CaptchaHelper.isValidResponse(recaptcha);
3440        }
3441
3442        return isValid;
3443    }
3444
3445    static clearAlerts() {
3446        $f('.forms-recaptcha').each(function () {
3447            formsApp.removeAlertTooltip(this);
3448        });
3449    }
3450}
3451
3452formsApp.executeInputMask = function(input, mask){
3453    let n = input.beforeData.start + Math.abs(input.beforeData.value.length - input.value.length),
3454        pos = mask.indexOf('_'),
3455        text = [],
3456        delData = [],
3457        start = null,
3458        updateStart = null,
3459        value = mask;
3460    if (input.value.length > input.beforeData.value.length || input.value.length == input.beforeData.value.length) {
3461        let i = 0;
3462        for (i; i < input.beforeData.start; i++) {
3463            if (mask[i] == '_' && /\d/.test(input.beforeData.value[i])) {
3464                text.push(input.beforeData.value[i]);
3465            }
3466        }
3467        i = input.beforeData.start;
3468        n = input.value.length > input.beforeData.value.length ? n : input.beforeData.end;
3469        for (i; i < n; i++) {
3470            if (/\d/.test(input.value[i])) {
3471                text.push(input.value[i]);
3472            }
3473        }
3474        start = pos;
3475        for (i = 0; i < text.length; i++) {
3476            start = mask.indexOf('_', start + 1);
3477            if (start == -1) {
3478                start = mask.length;
3479            }
3480        }
3481        for (i = input.beforeData.end; i < input.beforeData.value.length; i++) {
3482            if (mask[i] == '_' && /\d/.test(input.beforeData.value[i])) {
3483                text.push(input.beforeData.value[i]);
3484            }
3485        }
3486    } else if (input.beforeData.start == 0 && input.beforeData.end == mask.length) {
3487        for (let i = 0; i < input.value.length; i++) {
3488            if (/\d/.test(input.value[i])) {
3489                text.push(input.value[i]);
3490            }
3491        }
3492        updateStart = true;
3493    } else if (input.beforeData.start < input.selectionStart) {
3494        let i = 0;
3495        for (i; i < input.selectionStart; i++) {
3496            if (mask[i] == '_' && /\d/.test(input.value[i])) {
3497                text.push(input.value[i]);
3498            }
3499        }
3500        start = i;
3501        for (let j = input.beforeData.end; j < input.beforeData.value.length; j++) {
3502            if (mask[j] == '_' && /\d/.test(input.beforeData.value[j])) {
3503                text.push(input.beforeData.value[j]);
3504            }
3505        }
3506    } else {
3507        let i = input.selectionStart < input.beforeData.start ? input.selectionStart : input.beforeData.start;
3508        n = input.selectionStart < input.beforeData.start ? input.beforeData.start : n;
3509        for (i; i < n; i++) {
3510            delData.push(i);
3511        }
3512    }
3513    if (delData.length == 1 && mask[delData[0]] != '_' && input.selectionStart < input.beforeData.start) {
3514        for (let i = input.selectionStart; i >= 0; i--) {
3515            if (mask[i] == '_') {
3516                delData[0] = i;
3517                break;
3518            }
3519        }
3520    } else if (delData.length == 1 && mask[delData[0]] != '_') {
3521        for (let i = input.selectionStart; i < mask.length; i++) {
3522            if (mask[i] == '_') {
3523                delData[0] = i;
3524                break;
3525            }
3526        }
3527    }
3528    if (delData.length) {
3529        start = delData[0];
3530        for (let i = 0; i < input.beforeData.value.length; i++) {
3531            if (delData.indexOf(i) == -1 && mask[i] == '_') {
3532                text.push(input.beforeData.value[i]);
3533            }
3534        }
3535    }
3536    input.value = value;
3537    text.forEach(function(el){
3538        if (pos != -1) {
3539            input.setSelectionRange(pos, ++pos);
3540            input.setRangeText(el);
3541            if (updateStart) {
3542                start = pos;
3543            }
3544        }
3545        pos = input.value.indexOf('_');
3546    });
3547    if (start !== null) {
3548        input.setSelectionRange(start, start);
3549    }
3550}
3551
3552formsApp.prepareInputMask = function(input, mask, event){
3553    let value = input.value ? input.value : mask;
3554    if (event) {
3555        input.beforeData = {
3556            start: input.selectionStart,
3557            end: input.selectionEnd,
3558            value: value
3559        };
3560    } else {
3561        input.beforeData = {
3562            start: 0,
3563            end: 0,
3564            value: value
3565        };
3566        input.value = value;
3567    }
3568}
3569
3570formsApp.executeMask = function(input, mask){
3571    let i = 0,
3572        flag = true,
3573        value = '';
3574    for (i = 0; i < input.value.length; i++) {
3575        flag = input.value[i] == mask[i] || mask[i] == '#' && /\d/.test(input.value[i])
3576        if (flag) {
3577            value += input.value[i];
3578        } else {
3579            break;
3580        }
3581    }
3582    if (!flag) {
3583        let subValue = input.value.substr(i),
3584            match = subValue.match(/\d+/),
3585            ind = 0;
3586        for (i; i < mask.length; i++) {
3587            if (mask[i] != '#') {
3588                value += mask[i];
3589            } else if (match && match[0][ind]) {
3590                value += match[0][ind++];
3591            } else {
3592                break;
3593            }
3594        }
3595        input.value = value;
3596    }
3597}
3598
3599formsApp.sortPoll = function(field){
3600    let array = Array.apply(null, field.querySelectorAll('.ba-form-checkbox-wrapper')),
3601        last = null,
3602        count = field.style.getPropertyValue('--checkbox-field-count') * 1,
3603        index = 1;
3604    array.sort(function(a, b){
3605        if (a.style.getPropertyValue('--poll-order') * 1 > b.style.getPropertyValue('--poll-order') * 1) {
3606            return 1;
3607        } else if (a.style.getPropertyValue('--poll-order') * 1 < b.style.getPropertyValue('--poll-order') * 1) {
3608            return -1;
3609        } else {
3610            return 0;
3611        }
3612    });
3613    array.forEach(function(div){
3614        if (index == count) {
3615            div.classList.add('last-row-checkbox-wrapper');
3616            index = 0;
3617        } else {
3618            div.classList.remove('last-row-checkbox-wrapper');
3619        }
3620        index++;
3621    });
3622    last = array.pop();
3623    last ? last.classList.add('last-row-checkbox-wrapper') : '';
3624}
3625
3626formsApp.refreshToken = async function(){
3627    const endPoint = `${JUri}index.php?option=com_baforms&task=form.getFormToken`;
3628    const token = await formsApp.fetch.request(endPoint);
3629    this.token = token.trim();
3630}
3631
3632document.addEventListener('DOMContentLoaded', formsApp.createForms);
3633if (document.readyState == 'complete') {
3634    formsApp.createForms();
3635}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.