PageSourceSearch

https://worldallergy.org/components/com_baforms/assets/js/ba-form.js?2.4.0

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

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.