PageSourceSearch

https://www.centralmedica.es/components/com_baforms/assets/js/ba-form.js?2.4.3.2

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

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.