PageSourceSearch

https://giving.stmargarets.qld.edu.au/assets/cache/stmargarets/2ad21a96719b563ea9c87faf8741ad5e.js

js stmargarets.qld.edu.au collected 2026-09-26 07:18:36 UTC 126,567 bytes, 3,090 lines download raw bytes

1/**
2 * Created with JetBrains PhpStorm.
3 * User: dave
4 * Date: 27/03/18
5 * Time: 12:13
6 * To change this template use File | Settings | File Templates.
7 */
8
9/*
10 |--------------------------------------------------------------------------
11 | GLOBAL "home-made" jQuery objects with parameters and functions
12 |--------------------------------------------------------------------------
13 */
14
15var AJAX;       // for general AJAX call stuffs, see part below at $.object_ajax
16var COMMON;       // for general useful functions
17var FORM;       // for general form control stuffs, see part below at $.object_form
18var FORM_CHECK; // for general input field checking stuffs, see part below at $.object_form_check
19
20 /*
21  |--------------------------------------------------------------------------
22  | Global varaiables
23  |--------------------------------------------------------------------------
24  */
25
26var ttGlobals = new Object();
27ttGlobals.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
28
29
30/*
31 |--------------------------------------------------------------------------
32 | DOCUMENT READY
33 |--------------------------------------------------------------------------
34 */
35 window.addEventListener('load',function(){
36    initGlobals();
37    initNavBar();
38    initUIFeatures();
39    initFooter();
40    COMMON.setPageUrl();
41	COMMON.loadJsFile(BASE_URI + 'addons/shared_addons/modules/front_end/js/helpers/_common.js');
42});
43
44
45/*
46 |--------------------------------------------------------------------------
47 | Set any global variables
48 |--------------------------------------------------------------------------
49 */
50
51function initGlobals() {
52
53    //Deetct if we're on mobile
54    if( /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) ) {
55        ttGlobals.isMobile=true;
56    } else {
57        ttGlobals.isMobile=false;
58    }
59
60    // For caching hover box content
61    ttGlobals.hoverBoxCache = {};
62}
63
64/*
65 |--------------------------------------------------------------------------
66 | Nav Bar
67 |--------------------------------------------------------------------------
68 */
69function initNavBar() {
70    var $menuBar = $('#menu-bar');
71    var $logoSection = $('#logo-area');
72    var $menuBarOverlay = $('.menu_bar_color_overlay_element');
73    var $logoBar = $('#logo-area');
74    var $logoBarOverlay = $('.logo_bar_color_overlay_element');
75    var isFullLogoBarVisible = ($(window).scrollTop() === 0);
76    var customisationSectionParams = [];
77    var countOfSections = 0;
78    var doNotApplyMenuBarTransparencyOnThePage = false;
79
80    if ( $('input.do-not-apply-menu-bar-transparency-on-the-page').length > 0 && $('input.do-not-apply-menu-bar-transparency-on-the-page').eq(0).val() == 1 ) {
81        doNotApplyMenuBarTransparencyOnThePage = true;
82    }
83
84    //Collection top values (in pixel) of customisation sections
85    $('.customisation-section').each(function() {
86        var $this = $(this);
87        if ($this && $this.attr('id')) {
88            customisationSectionParams.push({
89                'top' : (countOfSections == 0) ? 0 : $this.offset().top,
90                'id' : $this.attr('id'),
91                'isMenuBarTransparent' : $this.hasClass('do-not-make-menubar-transparent') ? false : true,
92            });
93           countOfSections++;
94        }
95    });
96
97    //show or hide logo in function bar on scroll
98    $(window).on('scroll', function (event) {
99        var scrollValue = $(window).scrollTop();
100
101        // Add some effects to the menu bar when user starts scrolling
102        if (scrollValue === 0) {
103            isFullLogoBarVisible = true;
104            $menuBar.removeClass('menu_bar_on_scroll');
105        } else {
106            isFullLogoBarVisible = false;
107            $menuBar.addClass('menu_bar_on_scroll');
108        }
109
110        // If menu open then the menu bar can not be transparent
111        if ( $('#mobile-menu').hasClass('show') ) {
112            setLogoBarTransparency(false, $logoBar);
113            setMenuBarTransparency(false, $menuBar, $menuBarOverlay);
114        } else {
115            setLogoBarTransparency(true, $logoBar);
116            setMenuBarTransparency(true, $menuBar, $menuBarOverlay);
117        }
118    });
119
120    var onTopOfThePageChangeCallbacks = [];
121    var onPageScrollCallbacks = [];
122    // Menu bar compression on scroll
123    // TODO: There's no way this couldn't be done simpler
124    if (THEME_SETTINGS.menu_bar_compression == 1  && $menuBar.hasClass('menu_bar_style_in_line')) {
125        (function() {
126            var $customLogoElement = $('.tt_custom_logo_1_element', $menuBar);
127            if ($customLogoElement.length === 0) {
128                return;
129            }
130            var $customLogo = $('.tt_custom_logo_1', $customLogoElement);
131
132            var maxLogoHeightInPixels = null;
133            var customLogoMaxHeightCSSValue = $customLogo.css('max-height');
134            if (customLogoMaxHeightCSSValue) {
135                var matches = customLogoMaxHeightCSSValue.match(/^(\d+)px$/);
136                if (matches && matches[1]) {
137                    maxLogoHeightInPixels = matches[1];
138                }
139            }
140
141            if ($customLogo[0].complete) {
142                menuBarCompressionOnScroll();
143            } else {
144                $customLogo.on('load', function() {
145                    menuBarCompressionOnScroll();
146                });
147            }
148
149            function menuBarCompressionOnScroll() {
150                var customLogoNaturalHeight = $customLogo[0].naturalHeight;
151                var customLogoNaturalWidth = $customLogo[0].naturalWidth;
152
153                if ($customLogo.attr('src').split('.').pop().toLowerCase() === 'svg') {
154                    if (customLogoNaturalWidth === 0 || customLogoNaturalHeight === 0) {
155                        var rect = $customLogo[0].getBoundingClientRect();
156                        customLogoNaturalHeight = rect.height;
157                        customLogoNaturalWidth = rect.width;
158                        if (customLogoNaturalWidth === 0 || customLogoNaturalHeight === 0) {
159                            return false;
160                        }
161                    }
162
163                    if (customLogoNaturalHeight > 150) {
164                        customLogoNaturalHeight = 150;
165                    }
166
167                    if (customLogoNaturalWidth > 150) {
168                        customLogoNaturalWidth = 150;
169                    }
170                }
171
172                var lastScrollValue;
173                var lastCustomLogoContainerWidth;
174                function fixCustomLogoDimensions() {
175                    if ( ! $customLogo.is(':visible')) {
176                        return;
177                    }
178
179                    var scrollValue = window.scrollY === 0 ? 0 : 1;
180                    var customLogoContainerWidth = $customLogoContainer.width();
181
182                    // check if something changed to reduce calcualtions
183                    if (lastScrollValue === scrollValue && lastCustomLogoContainerWidth === customLogoContainerWidth) {
184                        return;
185                    }
186                    lastScrollValue = scrollValue;
187                    lastCustomLogoContainerWidth = customLogoContainerWidth;
188
189                    var calculatedWidth;
190
191                    if (scrollValue === 0) {
192                        calculatedWidth = customLogoContainerWidth;
193                        setTimeout(function() {
194                            $customLogoContainer.addClass('text-center');
195                        }, 500);
196                    } else {
197                        calculatedWidth = compressedHeight * customLogoRatio;
198                        if (calculatedWidth > customLogoContainerWidth) {
199                            calculatedWidth = customLogoContainerWidth;
200                        }
201
202                        $customLogoContainer.removeClass('text-center');
203                    }
204
205                    $customLogoElement.width(calculatedWidth);
206                    if ( (calculatedWidth / customLogoRatio) > 200 ) {
207                        $customLogo[0].style.setProperty('max-height', (calculatedWidth / customLogoRatio) + 'px');
208                    }
209                }
210
211                // Fixed compressed height in px
212                var compressedHeight = 50;
213                if (customLogoNaturalHeight && customLogoNaturalHeight < compressedHeight) {
214                    compressedHeight = customLogoNaturalHeight;
215                }
216
217                var customLogoRatio = customLogoNaturalWidth / customLogoNaturalHeight;
218                var $customLogoContainer = $customLogoElement.closest('.custom-logo-container');
219
220                fixCustomLogoDimensions();
221
222                $(window).on('resize scroll', function() {
223                    fixCustomLogoDimensions();
224                });
225            }
226        })();
227    }
228
229    // If menu bar is transparent, stretch welcome style custom panel on top of the page
230    if (THEME_SETTINGS.logo_bar_transparent_at_top_of_page == 1 || THEME_SETTINGS.menu_bar_transparent_at_top_of_page == 1) {
231		var isAllowedToSetMenubarTransparency = false;
232
233        (function() {
234			const resizeObserver = new ResizeObserver(() => {
235				//console.log('size changed');
236				recalculateDimensions();
237			});
238
239            // check if first section is stretchable
240            var $firstSection = $('.customisation-section').first();
241            if ( ! $firstSection.hasClass('stretch-when-transparent-header')) {
242                return;
243            }
244
245            // Check if it has background container (of course it should have)
246            var $bgContainer = $firstSection.find('.tt-section-background-container');
247            if ($bgContainer.length === 0) {
248                return;
249            }
250
251			var $section = $bgContainer.closest('.customisation-section');
252
253			resizeObserver.observe($section[0]);
254
255			//Adding fictional values - which surely enough - for height and margin top property in case of first overlay - not calculated properly
256			$section.find('.tt-section-overlay')[0].style.setProperty('height', '10000px', 'important');
257			$section.find('.tt-section-overlay')[0].style.setProperty('margin-top', '-3000px');
258
259            // If we need to take logo area height into consideration
260            var addLogoHeight = false;
261            if (THEME_SETTINGS.logo_bar_transparent_at_top_of_page == 1) {
262                if ($logoBar.length > 0) {
263                    $logoBarOverlay.addClass('transparent-on-top');
264                    addLogoHeight = true;
265                }
266            }
267
268            if (THEME_SETTINGS.menu_bar_transparent_at_top_of_page == 1) {
269                $menuBarOverlay.addClass('transparent-on-top');
270            }
271
272            var $customLogoContainer = $logoSection.find('.custom-logo-container');
273            var $customLogo = $customLogoContainer.find('.custom-logo-container-stacked');
274            // Precalculate certain values
275            if (THEME_SETTINGS.menu_bar_style === 'in_line' && THEME_SETTINGS.menu_bar_compression == 1) {
276                var customLogoNaturalHeight = $customLogo.height();
277                var customLogoNaturalWidth = $customLogo.width();
278                var customLogoRatio = customLogoNaturalWidth / customLogoNaturalHeight;
279            }
280
281            // Recalculate dimensions of the background container so it stretches up to the top of the viewport
282            function recalculateDimensions() {
283                if (THEME_SETTINGS.menu_bar_transparency_on_scroll == 0 && THEME_SETTINGS.menu_bar_transparent_at_top_of_page == 0) {
284                    return;
285                }
286
287                var outerHeight = $firstSection.outerHeight();
288                var maxMenuHeight = $menuBar.outerHeight();
289                var additionalHeight = 0;
290
291                // We need to calculate max possible menu height when 'menu_bar_compression' is on.
292                // This will happen when scroll is on top, page is not necessarely at the top at the moment
293                // so we need to calculate it.
294                // See fixCustomLogoDimensions()
295                if (THEME_SETTINGS.menu_bar_style === 'in_line' && THEME_SETTINGS.menu_bar_compression && $customLogo.is(':visible')) {
296                    var customLogoContainerWidth = $customLogoContainer.width();
297                    var maxLogoHeight = customLogoContainerWidth / customLogoRatio;
298
299                    // Max height = image height + (vertical white spaces like padding etc.)
300                    var totalMaxHeight = maxLogoHeight + ($customLogoContainer.outerHeight() - $customLogoContainer.height());
301
302                    if (totalMaxHeight > maxMenuHeight) {
303                        maxMenuHeight = totalMaxHeight;
304                    }
305                }
306
307                var additionalHeight = maxMenuHeight;
308                if (addLogoHeight) {
309                    additionalHeight += $logoBar.outerHeight();
310                }
311
312                var newHeight = $section.outerHeight() + additionalHeight + 100;
313                $bgContainer[0].style.setProperty('height', newHeight + 'px', 'important');
314				$section.find('.set-same-height-of-tt-section').each(function() {
315                    $(this)[0].style.setProperty('height', newHeight + 'px', 'important');
316                });
317            }
318
319            var $logoImageElement = $('.custom-logo-container img');
320            if ( $logoImageElement.length > 0 ) {
321                if ($logoImageElement[0].complete) {
322                   recalculateDimensions();
323                } else {
324                    $logoImageElement.on('load', function() {
325                        recalculateDimensions();
326                    });
327                }
328            } else {
329                recalculateDimensions();
330            }
331
332            setMenuBarTransparency(true);
333
334            $(window).on('resize', function() {
335                recalculateDimensions();
336                setMenuBarTransparency(true);
337            });
338        })();
339    }
340
341    function fixMenuBarHeight() {
342        if ( ! $('#collapseProfileMenu').is(':visible')) {
343            var functionalBarHeight = Math.floor($('#functional-bar').height());
344            $logoBar.css('top', functionalBarHeight + 'px').css('z-index', '1020');
345            $menuBar.css('top', functionalBarHeight + 'px').css('margin-top', functionalBarHeight + 'px');
346        }
347    }
348
349    fixMenuBarHeight();
350
351    var onTopOfThePage = (window.scrollY === 0);
352    $(window).on('scroll', function () {
353        var scrollValue = window.scrollY;
354
355        for (var i = 0; i < onPageScrollCallbacks.length; i++) {
356            onPageScrollCallbacks[i](scrollValue);
357        }
358
359        var currentOnTopOfThePage = (scrollValue === 0);
360        if (onTopOfThePage !== currentOnTopOfThePage) {
361            onTopOfThePage = currentOnTopOfThePage;
362            for (var i = 0; i < onTopOfThePageChangeCallbacks.length; i++) {
363                onTopOfThePageChangeCallbacks[i](onTopOfThePage);
364            }
365        }
366    });
367
368    var onScrollChange = function (scrollValue) {
369        scrollValue = scrollValue || window.scrollY;
370        if (scrollValue > 40) {
371            $('.functional-bar-logo').addClass('show-fade').removeClass('hide-fade');
372
373        } else{
374            $('.functional-bar-logo').addClass('hide-fade').removeClass('show-fade');
375        }
376    };
377    onScrollChange();
378    onPageScrollCallbacks.push(onScrollChange);
379
380    var onTopOfThePageChange = function (onTheTop) {
381        onTheTop = onTheTop || (window.scrollY === 0);
382
383        // Add some effects to te menu bar when user starts scrolling
384        if (onTheTop) {
385            $menuBar.removeClass('menu_bar_on_scroll');
386            if (!$('#mobile-menu').hasClass('show')) {
387                setLogoBarTransparency(true, $logoBar);
388                setMenuBarTransparency(true, $menuBar, $menuBarOverlay);
389            } else {
390                setLogoBarTransparency(false, $logoBar);
391                setMenuBarTransparency(false, $menuBar, $menuBarOverlay);
392            }
393        } else {
394            $menuBar.addClass('menu_bar_on_scroll');
395            if (THEME_SETTINGS.menu_bar_transparent_on_scroll) {
396                $menuBar.data('transparent_background_was_removed', true);
397            }
398            $('.transparent-on-top').removeClass('transparent-background');
399        }
400    };
401    onTopOfThePageChange();
402    onTopOfThePageChangeCallbacks.push(onTopOfThePageChange);
403
404    $(window).on('resize', function() {
405        fixMenuBarHeight();
406    });
407
408    // Main menu max-height (mobile view)
409    var $mobileMenu = $('#mobile-menu');
410    function fixMobileMenuMaxHeight() {
411        var space = window.innerHeight + window.pageYOffset - ($menuBar[0].offsetTop + $menuBar[0].offsetHeight);
412        $mobileMenu.css('max-height', (space - 8) + 'px');
413    }
414
415    var $mobileMenuOverlayElements = $('#mobile-menu-bar-overlay, .mobile-menu-bar-partial-overlay');
416    var $functionalBarPaddingWrapper = $('#functional-bar-padding-wrapper');
417
418    function setMenuBarTransparency(isTransparent, $menuBar, $menuBarOverlay) {
419        if ( doNotApplyMenuBarTransparencyOnThePage ) {
420            return;
421        }
422
423        if ($menuBar == undefined) {
424            $menuBar = $('#menu-bar');
425        }
426
427        //some pages don't include any menu bar e.g email unsubscribes
428        //return so we don't generate js errors
429        if($menuBar.length<=0) {
430            return;
431        }
432
433        var i = 0;
434        var isMenuAboveSection = false;
435        var menuBarBottomPos = Math.floor($menuBar.position().top + $menuBar.outerHeight(true));
436        while( !isMenuAboveSection && i < countOfSections ) {
437            var $section = $('#' + customisationSectionParams[i].id);
438            var sectionTopPos = Math.floor($section.position().top);
439            var sectionBottomPos = Math.floor(sectionTopPos + $section.outerHeight(true));
440
441            if ( menuBarBottomPos >= sectionTopPos && menuBarBottomPos <= sectionBottomPos ) {
442                isMenuAboveSection = true;
443            }
444
445            i++;
446        }
447
448        if ( isMenuAboveSection ) {
449            isAllowedToSetMenubarTransparency = customisationSectionParams[i-1].isMenuBarTransparent;
450        }
451
452        if ($menuBarOverlay == undefined) {
453            $menuBarOverlay = $('.menu_bar_color_overlay_element');
454        }
455
456        var isSetVisibility = (
457            (THEME_SETTINGS.menu_bar_transparency_on_scroll_enabled == 1 && !isFullLogoBarVisible)
458            ||
459            (THEME_SETTINGS.menu_bar_transparent_at_top_of_page == 1 && isFullLogoBarVisible)
460        )
461
462        if ( isAllowedToSetMenubarTransparency ) {
463            if ( !isTransparent || !isSetVisibility ) {
464                $menuBar.removeClass('transparent-background');
465                $menuBar.removeClass('menu_bar_color_overlay_element_on_scroll');
466                $menuBarOverlay.removeClass('transparent-background');
467                $menuBarOverlay.removeClass('menu_bar_color_overlay_element_on_scroll');
468                $('.transparent-on-top').removeClass('transparent-background');
469            }
470            else if (isTransparent && isSetVisibility) {
471                //$menuBar.addClass('menu_bar_color_overlay_element_on_scroll');
472                $menuBar.addClass('transparent-background');
473                $menuBarOverlay.addClass('transparent-background');
474                $menuBarOverlay.addClass('menu_bar_color_overlay_element_on_scroll');
475                $menuBar.addClass('transparent-background');
476
477                if (isFullLogoBarVisible) {
478                    $('.transparent-on-top').addClass('transparent-background');
479                }
480            }
481
482            if (window.scrollY == 0) {
483                $menuBarOverlay.removeClass('menu_bar_color_overlay_element_on_scroll');
484                if ( THEME_SETTINGS.menu_bar_transparent_at_top_of_page == 1 ) {
485                    $menuBar.addClass('transparent-background');
486                } else {
487                    $menuBar.removeClass('transparent-background');
488                }
489            } else {
490                $menuBarOverlay.addClass('menu_bar_color_overlay_element_on_scroll');
491            }
492        } else {
493            $menuBar.removeClass('transparent-background');
494            $menuBar.removeClass('menu_bar_color_overlay_element_on_scroll');
495            $menuBarOverlay.removeClass('transparent-background');
496            $menuBarOverlay.removeClass('menu_bar_color_overlay_element_on_scroll');
497            $('.transparent-on-top').removeClass('transparent-background');
498        }
499    } //setMenuBarTransparency
500
501    function setLogoBarTransparency(isTransparent, $logoBar) {
502        if ($logoBar == undefined) {
503            $logoBar = $('#logo-area');
504        }
505
506        if (!isTransparent && ($logoBar.hasClass('transparent-background') || $logoBar.hasClass('menu_bar_color_overlay_element_on_scroll'))) {
507            $logoBar.removeClass('menu_bar_color_overlay_element_on_scroll');
508            $logoBar.removeClass('transparent-background');
509            $logoBar.data('transparent_background_was_removed', true);
510        } else if (isTransparent && $logoBar.data('transparent_background_was_removed') === true) {
511            if (isFullLogoBarVisible) {
512                $logoBar.addClass('menu_bar_color_overlay_element_on_scroll');
513                $logoBar.addClass('transparent-background');
514                $logoBar.data('transparent_background_was_removed', false);
515            }
516        }
517    } //setLogoBarTransparency
518
519    // Toggle show/hide main menu icon (in mobile view)
520    $('#main-menu-toggler').on('click', function(e) {
521		e.preventDefault();
522		e.stopPropagation();
523
524		var $this = $(this);
525
526        var $icon = $this.find('svg');
527        var currentIcon = $icon.attr('data-icon');
528        var $menuBar = $('#menu-bar');
529        $icon.attr('data-icon', currentIcon === 'bars' ? 'xmark' : 'bars');
530		$icon.attr('height', '25px');
531
532        // Fix mobile menu height before opening
533        if (currentIcon === 'bars') {
534            fixMobileMenuMaxHeight();
535        }
536
537        $functionalBarPaddingWrapper.toggleClass('pt-2');
538		var tmp = currentIcon + ($mobileMenu.hasClass('show') ? 'show' : 'hide')
539
540        // Menu bar and logo bar shoudn't be transparent when mobile menu is opened
541        if (tmp === 'barshide') {
542			setLogoBarTransparency(false, $logoBar);
543            setMenuBarTransparency(false, $mobileMenu, $menuBarOverlay);
544			$mobileMenu.addClass('show');
545            $mobileMenuOverlayElements.css('display', 'block');
546        }
547		else if (tmp === 'timesshow') {
548            setLogoBarTransparency(true, $logoBar);
549            setMenuBarTransparency(true, $mobileMenu, $menuBarOverlay);
550			$mobileMenu.removeClass('show');
551            $mobileMenuOverlayElements.css('display', 'none');
552        }
553		else {//Just set to base - sometimes UI goes crazy when menu clicked too quickly after each other, in that case we set to base
554			$icon.attr('bars');
555			$mobileMenu.removeClass('show');
556            $mobileMenuOverlayElements.css('display', 'none');
557		}
558    });
559
560    // Toggle show/hide sub menus icon (in mobile view)
561    $('.sub-menu-toggler').on('click', function() {
562        var $icon = $(this).find('svg');
563        $icon.attr('data-icon', $icon.attr('data-icon') === 'plus' ? 'minus' : 'plus');
564
565        var $target = $($(this).data('target'));
566        if ($target.hasClass('mobile-submenu-open')) {
567            $target.slideUp(function() {
568                $(this).removeClass('mobile-submenu-open');
569            });
570        } else {
571            $target.addClass('mobile-submenu-open').slideDown();
572        }
573    });
574
575    //show or hide search box in functional bar
576    $("#topnav-search-icon").on('click', function(e){
577        e.preventDefault();
578        var $topNavSearchContainer = $('#topnav-search-container');
579        if ( ! $topNavSearchContainer.is(":visible") ) {
580            $topNavSearchContainer.css('display', 'none').removeClass('hidden');
581        }
582        $topNavSearchContainer.animate({
583            width: "toggle"
584        },300);
585    });
586
587
588    //add active class to menu item if we're on that page
589    var url = window.location;
590    $('.navbar-nav li a[href="'+ url.href +'"]').addClass('active');
591    $('.navbar-nav li a').filter(function() {
592        return this.href == url;
593    }).parent().addClass('active');
594
595
596    //Init People search
597    var peopleSearch = new Bloodhound({
598        datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
599        queryTokenizer: Bloodhound.tokenizers.whitespace,
600        sufficient: 20,
601        remote: {
602            url: '/tags/ajax/search/users/online/1/?term=%QUERY',
603            wildcard: '%QUERY'
604        }
605    });
606
607    $('#topnav-search-container .typeahead').typeahead(null, {
608        name: 'people-search',
609        display: 'value',
610        source: peopleSearch,
611        limit: 20,
612        //Set class names for the navbar menu search so defaults for regualr form inputs not messed up
613        templates: {
614            header: '<div class="row px-1 py-1 ms-1 me-0 ps-0 pe-0" style="background-color: white;">'  +
615                        '<div class="col-12"><h5>' + lang('general:members') + '</h5></div>' +
616                    '</div>',
617            empty: [
618                '<div class="px-1 py-1" style="background-color: white;min-width:300px;">',
619                lang('general:no_results'),
620                '</div>'
621            ].join('\n'),
622
623            suggestion: Handlebars.compile(
624                '<div class="row ms-1 me-0 ps-0 pe-0" style="background-color: white; ">' +
625                    '<div class="col-2 px-1 py-1"><a href="{{link}}"><img src="{{image}}" class="rounded-circle" /></a></div>' +
626                    '<div class="col-10 px-1 py-1"><a href="{{link}}">{{label}}<br/>{{info}}</a></div>' +
627                 '</div>'
628
629            )
630        }
631    }).on('typeahead:selected', function(event, ui) {
632        // Open item's link on select (keyboard or mouse)
633        event.preventDefault();
634        location.href = ui.link;
635    });
636
637    // code for functional menu bar
638    function fadeInFunctionalMenu() {
639        $('#collapseProfileMenu').stop(true,true).fadeIn(150);
640        $('.fa-chevron-down').css({
641            '-webkit-transform' : 'rotate(180deg)',
642            '-moz-transform'    : 'rotate(180deg)',
643            '-ms-transform'     : 'rotate(180deg)',
644            '-o-transform'      : 'rotate(180deg)',
645            'transform'         : 'rotate(180deg)',
646            'transition'        : 'transform 0.3s'
647        });
648    }
649
650    function fadeOutFunctionalMenu() {
651        $('#collapseProfileMenu').stop(true,true).fadeOut(100);
652        $('.fa-chevron-down').css({
653            '-webkit-transform' : 'rotate(0deg)',
654            '-moz-transform'    : 'rotate(0deg)',
655            '-ms-transform'     : 'rotate(0deg)',
656            '-o-transform'      : 'rotate(0deg)',
657            'transform'         : 'rotate(0deg)',
658            'transition'        : 'transform 0.3s'
659        });
660    }
661
662    $('.profileMenuShowHide').on('click', function(event){
663        if ($('#collapseProfileMenu').css('display') === 'none') {
664            fadeInFunctionalMenu();
665        } else {
666           fadeOutFunctionalMenu();
667        }
668    });
669
670    $(document).on('click', function(event) {
671        if ($(event.target).closest('.profileMenuShowHide').length == 0 && !$(event.target).closest('#collapseProfileMenu').length > 0) {
672            fadeOutFunctionalMenu();
673        }
674
675        if ($(event.target).closest('nav.navbar').length == 0) {
676            $('.navbar-toggler[aria-expanded=true]:not(.navbar-toggler-do-not-colapse-on-click)').each(function() {
677                $($(this).data('target')).collapse('toggle');
678            });
679        }
680    });
681
682    // Languages menu
683    if ($('#languagesDropdownMenu').length > 0) {
684        $(function() {
685            var $languageMenuItems = $('#languagesDropdownMenu .dropdown-menu .dropdown-item');
686            $languageMenuItems.on('click', function() {
687                var $thisItem = $(this);
688
689                if ( ! $thisItem.hasClass('bg-secondary')) {
690                    $languageMenuItems.removeClass('bg-secondary');
691                    $thisItem.addClass('bg-secondary');
692
693                    ourHelpingFunctions.setCookie('language', $thisItem.data('language'), 30);
694
695                    window.location.reload();
696                }
697
698                return true;
699            });
700        });
701    }
702}
703
704
705/*
706 |--------------------------------------------------------------------------
707 | Footer
708 |--------------------------------------------------------------------------
709 */
710function initFooter() {
711    //move any panels that have tt-footer-panel class into the footer
712    if($('.tt-footer-panel').length) {
713        $( '.tt-footer-panel' ).each(function( index ) {
714            $(".container-fluid", this).appendTo('#footer-panels');
715            $('#footer-panels').addClass('mt-3');
716        });
717    }
718
719    // Toggle site translations feature
720    $('#tt-admin-translations-toggle').on('click', function() {
721        $.ajax({
722            url: '/translator/translator/preferences',
723            success: function(response) {
724                if (response.status === 'success') {
725                    location.reload();
726                }
727            }
728        });
729    });
730}
731
732
733/*
734 |--------------------------------------------------------------------------
735 | Init UI Features
736 |--------------------------------------------------------------------------
737 */
738function initUIFeatures() {
739    "use strict";
740
741    /*
742     |--------------------------------------------------------------------------
743     | OWL CAROUSEL
744     | https://owlcarousel2.github.io/OwlCarousel2/docs/api-options.html
745     |--------------------------------------------------------------------------
746     */
747
748     //Handle read more expander links in owl carousel items
749     //as regualt boostrap  show/hide won't work with multiple read mores inside an owl carousel
750     if($('.owl-carousel-read-more').length) {
751
752         //read more clicked - show full content
753         $(".owl-carousel-read-more").on('click', function(e) {
754             e.preventDefault();
755             var id = $(this).data('id');
756
757             //hide summary - use classes as id's not working in owl?
758             $(".readMoreSummary").hide();
759
760             //Show this read more content - use classes as id's not working in owl?
761             $(".readMoreFull").show();
762
763             //Hide all the other read more's in other carousel items so
764             //this one gets resized to the correct height
765             $(".readMoreFull").each(function( index ) {
766                 if($(this).data('id') != id) {
767                        $(this).css('display', 'none');
768                 }
769             });
770
771         });
772
773         //read less clicked - hide full content and just show summary
774         $(".owl-carousel-read-less").on('click', function(e) {
775             e.preventDefault();
776
777             //hide full content  - use classes as id's not working in owl?
778             $(".readMoreFull").hide();
779
780             //show summary content  - use classes as id's not working in owl?
781             $(".readMoreSummary").show();
782         });
783    }
784
785    if($('.owl-carousel').length){
786
787        // This is hacky, documentation for owl is lacking so this is the best I can do
788        function setOwlInfiniteLoop(event) {
789            // First we're looking to see if data('owl.carousel') has been set
790            var data = $(event.target).data('owl.carousel');
791            if (!data) {
792                setTimeout(function() {
793                    setOwlInfiniteLoop(event);
794                }, 500);
795                return;
796            }
797
798            // Now we work out if there are less items in carousel than can be seen at once
799            // When there are, we will disable infinite loop to not show repeats of items.
800            // If we have disabled this already and screen is resized larger - we turn the infinite loop back on
801            // Only doing this when autoplay was enabled in the first place
802            if ((data.options.autoplay || data.options.loop) && typeof event.item !== 'undefined' && typeof event.item.count !== 'undefined') {
803                if (event.item.count <= event.page.size) {
804                    data.options.nav = false;
805                    data.options.loop = false;
806                    $(event.target).trigger('refresh.owl.carousel');
807                } else {
808                    data.options.nav = true;
809                    data.options.loop = true;
810                    $(event.target).trigger('refresh.owl.carousel');
811                }
812            }
813        }
814
815        $( '.owl-carousel' ).each(function( index ) {
816
817
818            $(this).on('changed.owl.carousel initialized.owl.carousel', function(event) {
819
820                //reset any read more links
821                if($('.owl-carousel-read-more').length) {
822                    $(".readMoreFull").each(function( index ) {
823                        $(this).css('display', 'none');
824                    });
825
826                    $(".readMoreSummary").show();
827                }
828
829                if(event.page.size>=3) {
830
831                    //dim the first and last items on screen
832                    $(event.target)
833                        .find('.owl-item').removeClass('last-item')
834                        .eq(event.item.index + event.page.size - 1).addClass('last-item');
835                    $(event.target)
836                        .find('.owl-item').removeClass('first-item')
837                        .eq(event.item.index ).addClass('first-item');
838                } else {
839                    //if only 1 item showing (mobile) don't dim
840                    $(event.target).find('.owl-item')
841                        .removeClass('last-item')
842                        .removeClass('first-item');
843                }
844
845            });
846
847            // Set infinite loop when originally initialized and after screen resize
848            $(this).on('initialized.owl.carousel resized.owl.carousel', function(event) {
849                setOwlInfiniteLoop(event);
850            });
851
852            $(this).owlCarousel({
853                loop:$(this).data('owl-loop'),
854                center:$(this).data('owl-center'),
855                stagePadding:$(this).data('owl-stagePadding'),
856                nav:$(this).data('owl-nav'),
857                mergeFit:$(this).data('owl-mergeFit'),
858                autoHeight:$(this).data('owl-auto-height'),
859                navText: $(this).data('owl-navtext-prev') ? [$(this).data('owl-navtext-prev'), $(this).data('owl-navtext-next')] : ['<div style="font-size:5em; color:black; opacity: 0.7;"><i class="fas fa-chevron-left"></i></div>','<div style="font-size:5em; color:black; opacity: 0.7;"><i class="fas fa-chevron-right"></i></div>'],
860                dots:$(this).data('owl-dots'),
861                lazyLoad:$(this).data('owl-lazyLoad'),
862                autoplay:$(this).data('owl-autoplay'),
863                slideSpeed:$(this).data('owl-slide-speed'),
864                autoplaySpeed: $(this).data('owl-autoplay-speed'),
865                autoplayTimeout: $(this).data('owl-autoplay-timeout'),
866                startPosition:$(this).data('owl-startposition'),
867                mouseDrag: $(this).data('owl-mousedrag'),
868                touchDrag: $(this).data('owl-touchdrag'),
869                singleItem: $(this).data('owl-single-item'),
870                responsive:{
871                    0:{
872                        items:$(this).data('owl-items-small'),
873                        nav: typeof $(this).data('owl-keep-navigation') !== 'undefined' ? $(this).data('owl-keep-navigation') : false,
874                        dots: typeof $(this).data('owl-responsive-dots') !== 'undefined' ? $(this).data('owl-responsive-dots') : true,
875                    },
876                    800:{
877                        items:$(this).data('owl-items-medium'),
878                        nav:true
879                    },
880                    1400:{
881                        items:$(this).data('owl-items-large'),
882                        nav:true
883                    }
884                },
885                video: $(this).data('video') ? $(this).data('video') : false,
886            });
887        });
888        // Arrows are not shown when all carousel items fit in the screen
889        // But in this case, first and last items are dimmed,
890        // which makes them look inactive.
891        // This custom option, data-owl-no-opacity="true"
892        // makes all carousel items not dimmed if nav-arrows are disabled
893        var $noOpacitiyOwls = $('.owl-carousel[data-owl-no-opacity="true"]');
894        $noOpacitiyOwls.on('resized.owl.carousel', function(event) {
895            var $this = $(this);
896            // if ($this.find('.owl-nav').hasClass('disabled')) {
897            //     $this.find('.owl-item').addClass('no-opacity');
898            // } else {
899            //     $this.find('.owl-item').removeClass('no-opacity');
900            // }
901        });
902        $noOpacitiyOwls.trigger('resized.owl.carousel');
903
904        // Animated arrows
905        $('.animate-owl-arrows').find('.owl-prev, .owl-next').addClass('hidden');
906        $('.animate-owl-arrows').on('mouseover', function() {
907            $(this).find('.owl-prev, .owl-next').not('.disabled').removeClass('hidden');
908        });
909
910        $('.animate-owl-arrows').on('mouseleave', function() {
911            $(this).find('.owl-prev, .owl-next').addClass('hidden');
912        });
913    }
914
915    /*
916     |--------------------------------------------------------------------------
917     | Quill
918     | https://quilljs.com
919     | MOVED TO: \assets\toucantechV3\quill-tt-init\init.js
920     |--------------------------------------------------------------------------
921     */
922
923     // putting back in here for now as breaking quil editor!
924     (function($) {
925        // Soft enter fix (SHIFT+ENTER)
926        var Parchment = Quill.import('parchment');
927        var Delta = Quill.import('delta');
928        let Break = Quill.import('blots/break');
929        let Embed = Quill.import('blots/embed');
930        let Block = Quill.import('blots/block');
931
932        Break.prototype.insertInto = function(parent, ref) {
933            Embed.prototype.insertInto.call(this, parent, ref);
934        };
935        Break.prototype.length= function() {
936            return 1;
937        };
938        Break.prototype.value= function() {
939            return '\n';
940        };
941
942        // This was causing issues with some values:
943        // <p><strong><br></strong></p>, <p><em><br></em></p> etc.
944//        function lineBreakMatcher() {
945//            var newDelta = new Delta();
946//            newDelta.insert({'break': ''});
947//            return newDelta;
948//        }
949
950        var defaultOptions = {
951            theme: 'snow',
952            modules: {
953                toolbar: [['bold', 'italic', 'underline', 'link'], ['clean']],
954//                clipboard: {
955//                    matchers: [
956//                        ['BR', lineBreakMatcher]
957//                    ]
958//                },
959                keyboard: {
960                    bindings: {
961                        linebreak: {
962                            key: 13,
963                            shiftKey: true,
964                            handler: function (range, context) {
965                                var nextChar = this.quill.getText(range.index + 1, 1);
966                                var ee = this.quill.insertEmbed(range.index, 'break', true, 'user');
967                                if (nextChar.length == 0) {
968                                    // second line break inserts only at the end of parent element
969                                    var ee = this.quill.insertEmbed(range.index, 'break', true, 'user');
970                                }
971                                this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
972                            }
973                        }
974                    }
975                }
976            }
977        };
978
979        // Initialize all Quill RTE inputs
980        // Element should have class="quill-rte", non-empty name attribute and
981        $('.quill-rte').each(function() {
982            // Check if not already initialised
983            if ( ! this.classList.contains('ql-container')) {
984                var options = getOptionsFromElement(this);
985                initializeElement(this, options);
986            }
987        });
988
989        // Initializes Quill editor on DOM element with given options
990        // Adds hidden input element to store editor innerHTML for form submit
991        function initializeElement(element, options) {
992            var $this = $(element);
993
994            // Initialize Quill editor
995            options = $.extend({}, defaultOptions, options);
996            var editor = new Quill(element, options);
997
998            // Remove extraneous new lines
999            var length = editor.getLength();
1000            var text = editor.getText(length - 2, 2);
1001
1002            if (text === '\n\n') {
1003                editor.deleteText(editor.getLength() - 2, 2);
1004            }
1005
1006            // Attach hidden input
1007            var $input = $('<input>').attr({
1008                'type': 'hidden',
1009                'name': $this.attr('name'),
1010                'value': $('.ql-editor', $this).html(),
1011                'class': 'ql-editor-content-hidden'
1012            });
1013
1014            if ($this.data('type') != undefined) {
1015                $input.attr('data-type', $this.data('type'));
1016            }
1017
1018            $this.before($input);
1019
1020            // Update hidden input on editor change
1021            editor.on('text-change', function() {
1022                $input.val($('.ql-editor', $this).html()).trigger('change');
1023            });
1024        }
1025
1026        // jQuery extension
1027        $.fn.quillRTEEditor = function(options, isSetInlineStyling) {
1028            var useDataValues = false;
1029            if ( options == undefined || options == null) {
1030                useDataValues = true;
1031            }
1032            if ( isSetInlineStyling == undefined ) {
1033                isSetInlineStyling = false;
1034            }
1035
1036            if ( isSetInlineStyling ) {
1037                // configure Quill to use inline styles so the email's format properly
1038                var AlignStyle = Quill.import('attributors/style/align');
1039                Quill.register(AlignStyle,true);
1040                var BackgroundStyle = Quill.import('attributors/style/background');
1041                Quill.register(BackgroundStyle,true);
1042                var ColorStyle = Quill.import('attributors/style/color');
1043                Quill.register(ColorStyle,true);
1044                var DirectionStyle = Quill.import('attributors/style/direction');
1045                Quill.register(DirectionStyle,true);
1046                var FontStyle = Quill.import('attributors/style/font');
1047                Quill.register(FontStyle,true);
1048                var SizeStyle = Quill.import('attributors/style/size');
1049                Quill.register(SizeStyle,true);
1050            }
1051
1052            return this.each(function() {
1053                if (useDataValues) {
1054                    options = getOptionsFromElement(this);
1055                }
1056
1057                initializeElement(this, options);
1058            });
1059        };
1060
1061        function getOptionsFromElement(element) {
1062            var $element = $(element);
1063            var options = $element.data('quill-rte-options') || {};
1064
1065            // Get element's placeholder
1066            var attr = $element.attr('placeholder');
1067
1068            if (typeof attr !== typeof undefined && attr !== false) {
1069                options.placeholder = attr;
1070            }
1071
1072            return options;
1073        }
1074    })($);
1075
1076    /*
1077     |--------------------------------------------------------------------------
1078     | Bootstrap select
1079     | Use native mobile picker on mobile
1080     |--------------------------------------------------------------------------
1081     */
1082    if(ttGlobals.isMobile) {
1083        //     //$('.selectpicker').selectpicker('mobile');
1084         $.fn.selectpicker.Constructor.DEFAULTS.mobile = true;
1085    }
1086
1087    /*
1088     |--------------------------------------------------------------------------
1089     | Bootstrap select
1090     | Deselect/close button
1091     |--------------------------------------------------------------------------
1092     */
1093    if(!ttGlobals.isMobile) {
1094        $(document).on('loaded.bs.select', '.selectpicker[data-close-button="true"]', function(e) {
1095            var $selectElement = $(e.target);
1096            var $innerElement = $selectElement.closest('.bootstrap-select').find('.filter-option-inner');
1097
1098            // Check if deselect/close button already exists
1099            var $closeButton = $innerElement.find('.filter-option-inner-close');
1100            if ($closeButton.length > 0) {
1101                return true;
1102            }
1103
1104            // Create deselect/close button
1105            $closeButton = $('<div class="filter-option-inner-close"></div>');
1106
1107            // Prepend
1108            $innerElement.prepend($closeButton);
1109
1110            // Close/deselect selection
1111            $closeButton.on('click', function() {
1112                $selectElement.val('').selectpicker('refresh').trigger('change');
1113                return false;
1114            });
1115
1116            // Selection change event - hide/show close button
1117            $selectElement.on('changed.bs.select', function () {
1118                var isToggle = ( $selectElement.val() != undefined )
1119                        ? Boolean(String($selectElement.val() ?? '').length)
1120                        : false;
1121                $closeButton.toggle(isToggle);
1122            });
1123
1124            // On refresh we'll trigger 'changed.bs.select' event
1125            $selectElement.on('refreshed.bs.select', function () {
1126                $selectElement.trigger('changed.bs.select');
1127            });
1128
1129            // Trigger on load
1130            $selectElement.trigger('changed.bs.select');
1131        });
1132    }
1133
1134    /*
1135     |--------------------------------------------------------------------------
1136     | iframe responsive class
1137     | Makes YouTube iframes responsive
1138     |--------------------------------------------------------------------------
1139     */
1140
1141    ourHelpingFunctions.responsiveIFrame($('.iframe-responsive'));
1142
1143    /*
1144     |--------------------------------------------------------------------------
1145     | youtube background video player
1146     | Use youtube iframe api to play yourtube background video with no controls
1147     |--------------------------------------------------------------------------
1148     */
1149
1150    if($('.youtube-background-video').length){
1151
1152        // loads Youtube IFrame Player API code
1153        var tag = document.createElement('script');
1154        tag.src = "https://www.youtube.com/iframe_api";
1155        var firstScriptTag = document.getElementsByTagName('script')[0];
1156        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
1157        var player;
1158        var bgVideoId = $('.youtube-background-video').data('video-id');
1159
1160        //youtube functions need to be in global scope
1161        window.onYouTubeIframeAPIReady = function (){
1162            // YouTube paints uncontrollable centre chrome whenever playback (re)starts, so a
1163            // seamless loop uses two stacked players: the standby restarts hidden (its chrome
1164            // fades unseen) and is crossfaded in just before the active player ends.
1165            var HANDOFF_LEAD = 5;    // seconds before the end to start the hidden standby
1166            var REVEAL_DELAY = 4000; // ms the start chrome needs to fade while hidden
1167
1168            var hostDiv = document.getElementById('yt-bg-player');
1169            var standbyDiv = document.createElement('div');
1170            standbyDiv.id = 'yt-bg-player-b';
1171            standbyDiv.className = hostDiv.className;
1172            hostDiv.parentNode.appendChild(standbyDiv);
1173
1174            var playerVars = {
1175                autoplay: 0,          // started via the API once both players are ready
1176                disablekb: 1,         // disble keyboard
1177                controls: 0,          // Hide pause/play buttons in player
1178                showinfo: 0,          // Hide the video title
1179                modestbranding: 1,    // Hide the Youtube Logo
1180                fs: 0,                // Hide the full screen button
1181                autohide: 0,          // Hide video controls when playing
1182                rel: 0,               // show releated videos
1183                enablejsapi: 1,       // allow control via api
1184                iv_load_policy: 3     // dont show annotations
1185            };
1186
1187            var players = [];
1188            var activeIdx = 0;
1189            var handingOff = false;
1190            var started = false;
1191            var readyCount = 0;
1192
1193            function setOpacity(p, value, fade) {
1194                var ifr = p.getIframe();
1195                ifr.style.transition = fade ? 'opacity 0.5s' : 'none';
1196                ifr.style.opacity = String(value);
1197            }
1198
1199            function onBgPlayerReady(e) {
1200                var ifr = e.target.getIframe();
1201                // Stack the players if the section CSS doesn't already position them
1202                if (getComputedStyle(ifr).position === 'static') {
1203                    ifr.style.position = 'absolute';
1204                    ifr.style.top = '0';
1205                    ifr.style.left = '0';
1206                    ifr.style.width = '100%';
1207                    ifr.style.height = '100%';
1208                }
1209                setOpacity(e.target, 0, false);
1210                e.target.mute();
1211                readyCount++;
1212                if (readyCount === 2) {
1213                    startActive();
1214                }
1215            }
1216
1217            function startActive() {
1218                if (started) {
1219                    return;
1220                }
1221                started = true;
1222                var active = players[activeIdx];
1223                active.setPlaybackQuality('hd1080');
1224                active.playVideo();
1225                scheduleReveal(active);
1226                watchForHandoff();
1227            }
1228
1229            function scheduleReveal(p) {
1230                if (window._ttBgYtRevealTimer) {
1231                    clearTimeout(window._ttBgYtRevealTimer);
1232                }
1233                window._ttBgYtRevealTimer = setTimeout(function () {
1234                    setOpacity(p, 1, true);
1235                }, REVEAL_DELAY);
1236            }
1237
1238            function watchForHandoff() {
1239                if (window._ttBgYtLoopTimer) {
1240                    clearInterval(window._ttBgYtLoopTimer);
1241                }
1242                window._ttBgYtLoopTimer = setInterval(function () {
1243                    try {
1244                        var active = players[activeIdx];
1245                        var standby = players[1 - activeIdx];
1246                        var duration = active.getDuration();
1247                        var remaining = duration - active.getCurrentTime();
1248
1249                        if (duration < HANDOFF_LEAD + 2) {
1250                            return; // too short to hand off — the ENDED fallback loops it
1251                        }
1252
1253                        if (! handingOff && remaining <= HANDOFF_LEAD) {
1254                            // Start the standby hidden; its start chrome fades while invisible
1255                            handingOff = true;
1256                            standby.seekTo(0, true);
1257                            standby.playVideo();
1258                        }
1259
1260                        if (handingOff && remaining <= 0.6) {
1261                            crossfade();
1262                        }
1263                    } catch (err) {}
1264                }, 250);
1265            }
1266
1267            function crossfade() {
1268                var active = players[activeIdx];
1269                var standby = players[1 - activeIdx];
1270                setOpacity(standby, 1, true);
1271                setOpacity(active, 0, true);
1272                setTimeout(function () {
1273                    active.pauseVideo(); // parked hidden until its next turn
1274                }, 600);
1275                activeIdx = 1 - activeIdx;
1276                handingOff = false;
1277            }
1278
1279            function onBgPlayerStateChange(e) {
1280                if (e && e.data === 1) {
1281                    var videoHolder = document.getElementById('home-banner-box');
1282                    if (videoHolder && videoHolder.id) {
1283                        videoHolder.classList.remove('loading');
1284                    }
1285                } else if (e && e.data === 0 && players[activeIdx] === e.target) {
1286                    // Handoff missed (stalled tab / very short video): restart hidden and
1287                    // reveal once the start chrome has faded
1288                    setOpacity(e.target, 0, false);
1289                    e.target.seekTo(0, true);
1290                    e.target.playVideo();
1291                    scheduleReveal(e.target);
1292                }
1293            }
1294
1295            players[0] = new YT.Player('yt-bg-player', {
1296                height: '390',
1297                width: '640',
1298                videoId: bgVideoId,
1299                playerVars: playerVars,
1300                events: {
1301                    onReady: onBgPlayerReady,
1302                    onStateChange: onBgPlayerStateChange
1303                }
1304            });
1305            players[1] = new YT.Player('yt-bg-player-b', {
1306                height: '390',
1307                width: '640',
1308                videoId: bgVideoId,
1309                playerVars: playerVars,
1310                events: {
1311                    onReady: onBgPlayerReady,
1312                    onStateChange: onBgPlayerStateChange
1313                }
1314            });
1315            player = players[0];
1316
1317            // If the second player never becomes ready, run with one
1318            setTimeout(function () {
1319                if (! started && readyCount > 0) {
1320                    startActive();
1321                }
1322            }, 8000);
1323        };
1324
1325        window.onPlayerReady = function(event) {
1326            event.target.playVideo();
1327        }
1328
1329        var done = false;
1330        window.onPlayerStateChange = function(event) {
1331            if (event.data == YT.PlayerState.PLAYING && !done) {
1332            setTimeout(stopVideo, 6000);
1333            done = true;
1334            }
1335        }
1336
1337        window.stopVideo = function() {
1338            player.stopVideo();
1339        }
1340
1341         // 3. This function creates an <iframe> (and YouTube player)
1342        //    after the API code downloads.
1343        // function onYouTubeIframeAPIReady() {
1344
1345        //     player = new YT.Player('player', {
1346        //     height: '390',
1347        //     width: '640',
1348        //     videoId: 'M7lc1UVf-VE',
1349        //     events: {
1350        //         'onReady': onPlayerReady,
1351        //         'onStateChange': onPlayerStateChange
1352        //     }
1353        //     });
1354        // }
1355
1356        // 4. The API will call this function when the video player is ready.
1357        // window.onPlayerReady = function(event) {
1358        //     event.target.playVideo();
1359        // }
1360
1361        // // 5. The API calls this function when the player's state changes.
1362        // //    The function indicates that when playing a video (state=1),
1363        // //    the player should play for six seconds and then stop.
1364        // var done = false;
1365        // function onPlayerStateChange(event) {
1366        //     if (event.data == YT.PlayerState.PLAYING && !done) {
1367        //     setTimeout(stopVideo, 6000);
1368        //     done = true;
1369        //     }
1370        // }
1371        // function stopVideo() {
1372        //     player.stopVideo();
1373        // }
1374
1375
1376    }
1377
1378
1379    // Bootstrap tooltips
1380    $('[data-bs-toggle="tooltip"]').tooltip({
1381        tooltipClass: "tt-tooltip"
1382    });
1383
1384    //--------------- Slick carousel (https://kenwheeler.github.io/slick/) ------------------//
1385
1386    (function() {
1387        // jQuery extension
1388        $.fn.slickTTVersion = function(options) {
1389            // Add to override Slick default options
1390            const defaultOptions = {};
1391
1392            return this.each(function() {
1393                let $this = $(this);
1394
1395                // Pick up settings from DOM element
1396                let dataOptions = {
1397                    dots: $this.data('slick-dots'),
1398                    infinite: $this.data('slick-infinite'),
1399                    speed: $this.data('slick-speed'),
1400                    fade: $this.data('slick-fade'),
1401                    cssEase: $this.data('slick-css-ease'),
1402                    autoplay: $this.data('slick-autoplay'),
1403                    autoplaySpeed: $this.data('slick-autoplay-speed'),
1404                    initialDelay: $this.data('slick-initial-delay')
1405                };
1406
1407                let config = $.extend({}, defaultOptions, dataOptions, options);
1408                $this.slick(config);
1409
1410                // Slik carousel doesn't implement delay
1411                if (config.initialDelay) {
1412                    $this.slick('pause');
1413                    setTimeout(function() {
1414                        $this.slick('play');
1415                    }, config.initialDelay);
1416                }
1417            });
1418        };
1419
1420        // Initialise carousels
1421        $('.slick-carousel').slickTTVersion();
1422    })();
1423
1424
1425    // expand/collapse sections with arrows
1426    if($('.collapse-with-arrows').length){
1427        $('.collapse-with-arrows').on('click', function () {
1428            $(this)
1429                .find('[data-fa-i2svg]')
1430                .toggleClass('fa-caret-down')
1431                .toggleClass('fa-caret-right');
1432        });
1433    };
1434
1435    // AOS animation
1436    AOS.init();
1437}
1438
1439var ourHelpingFunctions = {
1440    toast: function toast(text, type) {
1441        type = type || 'normal';
1442        var toastClass = (type === 'error') ? 'toast danger' : 'toast';
1443
1444        var $toast = $('<div class="' + toastClass + '"><div class="toast-inner">
1444' + text + '</div></div>');
1445        $toast.appendTo(document.body);
1446        $toast.animate({
1447            top: '20%'
1448        }, 300, function() {
1449            setTimeout(function(){
1450                $toast.animate({
1451                    top: '10%',
1452                    opacity: 0
1453                }, 300, function() {
1454                    $toast.remove;
1455                });
1456            }, 3000);
1457        });
1458    },
1459
1460    // Use this as an alternative to JS form.reset()
1461    // if you want to reset inputs inside jQuery object
1462    resetInputs: function($container) {
1463        $container.find(':input').each(function() {
1464            switch(this.type) {
1465                case 'password':
1466                case 'text':
1467                case 'textarea':
1468                case 'file':
1469                case 'date':
1470                case 'number':
1471                case 'tel':
1472                case 'email':
1473                    $(this).val('');
1474                    break;
1475                case 'select-one':
1476                case 'select-multiple':
1477                    $(this).find('option').each(function() {
1478                        $(this).prop('selected', this.getAttribute('selected'));
1479                    });
1480                    break;
1481                case 'checkbox':
1482                case 'radio':
1483                    this.checked = this.getAttribute('checked');
1484                    break;
1485            }
1486
1487            $(this).trigger('blur');
1488        });
1489
1490        // Remove image
1491        $('.croppie-file-input', $container).attr('src', '').hide();
1492
1493        // Empty RTE editors
1494        $('.ql-editor', $container).html('');
1495
1496        // Empty tags inputs
1497        if ($.fn.tagit) {
1498            $('.tagit-hidden-field', $container).tagit('removeAll');
1499        }
1500
1501        // Empty multi-level multi-select
1502        $('.multilevel-multiselect-group', $container).each(function() {
1503            var $multiSelect = $(this);
1504            var selectedOptions = $multiSelect.data('selected-options');
1505            var selectedOptionsIDs = selectedOptions ? selectedOptions.split(',') : [];
1506
1507            $('.multilevel-container li', $multiSelect).each(function() {
1508                var $item = $(this);
1509                var id = $item.data('id');
1510
1511                if (
1512                    selectedOptionsIDs.includes(id + '') && ! $item.hasClass('selected')
1513                    ||
1514                    $item.hasClass('selected') && ! selectedOptionsIDs.includes(id + '')
1515                ) {
1516                    $item.trigger('click');
1517                }
1518            });
1519
1520            if (selectedOptionsIDs.length === 0) {
1521                $multiSelect.trigger('blur');
1522            } else {
1523                $multiSelect.trigger('input');
1524            }
1525        });
1526
1527        // Refresh selectpicker
1528        if ($.fn.selectpicker) {
1529            $('.selectpicker', $container).selectpicker('refresh');
1530        }
1531
1532        // Clear validation
1533        $container.removeClass('was-validated');
1534
1535        // Custom validation (in cases when bootstrap doesn't work)
1536        $('.custom-validation', $container).parent().next('.invalid-feedback').hide();
1537    },
1538
1539    inputHasValue: function($input) {
1540        if ( $input.is(':checkbox') ) {
1541            var value = $input.is(':checked') ? 1 : '';
1542        } else {
1543            var value = $input.val();
1544        }
1545
1546        return  !(value === '' || value === null || value === undefined || value === null);
1547    },
1548
1549    fieldContainerHasInputData: function($container) {
1550        var isThereData = false;
1551
1552        $container.find(':input.new-field, :input.selected_ids').each(function() {
1553            if ( ! isThereData && ourHelpingFunctions.inputHasValue($(this)) ) {
1554                isThereData = true;
1555                return false;
1556            }
1557        });
1558
1559        return isThereData;
1560    },
1561
1562    // If you want to empty inputs inside jQuery object
1563    emptyInputs: function($container, emptyHidden = false) {
1564        $container.find(':input').each(function() {
1565            switch(this.type) {
1566                case 'password':
1567                case 'text':
1568                case 'textarea':
1569                case 'file':
1570                case 'date':
1571                case 'number':
1572                case 'tel':
1573                case 'email':
1574                case 'select-one':
1575                case 'select-multiple':
1576                    $(this).val('');
1577                    break;
1578                case 'checkbox':
1579                case 'radio':
1580                    this.checked = false;
1581                    break;
1582                case 'hidden':
1583                    if (emptyHidden) {
1584                        $(this).val('');
1585                    }
1586                    break;
1587            }
1588
1589            $(this).trigger('blur');
1590        });
1591
1592        // Remove image
1593        $('.croppie-file-input', $container).attr('src', '').hide();
1594
1595        // Empty RTE editors
1596        $('.ql-editor', $container).html('');
1597
1598        // Empty tags inputs
1599        if ($.fn.tagit) {
1600            $('.tagit-hidden-field', $container).tagit('removeAll');
1601        }
1602
1603        // Empty multi-level multi-select
1604        $('.multilevel-multiselect-group', $container).each(function() {
1605            var $multiSelect = $(this);
1606
1607            $('.multilevel-container li', $multiSelect).each(function() {
1608                var $item = $(this);
1609                if ($item.hasClass('selected')) {
1610                    $item.trigger('click');
1611                }
1612            });
1613
1614            $multiSelect.trigger('blur');
1615        });
1616
1617        // Refresh selectpicker
1618        if ($.fn.selectpicker) {
1619            $('.selectpicker', $container).selectpicker('refresh');
1620        }
1621
1622        // Clear validation
1623        $container.removeClass('was-validated');
1624
1625        // Custom validation (in cases when bootstrap doesn't work)
1626        $('.custom-validation', $container).parent().next('.invalid-feedback').hide();
1627    },
1628
1629    displayNotices: function($ele, notices, defaultType) {
1630        // Empty element
1631        $ele.html('');
1632
1633        defaultType = defaultType || 'warning';
1634
1635        if (typeof notices === 'string') {
1636            notices = [notices];
1637        }
1638
1639        notices.forEach(function (notice) {
1640            if (typeof notice === 'string') {
1641                notice = {
1642                    text: notice,
1643                    type: defaultType
1644                };
1645            } else {
1646                notice.type = notice.type || defaultType;
1647            }
1648
1649            var noticeHtml =
1650                '<div class="alert alert-' + notice.type + ' alert-dismissible fade show mb-1 p-2" role="alert">' +
1651                    notice.text +
1652                    '<button type="button" class="btn-close p-2" data-bs-dismiss="alert" aria-label="Close">' +
1653                        '<span aria-hidden="true">&times;</span>' +
1654                    '</button>' +
1655                '</div>';
1656            $ele.append(noticeHtml);
1657        });
1658    },
1659
1660    // Activates hover box for all element inside $container with data-hoverbox-uri attribute
1661    hoverBoxRender: function($container) {
1662        if ($container) {
1663            var $hoverBoxElements = $('[data-hoverbox-uri]', $container);
1664        } else {
1665            var $hoverBoxElements = $('[data-hoverbox-uri]');
1666        }
1667
1668        $hoverBoxElements.tipsy({
1669            gravity: $.fn.tipsy.autoBounds(300, 'nw'),
1670            fade: false,
1671            html: true,
1672            trigger: 'manual',
1673            opacity: 1,
1674            title: function() {
1675                return ttGlobals.hoverBoxCache[$(this).data('hoverbox-uri')];
1676            }
1677        });
1678
1679        $hoverBoxElements.hoverIntent({
1680            interval: 100,
1681            timeout: 100,
1682            over: function(e) {
1683                var $self = $(e.currentTarget);
1684                var uri = $self.data('hoverbox-uri');
1685                $self.data('show-profile-hoverbox', true);
1686
1687                var show_tipsy = function(){
1688                    if (!$self.data('show-profile-hoverbox'))
1689                        return;
1690
1691                    $self.tipsy('show');
1692
1693                    $('div.tipsy').hoverIntent({
1694                        interval: 0,
1695                        timeout: 100,
1696                        over: function() {
1697                            $self.data('in_hoverbox', true);
1698                        },
1699                        out: function() {
1700                            $self.data('in_hoverbox', false);
1701                            $self.data('show-profile-hoverbox', false);
1702                            $self.tipsy('hide');
1703                        }
1704                    });
1705
1706                    $('div.tipsy .tipsy-inner a').on('click', function(e) {
1707                        // If we remove it right now, other live events on this
1708                        // element will not be triggered (esp. message popup)
1709                        setTimeout(function(){$self.tipsy('hide');}, 10);
1710                    });
1711                };
1712
1713                if (ttGlobals.hoverBoxCache[uri] === undefined){
1714                    $.ajax({
1715                        url: BASE_URI + uri,
1716                        success: function(response) {
1717                            if (response.status === 'success') {
1718                                ttGlobals.hoverBoxCache[uri] = response.html;
1719                                show_tipsy();
1720                            }
1721                        }
1722                    });
1723                } else {
1724                    show_tipsy();
1725                }
1726
1727            },
1728            out: function(e) {
1729//                var $self = $(e.currentTarget);
1730//                if (!$self.data('in_hoverbox')) {
1731//                    $self.data('show-profile-hoverbox', false);
1732//                    $self.tipsy('hide');
1733//                }
1734            }
1735        });
1736    },
1737
1738    // Adjusts iFrames to be more responsive
1739    responsiveIFrame: function($container) {
1740        // Modify iframe width to 100% max
1741        $container.find('iframe').css({"max-width": "100%"});
1742
1743        // iframes with fixed width and height
1744        var $iframes = $container.find('iframe[width][height]');
1745
1746        // We'll resize them to keep original aspect ratio on window resize
1747        $(window).on('resize', function() {
1748            $iframes.each(function() {
1749                var $this = $(this);
1750                var origWidth = $this.prop('width');
1751                // Should be, but just to be sure, we don't want div by zero error
1752                if (origWidth > 0) {
1753                    var origHeight = $this.prop('height');
1754                    $this.css({'height': $this.width() * (origHeight / origWidth) + 'px' });
1755                }
1756            });
1757        });
1758
1759        $(window).trigger('resize');
1760    },
1761
1762    setInputFilter: function (textbox, inputFilter) {
1763        ['input', 'keydown', 'keyup', 'mousedown', 'mouseup', 'select', 'contextmenu', 'drop'].forEach(function(event) {
1764            textbox.addEventListener(event, function() {
1765                if (inputFilter(this.value)) {
1766                    this.oldValue = this.value;
1767                    this.oldSelectionStart = this.selectionStart;
1768                    this.oldSelectionEnd = this.selectionEnd;
1769                } else if (this.hasOwnProperty('oldValue')) {
1770                    this.value = this.oldValue;
1771                    this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
1772                } else {
1773                    this.value = '';
1774                }
1775            });
1776        });
1777    },
1778
1779    // Capitalise first letter of the string
1780    ucfirst: function (string) {
1781        return string.charAt(0).toUpperCase() + string.slice(1);
1782    },
1783
1784    // Lower case first letter of the string
1785    lcfirst: function (string) {
1786        return string.charAt(0).toLowerCase() + string.slice(1);
1787    },
1788
1789    ellipsize: function(str, maxLength = 100, ellipsis = '...') {
1790        // Is the string long enough to ellipsize?
1791        if (str.length <= maxLength) {
1792            return str;
1793        }
1794
1795        return str.substring(0, maxLength) + ellipsis;
1796    },
1797
1798    setCookie: function(cname, cvalue, exdays) {
1799        var d = new Date();
1800        d.setTime(d.getTime() + (exdays*24*60*60*1000));
1801        var expires = "expires="+ d.toUTCString();
1802        document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
1803    },
1804
1805    getCookie: function(cname) {
1806        var name = cname + "=";
1807        var decodedCookie = decodeURIComponent(document.cookie);
1808        var ca = decodedCookie.split(';');
1809        for(var i = 0; i <ca.length; i++) {
1810            var c = ca[i];
1811            while (c.charAt(0) == ' ') {
1812                c = c.substring(1);
1813            }
1814            if (c.indexOf(name) == 0) {
1815                return c.substring(name.length, c.length);
1816            }
1817        }
1818        return "";
1819    }
1820};
1821
1822window.showBSModal = function self(options) {
1823
1824    var options = $.extend({
1825            title : '',
1826            body : '',
1827            remote : false,
1828            backdrop : 'static',
1829            size : false,
1830            onShow : false,
1831            onHide : false,
1832            actions : false,
1833            noFooter : false,
1834            noHeader : false
1835        }, options);
1836
1837    self.onShow = typeof options.onShow == 'function' ? options.onShow : function () {};
1838    self.onHide = typeof options.onHide == 'function' ? options.onHide : function () {};
1839
1840    if (self.$modal == undefined) {
1841        self.$modal = $('<div class="modal fade"><div class="modal-dialog"><div class="modal-content"></div></div></div>').appendTo('body');
1842        self.$modal.on('shown.bs.modal', function (e) {
1843            self.onShow.call(this, e);
1844        });
1845        self.$modal.on('hidden.bs.modal', function (e) {
1846            self.onHide.call(this, e);
1847        });
1848    }
1849
1850    var modalClass = {
1851        small : "modal-sm",
1852        large : "modal-lg"
1853    };
1854
1855    self.$modal.data('bs.modal', false);
1856    self.$modal.find('.modal-dialog').removeClass().addClass('modal-dialog ' + (modalClass[options.size] || ''));
1857
1858    var modalContent = '';
1859    if  ( ! options.noHeader) {
1860        modalContent += '<div class="modal-header"><h4 class="modal-title">${title}</h4><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div>';
1861    }
1862
1863    modalContent += '<div class="modal-body">${body}</div>';
1864
1865    if ( ! options.noFooter) {
1866        modalContent += '<div class="modal-footer"></div>';
1867    }
1868
1869    self.$modal.find('.modal-content').html(modalContent.replace('${title}', options.title).replace('${body}', options.body));
1870
1871    var footer = self.$modal.find('.modal-footer');
1872    if (footer.length > 0) {
1873        if (Object.prototype.toString.call(options.actions) == "[object Array]") {
1874            for (var i = 0, l = options.actions.length; i < l; i++) {
1875                options.actions[i].onClick = typeof options.actions[i].onClick == 'function' ? options.actions[i].onClick : function () {};
1876                $('<button type="button" class="btn ' + (options.actions[i].cssClass || '') + '">' + (options.actions[i].label || '{Label Missing!}') + '</button>').appendTo(footer).on('click', options.actions[i].onClick);
1877            }
1878        } else {
1879            $('<button type="button" class="btn btn-default" data-bs-dismiss="modal">Close</button>').appendTo(footer);
1880        }
1881    }
1882
1883    self.$modal.modal(options);
1884    self.$modal.modal('show');
1885
1886    return self.$modal;
1887};
1888
1889
1890//Creating Callback function for Google captcha multiple instances script which will call the redefined gCaptchaApp.callback function
1891var multipleRecaptchaRenderDetails = [];
1892function multipleCaptchaCallback() {
1893    $('.multiple-g-recaptcha').each(function() {
1894        var $this = $(this);
1895        var id = $this.attr('id');
1896        var widgetID = grecaptcha.render($this.attr('id'), {'sitekey' : $this.attr('data-sitekey')});
1897        multipleRecaptchaRenderDetails.push({
1898            'formID' : id,
1899            'widgetID' : widgetID,
1900        });
1901    });
1902};
1903
1904function checkMultipleRecaptchaResponse($form) {
1905    if ( $form.find('.multiple-g-recaptcha').length > 0 ) {
1906        try {
1907            index = multipleRecaptchaRenderDetails.findIndex(item => item.formID === $form.find('.multiple-g-recaptcha').attr("id"));
1908
1909            if ( index !== -1 ) {
1910                var response = grecaptcha.getResponse(multipleRecaptchaRenderDetails[index].widgetID);
1911            } else {
1912                var response = null;
1913            }
1914
1915            if ( ! response) {
1916                var $capthcaIFrame = $('iframe[role=presentation]', $form);
1917                $capthcaIFrame.css('border', 'solid 1px red');
1918                $('.captcha-error-msg', $form).removeClass('hidden');
1919                return false;
1920            } else {
1921                return true;
1922            }
1923        } catch (err) {
1924            return true;
1925        }
1926    }
1927
1928    return true;
1929};
1930
1931
1932$(function() {
1933    // Dimm images on hover
1934    $(document).on('mouseenter', '.image-zoom-container', function() {
1935        var $overlay = $('.image-zoom-container-overlay', $(this));
1936        $overlay.addClass('image-zoom-container-overlay-hover');
1937
1938        $('img', $(this)).addClass('image-zoom-container-img-hover');
1939    });
1940
1941    $(document).on('mouseleave', '.image-zoom-container', function() {
1942        var $overlay = $('.image-zoom-container-overlay', $(this));
1943        $overlay.removeClass('image-zoom-container-overlay-hover');
1944
1945        $('img', $(this)).removeClass('image-zoom-container-img-hover');
1946    });
1947
1948
1949    // Adjust anchors (in url) which pointing to ID
1950    var href = window.location.href;
1951    if (href.indexOf("#") >= 0) {
1952        var elementID = href.substr(href.indexOf("#"));
1953        if (elementID.length > 1 && $(elementID).length > 0) {
1954            window.scrollBy(0,-60);
1955        }
1956    }
1957
1958    /*
1959        Google recatpcha SETUP for multiple instances
1960            Other necessary script is loaded by JavaScript when page loaded
1961            See example how captcha defined in view here: modules\network_settings\views\customization\customSections\templates\email_capture_form.php
1962                How using random id and multiple-g-recaptcha class selector, plus adding sitekey data attribute
1963    */
1964    if ( $('.multiple-g-recaptcha').length > 0 ) {
1965        $.getScript("https://www.google.com/recaptcha/api.js?onload=multipleCaptchaCallback&render=explicit");
1966    }
1967
1968    $('body').addClass('footerBgColor');
1969
1970	//Checking WEBM files - showing background or not
1971	$('.js-control-check-webm-file-existence').each(function(){
1972		var $this = $(this);
1973		var videoAttrId = $this.find('video').attr('id');
1974		var video = document.getElementById(videoAttrId);
1975		var canPlayWebm = video.canPlayType('video/webm; codecs="vp8.0, vorbis"');
1976
1977		if (canPlayWebm != "") {
1978			video.addEventListener('loadeddata', function(event) {
1979				video.style.background = 'none';
1980			}, false);
1981		}
1982	});
1983
1984    //Set property of embedded YouTube videos as backgrounds
1985    //ttGlobal_SetYouTubeVideoAsBg();
1986
1987    $(window).on("resize", function() {
1988        //ttGlobal_SetYouTubeVideoAsBg()
1989    });
1990
1991	$(document).on('click', '.front-end-v3-msg-container .close', function() {
1992		$(this).closest('.front-end-v3-msg-container').remove();
1993	});
1994}); 
1995
1996    function ttGlobal_SetYouTubeVideoAsBg() {
1997        var aspectRatio = 16/9;
1998        var $videoBackgrounds = $(".youtube-background-video");
1999
2000        $videoBackgrounds.each(function() {
2001            var $this = $(this);
2002            var $videoBgContainer = $this.parent();
2003            var videoBgHeight = $videoBgContainer.outerHeight();
2004            var videoBgWidth = $videoBgContainer.outerWidth();
2005
2006            if ( videoBgHeight < videoBgWidth ) {
2007                var newWidth = videoBgHeight*aspectRatio;
2008                var halfNewWidth = newWidth/2;
2009                $this.css({"height":videoBgHeight,"width":newWidth+"px","left":"50%","margin-left":"-"+halfNewWidth+"px"});
2010            } else {
2011                var newHeight = videoBgWidth/aspectRatio;
2012                $this.css({"height":newHeight,"width":videoBgWidth+"px"});
2013            }
2014        });
2015    } //ttGlobal_SetYouTubeVideoAsBg
2016
2017
2018    /*
2019     |--------------------------------------------------------------------------
2020     | FORM global object - with general form control stuffs (functions and settings)
2021     | FORM_CHECK global object - with general input field checking stuffs (functions and settings)
2022     | AJAX global object - with general AJAX call handling stuffs
2023     |--------------------------------------------------------------------------
2024     */
2025
2026    //Put some function into jQuery realm
2027    (function($) {
2028
2029        'use strict';
2030
2031        /* base of AJAX global object */
2032        $.object_ajax = {
2033
2034            //variables
2035            //Default method: POST or GET
2036            defaultMethod : "POST",
2037
2038            //Default base URL for AJAX call
2039            defaultBaseUrl : '',
2040
2041            //Default data : can be any, default is an empty object
2042            defaultSettings : {},
2043
2044            //Default data type: text, xml, json, script or html
2045            defaultDataType : "text",
2046
2047            //Default callback function when AJAX call was success
2048            defaultSuccessFunction : function(){},
2049
2050            //Default callback function when AJAX call got an error
2051            defaultErrorFunction : function(response) { console.log(response); },
2052
2053            //Default callback function when AJAX call completed
2054            defaultCompleteFunction : function(response) {
2055                try {
2056                    var rObj = JSON.parse(response.responseText);
2057                    if (rObj.responses) {
2058                        if (rObj.responses.permission === false) {
2059                            location.href = BASE_URI + 'no-permission/';
2060                        }
2061                    }
2062                } catch (e) {
2063
2064                    console.error('Error parsing JSON:', e);
2065                }
2066            },
2067
2068            //Using cache or not
2069            defaultCache :	false,
2070
2071            call : function(request_url, sent_params, callback_success, callback_error, callback_complete, options)
2072            {
2073                var data = (sent_params != undefined) ? sent_params : $.object_ajax.defaultData;
2074                if ( callback_success == undefined) {
2075                    callback_success = $.object_ajax.defaultSuccessFunction;
2076                }
2077                if ( callback_error == undefined) {
2078                    callback_error = $.object_ajax.defaultErrorFunction;
2079                }
2080                if ( callback_complete == undefined) {
2081                    callback_complete = $.object_ajax.defaultCompleteFunction;
2082                }
2083
2084                if ($.isPlainObject(callback_success)) {
2085                    options = callback_success;
2086                }
2087                else if ($.isPlainObject(callback_error)) {
2088                    options = callback_error;
2089                }
2090                else if ($.isPlainObject(callback_complete)) {
2091                    options = callback_complete;
2092                }
2093
2094                //Extend complete function with window resize function
2095                var callback_complete_extended = function(response) {
2096                    callback_complete(response);
2097                    $(window).trigger('resize');
2098                }
2099
2100                if (options == undefined) {
2101                    options = $.object_ajax.defaultSettings;
2102                }
2103
2104                var fullAjaxSettings = $.extend({},
2105                    {
2106                        'url' 	    : (( $.object_ajax.defaultBaseUrl != "") ? defaultBaseUrl : BASE_URI) + request_url,
2107                        'type' 	    : $.object_ajax.defaultMethod,
2108                        'data'	    : data,
2109                        'dataType'  : $.object_ajax.defaultDataType,
2110                        'async'	    : true,
2111                        'success'	: callback_success,
2112                        'error'	    : callback_error,
2113                        'complete'  : callback_complete_extended,
2114                        'cache'     : false,
2115                    },
2116                    options
2117                );
2118
2119                $.ajax(fullAjaxSettings);
2120            },
2121        };
2122        AJAX = 	$.object_ajax;
2123
2124
2125        /* base of COMMON global object */
2126        $.object_common = {
2127            stripContent : function(content, $inputElement, isSettingNewContent, isStrippingContent) {
2128                if ($inputElement.attr("type") == "file") {
2129                    return content;
2130                }
2131
2132                if ( Array.isArray(content) || content == undefined || content.trim() == "") {
2133                    return content;
2134                }
2135
2136                if ( $inputElement != undefined && ( isStrippingContent == undefined || isStrippingContent || $inputElement.hasClass("strip-content")) ) {
2137                    isStrippingContent = true;
2138                }
2139
2140                if ( isStrippingContent ) {
2141                    content = (content.replace(/<\/?[^>]+>/gi, '')).trim()
2142                }
2143
2144                //Just for sure, removing single opening and closing tag marks
2145                content = content.replace(/</gi, '');
2146                content = content.replace(/>/gi, '');
2147
2148                if ( isSettingNewContent == undefined || isSettingNewContent == true ) {
2149                    $inputElement.val(content);
2150                }
2151
2152                return content;
2153            },
2154
2155            stripTags : function($contentContainer, exceptionalTags)
2156            {
2157                $contentContainer.find("*").each(function() {
2158                    $(this).replaceWith($(this)[0].outerHTML + '<br>');
2159                });
2160
2161                //Get container content with stripped tags avoiding to strip the expectional tags
2162                if ( exceptionalTags == undefined || exceptionalTags == "" ) {
2163                    $contentContainer.find("*").each(function() {
2164                        $(this).replaceWith(this.innerHTML);
2165                    });
2166                }
2167                else {
2168                    $contentContainer.find("*").not(exceptionalTags).each(function() {
2169                        $(this).replaceWith(this.innerHTML);
2170                    });
2171                }
2172
2173                return $.object_common.stripContent($contentContainer.html());
2174            },
2175
2176            stripTagsNew : function($contentContainer, exceptionalTags)
2177            {
2178                var contentToPost ='';
2179                contentToPost = $contentContainer.html();
2180                return contentToPost.trim();
2181            },
2182
2183            loadJsFile : function (url, isCutParams)
2184            {
2185                var found = false;
2186                var scripts = document.querySelectorAll('script'), i;
2187
2188                if ( isCutParams == undefined || isCutParams ) {
2189                    url = url.split('?')[0] ; //removing all added params
2190                }
2191
2192                for ( var i = 0; i < scripts.length; i++ ) {
2193                    if (scripts[i].src == url) {
2194                        found = true;
2195                        break;
2196                    }
2197                }
2198
2199                if ( ! found ) {
2200                    let script = document.createElement('script');
2201                    script.src = url;
2202                    document.getElementsByTagName('head')[0].appendChild(script);
2203                }
2204
2205            },
2206
2207            loadCssFile : function (url, isCutParams)
2208            {
2209                var found = false;
2210
2211                if ( isCutParams == undefined || isCutParams ) {
2212                    url = url.split('?')[0] ; //removing all added params
2213                }
2214
2215                for ( var i = 0; i < document.styleSheets.length; i++ ) {
2216                    if (document.styleSheets[i].href == url) {
2217                        found = true;
2218                        break;
2219                    }
2220                }
2221
2222                if ( ! found ) {
2223                    $('head').append($('<link rel="stylesheet" type="text/css" href="' + url + '" />'));
2224                }
2225            },
2226
2227            encodePostData : function(dataToPost, postInputSlug)
2228            {
2229                var jSonPostedData = JSON.stringify(dataToPost);
2230                var encodedPostData = window.btoa(jSonPostedData);
2231                var result = {};
2232
2233                result[postInputSlug] = encodedPostData;
2234
2235                return result;
2236            },
2237
2238            setPageUrl : function(fieldSelector, $formContainer) {
2239                if ( fieldSelector == undefined ) {
2240                    fieldSelector = '.tt-global-common-fnc-set-page-url';
2241                }
2242
2243                if ( $formContainer == undefined ) {
2244                    $formContainer = $(document);
2245                }
2246
2247                var pageUrl = window.location.pathname;
2248
2249                if ( pageUrl.indexOf('/') >= 0 ) {
2250                    pageUrl = pageUrl.substring(1);
2251                }
2252
2253                $formContainer.find(fieldSelector).val(pageUrl);
2254            },
2255        };
2256        COMMON = $.object_common;
2257
2258
2259        /* base of FORM global object */
2260        $.object_form = {
2261
2262            hidingUndesirablePlaceholders : function(placeholderSelector) {
2263                $(placeholderSelector).each(function() {
2264                    var $placeholder = $(this);
2265                    var $parentContainer = $placeholder.parent();
2266
2267                    $parentContainer.find(':input:not(:hidden)').each(function() {
2268                        if ( String($(this).val() ?? '').trim() != "" ) {
2269                           $placeholder.css('display', 'none');
2270                           return false;
2271                        }
2272                    });
2273                });
2274            },
2275
2276
2277            setControlOnPlaceholders_AutoHide : function(placeholderSelector, inputSelector, $container)
2278            {
2279                var getPlaceholderObj = function(obj) {
2280                    var $placeholderParent = obj.closest('div.input-group');
2281                    var $placeholder = $placeholderParent.find(placeholderSelector);
2282
2283                    if ( $placeholder.length == 0 ) {
2284                        $placeholder = obj.closest('div.row').find(placeholderSelector);
2285                    }
2286
2287                    return $placeholder;
2288                };
2289
2290                if ( $container == undefined ) {
2291                    $container = $(document);
2292                }
2293
2294                //Hiding placeholders if click happens on the placeholder
2295                $container.find(placeholderSelector).each(function() {
2296                    var $this = $(this);
2297
2298                    if ($this.data('click_event_already_bound') == undefined) {
2299                        $this.data('click_event_already_bound', true);
2300                        $this.data('input_selectors', inputSelector);
2301
2302                        $this.on('click', function(e) {
2303                            e.preventDefault();
2304                            e.stopPropagation();
2305
2306                            var $pHolder = $(this);
2307                            var selectors = $pHolder.data('input_selectors').split(",");
2308
2309                            if ( selectors.length > 0 ) {
2310                                var $input = null;
2311                                var i = 0;
2312                                while (i < selectors.length && ($input == null || $input == undefined)) {
2313                                    if ($pHolder.parent().find('select').length > 0) {
2314                                        $input = $pHolder.parent().find('select');
2315                                    } else {
2316                                        $input = $pHolder.parent().find((selectors[i]).trim());
2317                                    }
2318                                    i++;
2319                                }
2320
2321                                if ( $input ) {
2322                                    if ($pHolder.parent().find('button.multilevel').length > 0) {
2323                                        $pHolder.parent().find('button.multilevel').trigger('click');
2324                                    } else if ($input.parent().find('.dropdown-toggle').length > 0) {
2325                                        $input.parent().find('.selectpicker').selectpicker('refresh');
2326
2327                                        var $toggle = $input.parent().find('.dropdown-toggle').first();
2328                                        var tsSelect = $toggle.closest('.ts-wrapper').prev('select')[0]
2329                                            || $toggle.closest('.ts-wrapper').find('select')[0];
2330
2331                                        if (tsSelect && tsSelect.tomselect) {
2332                                            // TomSelect control. The BS5 shim tags .ts-control as
2333                                            // .dropdown-toggle so Bootstrap draws the caret, but its menu is
2334                                            // .ts-dropdown, not .dropdown-menu - so Bootstrap's Dropdown finds
2335                                            // no menu, stores null and throws on null.classList in _isShown.
2336                                            // Open it through TomSelect's own API instead.
2337                                            tsSelect.tomselect.open();
2338                                        } else if ($toggle.next('.dropdown-menu').length > 0) {
2339                                            $toggle.dropdown('toggle');
2340                                        } else {
2341                                            $toggle.trigger('click');
2342                                        }
2343                                    } else {
2344                                        $input.trigger('focus');
2345                                    }
2346                                }
2347                            }
2348                        });
2349                    }
2350                });
2351
2352                $(document).on('blur', inputSelector, function(event) {
2353                    var $this = $(this);
2354
2355                    //If we have a bootstrap replaced selectpicker - changing object to the hidden select element
2356                    var $tmp = $this.closest('.input-group').find('.selectpicker');
2357                    if ( $tmp.length > 0 ) {
2358                        $this = $tmp;
2359                    }
2360
2361                    //If this a tag-it field, hten changing object to the hidden input field
2362                    if ( $this.hasClass("tagit") ) {
2363                        $tmp = $this.closest('.input-group').find('input');
2364                        if ( $tmp.length > 0 ) {
2365                            $this = $tmp;
2366                        }
2367                    }
2368
2369                    //If this is a multilevel-multiselect field, then changing object to the hidden input field
2370                    if ($this.hasClass("multilevel-multiselect-group")) {
2371                        $tmp = $this.closest('.input-group').find('.selected_ids');
2372                        if ($tmp.length > 0) {
2373                            $this = $tmp;
2374                        }
2375                    }
2376
2377                    var $placeholderObj = getPlaceholderObj($this);
2378
2379                    ( String($this.val() ?? '').trim() == "" )
2380                        ? $placeholderObj.css('display', 'block')
2381                        : $placeholderObj.css('display', 'none');
2382                });
2383
2384                $(document).on('input', inputSelector, function(event) {
2385                    var $placeholderObj = getPlaceholderObj($(this));
2386                    $placeholderObj.css('display', 'none');
2387                });
2388
2389                $(document).on('change', '.selectpicker', function(event) {
2390                    var $placeholderObj = getPlaceholderObj($(this));
2391                    var value = $(this).val();
2392
2393                    if ( Array.isArray(value) ) {
2394                        value = value.filter(n => n);
2395                        if ( value.length == 0 ) {
2396                            value = null;
2397                        }
2398                    }
2399
2400                    (value) ? $placeholderObj.css('display', 'none') : $placeholderObj.css('display', 'block');
2401                });
2402            },
2403
2404            setControlOnPassword_Hint : function(pwdFieldIdSelector, showBtnSelector, hideBtnSelector)
2405            {
2406                var $pwdField = $(pwdFieldIdSelector);
2407                var $showBtn = $pwdField.parent().find(showBtnSelector);
2408                var $hideBtn = $pwdField.parent().find(hideBtnSelector);
2409
2410                $showBtn.data('pwd_field_id_selector', pwdFieldIdSelector).data('show_btn_selector', showBtnSelector).data('hide_btn_selector', hideBtnSelector);
2411                $hideBtn.data('pwd_field_id_selector', pwdFieldIdSelector).data('show_btn_selector', showBtnSelector).data('hide_btn_selector', hideBtnSelector);
2412
2413                $(document).on('click', showBtnSelector, function() {
2414                    var $this = $(this);
2415                    var $pwdField = $($this.data('pwd_field_id_selector'));
2416
2417                    $pwdField.prop("type", "text");
2418                    $pwdField.parent().find($this.data('show_btn_selector')).addClass('hidden');
2419                    $pwdField.parent().find($this.data('hide_btn_selector')).removeClass('hidden');
2420
2421                });
2422
2423                $(document).on('click', hideBtnSelector, function() {
2424                    var $this = $(this);
2425                    var $pwdField = $($this.data('pwd_field_id_selector'));
2426
2427                    $pwdField.prop("type", "password");
2428                    $pwdField.parent().find($this.data('show_btn_selector')).removeClass('hidden');
2429                    $pwdField.parent().find($this.data('hide_btn_selector')).addClass('hidden');
2430
2431                });
2432            },
2433
2434            stripTagInputFieldValues : function($container, inputSelector) {
2435                if (inputSelector == undefined) {
2436                    inputSelector = "";
2437                }
2438
2439                if ( typeof $container === 'string' ) {
2440                    $container = $($container);
2441                }
2442
2443                $container.find(':input'+inputSelector).each(function() {
2444                    $.object_common.stripContent($(this).val(), $(this));
2445                });
2446            },
2447        };
2448        FORM = $.object_form;
2449
2450
2451        /* base of FORM_CHECK global object */
2452        $.object_form_check = {
2453            defaultErrorMsgContainerCssClass : "form-error-msg-container", //css styled in base.css
2454            defaultErrorMsgContainer : '<div class="msgConainterCssClass objAttrId" style="left: 0;margin-top: 5px;"><span class="bg-white text-on-white-bg">errorMsg</span></div>',
2455
2456            displayErrorMessage : function(obj, numberOfErrors, errorMsg, displayMessage, scrollingToTheFirstError, errorContainer)
2457            {
2458                if ( obj.data("field_container_selector") != undefined ) {
2459                    var $fieldContanier = $(obj.data("field_container_selector"));
2460                    $fieldContanier.addClass("error-border");
2461                }
2462
2463                obj.addClass("error");
2464                obj.parent().find('.btn.dropdown-toggle').addClass('error'); //This is for bootstrap changed select fields
2465                obj.parent().find('.chosen-container').addClass('error'); //This is for jQuery changed select fields
2466
2467                if ( errorMsg == undefined ) {
2468                    errorMsg = "";
2469                }
2470
2471                if ( errorContainer == undefined || errorContainer == null ) {
2472                    errorContainer = $.object_form_check.defaultErrorMsgContainer
2473                        .replace('msgConainterCssClass', $.object_form_check.defaultErrorMsgContainerCssClass)
2474                        .replace('objAttrId', obj.attr('id') != undefined ? obj.attr('id') : obj.attr('name'))
2475                        .replace('objOffsetleft', obj.offset().left)
2476                        .replace('errorMsg', errorMsg)
2477                }
2478
2479                if ( numberOfErrors == undefined ) {
2480                    numberOfErrors = -1;
2481                }
2482
2483                if ( displayMessage == undefined ) {
2484                    displayMessage = true;
2485                }
2486
2487                if ( scrollingToTheFirstError == undefined ) {
2488                    scrollingToTheFirstError = true;
2489                }
2490                if ( errorMsg != "" ) {
2491                    if ( displayMessage ) {
2492                        var parentObj = (obj.hasClass("html-check-empty") || obj.hasClass("quill-check-empty")) ? obj.closest(".html-input-group") : obj.closest(".input-group");
2493                        parentObj.find('.' + $.object_form_check.defaultErrorMsgContainerCssClass).remove();
2494                        parentObj.append(errorContainer);
2495                    }
2496
2497                    if ( numberOfErrors == 1 ) {
2498                        if ( scrollingToTheFirstError === true ) {
2499                            var positionTop = obj.offset().top - $('body').offset().top + $('body').scrollTop() - obj.outerHeight() - 60;
2500                            $('html, body').animate({scrollTop: positionTop + 'px'}, 400);
2501                        }
2502                    }
2503                }
2504            },
2505
2506            removeErrorMessage : function(obj)
2507            {
2508                obj.removeClass("error");
2509                if ( obj.data("field_container_selector") != undefined ) {
2510                    var $fieldContanier = $(obj.data("field_container_selector"));
2511                    $fieldContanier.removeClass("error-border");
2512                }
2513
2514                /*
2515                Previous version, but probably sometimes gives wrong parentObj - still leave in the code
2516                Note was added by Lali, on 29.06.2019 - after 2 months testing these changes, these part can be deleted
2517                var parentObj = ( obj.is(':checkbox') )
2518                            ? obj.parent().parent()
2519                            : obj.parent();
2520                */
2521                var parentObj = obj.hasClass("html-check-empty") ? obj.closest(".html-input-group") : obj.closest(".input-group");
2522
2523                parentObj.find('.btn.dropdown-toggle').removeClass('error'); //This is for Bootstrap changed select fields
2524                parentObj.find('.chosen-container').removeClass('error'); //This is for jQuery changed select fields
2525                parentObj.find('.' + $.object_form_check.defaultErrorMsgContainerCssClass).remove();
2526
2527                //Checking no more same named form element
2528                var objName = obj.attr('name');
2529                if ( $('input[type="radio"][name="' + objName + '"]').length > 0 ) {
2530                    $('input[type="radio"][name="' + objName + '"]').each(function() {
2531                        var $radio = $(this);
2532                        var $parentRadio = ( $radio.is(':checkbox') )
2533                            ? $radio.parent().parent()
2534                            : $radio.parent();
2535
2536                        $radio.removeClass("error");
2537                        $parentRadio.find('.btn.dropdown-toggle').removeClass('error');
2538                        $parentRadio.find('.' + $.object_form_check.defaultErrorMsgContainerCssClass).remove();
2539                    });
2540                }
2541            },
2542
2543            removeAllErrorMessages : function(blockSelector) {
2544                $(blockSelector + ' :input').each(function() {
2545                    $.object_form_check.removeErrorMessage($(this));
2546                });
2547            },
2548
2549            isInputFieldsContainerValid : function(blockSelector, isScrollingToTheFirstError, isShowingErrorMessages, selectorsForExcludedContainers, inputSelectors)
2550            {
2551                var $container = $(blockSelector);
2552
2553                if (selectorsForExcludedContainers != undefined && selectorsForExcludedContainers.length > 0) {
2554                    var inputFields = $container.find(':input:not([type=button]):not
2554([type=submit]):not([type=reset]), div.quill-rte').not($(selectorsForExcludedContainers).find(':input'));
2555                } else if (inputSelectors != undefined && selectorsForExcludedContainers.length > 0) {
2556                    var inputFields = $container.find(inputSelectors);
2557                } else {
2558                    var inputFields = $container.find(':input:not([type=button]):not([type=submit]):not([type=reset]), div.quill-rte');
2559                }
2560
2561                var valid = true;
2562                var numberOfErrors = 0;
2563                isScrollingToTheFirstError = ( isScrollingToTheFirstError == undefined ) ? true : isShowingErrorMessages;
2564                isShowingErrorMessages = ( isShowingErrorMessages == undefined ) ? true : isShowingErrorMessages;
2565
2566                inputFields.each(function() {
2567                    var $input = $(this);
2568                    var msg = $.object_form_check.isErrorOnInputField($input);
2569
2570                    if ( $input.attr("name") != undefined ) {
2571                        if ( msg !== false ) {
2572                            numberOfErrors++;
2573                            if (isShowingErrorMessages) {
2574                                $.object_form_check.displayErrorMessage($input, numberOfErrors, msg, true);
2575                            }
2576                            $input.data('is_error_removed', false);
2577                        }
2578                        else {
2579                            $input.data('is_error_removed', true);
2580                            /*
2581                            Previous version, but probably sometimes gives wrong parentObj - still leave in the code
2582                            Note was added by Lali, on 29.06.2019 - after 2 months testing these changes, these part can be deleted
2583                            var parentObj = ( $this.is(':checkbox') )
2584                                        ? $this.parent().parent()
2585                                        : $this.parent();
2586                            */
2587                            var parentObj = $input.closest(".input-group");
2588                            parentObj.find('.' + $.object_form_check.defaultErrorMsgContainerCssClass).remove();
2589                            $input.removeClass("error");
2590                        }
2591                    }
2592                });
2593                //Set control for removing error fields if already not done
2594                if ( numberOfErrors > 0 && isShowingErrorMessages ) {
2595                    var setChanges = ( $container.data('is_set_change_event_listener') == undefined || ! $container.data('is_set_change_event_listener') ) ? false : true;
2596                    if ( ! setChanges ) {
2597                        $container.find(':input').each(function() {
2598                            var $this = $(this);
2599                            $this.on('change', function() {
2600                                if ( ! $this.data('is_error_removed') ) {
2601                                    $('.' + $.object_form_check.defaultErrorMsgContainerCssClass + '.' + $this.attr('id')).remove();
2602                                    $.object_form_check.removeErrorMessage($this);
2603                                    $this.data('is_error_removed', true);
2604                                }
2605                            })
2606                        });
2607
2608                        $container.on('DOMSubtreeModified', ".quill-rte", function() {
2609                            var $this = $(this);
2610                            if ( ! $this.data('is_error_removed') ) {
2611                                $('.' + $.object_form_check.defaultErrorMsgContainerCssClass + '.' + $this.attr('id')).remove();
2612                                $.object_form_check.removeErrorMessage($this);
2613                                $this.data('is_error_removed', true);
2614                            }
2615                        });
2616
2617                        $container.data('is_set_change_event_listener', true);
2618                    }
2619                }
2620
2621                return numberOfErrors == 0;
2622            },
2623
2624            isErrorOnInputField : function($this)
2625            {
2626                var $errors = [];
2627                if ( ! $this.hasClass('do-not-validate') ) {
2628                    if ( $this.hasClass('check-required') ) {
2629                        if ( $.object_form_check.isEmpty($this) ) {
2630                            $errors.push($.object_form_check.defaultIsEmptyErrorMsg)
2631                        }
2632                    }
2633                    if ( $this.hasClass('check-empty') ) {
2634                        if ( $.object_form_check.isEmpty($this) ) {
2635                            $errors.push($.object_form_check.defaultIsEmptyErrorMsg)
2636                        }
2637                    }
2638                    if ( $this.hasClass('html-check-empty') ) {
2639                        if ( $.object_form_check.isEmpty($this) ) {
2640                            $errors.push($.object_form_check.defaultIsEmptyErrorMsg)
2641                        }
2642                    }
2643                    if ( $this.hasClass('quill-check-empty') ) {
2644                        if ( ($this.find(".ql-editor").text()).trim() == "" ) {
2645                            $errors.push($.object_form_check.defaultIsEmptyErrorMsg);
2646                        }
2647                    }
2648                    if ( $this.hasClass('check-selected') ) {
2649                        if ( ! $.object_form_check.isSelected($this) ) {
2650                            $errors.push($.object_form_check.defaultIsSelectedErrorMsg);
2651                        }
2652                    }
2653                    if ( $this.hasClass('check-email') ) {
2654                        if ( ! $.object_form_check.isEmail($this) ) {
2655                            $errors.push($.object_form_check.defaultIsEmailErrorMsg);
2656                        }
2657                    }
2658                    if ( $this.hasClass('check-email-and-empty-vali
2658d') ) {
2659                        if ( ! $.object_form_check.isEmail($this, true) ) {
2660                            $errors.push($.object_form_check.defaultIsEmailErrorMsg);
2661                        }
2662                    }
2663                    if ( $this.hasClass('check-checked') ) {
2664                        if ( ! $.object_form_check.isChecked($this) ) {
2665                            $errors.push($.object_form_check.defaultIsCheckedErrorMsg);
2666                        }
2667                    }
2668                    if ( $this.hasClass('check-confirm') ) {
2669                        if ( ! $.object_form_check.isConfirmed($this, 'confirm_field') ) {
2670                            $errors.push($.object_form_check.defaultIsConfirmedErrorMsg);
2671                        }
2672                    }
2673
2674                    if ( $this.hasClass('check-min-length') ) {
2675                        if ( ! $.object_form_check.isMinLength($this) ) {
2676                            $errors.push($.object_form_check.defaultIsMinLengthErrorMsg.replace("{{PLCHLDR_1}}", $this.attr('minlength')));
2677                        }
2678                    }
2679
2680                    if ( $this.hasClass('check-month-number') ) {
2681                        if ( ! $.object_form_check.isMonthNumber($this) ) {
2682                            $errors.push($.object_form_check.defaultIsMonthNumberErrorMsg);
2683                        }
2684                    }
2685                    if ( $this.hasClass('check-year-number') ) {
2686                        if ( ! $.object_form_check.isMonthNumber($this) ) {
2687                            $errors.push($.object_form_check.defaultIsYearNumberErrorMsg);
2688                        }
2689                    }
2690                    if ( $this.hasClass('check-radio-selected') ) {
2691                        if ( ! $.object_form_check.isRadioSelected($this) ) {
2692                            $errors.push($.object_form_check.defaultIsRadioSelectedErrorMsg);
2693                        }
2694                    }
2695                    if ( $this.hasClass('check-selected-if-visible') ) {
2696                        if ( ! $.object_form_check.isSelectedIfVisible($this) ) {
2697                            $errors.push($.object_form_check.defaultIsSelectedErrorMsg);
2698                        }
2699                    }
2700                    if ( $this.hasClass('check-empty-if-visible') ) {
2701                        if ($.object_form_check.isEmptyIfVisible($this) ) {
2702                            $errors.push($.object_form_check.defaultIsEmptyErrorMsg);
2703                        }
2704                    }
2705                    if ( $this.hasClass('check-name') ) {
2706                        if (! $.object_form_check.isName($this) ) {
2707                            $errors.push($.object_form_check.defaultIsNameErrorMsg);
2708                        }
2709                    }
2710                    if ( $this.hasClass('check-address') ) {
2711                        if (! $.object_form_check.isAddress($this) ) {
2712                            $errors.push($.object_form_check.defaultIsNameErrorMsg);
2713                        }
2714                    }
2715                    if ( $this.hasClass('check-croppie-empty') ) {
2716                        if ($this.val() == "") {
2717                            var $imgContainer = $this.closest('.input-group').find('[data-image_name]');
2718                            if ($imgContainer.data("image_name") == "") {
2719                                $errors.push($.object_form_check.defaultIsEmptyErrorMsg);
2720                            }
2721                        }
2722                    }
2723                    if ( $this.hasClass('check-date-range') ) {
2724                        if (! $.object_form_check.isDateRangeValid($this) ) {
2725                            $errors.push($.object_form_check.defaultIsDateRangeValidErrorMsg);
2726                        }
2727                    }
2728
2729                    if ( $this.hasClass('check-max-min-value') ) {
2730                        if ( ! $.object_form_check.isMaxMinValue($this) ) {
2731                            $errors.push($.object_form_check.defaultIsMaxMinValueErrorMsg.replace("{{PLCHLDR_1}}", $this.attr('minvalue')).replace("{{PLCHLDR_2}}", $this.attr('maxvalue')));
2732                        }
2733                    }
2734                    
2735                    if ($this.attr('data-is_required') === '1') {
2736                        if ($this.hasClass('check-file') && $.object_form_check.isFileUploadEmpty($this)) {
2737                            $errors.push($.object_form_check.defaultIsFileValidErrorMsg);
2738                        }
2739                    }
2740                    if ($this.attr('data-is_required') === '1') {
2741                        if ($this.hasClass('check-file-title') && $.object_form_check.isFileTitleEmpty($this)) {
2742                            $errors.push($.object_form_check.defaultIsFileTitleValidErrorMsg);
2743                        }
2744                    }
2745
2746                }
2747                return ($errors.length)? $errors.join(' '): false;
2748			},
2749
2750            isFileTitleEmpty : function($inputField, defaultEmptyValue) {
2751                if (defaultEmptyValue === undefined) {
2752                    defaultEmptyValue = "";
2753                }
2754
2755                var value = $inputField.val();
2756                return ( COMMON.stripContent(value, $inputField) === defaultEmptyValue );
2757            },
2758
2759            isFileUploadEmpty : function($inputField) {
2760                const inputEl = $inputField.get(0);
2761                if (!inputEl) return true; // invalid element → treat as empty
2762
2763                if (inputEl.files && inputEl.files.length > 0) {
2764                    return false; // not empty
2765                }
2766
2767                // Check docId from input ID
2768                const id = $inputField.attr('id'); // e.g., new_field__142__file__1-5828
2769                if (id) {
2770                    const parts = id.split('-');
2771                    const docId = parseInt(parts[1], 10);
2772
2773                    // Existing file valid even if no new file selected
2774                    if (!isNaN(docId) && docId > 0 || docId === 0) {
2775                        return true;
2776                    }
2777                }
2778                // No existing doc ID, validate file upload
2779                return false;
2780            },
2781
2782            isEmpty : function($inputField, defaultEmptyValue)
2783            {
2784                if (defaultEmptyValue == undefined) {
2785                    defaultEmptyValue = "";
2786                }
2787
2788                $inputField.val( String($inputField.val() ?? '').trim() );
2789
2790                return ( COMMON.stripContent($inputField.val(), $inputField) == defaultEmptyValue );
2791            },
2792
2793            isHtmlEmpty : function($inputField, defaultEmptyValue)
2794            {
2795                var value = $inputField.html();
2796
2797                if (defaultEmptyValue == undefined) {
2798                    defaultEmptyValue = "";
2799                }
2800
2801                return ( COMMON.stripContent(value, $inputField) == defaultEmptyValue );
2802            },
2803
2804            isStrippedEmpty : function(content, defaultEmptyValue)
2805            {
2806                var value = COMMON.stripContent(content, $inputField);
2807
2808                if (defaultEmptyValue == undefined) {
2809                    defaultEmptyValue = "";
2810                }
2811
2812                return ( COMMON.stripContent(value, $inputField) == defaultEmptyValue );
2813            },
2814
2815            isSelected : function($inputField, defaultEmptyValue)
2816            {
2817                // Check if this select is linked with 'currently at' field
2818                if ($inputField.data('currently-at-field-name')) {
2819                    var $currentlyAtField = $('[name="' + $inputField.data('currently-at-field-name') + '"]');
2820
2821                    if ($currentlyAtField.is(':checked')) {
2822                        return true;
2823                    };
2824                }
2825
2826                if (defaultEmptyValue == undefined) {
2827                    defaultEmptyValue = "";
2828                }
2829
2830                var selectFieldSelector = ( $inputField.attr('id') == undefined )
2831                    ? '[name="' + $inputField.attr('name') + '"]'
2832                    : '#' + $inputField.attr('id');
2833
2834                return ( $(selectFieldSelector + ' option:selected').length > 0 && $inputField.val() != defaultEmptyValue )
2835            },
2836
2837            isRadioSelected : function($inputField)
2838            {
2839                var fieldNameAttr = $inputField.attr('name');
2840
2841                return ($('input[type="radio"][name="' + fieldNameAttr + '"]:checked').length > 0);
2842            },
2843
2844            isChecked : function($inputField)
2845            {
2846                return $inputField.is(":checked");
2847            },
2848
2849            isEmail : function($inputField, isEmptyValid)
2850            {
2851                if ( isEmptyValid == undefined ) {
2852                    isEmptyValid = false;
2853                }
2854
2855                var pattern = /^([a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+(\.[a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)*|"((([ \t]*\r\n)?[ \t]+)?([\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*(([ \t]*\r\n)?[ \t]+)?")@(([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.)+([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.?$/i;
2856                var fieldValue = $inputField.val();
2857
2858                return ( ! isEmptyValid || fieldValue != "" )
2859                    ? pattern.test(fieldValue)
2860                    : true;
2861            },
2862
2863            isConfirmed : function($inputFieldConfirm, dataFieldName)
2864            {
2865                var fieldValueConfirm = String($inputFieldConfirm.val() ?? '').trim();
2866                var fieldValue = "";
2867                var fieldName = $inputFieldConfirm.data(dataFieldName)
2868
2869                if ( dataFieldName != undefined ) {
2870                    var $inputField = $('input[name="' + fieldName + '"]').length > 0
2871                        ? $('input[name="' + fieldName + '"]')
2872                        : $('#' + fieldName);
2873                    fieldValue = String($inputField.val() ?? '').trim();
2874                }
2875                else {
2876                    var fieldNameConfrim = fieldName + "_confirmed";
2877                    var $inputFieldConfirm = $('input[name="'+ fieldNameConfrim +'"]');
2878
2879                    if ( $inputFieldConfirm.length > 0 ) {
2880                        fieldValue = String($inputFieldConfirm.val() ?? '').trim();
2881                    }
2882                }
2883
2884                if ( $inputFieldConfirm.attr('data-is_mandatory') == 1 || ( $inputFieldConfirm.attr('data-is_mandatory') == 0 && fieldValueConfirm != "" ) ) {
2885                    return ( fieldValueConfirm == fieldValue && fieldValue != ""  && fieldValueConfirm != "" );
2886                } else {
2887                    return true;
2888                }
2889            },
2890
2891            isMonthNumber : function($inputField)
2892            {
2893                var val = parseInt(COMMON.stripContent($inputField.val(), $inputField));
2894                return (val > 0 && val < 13);
2895            },
2896
2897            isYearNumber : function($inputField)
2898            {
2899                var val = parseInt(COMMON.stripContent($inputField.val(), $inputField));
2900                return (val > 1900 && val < 2050);
2901            },
2902
2903            isMinLength : function($inputField) {
2904                if ( $inputField.attr('minlength') == undefined ) {
2905                    return true;
2906                }
2907
2908                return (COMMON.stripContent($inputField.val(), $inputField).length >= $inputField.attr('minlength') );
2909            },
2910
2911            isSelectedIfVisible : function($inputField) {
2912                if ( $inputField.hasClass("input-visible") ) {
2913                    return $.object_form_check.isSelected($inputField);
2914                } else {
2915                    return true;
2916                }
2917            },
2918
2919            isEmptyIfVisible : function($inputField) {
2920                if ( $inputField.hasClass("input-visible") ) {
2921                    return $.object_form_check.isEmpty($inputField);
2922                } else {
2923                    return false;
2924                }
2925            },
2926
2927            isName : function($inputField) {
2928                return (!/[~`!@#$%\^&*()+=\[\]\\;,\/{}|\\":<>\?]/g.test($inputField.val()));
2929            },
2930
2931            isAddress : function($inputField) {
2932                return (!/[~`!@$%\^+=\\;|\\?]/g.test($inputField.val()));
2933            },
2934
2935            isDateRangeValid: function($inputField) {
2936                var $container = $inputField.closest('.custom-field-group-container');
2937                var $selects = $('.date-range', $container);
2938                var date_range_day1 = 1;
2939                var date_range_month1 = 1;
2940                var date_range_year1 = 1900;
2941                var date_range_day2 = 31;
2942                var date_range_month2 = 12;
2943                var date_range_year2 = new Date().getFullYear();
2944                var value;
2945                $selects.each(function(){
2946                    switch ($(this).data('range-name')) {
2947                        case 'date_range_day1':
2948                            value = $(this).find('option:selected').val();
2949                            if (typeof value !== 'undefined') {
2950                                date_range_day1 = value;
2951                            }
2952                            break;
2953                        case 'date_range_month1':
2954                            value = $(this).find('option:selected').val();
2955                            if (typeof value !== 'undefined') {
2956                                date_range_month1 = value;
2957                            }
2958                            break;
2959                        case 'date_range_year1':
2960                            value = $(this).find('option:selected').val();
2961                            if (typeof value !== 'undefined') {
2962                                date_range_year1 = value;
2963                            }
2964                            break;
2965                        case 'date_range_day2':
2966                            value = $(this).find('option:selected').val();
2967                            if (typeof value !== 'undefined') {
2968                                date_range_day2 = value;
2969                            }
2970                            break;
2971                        case 'date_range_month2':
2972                            value = $(this).find('option:selected').val();
2973                            if (typeof value !== 'undefined') {
2974                                date_range_month2 = value;
2975                            }
2976                            break;
2977                        case 'date_range_year2':
2978                            value = $(this).find('option:selected').val();
2979                            if (typeof value !== 'undefined') {
2980                                date_range_year2 = value;
2981                            }
2982                            break;
2983                    }
2984                });
2985
2986				var date1 = ( date_range_year1 != '' )
2987					? new Date(date_range_year1, date_range_month1 - 1, date_range_day1)
2988					: false;
2989                var date2 = ( date_range_year2 != '' )
2990					? new Date(date_range_year2, date_range_month2 - 1, date_range_day2)
2991					: false;
2992
2993				if (date1 === false && date2 === false) return true;
2994				return (date1 === false || date2 === false || date1 > date2) ? false : true;
2995            },
2996
2997            isMaxMinValue : function($inputField) {
2998                if ( $inputField.attr('maxvalue') == undefined ) {
2999                    return true;
3000                }
3001
3002                if($inputField.attr('maxvalue') == '') {
3003                    return true;
3004                }
3005
3006                if ( $inputField.attr('minvalue') == undefined ) {
3007                    return true;
3008                }
3009
3010                if($inputField.attr('minvalue') == '') {
3011                    return true;
3012                }
3013
3014                var val = parseInt(COMMON.stripContent($inputField.val(), $inputField));
3015
3016                //this for non required field
3017                if ( ! $inputField.hasClass("check-selected") ) {
3018                    if(val >= $inputField.attr('maxvalue') || val <= $inputField.attr('minvalue')) {
3019                        return (val <= $inputField.attr('maxvalue') && val >= $inputField.attr('minvalue'));
3020                    }
3021                    else {
3022                        return true;
3023                    }
3024                }
3025
3026                return (val <= $inputField.attr('maxvalue') && val >= $inputField.attr('minvalue'));
3027            },
3028        };
3029
3030        $(function() {
3031            // Merge object2 into object1
3032            $.extend($.object_form_check, {
3033                defaultIsEmptyErrorMsg : lang('customization_form:this_field_is_required'),
3034                defaultIsSelectedErrorMsg : lang('forms:not_selected'),
3035                defaultIsEmailErrorMsg : lang('forms:invalid_email_format'),
3036                defaultIsCheckedErrorMsg : lang('forms:not_ticked'),
3037                defaultIsConfirmedErrorMsg : lang('forms:invalid_confirmation'),
3038                defaultIsMinLengthErrorMsg : lang('forms:at_least_n_characters'),
3039                defaultIsYearNumberErrorMsg : lang('forms:invalid_year'),
3040                defaultIsMonthNumberErrorMsg : lang('forms:invalid_month'),
3041                defaultIsRadioSelectedErrorMsg : lang('forms:not_selected'),
3042                defaultIsNameErrorMsg : lang('forms:no_special_characters_allowed'),
3043                defaultIsDateRangeValidErrorMsg : lang('forms:invalid_date_range'),
3044                defaultIsMaxMinValueErrorMsg : lang('forms:at_least_n_max_min_values'),
3045                defaultIsFileValidErrorMsg : lang('forms:missing_file'),
3046                defaultIsFileTitleValidErrorMsg : lang('forms:missing_file_title'),
3047
3048            });
3049        });
3050
3051        FORM_CHECK = $.object_form_check;
3052
3053    })(jQuery);
3054
3055
3056    $(window).on('load', function() {
3057        ttGlobal_SetBgColorFadeEffect();
3058        ttGlobal_RemoveSlickCarouselCover();
3059    });
3060
3061
3062    function ttGlobal_SetBgColorFadeEffect() {
3063        $('.js-control-bg-color-fade-effect').each(function() {
3064            var $element = $(this);
3065            var opacityEnd = $element.data('background_color_transparency') != undefined ? $element.data('background_color_transparency') : 0;
3066            var fadeEffectSpeed = $element.data('fade_effect_speed') != undefined ? $element.data('fade_effect_speed') : 1500;
3067
3068            if (opacityEnd > 1) {
3069                opacityEnd = 1-(opacityEnd/100).toFixed(2);
3070            }
3071
3072            $element.animate({opacity: opacityEnd}, fadeEffectSpeed);
3073        });
3074    } //ttGlobal_SetBgColorFadeEffect
3075
3076
3077    function ttGlobal_RemoveSlickCarouselCover() {
3078        $('.js-control-remove-slick-carousel-cover').each(function() {
3079            var $carousel = $(this);
3080            var $cover = $carousel.closest('section').find('.slick-carousel-cover');
3081
3082            $cover.removeClass('d-block').addClass('d-none');
3083            $carousel.removeClass('d-none');
3084        });
3085    } //ttGlobal_RemoveSlickCarouselCover
3086
3087
3088$(function() {
3089
3090});

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.