1(function ($) { 2 $(document).ready(function () { 3 4 // ================================================ 5 // FACETWP SETUP: Wait until FWP is available 6 // ================================================ 7 function initFacetWP() { 8 if (typeof FWP === 'undefined' || typeof FWP.hooks === 'undefined') { 9 setTimeout(initFacetWP, 100); 10 return; 11 } 12 13 /* ============================================================ 14 Load More override 15 ============================================================ */ 16 FWP.hooks.addFilter('facetwp/template_html', function (resp, params) { 17 if (FWP.is_load_more) { 18 FWP.is_load_more = false; 19 20 var $html = $(params.html); 21 // layout builder 22 if (0 < $('.fwpl-layout').length) { 23 $('.fwpl-layout').append($html.html()); 24 } else { 25 $('.facetwp-template').append($html); 26 } 27 28 $('a', $html).first().focus(); // accessibility fix 29 return true; 30 } 31 return resp; 32 }, 8); 33 34 // Search button reimplementation 35 $(document).on('click', '.facetwp-type-search .search-button', function () { 36 if (!FWP.is_refresh) { 37 FWP.autoload(); 38 } 39 }); 40 41 // Sort as links instead of dropdown 42 $(document).on('click', '.facetwp-sort-links a', function (e) { 43 e.preventDefault(); 44 FWP.extras.sort = $(this).data('value'); 45 FWP.autoload(); 46 }); 47 48 // Overlay on refresh 49 $(document).on('facetwp-refresh', function () { 50 if (!FWP.soft_refresh) { 51 $(document).on('facetwp-loaded', loadSlickSlider); 52 } 53 let $el = $('.facetwp-sort'); 54 $el.prepend('<div class="facetwp-overlay">'); 55 $el.find('.facetwp-overlay').css({ 56 width: $el.width(), 57 height: $el.height() 58 }); 59 }); 60 61 $(document).on('facetwp-loaded', function () { 62 $('.facetwp-sort .facetwp-overlay').remove(); 63 }); 64 65 function loadSlickSlider() { 66 var $domains = $('.domains'); 67 if (!$domains.length || $domains.hasClass('slick-initialized')) { 68 $(document).off('facetwp-loaded', loadSlickSlider); 69 return; 70 } 71 72 // Roving tabindex: only the current (center) slide link is focusable. Slick clones 73 // slides for infinite mode, so we must scope to the whole slider and use .slick-current. 74 function setDomainRovingTabindex(event, slick) { 75 if (!slick || !slick.$slider) { return; } 76 77 // hide everything including clones 78 slick.$slider.find('.slick-slide') 79 .attr('aria-hidden', 'true') 80 .find('a, button, input, select') 81 .attr('tabindex', '-1'); 82 83 // expose only the current slide 84 slick.$slider.find('.slick-current') 85 .attr('aria-hidden', 'false') 86 .find('.facetwp-domain') 87 .attr('tabindex', '0'); 88 } 89 $domains.on('init afterChange', setDomainRovingTabindex); 90 91 // This announces the current domain to screen readers when forward/backward buttons are used 92 // Since the entire carousel changes, we can't just use it on the current slide 93 function announceDomainChange(event, slick, currentSlide) { 94 var text = slick.$slides 95 .eq(currentSlide) 96 .find('a') 97 .text(); 98 99 $('#js-domain-carousel-announce').text(text + ', ' + (currentSlide + 1) + ' of ' + slick.slideCount); 100 } 101 $domains.on('afterChange', announceDomainChange); 102 103 $domains.slick({ 104 infinite: true, 105 centerMode: true, 106 centerPadding: '100px', 107 accessibility: false, 108 focusOnSelect: false, 109 focusOnChange: false, 110 speed: 250, 111 prevArrow: '<button type="button" class="slick-prev" aria-label="Previous domain">Previous</button>', 112 nextArrow: '<button type="button" class="slick-next" aria-label="Next domain">Next</button>', 113 slidesToShow: 9, 114 slidesToScroll: 1, 115 responsive: [ 116 { breakpoint: 1885, settings: { slidesToShow: 8 } }, 117 { breakpoint: 1660, settings: { slidesToShow: 7 } }, 118 { breakpoint: 1435, settings: { slidesToShow: 6 } }, 119 { breakpoint: 1210, settings: { slidesToShow: 5 } }, 120 { breakpoint: 1050, settings: { slidesToShow: 4 } }, 121 { breakpoint: 885, settings: { slidesToShow: 3 } }, 122 { breakpoint: 720, settings: { slidesToShow: 2 } }, 123 { breakpoint: 575, settings: { slidesToShow: 2, centerPadding: '60px' } }, 124 { breakpoint: 440, settings: { slidesToShow: 1, centerPadding: '100px' } }, 125 { breakpoint: 380, settings: { slidesToShow: 1, centerPadding: '80px' } } 126 ] 127 }); 128 129 $(document).off('facetwp-loaded', loadSlickSlider); 130 } 131 132 // Highlight current sort link 133 $(document).on('facetwp-loaded', function () { 134 $('.facetwp-dropdown').attr('id', 'facetwp-dropdown'); 135 $('.facetwp-sort-links a').removeClass('selected'); 136 $('.facetwp-sort-links a.sort-' + FWP.extras.sort).addClass('selected'); 137 }); 138 139 /* ============================================================ 140 Google Tag Manager events 141 ============================================================ */ 142 let previousFilters = {}; 143 let isLoadMoreClicked = false;
144 145 function getCurrentFilters() { 146 let filters = FWP.facets || {}; 147 let filterData = {}; 148 Object.keys(filters).forEach(function (key) { 149 let value = filters[key]; 150 if (Array.isArray(value) && value.length > 0) { 151 value = value.join(', '); 152 } else if (typeof value === 'string' && value.trim() !== '') { 153 value = value; 154 } else { 155 return; 156 } 157 filterData[`filter_${key}`] = value; 158 }); 159 return filterData; 160 } 161 162 $(document).on('click', '.facetwp-load-more', function () { 163 isLoadMoreClicked = true; 164 let loadMoreEvent = { 165 'event': 'facetwp_load_more_clicked', 166 ...getCurrentFilters() 167 }; 168 window.dataLayer = window.dataLayer || []; 169 window.dataLayer.push(loadMoreEvent); 170 }); 171 172 $(document).on('facetwp-loaded', function () { 173 if (typeof FWP === 'undefined') return; 174 if (isLoadMoreClicked) { 175 isLoadMoreClicked = false; 176 return; 177 } 178 179 let currentFilters = getCurrentFilters(); 180 let hasChanges = false; 181 let eventData = { 'event': 'facetwp_filter_applied', ...currentFilters }; 182 183 Object.keys(currentFilters).forEach(function (key) { 184 if (previousFilters[key] !== currentFilters[key]) { 185 hasChanges = true; 186 } 187 }); 188 189 if (hasChanges) { 190 window.dataLayer = window.dataLayer || []; 191 window.dataLayer.push(eventData); 192 previousFilters = { ...currentFilters }; 193 } 194 }); 195 196 /* ============================================================ 197 Preserve /trainings/{domain}/{subdomain} 198 Prevent FacetWP from wiping the pretty path on soft refresh 199 ============================================================ */ 200 (function () { 201 if (typeof FWP === 'undefined') return; 202 203 let lastPretty = ''; 204 205 function slug(x) { return (x || '').toString().toLowerCase(); } 206 207 function getFacetValue(facetName) { 208 // Return the current facet selection if available, else fallback from URL 209 if (FWP.facets && Array.isArray(FWP.facets[facetName]) && FWP.facets[facetName].length) { 210 return slug(FWP.facets[facetName][0]); 211 } 212 213 // Fallback: parse from URL path when FWP.facets not ready 214 const pathParts = window.location.pathname.split('/').filter(Boolean); 215 const idx = pathParts.indexOf('trainings'); 216 if (idx > -1) { 217 if (facetName === '_domains' && pathParts[idx + 1]) return slug(pathParts[idx + 1]); 218 if (facetName === '_sub_domains' && pathParts[idx + 2]) return slug(pathParts[idx + 2]); 219 } 220 return ''; 221 } 222 223 function buildPrettyPath() { 224 // const domainSel = (FWP.facets && FWP.facets['_domains']) || []; 225 // const subdomainSel = (FWP.facets && FWP.facets['_sub_domains']) || []; 226 const domainSel = getFacetValue('_domains'); 227 const subdomainSel = getFacetValue('_sub_domains'); 228 const domain = slug(domainSel || ''); 229 const subdomain = slug(subdomainSel || ''); 230 const segments = ['trainings']; 231 if (domain && domain !== 'default') segments.push(encodeURIComponent(domain)); 232 if (subdomain && subdomain !== 'default') segments.push(encodeURIComponent(subdomain)); 233 return '/' + segments.join('/') + '/'; 234 } 235 236 function buildQueryString() { 237 const params = []; 238 if (FWP && FWP.facets) {
239 Object.keys(FWP.facets).forEach(function (key) { 240 if ( 241 key === 'domains' || key === 'sub_domains' || 242 key === '_domains' || key === '_sub_domains' 243 ) return; // skip both legacy + underscored facet keys 244 const val = FWP.facets[key]; 245 if (Array.isArray(val) && val.length) { 246 params.push(encodeURIComponent(key) + '=' + encodeURIComponent(val.join(','))); 247 } else if (typeof val === 'string' && val) { 248 params.push(encodeURIComponent(key) + '=' + encodeURIComponent(val)); 249 } 250 }); 251 } 252 if (FWP && FWP.extras && FWP.extras.searched) { 253 params.push('_s=' + encodeURIComponent(FWP.extras.searched)); 254 } 255 if (FWP && FWP.paged && FWP.paged > 1) { 256 params.push('paged=' + encodeURIComponent(FWP.paged)); 257 } 258 if (FWP && FWP.sort && FWP.sort !== 'default') { 259 params.push('_sort=' + encodeURIComponent(FWP.sort)); 260 } 261 return params.length ? ('?' + params.join('&')) : ''; 262 } 263 264 function updatePrettyURL(force = false) { 265 const pretty = buildPrettyPath() + buildQueryString(); 266 if (force || (pretty !== lastPretty && window.location.pathname + window.location.search !== pretty)) { 267 history.replaceState(null, '', pretty); 268 lastPretty = pretty; 269 } 270 } 271 // This replaces anyone landing on the page with the OLD style of parameters with the 272 // New full path 273 // Run only once after first real load (not pre-AJAX) 274 let initialized = false; 275 $(document).on('facetwp-loaded', function () { 276 if (!initialized) { 277 initialized = true; 278 // Delay a little to let FacetWP finish any internal hash cleanup 279 setTimeout(() => updatePrettyURL(true), 50); 280 } else { 281 // On subsequent refreshes, update only if path facets changed 282 updatePrettyURL(); 283 } 284 }); 285 286 // Intercept any direct call to FWP.setHash (FacetWP internal) 287 FWP.setHash = function () { 288 updatePrettyURL(); 289 }; 290 })(); 291 292 293 } 294 295 // Wait for FacetWP global 296 initFacetWP(); 297 298 }); // end document ready 299})(jQuery);
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.