1ACC.refinements = { 2 3 _autoload: [ 4 "handlebarsHelpers", 5 "handleHandlebarsFacets", 6 "bindProdListStockInfo", 7 "bindGeneral", 8 ["bindMoreLessToggles", $(".js-handlebars-facets").length !== 0] 9 ], 10 11 currentProdList: [], 12 mobileOpenFacet: [], 13 14 bindGeneral: function() { 15 localStorage.removeItem('fullFacetsList'); 16 }, 17 18 bindProdListStockInfo: function () { 19 if ($(".js-product-stock").length) { 20 $(".js-product-stock").each(function () { 21 var code = $(this).data("code"); 22 if(code) { 23 ACC.refinements.currentProdList.push(code.toString()); 24 } 25 }); 26 ACC.refinements.getProdStocks(); 27 } 28 }, 29 30 getProdStocks: function (sku) { 31 var codes = sku || ACC.refinements.currentProdList.join(","); 32 if(codes && ACC.userLogged) { 33 $.ajax({ 34 type: "GET", 35 url: ACC.config.encodedContextPath + "/rest/products/product/" + codes + "/availability", 36 contentType: "application/json", 37 headers: { 38 CSRFToken: ACC.config.CSRFToken, 39 }, 40 success: function (data) { 41 $(".js-product-stock:empty").each(function () { 42 var code = $(this).data("code"); 43 44 if(code && !sku || !code && sku ) { 45 var codeOrSku = sku || code.toString(); 46 var stock = data.find(function (item) { 47 return item.product.code === codeOrSku; 48 })?.product.stock.stockLevelStatus.code; 49 50 if(stock) { 51 // outOfStock | lowStock | inStock 52 var out = stock === "outOfStock"; 53 var typeVal = out ? "excl" : "tick"; 54 var colorType = out ? "blue" : stock === "inStock" ? "green" : "yellow"; 55 56 $(this) 57 .text(ACC.text[stock]) 58 .prepend(ACC.global.getSvgStockInfo(typeVal, colorType)) 59 .removeClass("hidden"); 60 } 61 } 62 }); 63 }, 64 error: function (data) { 65 ACC.navigation.handleSessionError(data, 400); 66 } 67 }); 68 } 69 }, 70 71 bindMoreLessToggles: function () { 72 $(document).on("click", ".js-more-handlebars-facets", function (e) { 73 e.preventDefault(); 74 75 var container = $(this).parents(".js-facet"); 76 container.find(".js-facet-list li:hidden").show().attr('data-more', 'open'); 77 $(this).parent().hide().next().show(); 78 }); 79 $(document).on("click", ".js-less-handlebars-facets", function (e) { 80 e.preventDefault(); 81 82 var container = $(this).parents(".js-facet"); 83 container.find(".js-facet-list li[data-more]").hide().removeAttr('data-more'); 84 $(this).parent().hide().prev().show(); 85 86 var fromTop = container.offset().top - $('.js-mainHeader').height(); 87 $('html, body').animate({scrollTop: fromTop}, 100); 88 }); 89 90 }, 91 92 handlebarsFacetsMethods: { 93 handleLocalStocked: (data) => { 94 const localStocked = data.facets?.find(({ code }) => code === 'inLocalStorage'); 95 const localStockedBtn = $('body').find('.js-plp-local-stocked-btn'); 96 localStockedBtn.addClass('hidden'); 97 98 if(!localStocked) { 99 return 100 } 101 102 const trueVal = localStocked.values.find(({ code }) => code === 'true'); 103 if(!trueVal) { 104 return 105 } 106 107 localStockedBtn 108 .attr("data-value", trueVal.query.value) 109 .attr("data-url", trueVal.query.url) 110 .removeClass("hidden") 111 .prev() 112 .prop("checked", trueVal?.selected); 113 }, 114 handleFacets: (data, id) => { 115 const d = { 116 ...data, 117 facets: data.facets 118 ?.filter( 119 (f) => 120 (f.slider && f.values.length > 1) || 121 ["type", "grade", "surface", "lowCO2Impact"].includes(f.code) 122 ) 123 .sort((a, b) => { 124 const nameA = a.name.toLowerCase(); 125 const nameB = b.name.toLowerCase(); 126 const aS = a.slider; 127 const bS = b.slider; 128 const eq = (aS || bS) && bS - aS; 129 return eq !== 0 130 ? eq 131 : nameA > nameB 132 ? 1 133 : nameB > nameA 134 ? -1 135 : 0; 136 }) 137 .map((f) => { 138 const topVal = f.values.splice(0, ACC.config.facetTopValues); 139 const values = [ 140 ...topVal, 141 ...f.values.sort((a, b) => { 142 const nameA = a.name.toLowerCase(); 143 const nameB = b.name.toLowerCase(); 144 const aS = a.selected; 145 const bS = b.selected; 146 return f.slider 147 ? 0 148 : aS || bS 149 ? bS - aS 150 : nameA > nameB 151 ? 1 152 : nameB > nameA 153 ? -1 154 : 0; 155 }), 156 ]; 157 return { ...f, values }; 158 }), 159 }; 160 const { handlebarsTpl, handleLocalStocked } = ACC.refinements.handlebarsFacetsMethods; 161 162 handlebarsTpl(d, 'facets'); 163 164 d.facets = data.facets.filter(f => f.code === 'category'); 165 handlebarsTpl(d, 'facetsCat'); 166 handleLocalStocked(data); 167 168 const val = $("body").find('[data-facet-id="' + id + '"] .js-facet-values'); 169 if (window.innerWidth >= 1024) { 170 val.removeClass("hidden").prev().addClass("open"); 171 } 172 }, 173 handlebarsTpl: (d, id, nextPage) => { 174 const tpl = $('#' + id + 'Handlebars').html() 175 const template = Handlebars.compile(tpl); 176 const { displayPrices, showPrices, userLogged } = ACC; 177 let data = { 178 ...d, 179 displayPrices, 180 showPrices, 181 userLogged 182 }; 183 const facetObj = template({data: data}); 184 185 if(nextPage) { 186 $(facetObj).insertBefore('.js-handlebars-newProducts'); 187 } else {
188 $('.js-handlebars-' + id).html(facetObj); 189 } 190 if(id === 'facets') { 191 ACC.refinements.sliderRange(data.facets, data.breadcrumbs); 192 } 193 if(id === 'products') { 194 ACC.product.handleListAttrsLength(); 195 } 196 }, 197 198 handlebarsAjaxCall: (url, callback, callbackError, trackResults = true, again) => { 199 if(url) { 200 if(url.includes('/search/')) { 201 url = url.replace('?text', '?q'); 202 } 203 204 if (url.includes("/facets")) { 205 if (localStorage.getItem("fullFacetsList")) { 206 $(".js-handlebars-facets") 207 .addClass("progressing") 208 .prepend("<div class='loader js-loader'></div>"); 209 } else { 210 if (!url.includes("/search/") && url.includes("q=")) { 211 const cleanUrl = url.slice(0, url.indexOf("q=")); 212 213 ACC.refinements.handlebarsFacetsMethods.handlebarsAjaxCall( 214 cleanUrl, 215 callback, 216 callbackError, 217 trackResults, 218 url 219 ); 220 return; 221 } 222 } 223 } 224 225 $.ajax({ 226 'type': 'GET', 227 'url': url, 228 'contentType': 'application/json', 229 'headers': { 230 'CSRFToken': ACC.config.CSRFToken 231 }, 232 success: function (data) { 233 // for facets call 234 if (url.includes('/facets')) { 235 if (!(url.includes('q='))) { 236 localStorage.setItem('fullFacetsList', JSON.stringify(data)); 237 } 238 if(again) { 239 ACC.refinements.handlebarsFacetsMethods.handlebarsAjaxCall( 240 again, 241 callback, 242 callbackError, 243 trackResults 244 ); 245 return; 246 } 247 } 248 249 if (typeof callback == 'function') 250 callback(data); 251 252 // for results 253 if (url.includes('/results')) { 254 ACC.refinements.currentProdList = []; 255 data.results.forEach(function(r){ 256 ACC.refinements.currentProdList.push(r.code) 257 }) 258 if (ACC.userLogged && ACC.config.searchMode === 'variant') { 259 ACC.refinements.getProdStocks(); 260 } 261 if (trackResults) { 262 ACC.track.trackProductsListGA4(data, location.search) 263 } 264 265 const { sort, originalQuery } = data.pagination; 266 ACC.refinements.setSortSelect(originalQuery); 267 268 if(originalQuery) { 269 $('.js-handlebars-clean-all-facets').attr('data-sort', sort); 270 } 271 ACC.refinements.handlebarsFacetsMethods.handleCleanAllBtn(); 272 } 273 274 if (url.includes('/facets')) { 275 if (window.innerWidth < 1024) { 276 $('.js-facet-values').removeClass('hidden') 277 $('.js-facet').each(function(){ 278 const facetId = $(this).data('facetId'); 279 280 if(ACC.refinements.mobileOpenFacet.find(id => id === facetId)){ 281 $(this).addClass('active'); 282 } 283 }) 284 ACC.refinements.mobileOpenFacet = []; 285 } 286 } 287 }, 288 error: function () { 289 if (typeof callbackError == 'function') 290 callbackError(); 291 }, 292 complete: function() { 293 $(".js-handlebars-facets") 294 .removeClass("progressing") 295 .find("js-loader") 296 .remove(); 297 } 298 }); 299 } 300 }, 301 302 handleCleanAllBtn: () => { 303 const btn = $('.js-handlebars-clean-all-facets'); 304 const sorts = ACC.refinements.getSortSelect(); 305 const isCampaign = sorts.includes(ACC.variables.CAMPAIGN_STRING); 306 const params = sorts.split(':') 307 308 customToggleClass(btn, 'hidden', !(params.length > (isCampaign ? 4 : 2))) 309 }, 310 311 buildUrl: (dest) => { 312 const search = location.search.replace('?text=', '?q=') 313 return location.pathname + dest + search; 314 }, 315 316 updateCallUrl: (url, dest) => { 317 return url?.replace('?q=', dest + '?q='); 318 } 319 }, 320 321 handleHandlebarsFacets: function(){ 322 const { 323 handlebarsAjaxCall, 324 handlebarsTpl, 325 updateCallUrl, 326 handleCleanAllBtn, 327 buildUrl, 328 handleFacets, 329 } = ACC.refinements.handlebarsFacetsMethods; 330
331 if($(".js-handlebars-facets").length !== 0) { 332 handlebarsAjaxCall(buildUrl('/facets'), function (data) { 333 handleFacets(data); 334 handleCleanAllBtn(); 335 }) 336 } 337 338 //click on facet 339 $('body').on("click", ".js-handlebars-facet", function (e) { 340 const dataUrl = $(this).attr('data-url'); 341 const parentId = $(this).parents('.js-facet').data('facetId') 342 343 if (window.innerWidth < 1024) { 344 $('.js-facet').each(function(){ 345 if($(this).hasClass('active')){ 346 ACC.refinements.mobileOpenFacet.push($(this).data('facetId')) 347 } 348 }) 349 } 350 351 handlebarsAjaxCall( 352 updateCallUrl(dataUrl, '/facets'), 353 function (data) { 354 handleFacets(data, parentId); 355 for (const [type, count] of Object.entries(data.itemsFoundPerSearchItemType)) { 356 handlebarsTpl({count: count}, 'total-results-' + type); 357 } 358 } 359 ); 360 361 handlebarsAjaxCall(updateCallUrl(dataUrl, '/results'), function (data) { 362 history.pushState(null, null, dataUrl); 363 handlebarsTpl(data, 'products'); 364 handlebarsTpl(data, 'pagination'); 365 }); 366 }); 367 368 $('.js-handlebars-pagination').on("click", ".js-pagination-link", function (e) { 369 e.preventDefault(); 370 var urlTarget = $(this).attr('href'); 371 372 handlebarsAjaxCall(urlTarget.replace('?','/results?'), function (data) { 373 history.pushState(null, null, urlTarget); 374 handlebarsTpl(data, 'products'); 375 handlebarsTpl(data, 'pagination'); 376 377 var fromTop = $('.js-mainHeader').height() + 110; 378 $('html, body').animate({scrollTop: fromTop}, 300); 379 }); 380 }); 381 382 document.addEventListener("click", function (e) { 383 if($('.js-product-facet').length && !$(e.target).hasClass('open')) { 384 if(!$(e.target).parents('.js-facet').length) { 385 $('body').find('.js-facet-select.open').removeClass('open').next().addClass('hidden'); 386 } 387 } 388 }, true); 389 390 $(document).on("click", ".js-facet-select", function () { 391 const clicked = $(this) 392 $('.js-facet-select').each(function() { 393 if($(this)[0] === clicked[0]) 394 $(this).toggleClass('open').next().toggleClass('hidden'); 395 else 396 $(this).removeClass('open').next().addClass('hidden'); 397 }) 398 }); 399 400 $(document).on("click", ".js-handlebars-clean-all-facets", function (e) { 401 e.preventDefault(); 402 const dataSort = $(this).data('sort'); 403 404 function urlClear(url) { 405 let dest = (url || '') + '?q='; 406 const isSearch = location.pathname.includes('/search'); 407 408 if(isSearch) { 409 const qParams = ACC.refinements.getSortSelect().split(':'); 410 dest += qParams[0] 411 } else { 412 dest += ACC.variables.URL_SEPARATOR + dataSort; 413 } 414 415 return location.pathname + dest; 416 } 417 418 handlebarsAjaxCall(urlClear('/facets'), function (data) { 419 handleFacets(data); 420 for (const [type, count] of Object.entries(data.itemsFoundPerSearchItemType)) { 421 handlebarsTpl({count: count}, 'total-results-' + type); 422 } 423 }); 424 425 handlebarsAjaxCall(urlClear('/results'), function (data) { 426 history.pushState(null, null, urlClear()); 427 handlebarsTpl(data, 'products'); 428 handlebarsTpl(data, 'pagination'); 429 }); 430 }); 431 }, 432 433 handlebarsHelpers: function(){ 434 Handlebars.registerHelper({ 435 inc: function(v1, v2) { return v1 + v2}, 436 dec: function(v1, v2) { return v1 - v2}, 437 eq: function(v1, v2) { return v1 === v2}, 438 ne: function(v1, v2) { return v1 !== v2}, 439 lt: function(v1, v2) { return v1 < v2}, 440 gt: function(v1, v2) { return v1 > v2}, 441 not: function(v) { return !v}, 442 co2Format: function (v) { return Number(v).toFixed(3)}, 443 idFacetCleaner: function (id) { 444 const re = /[\W_]/g; 445 return id.replace(re, ''); 446 }, 447 getPrimaryImageUrl: function (images, format) { 448 return ( 449 images?.find( 450 (i) => i.imageType === "PRIMARY" && i.format === format 451 )?.url || undefined 452 ); 453 }, 454 paginationStart: function (limit, pagination, halfLimit) { 455 if(limit > pagination.numberOfPages) { 456 return 1 457 } else if(pagination.currentPage + halfLimit >= limit) { 458 if(pagination.currentPage + halfLimit < pagination.numberOfPages){ 459 if(pagination.currentPage + 1 - halfLimit > 0){ 460 return pagination.currentPage + 1 - halfLimit
461 } else { 462 return 1 463 } 464 } else { 465 return pagination.numberOfPages + 1 - limit 466 } 467 } else { 468 return 1 469 } 470 }, 471 pagesList: function (limit, start, numOfPages) { 472 var arr = []; 473 var length = numOfPages > limit ? limit : numOfPages; 474 for (let i = 0; i < length; i++) { 475 arr[i] = start + i; 476 } 477 return arr 478 }, 479 topVal: function (index, selected) { 480 return !(selected || index < ACC.config.facetTopValues); 481 }, 482 selectedName: function (arr) { 483 const count = arr.reduce( 484 (acc, item) => (item.selected ? [...acc, item.name] : acc), 485 [] 486 ); 487 return ( 488 (!count.length && ACC.text.selectDefault) || 489 (count.length === 1 && count[0]) || 490 `${ACC.text.selectSelected} (${count.length})` 491 ); 492 }, 493 showLessMore: function(obj) { 494 return obj.length > ACC.config.facetTopValues 495 }, 496 getUrl: function (page) { 497 let { href, search, pathname } = location; 498 const p = "page="; 499 const isPager = href.indexOf(p); 500 const sort = ACC.refinements.getSortSelect(); 501 502 if (search) { 503 if (isPager > -1) { 504 const filtered = search 505 .split("&") 506 .filter((el) => !el.includes(p)); 507 href = pathname + filtered.join("&"); 508 } 509 } else { 510 href += `?q=${ACC.variables.URL_SEPARATOR}${sort}`; 511 } 512 513 return `${href}&${p}${page}`; 514 }, 515 trimString: function (str) { 516 return str.length > 25 517 ? new Handlebars.SafeString(str.substring(0, 20)) + "..." 518 : str; 519 }, 520 isLoginOnPriceList: (userLogged, displayPrices, showPrices) => 521 (userLogged || displayPrices) && showPrices 522 }); 523 }, 524 525 setSortSelect: (val) => { 526 // set the correct value for sorting 527 $('[id*="sortForm"').each(function() { 528 $(this).find('input[name="q"]').val(val); 529 }); 530 }, 531 532 getSortSelect: () => $('body').find('[id*="sortForm"] input[name="q"]')[0]?.value, 533 534 sliderRange: function(facets, breadcrumbs){ 535 const range = $(".js-facet-range-slider"); 536 537 let apply; 538 const applySlider = (el) => { 539 if(apply) { 540 clearTimeout(apply); 541 } 542 apply = setTimeout(() => { 543 el.parents('.js-facet-range').find(".js-facet-range-btn").trigger('click'); 544 }, 1000); 545 } 546 547 function cleanArr( arr, code) { 548 return arr.split(':').filter((a, i, ar) => a!== code && ar[i-1] !== code).join(':') 549 } 550 551 function createUrl(url, rangeVal, parent, code) { 552 const btn = parent.find(".js-facet-range-btn"); 553 const q = `:${code}:${rangeVal[0]}:${code}:${rangeVal[1]}`; 554 const newUrl = (location.pathname + "?q=" + cleanArr(url, code) + q).replaceAll(":", `${ACC.variables.URL_SEPARATOR}`); 555 556 btn.attr("data-url", newUrl); 557 } 558 559 if (range.length) { 560 const fullFacetsList = JSON.parse(localStorage.getItem('fullFacetsList')); 561 range.each(function () { 562 const code = $(this).attr('data-code'), 563 parent = $(this).parent(); 564 565 parent.removeClass('hidden'); 566 567 const valMin = parent.find(".js-val-range-min"), 568 valMax = parent.find(".js-val-range-max"), 569 valUnit = parent.parents('.js-facet').find(".js-val-range-unit"), 570 571 facet = facets.find(function(facet){ 572 return facet.code === code 573 }), 574 fullFacet = fullFacetsList?.facets.find(function(facet){ 575 return facet.code === code 576 }), 577 578 values = facet.values, 579 fullValues = fullFacet?.values, 580 useValues = fullValues || values, 581 582 min = useValues?.[0].code * 1, 583 max = useValues[useValues.length - 1].code * 1, 584 585 selectedValues = breadcrumbs.filter(breadcrumb => breadcrumb.facetCode === code) 586 .sort((a, b) =>
586 (a.facetValueCode * 1) - (b.facetValueCode * 1)), 587 minSelected = selectedValues.length ? selectedValues[0].facetValueCode * 1 : values[0].code * 1, 588 maxSelected = selectedValues.length ? selectedValues[selectedValues.length - 1].facetValueCode * 1 : values[values.length - 1].code * 1, 589 590 unit = facet.featureUnit.symbol || '', 591 url = values[0].query.query.value, 592 593 sliderSelected = [minSelected, maxSelected]; 594 595 const slider = $(this).slider({ 596 range: true, 597 min: min, 598 max: max + 0.0001, // fix for roundings in floats 599 step: 0.05, 600 values: sliderSelected, 601 slide: function (_, ui) { 602 valMin.val(ui.values[0]); 603 valMax.val(ui.values[1]); 604 createUrl(url, ui.values, parent, code); 605 606 applySlider($(this)); 607 }, 608 create: function () { 609 createUrl(url, sliderSelected, parent, code); 610 }, 611 }); 612 613 valUnit.text(unit); 614 valMin.val($(this).slider("values", 0)); 615 valMax.val($(this).slider("values", 1)); 616 617 const rangeInput = $(parent).find( '.js-val-range'); 618 619 rangeInput.on('change', function(){ 620 var isMin = $(this).hasClass('js-val-range-min'); 621 var val = $(this).val().replace(',', '.'); 622 var minVal = slider.slider("values", 0); 623 var maxVal = slider.slider("values", 1); 624 625 if (isMin) { 626 if (val > maxVal) { 627 val = maxVal; 628 } 629 if (val < min) { 630 val = min; 631 } 632 } else { 633 if (val < minVal) { 634 val = minVal; 635 } 636 if (val > max) { 637 val = max; 638 } 639 } 640 641 arr = [ 642 isMin ? val : minVal, 643 !isMin ? val : maxVal 644 ]; 645 646 slider.slider("option", "values", arr); 647 $(this).val(val); 648 createUrl(url, arr, parent, code); 649 }) 650 651 rangeInput.on('keypress blur', (e) => { 652 if (e.key === "Enter" || e.type === 'blur') { 653 applySlider($(this)); 654 } 655 }) 656 }); 657 } 658 } 659};
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.