PageSourceSearch

https://geolonsoft.by/components/com_baforms/assets/js/ba-form.js?2.4.2

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

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.