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">×</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.