1// ---- VARIABLES ---- // 2const r = $(":root"); 3const onSurface = getComputedStyle(document.documentElement).getPropertyValue("--onSurface"); 4const TopMenuPos = $(".navbar").position().top; 5const currentCategory = $(".categoryProducts")?.data("category"); 6 7class CommentLine { 8 constructor(salesquoteid, lineid, lineno, masterdatacode, masterdatavalue, value, isFile, fileData) { 9 this.salesquoteid = salesquoteid; 10 this.lineid = lineid; 11 this.lineno = lineno; 12 this.masterdatacode = masterdatacode; 13 this.masterdatavalue = masterdatavalue; 14 this.value = value; 15 this.isFile = isFile; 16 this.fileData = fileData; 17 } 18} 19 20var page; 21var search_timer; 22var toast_interval; 23var logoSectionWidth; 24var extendedMainNavWidth; 25var clearTimeout_updateProduct; 26var searchParams; 27var mobile; 28var footerHeight; 29var sortingMethod; 30var defaultSortingMethod; 31var totalCustomerPages; 32var customerCount; 33var visibleCustomersList; 34var productJson; 35var screenWidth; 36 37// Endless scroll state for subcategory products 38var endlessScrollLoading = false; 39var endlessScrollCurrentPage = 1; 40var endlessScrollHasMore = true; 41var endlessScrollPageSize = 48; 42var endlessScrollTotalCount = 0; 43var endlessScrollCategoryNo = ""; 44var endlessScrollMinVisibleProducts = 48; // Minimum products to show before auto-loading more when filtering 45 46var cookie_consent = false; 47var search_string = ""; 48var CurrentCatID = ""; 49var navbarExpanded = true; 50var collapseMainNav = false; 51var boot = true; 52var URIJoiner = "|:|"; 53var URISplitter = "&&"; 54var URIEquals = "::"; 55var hasFired = false; 56var quickAddInitialized = false; 57var currentGalleryClass; 58var basketChangedTimeout = 300; 59var currentCustomerPage = 1; 60$.easing.myEasing = function (x, t, b, c, d) { 61 return -c * ((t = t / d - 1) * t * t * t - 1) + b; 62}; 63 64var manyCategories = false; 65var multiLogos = ""; 66var autoCollapseNav = true; 67var navCollapseWidth = 0; 68var sideNav = true; 69var sideNavRight = false; 70var sidebarEnabled = false; 71var sidebarCollapseWidth = 800; 72var hoverNavItems = false; 73var sideFilter = false; 74var sideFilterCollapseWidth = 1000; 75var galleryClasses = ["NormalView", "GridView", "ListView"]; 76var defaultGalleryClass = "NormalView"; 77var fullWidthRelatedItems = false; 78var horizontalRelatedItems = false; 79var customersPerPage = 200; 80var inventoryLocDisplay = "inline"; 81 82$.urlParam = function (name) { 83 let results = new RegExp("[?&]" + name + "=([^&#]*)").exec(window.location.search); 84 85 return results !== null ? results[1] || 0 : false; 86}; 87 88// ---- DOCUMENT READY ---- // 89$(function () { 90 //global functions 91 variablesInit(); 92 lazyload(); 93 MakeMenuStructure(); 94 AddToCartBtn(); 95 listenersInit(); 96 ajaxQueueInit(); 97 niceSelectInit(); 98 searchSelectInit(); 99 resizeNavbar($(window).scrollTop()); 100 calculateScrollbarWidth(); 101 102 //init pages 103 pageInit(); 104 105 //gallery items 106 galleryItemsInit(); 107 108 //variable related 109 bootDynamicAspects(); 110 111 boot = false; 112 113 console.info("UNBOX-ECOMMERCE::V2.3.0.5::COCOON"); 114}); 115function galleryItemsInit() { 116 if ($("#sortDropdown").length) { 117 currentGalleryClass = defaultGalleryClass; 118 sortDropdownInit(); 119 filterInit(); 120 checkUrlParams(); 121 CategoryPagerV2.init(); 122 } 123 if ($(".sideControls, .sidebarEnabled").length) adjustSideControlsHeight(); 124 if ($(".relatedProduct").length) { 125 } 126 if ($(".categoryList").length) { 127 $(".categoryList").addClass("containsAnyImages" + ($(".categoryList .containsImageTrue").length > 0 ? "True" : "False")); 128 $(".categoryList .containsImageTrue img").show(); 129 } 130 // Initialize endless scroll for subcategory products 131 if ($("#subCatContainer").length) { 132 endlessScrollInit(); 133 } 134 if ($("#GeneratedDealerList").length) { 135 $.ajax({
136 method: "GET", 137 url: "/shop/api/GetDealerList", 138 dataType: "json", 139 contentType: "application/json", 140 }).done(function (json) { 141 $("#GeneratedDealerList").html(""); 142 143 json.sort(function (a, b) { 144 return a.name.localeCompare(b.name); 145 }); 146 147 for (let i = 0; i < json.length; i++) { 148 let item = json[i]; 149 $("#GeneratedDealerList").append( 150 '<li class="GeneratedDealerListLi"><b>' + 151 item.name + 152 "</b><br>" + 153 item.address + 154 "<br>" + 155 item.zipcode + 156 " " + 157 item.city + 158 "<br><a class='phone' href='tel:" + 159 item.phone + 160 "'>" + 161 item.phone + 162 "</a><a class='location' href=\"https://www.google.dk/maps/dir/''/" + 163 item.address + 164 " " + 165 item.zipcode + 166 " " + 167 item.city + 168 '/">' + 169 ShowTheWayDealerList + 170 "</a><br></li>", 171 ); 172 } 173 }); 174 } 175} 176function bootDynamicAspects() { 177 if (sideNav) { 178 $("#main_nav").addClass("collapse-horizontal"); 179 if (sideNavRight) $("#main_nav").addClass("horizontal-right"); 180 } 181 if (hoverNavItems) $("body").addClass("hoverNav"); 182 if (multiLogos != "") appendLogos(); 183 184 if ($(".loggedIn").length) { 185 changeCustomerType(); 186 changeAltDelivery(); 187 if ($(".scShopCheckout").length) getBasketInfo(); 188 189 setTimeout(function () { 190 $.ajax({
191 method: "GET", 192 url: "/api/basket", 193 cache: false, 194 }).done(function (data) { 195 if (data.success == false) { 196 alert(data.message); 197 } else { 198 parseBasket(data); 199 } 200 }); 201 }, 500); 202 203 incDecInit(); 204 } 205 if ($(".scShopCheckout").length) { 206 $(".addressSearch") 207 .autocomplete({ 208 source: function (request, response) { 209 $.ajax({ 210 url: "https://dawa.aws.dk/vejnavnpostnummerrelationer/autocomplete?q=" + request.term, 211 dataType: "jsonp", 212 success: function (data) { 213 response(data); 214 }, 215 }); 216 }, 217 minLength: 2, 218 select: function (event, ui) { 219 $(".formZipcode").val(ui.item.vejnavnpostnummerrelation.postnr); 220 $(".formCity").val(ui.item.vejnavnpostnummerrelation.postnrnavn); 221 //setTimeout(function () { 222 $(".formAddress").val(ui.item.vejnavnpostnummerrelation.vejnavn + " "); 223 //}, 400); 224 setBasketInfo(); 225 return false; 226 }, 227 change: function (event, ui) {}, 228 }) 229 .autocomplete("instance")._renderItem = function (ul, item) { 230 item = item.tekst; 231 return $("<li>").append(item).appendTo(ul); 232 }; 233 } 234 235 $('input[name="quicksku"]').trigger("focus"); 236 237 if ($(".pageViewPaymentHandlingPage").length != 0) { 238 //Are we validating a payment? 239 if (validatingpayment === true) { 240 checkIfPaymentIsComplete(); 241 } 242 } 243} 244 245// --- PAGE INITIALIZATIONS --- // 246function pageInit() { 247 //global conditional functions 248 if (window.self.location.host !== window.top.location.host) { 249 window.top.location.href = window.self.location.href.split("#")[0]; 250 } 251 if (cookie_consent != "") { 252 $("#cookieNotice").css("display", "none"); 253 } else { 254 $("#cookieNotice").css("display", "block"); 255 } 256 257 bannerInit(); 258 259 //page specific functions 260 page = $("body").attr("class").split(" ")[0].split("pageView")[1].toLowerCase(); 261 262 if (page == "product") { 263 productPageInit(); 264 } else if (page == "basket") { 265 basketPageInit(); 266 } else if (page == "checkout") { 267 checkoutPageInit(); 268 } else if (page == "salesperson") { 269 salespersonPageInit(); 270 } else if (page == "favorites") { 271 favoritesPageInit(); 272 } else if (page == "account") { 273 accountPageInit(); 274 } else if (page == "itemcompare") { 275 itemComparePageInit(); 276 } else if (page == "page") { 277 pagePageInit(); 278 } 279 280 onPageInit(page); 281} 282function bannerInit() { 283 const banners = $(".scBanner"); 284 if (!banners.length) { 285 return; 286 } 287 288 // popups stay display:none until .show below, so the heading moves while still hidden 289 banners.filter(".scBanner-popup").each(function () { 290 moveBannerHeading($(this)); 291 }); 292 293 banners.addClass("show"); 294 295 resizeNavBanners(); 296 // an image finishing late changes the height the layout was measured from 297 $("#primary_nav .scBanner img").on("load", resizeNavBanners); 298 299 $(document).on("click", ".scBannerClose", function (e) { 300 e.preventDefault(); 301 const banner = $(this).closest(".scBanner"); 302 const layer = banner.closest(".scBannerLayer"); 303 banner.removeClass("show").remove(); 304 if (!layer.find(".scBanner").length) { 305 layer.remove(); 306 } 307 }); 308} 309// navigation banners are the only in-flow content of the fixed #primary_nav, so their measured 310// height is what --_topHeight is missing when it reserves room for the bars above the page 311function resizeNavBanners() { 312 let height = 0; 313 314 $("#primary_nav>.scBannerLayer").each(function () { 315 height += $(this).outerHeight(true); 316 });
317 318 $(":root").css("--navBannerHeight", height + "px"); 319} 320// the header is rendered for every popup so it can hold the close button on its own. When the admin 321// marked a text block for it, that block is lifted out of the body flow and into the header. 322function moveBannerHeading(banner) { 323 const header = banner.children(".scBannerHeader"); 324 const heading = banner.find('.scBannerContent>[block="text"][header]').first(); 325 if (!header.length || !heading.length) { 326 return; 327 } 328 329 header.prepend(heading); 330} 331function checkoutPageInit() { 332 $(".niceSelect").niceSelect(); 333 $(".searchSelect").searchSelect(); 334 $(".checkoutDiv input").on("input paste change", function () { 335 $(this).removeClass("form-error"); 336 }); 337 $(".checkoutDiv select").on("change", function () { 338 $(this).removeClass("form-error"); 339 }); 340 $(".amountINPUT").each(function () { 341 let UOMCode = ""; 342 if ($(this).find(".UnitOfMeasureCode").html()) { 343 UOMCode = $(this).find(".UnitOfMeasureCode").html(); 344 } 345 $(this).html($(this).find("input").val() + " " + UOMCode); 346 }); 347 $(".basketTableSection :is(.actions, .actions_record_delete)").remove(); 348 $(".orderInfoRow:has([required])").addClass("required").find("label").append(requiredText); 349 if ($(".alert.alert-danger").length) { 350 $([document.documentElement, document.body]).animate( 351 { 352 scrollTop: $(".alert.alert-danger").first().offset().top, 353 }, 354 300, 355 ); 356 } 357 setInterval( 358 function () { 359 document.location.href = document.location.href; 360 }, 361 1000 * 60 * 60, 362 ); 363} 364function salespersonPageInit() { 365 $(".salesCustomerSearch") 366 .val("") 367 .on("input", debounce(customerSearch, 300)) 368 .on("keydown", function (e) { 369 if (e.keyCode === 27) { 370 $(this).val("").trigger("blur"); 371 e.stopPropagation(); 372 return; 373 } 374 }); 375 if ($(".tab-control").length) { 376 $(".tab-control .btn").on("click", function () { 377 $(this).addClass("active").siblings().removeClass("active"); 378 }); 379 } 380 381 $(".salesCustomerList tr[data-customer]:not(.blocked)").on("click", function () { 382 if ($(this).hasClass("active")) return; 383 $("body").addClass("quoteActive"); 384 showDrawer($(this)); 385 }); 386 $(".pageSelect .first-page").on("click", function () { 387 currentCustomerPage = 1; 388 updatePage(false, $(this).parent().attr("class").includes("bottom")); 389 }); 390 $(".pageSelect .prev-page").on("click", function () { 391 currentCustomerPage--; 392 updatePage(false, $(this).parent().attr("class").includes("bottom")); 393 }); 394 $(".pageSelect .next-page").on("click", function () { 395 currentCustomerPage++; 396 updatePage(false, $(this).parent().attr("class").includes("bottom")); 397 }); 398 $(".pageSelect .last-page").on("click", function () { 399 currentCustomerPage = totalCustomerPages; 400 updatePage(false, $(this).parent().attr("class").includes("bottom")); 401 }); 402 $(".pageSelect input").on("change", function () { 403 if ($(this).val() > $(this).attr("max")) { 404 $(this).val($(this).attr("max")); 405 } 406 if ($(this).val() < 1) { 407 $(this).val($(this).attr("min")); 408 } 409 currentCustomerPage = $(this).val(); 410 updatePage(); 411 }); 412 $("input.rowsPerPage") 413 .val(customersPerPage) 414 .on("keyup", function (e) { 415 //enter key 416 if (e.keyCode === 13) { 417 $(this).trigger("blur"); 418 } 419 }) 420 .on("blur", function () { 421 if ($(this).val() > 1000) $(this).val(1000); 422 customersPerPage = parseInt($(this).val()); 423 totalCustomerPages = null; 424 currentCustomerPage = 1; 425 updatePage(true); 426 }); 427 updatePage(); 428} 429function accountPageInit() { 430 //For navigation 431 switch ($.urlParam("type")) { 432 case "": 433 $('.pageViewAccount .leftMenu option[value$="/"]').prop("selected", true); 434 break; 435 case "open": 436 $('.pageViewAccount .leftMenu option[value$="open"]').prop("selected", true); 437 break; 438 case "invoice": 439 $('.pageViewAccount .leftMenu option[value$="invoice"]').prop("selected", true); 440 break; 441 case "creditmemo": 442 $('.pageViewAccount .leftMenu option[value$="creditmemo"]').prop("selected", true); 443 break; 444 } 445} 446function toggleInventoryStatusNotAvailable(inventoryClass) { 447 $('span[replacekey="GetInventoryStatusText"]').toggleClass("notAvailable", inventoryClass == "outOfStock"); 448} 449function productPageInit() { 450 if ($(".ProductMatrixTable").length & $('*[replacekey="GetProductDescription"]').length) { 451 let ProdDescItem = $('*[replacekey="GetProductDescription"]'); 452 } 453 if (fullWidthRelatedItems) $(".ProductRelated").addClass("fullWidth"); 454 if (horizontalRelatedItems) $(".ProductRelated").addClass("horizontal"); 455 456 $.ajax({
457 method: "GET", 458 url: "/shop/api/GetProduct?productId=" + $('span[ReplaceKey="GetNo"]').text() + "&$expand=variants", 459 cache: false, 460 }).done(function (data) { 461 productJson = data; 462 ////SIMPLE VARIANTS// 463 $(".ProductBCVariantDropDown .itemVariantId option").each(function () { 464 elm = productJson.value.variantsBc.find((o) => o.no == $(this).val()); 465 $(this).attr("data-priceTag", elm.priceTag); 466 if (elm.inventory.toString() != "") { 467 $(this).attr("data-stock", " (" + elm.inventory + ")"); 468 } else { 469 $(this).attr("data-stock", ""); 470 } 471 472 $(this).attr("data-stocktext", elm.inventoryStatusText); 473 }); 474 $("select.niceSelect").niceSelect(); 475 $(".nice-select .option").each(function () { 476 $(this).attr("title", $(this).find(".valuetext").html()); 477 }); 478 $("select.niceSelect:not(.newSelect)").on("change", function () { 479 elm = productJson.value.variantsBc.find((o) => o.no == $(this).val()); 480 481 $('span[replacekey="GetPriceTag"]').text(elm.priceTag); 482 $('span[replacekey="GetListPriceTag"]').text(elm.listPriceTag); 483 $('span[replacekey="GetPriceDiscountAmountTag"]').text(elm.priceDiscountAmountTag); 484 $('span[replacekey="GetPriceDiscountPcsTag"]').text(elm.listPriceTag); 485 $('span[replacekey="GetProductDescription"]').text(elm.productDescription); 486 $('span[replacekey="GetInventoryQty"]').text(elm.inventory); 487 $('span[replacekey="GetInventoryByLocation"]').text(elm.getInventoryByLocationHtml); 488 $('span[replacekey="GetGTIN"]').text(elm.gtin); 489 $('span[replacekey="GetTarif"]').text(elm.tarif); 490 $('span[replacekey="GetOrigin"]').text(elm.origin); 491 $('span[replacekey="GetInventoryStatusText"]').text(elm.inventoryStatusText); 492 toggleInventoryStatusNotAvailable(elm.inventoryClass); 493 $('span[replacekey="GetAvailabalilityHtml"]').html(elm.availabilityText); 494 $('span[replacekey="GetVariantCode"]').text(" (" + elm.variantCode + ")"); 495 }); 496 }); 497 498 $(".available .inputWrapper").each(function () { 499 let addEvent = "event"; 500 if ($(this).siblings(".outOfStock").length) { 501 $(this).find("input").attr("disabled", true); 502 addEvent = ""; 503 } 504 $(this).prepend(`<div class="input-group-text ${addEvent}DecAmount">-</div>`); 505 $(this).append(`<div class="input-group-text ${addEvent}AddAmount">+</div>`); 506 }); 507 $(".available, .notAvailable").on("click", function () { 508 $("#mainimage").removeClass("lazyload"); 509 if ($(this).attr("large-image") != "") { 510 $("#mainimage").attr("src", $(this).attr("large-image")); 511 } else { 512 $("#mainimage").attr("src", $("#mainimage").data("src")); 513 } 514 515 clickElm = null; 516 if (productJson != null) { 517 //MDI 518 try { 519 let data = productJson.value.variantsMdi.find((o) => o.result.value.no == $(this).data("itemno")); 520 if (data != null) { 521 clickElm = data.result.value; 522 } 523 } catch (error) {} 524 try { 525 let data = productJson.value.variantsMdi.find((o) => o.value.no == $(this).data("itemno")); 526 if (data != null) { 527 clickElm = data.value; 528 } 529 } catch (error) {} 530 //BC Variants 531 if (productJson.value.variantsBc.find((o) => o.no == $(this).find(".ProductMatrixVariantId").val()) != null) { 532 clickElm = productJson.value.variantsBc.find((o) => o.no == $(this).find(".ProductMatrixVariantId").val()); 533 } 534 } 535 536 if (clickElm != null) { 537 const priceTag = clickElm.priceTag != null && clickElm.priceTag.trim().length ? clickElm.priceTag : productJson.value.priceTag; 538 $('span[replacekey="GetPriceTag"]').text(priceTag).attr("data-price", priceTag); 539 540 const listPriceTag = 541 clickElm.listPriceTag != null && clickElm.listPriceTag.trim().length 542 ? clickElm.listPriceTag 543 : productJson.value.listPriceTag; 544 $('span[replacekey="GetListPriceTag"]').text(listPriceTag).attr("data-price", listPriceTag); 545 546 const discountAmountTag = 547 clickElm.priceDiscountAmountTag != null && clickElm.priceDiscountAmountTag.trim().length 548 ? clickElm.priceDiscountAmountTag 549 : productJson.value.priceDiscountAmountTag; 550 $('span[replacekey="GetPriceDiscountAmountTag"]').text(discountAmountTag).attr("data-price", discountAmountTag); 551 552 const discountPctTag = 553 clickElm.priceDiscountPctTag != null && clickElm.priceDiscountPctTag.trim().length 554 ? clickElm.priceDiscountPctTag 555 : productJson.value.priceDiscountPctTag; 556 $('span[replacekey="GetPriceDiscountPcsTag"]').text(discountPctTag).attr("data-price", discountPctTag); 557 558 if (clickElm.productDescription != null && clickElm.productDescription.trim().length) { 559 $('span[replacekey="GetProductDescription"]').html(clickElm.productDescription); 560 } else { 561 $('span[replacekey="GetProductDescription"]').html(productJson.value.productDescription); 562 } 563 564 if (clickElm.inventoryQty != null && clickElm.inventoryQty.trim().length) { 565 $('span[replacekey="GetInventoryQty"]').text(clickElm.inventoryQty); 566 } else { 567 $('span[replacekey="GetInventoryQty"]').text(productJson.value.inventoryQty); 568 } 569 570 if (clickElm.getInventoryByLocationHtml != null && clickElm.getInventoryByLocationHtml.trim().length) { 571 console.log(clickElm.getInventoryByLocationHtml); 572 $('div[replacekey="GetInventoryByLocation"]').html(clickElm.getInventoryByLocationHtml); 573 } else { 574 $('div[replacekey="GetInventoryByLocation"]').html(productJson.value.getInventoryByLocationHtml); 575 } 576 577 if (clickElm.gtin != null && clickElm.gtin.trim().length) { 578 $('span[replacekey="GetGTIN"]').text(clickElm.gtin); 579 $('span[replacekey="GetGTIN"]').closest(".d-none").removeClass("d-none"); 580 } else { 581 $('span[replacekey="GetGTIN"]').text(productJson.value.gtin); 582 $('span[replacekey="GetGTIN"]').closest(".d-none").addClass("d-none"); 583 } 584 if (clickElm.tarif != null && clickElm.tarif.trim().length) { 585 $('span[replacekey="GetTarif"]').text(clickElm.tarif); 586 $('span[replacekey="GetTarif"]').closest(".d-none").removeClass("d-none"); 587 } else { 588 $('span[replacekey="GetTarif"]').text(productJson.value.tarif); 589 $('span[replacekey="GetTarif"]').closest(".d-none").addClass("d-none"); 590 } 591 if (clickElm.origin != null && clickElm.origin.trim().length) { 592 $('span[replacekey="GetOrigin"]').text(clickElm.origin); 593 $('span[replacekey="GetOrigin"]').closest(".d-none").removeClass("d-none"); 594 } else { 595 $('span[replacekey="GetOrigin"]').text(productJson.value.origin); 596 $('span[replacekey="GetOrigin"]').closest(".d-none").addClass("d-none"); 597 } 598 599 if (clickElm.inventoryStatusText != null && clickElm.inventoryStatusText.trim().length) { 600 $('span[replacekey="GetInventoryStatusText"]').html(clickElm.inventoryStatusText); 601 toggleInventoryStatusNotAvailable(clickElm.inventoryClass); 602 $('span[replacekey="GetInventoryStatusText"]').closest(".d-none").removeClass("d-none"); 603 } else { 604 $('span[replacekey="GetInventoryStatusText"]').html(productJson.value.inventoryStatusText); 605 toggleInventoryStatusNotAvailable(productJson.value.inventoryClass); 606 $('span[replacekey="GetInventoryStatusText"]').closest(".d-none").addClass("d-none"); 607 } 608 609 //If we got specific information for this item, show it, else hide the line 610 if (clickElm.availabilityText != null && clickElm.availabilityText.trim().length) { 611 $('span[replacekey="GetAvailabalilityHtml"]').html(clickElm.availabilityText); 612 $('span[replacekey="GetAvailabalilityHtml"]').closest(".d-none").removeClass("d-none"); 613 } else { 614 $('span[replacekey="GetAvailabalilityHtml"]').html(""); 615 $('span[replacekey="GetAvailabalilityHtml"]').closest(".AvailabalilitySection").parent().addClass("d-none"); 616 } 617 618 if (clickElm.specifications != null) { 619 $('span[replacekey="GetSpecifications"]').html(clickElm.specifications); 620 } 621 622 if (clickElm.no != null) { 623 if (clickElm.variantCode != null) { 624 $('span[replacekey="GetVariantCode"]').html(" (" + clickElm.variantCode + ")"); 625 } else { 626 $('span[replacekey="GetNo"]').html(clickElm.no); 627 } 628 } 629 630 $(this).find(".ProductMatrixAmountInput").trigger("focus").trigger("select"); 631 } 632 });
633 634 $(".ProductMatrixAmountInput") 635 .on("blur", function () { 636 var val = $(this).val(); 637 var step = $(this).attr("step"); 638 var min = $(this).attr("min"); 639 640 if (!isNaN(step) && !isNaN(min) && !isNaN(val)) { 641 if (Number(val) < Number(min) && Number(val) != 0) { 642 val = min; 643 } else { 644 if (Number(step) != 1) { 645 if (Number(val) % Number(step) != 0) { 646 val = val - (Number(val) % Number(step)); 647 } 648 } 649 } 650 } 651 $(this).val(val); 652 }) 653 .on("input", function () { 654 checkAmountValidity(this); 655 }); 656 657 $(".eventAddAmount").on("click", function () { 658 let elm = $(this).siblings(".ProductMatrixAmountInput"); 659 let oldVal = parseInt(elm.val()); 660 let min = parseInt(elm.attr("min")); 661 let max = parseInt(elm.attr("max")); 662 let step = parseInt(elm.attr("step")); 663 if (min && min > oldVal) { 664 elm.val(min); 665 return; 666 } 667 step = step ? step : 1; 668 newVal = Math.round(oldVal / step) * step; 669 if (newVal <= oldVal) newVal = newVal + step; 670 if (max && newVal > max) { 671 elm.val(max); 672 return; 673 } 674 elm.val(newVal); 675 }); 676 677 $(".eventDecAmount").on("click", function () { 678 let elm = $(this).siblings(".ProductMatrixAmountInput"); 679 let oldVal = parseInt(elm.val()); 680 let min = parseInt(elm.attr("min")); 681 let max = parseInt(elm.attr("max")); 682 let step = parseInt(elm.attr("step")); 683 if (max && max < oldVal) { 684 elm.val(max); 685 return; 686 } 687 step = step ? step : 1; 688 newVal = Math.round(oldVal / step) * step; 689 if (newVal >= oldVal) newVal = newVal - step; 690 if (min && newVal < min) { 691 elm.val(0); 692 return; 693 } 694 elm.val(newVal); 695 }); 696 697 $(".InventoryLocation").addClass(inventoryLocDisplay); 698 699 favoritesButtonInit(); 700} 701function basketPageInit() { 702 $(".elasticTextarea").each(function () { 703 reElastic($(this)); 704 }); 705 setInterval(function () { 706 $(".elasticTextarea").each(function () { 707 reElastic($(this)); 708 }); 709 }, 1000); 710 $(".elasticTextarea").on("keyup, keydown keypress", function () { 711 reElastic($(this)); 712 }); 713 if ($(".salesQuoteSection").length) { 714 mutlipleSalesQuotesInit(); 715 } 716 $(".priceUnit button").on("mouseup", function () { 717 quickaddtocart(this); 718 }); 719 favoritesButtonInit(); 720 setInterval( 721 function () { 722 document.location.href = document.location.href; 723 }, 724 1000 * 60 * 60, 725 ); 726} 727function favoritesPageInit() { 728 if ($(".createFavListButton").length) { 729 $(".createFavListButton").on("click", function () { 730 showFavsListForm(this); 731 }); 732 } 733} 734function itemComparePageInit() { 735 itemCompareOverflow(); 736} 737function pagePageInit() { 738 $("img:not([alt])").attr("alt", "User inserted mage without alt text - please contact site administrator"); 739} 740function listenersInit() { 741 $(document) 742 .on("keyup", function (e) { 743 if (e.key === "Escape") { 744 $(".searchLiveResult").html(""); 745 $("#searchLive").trigger("blur"); 746 } 747 }) 748 .on("click", function (e) { 749 if (e.target.id != "searchLive" && $(e.target).parents(".searchLiveResult").length == 0) { 750 $(".searchLiveResult").html(""); 751 $("#searchLive").trigger("blur"); 752 } 753 $(".errorOverlay").remove(); 754 if (!$(e.target).closest(".topBarUserSection").length && $(".otherAccounts.show").length) { 755 $(".otherAccounts").removeClass("show"); 756 } 757 }) 758 .on("click", "[data-href]", function (e) { 759 const target = $(e.target); 760 if (target.attr("onclick") != null) return; 761 if ( 762 target.parents().filter(function () { 763 return $(this).attr("onclick") != null; 764 }).length 765 ) 766 return; 767 if ( 768 target.is(".confirm_edit") || 769 target.is(".discard_edit") || 770 target.is(".confirm_delete") || 771 target.is(".cancel_delete") || 772 target.closest("tr").is(".editing") 773 ) 774 return; 775 if (target.hasClass("input-group-text")) return; 776 if (target.is("input")) return; 777 if (target.closest("label").length || target.is("label")) return; 778 if ($(this).find(".editing").length) return; 779 if (target.closest(".lotDropdown").length) return; 780 if (target.hasClass("favAdd") || target.hasClass("favAmount")) return; 781 782 const href = $(this).data("href"); 783 location.href = href.includes("#") ? href.split("#")[0] : href; 784 }); 785 $(window).on("scroll", function () { 786 let scroll = $(this).scrollTop(); 787 if ($(".sideControls, .sidebarEnabled").length) adjustSideControlsHeight(); 788 resizeNavbar(scroll); 789 if (TopMenuPos > $(window).scrollTop()) { 790 $(".minilogo img").css("opacity", ".2"); 791 } else { 792 $(".minilogo img").css("opacity", "1"); 793 } 794 onScroll(scroll); 795 });
796 $(window).on("resize", function () { 797 screenWidth = $(window).width(); 798 if ($(".sideControls, .sidebarEnabled").length) adjustSideControlsHeight(); 799 checkWidthDependantElements(); 800 calculateScrollbarWidth(); 801 resizeNavBanners(); 802 resizeNavbar($(this).scrollTop()); 803 onWindowResize(screenWidth); 804 }); 805 $("body").on("keydown", function (e) { 806 if (e.shiftKey && e.ctrlKey && e.keyCode == 70) { 807 $(".form-control.search ").trigger("focus").select(); 808 } 809 }); 810 $(".lang-option").on("click", function () { 811 let val = $(this).find(".fflag").data("lang"); 812 813 let date = new Date(); 814 date.setTime(date.getTime() + 365 * 24 * 60 * 60 * 1000); 815 816 document.cookie = "_scLanguage=" + val + ";expires=" + date.toUTCString() + ";path=/"; 817 818 location.reload(); 819 }); 820 $(".OpenClose").on("click", function () { 821 $(".AdminPanel").toggleClass("open"); 822 }); 823 $(".smallimage").on("click", function () { 824 $(".videoplayer").hide(); 825 $(".videoplayer").html(""); 826 $(".imagediv").show(); 827 828 $("#mainimage").removeClass("lazyload"); 829 if ($(this).attr("large-image") != "") { 830 $("#mainimage").attr("src", $(this).attr("large-image")); 831 } 832 }); 833 $("#searchLive").on("focus", function () { 834 $("#searchLive").trigger("keyup"); 835 }); 836 $("#searchLive").on("keyup", function (e) { 837 // Handle enter key for navigation to full search 838 if (e.keyCode === 13) { 839 $(this).addClass('loading'); 840 window.location.href = "/search?query=" + $(this).val(); 841 return; 842 } 843 844 let val = $(this).val(); 845 846 if (search_string == val.trim() && $(".searchLiveResult").html().length > 0) return; 847 848 search_string = val.trim(); 849 850 clearTimeout(search_timer); 851 852 if (val.length < 2) { 853 $(".searchLiveResult").html(""); 854 return; 855 } 856 857 search_timer = setTimeout(function () { 858 $.ajax({
859 method: "GET", 860 url: "/api/search/" + val, 861 cache: false, 862 }).done(function (data) { 863 $(".searchLiveResult").html(""); 864 865 $(data).each(function (k, v) { 866 let addToCartHtml = ""; 867 if (v.canBuy) { 868 addToCartHtml = 869 '<div class="quickSearchAdd">' + 870 '<div class="input-group input-group-sm IncDecSection mini flex-nowrap"><div class="input-group-text decButton">-</div><input class="ProductAmountInput form-control incDecInput" type="text" data-decimals="0" value="' + 871 v.minQty + 872 '" min="' + 873 v.minQty + 874 '" step="' + 875 v.colliQty + 876 '"><div class="input-group-text incButton">+</div></div>' + 877 '<button class="btn btn-success btn-sm quickSearchAddBtn" ' + 878 'data-itemno="' + 879 v.no + 880 '" data-uom="' + 881 v.uom + 882 '"><i class="fas fa-shopping-basket"></i></button>' + 883 "</div>"; 884 } 885 886 $(".searchLiveResult").append( 887 '<li class="quickSearchItem">' + 888 '<a href="' + 889 v.link + 890 '" class="quickSearchLink">' + 891 '<img width="40" src="' + 892 v.image + 893 '">' + 894 '<div class="quickSearchInfo">' + 895 '<div class="name">' + 896 v.name + 897 "</div>" + 898 '<div class="price">' + 899 v.price + 900 "</div>" + 901 "</div>" + 902 "</a>" + 903 addToCartHtml + 904 "</li>", 905 ); 906 }); 907 }); 908 }, 200); 909 }); 910 // Bind add-to-cart click handlers for quick search results 911 $(".searchLiveResult") 912 .on("click", ".quickSearchAddBtn", function (e) { 913 e.preventDefault(); 914 e.stopPropagation(); 915 let btn = $(this); 916 let itemNo = btn.data("itemno"); 917 let uom = btn.data("uom"); 918 let qty = btn.siblings(".IncDecSection").find("input").val() || 1; 919 920 btn.addClass("loading"); 921 922 $.ajax({
923 method: "POST", 924 url: "/api/basket/add/" + encodeURIComponent(itemNo) + "/quantity/" + qty + "/uom/" + uom, 925 cache: false, 926 dataType: "json", 927 contentType: "application/json", 928 }) 929 .done(function (data) { 930 parseBasket(data); 931 btn.removeClass("loading").addClass("success"); 932 setTimeout(function () { 933 btn.removeClass("success"); 934 }, 2000); 935 }) 936 .fail(function () { 937 btn.removeClass("loading").addClass("error"); 938 setTimeout(function () { 939 btn.removeClass("error"); 940 }, 2000); 941 }); 942 }) 943 // Prevent link navigation when clicking on the input 944 .on("click", ".quickSearchQty", function (e) { 945 e.preventDefault(); 946 e.stopPropagation(); 947 }) 948 // Handle increment/decrement buttons 949 .on("click", ".incButton, .decButton", function (e) { 950 adjustValue(this, $(this).hasClass("incButton")); 951 }) 952 .on("blur", ".ProductAmountInput", function () { 953 checkAmountValidity(this); 954 }); 955 $("#CustomerType").on("change", function (e) { 956 changeCustomerType(); 957 }); 958 $("#AltDelivery").on("change", function (e) { 959 changeAltDelivery(); 960 }); 961 $(".navbar-toggler").on("click", function () { 962 $("body").removeClass("showSideControls"); 963 }); 964 $(".sectionPricelists button").on("click", function () { 965 let elm = $(this); 966 967 let spanContent = $(elm).find("span").html(); 968 $(elm).css({ 969 width: $(elm).outerWidth() + "px", 970 height: $(elm).outerHeight() + "px", 971 }); 972 $(elm).find("span").html(""); 973 $(elm).addClass("loading"); 974 $("body").addClass("loading"); 975 fetch($(elm).attr("content")) 976 .then((resp) => resp.blob()) 977 .then((blob) => { 978 const url = window.URL.createObjectURL(blob); 979 const a = document.createElement("a"); 980 a.style.display = "none"; 981 a.href = url; 982 let mDate = new Date(Date.now()); 983 a.download = $(elm).data("filename"); 984 document.body.appendChild(a); 985 let classToAdd = "success"; 986 blob.size ? a.click() : (classToAdd = "error"); 987 a.remove(); 988 window.URL.revokeObjectURL(url); 989 990 $(elm).removeClass("loading").addClass(classToAdd).attr("last-class", classToAdd); 991 $("body").removeClass("loading"); 992 setTimeout(function () { 993 $(elm).removeClass(classToAdd).find("span").html(spanContent); 994 $(elm).css({ width: "", height: "" }); 995 }, 2000); 996 }) 997 .catch(() => {}); 998 }); 999 $("#search_input") 1000 .on("keydown", function (e) { 1001 if (e.keyCode === 13) { 1002 window.location.href = "/search?query=" + $(this).val(); 1003 } 1004 }) 1005 .on("focus", function () { 1006 this.setSelectionRange(0, this.value.length); 1007 }); 1008} 1009function variablesInit() { 1010 let bg = 1011 "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='" + 1012 onSurface.replace("#", "%23") + 1013 "' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2.5' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\")"; 1014 $(".navbar-toggler-icon").css("background-image", bg); 1015 cookie_consent = getCookie("user_cookie_consent"); 1016 mobile = $(".mobileTrue").length > 0; 1017 footerHeight = $("footer").outerHeight(); 1018 $(":root").css("--_footerHeight", footerHeight + "px"); 1019 const lang = getCookie("_scLanguage") || $("#lang-dropdown div").attr("data-lang") || "en-US"; 1020 $("html").attr("lang", lang.split("-")[0]); 1021} 1022 1023// --- CATEGORY & PRODUCT --- //
1024function updateProduct(elm, basketGuid, lineGuid) { 1025 clearTimeout(clearTimeout_updateProduct); 1026 1027 clearTimeout_updateProduct = setTimeout(function () { 1028 elm.removeClass("scError"); 1029 1030 let qty = elm.val(); 1031 1032 $("body").addClass("loading"); 1033 1034 $.ajax({ 1035 method: "POST", 1036 url: "/api/basket/edit/" + basketGuid + "/lines/" + lineGuid + "/quantity/" + qty, 1037 cache: false, 1038 }) 1039 .done(function (data) { 1040 parseBasket(data); 1041 }) 1042 .fail(function (xhr, status, error) { 1043 alert(xhr.responseText); 1044 document.location.href = document.location.href.split("#")[0]; 1045 }) 1046 .always(function () { 1047 $("body").removeClass("loading"); 1048 }); 1049 }, 600); 1050} 1051function addProduct(e, elm, product, uom, variantcode, showlotdialog) { 1052 if ($(elm).closest(".interactDisabled").length) { 1053 return; 1054 } 1055 if (window.self !== window.top) return; 1056 e.preventDefault(); 1057 let qty; 1058 1059 for (let i = 0; i < 10; i++) { 1060 if (elm.parents().eq(i).find(".ProductAmountInput").length == 1) { 1061 qty = elm.parents().eq(i).find(".ProductAmountInput").val(); 1062 break; 1063 } 1064 } 1065 1066 if (showlotdialog) { 1067 createLotDialog(elm, product, uom, variantcode, qty); 1068 return; 1069 } 1070 1071 $("body").addClass("loading"); 1072 1073 //For the product card 1074 let itemVariantId; 1075 for (let i = 1; i < 10; i++) { 1076 if (elm.parents().eq(i).find(".itemVariantId option:selected").length == 1) { 1077 itemVariantId = elm.parents().eq(i).find(".itemVariantId option:selected").val(); 1078 break; 1079 } 1080 } 1081 1082 //For the Favorites page 1083 if (variantcode != "" && variantcode != null && variantcode != undefined) { 1084 itemVariantId = variantcode; 1085 } 1086 1087 //To make sure we do not get variant code from product card when adding related items we look for the closets relatedProductsTable because if we find one, then we know we are in the related items table 1088 if (elm.closest(".relatedProductsTable").length > 0) { 1089 itemVariantId = variantcode; 1090 } 1091 1092 qty = qty || 1; 1093 1094 if (uom != null && uom != "") { 1095 uom = "/uom/" + uom; 1096 } 1097 1098 if (itemVariantId != null && itemVariantId != undefined && itemVariantId != "") { 1099 itemVariantId = "/itemvariantid/" + itemVariantId; 1100 } else { 1101 itemVariantId = ""; 1102 } 1103 1104 let spanContent = $(elm).find("span").html(); 1105 $(elm).css({ 1106 width: $(elm).outerWidth() + "px", 1107 height: $(elm).outerHeight() + "px", 1108 }); 1109 $(elm).find("span").html(""); 1110 $(elm).addClass("loading"); 1111 1112 let classToAdd = "success"; 1113 1114 if (product.indexOf("/") > 0) { 1115 product = product.replace("/", "%2F"); 1116 } 1117 1118 $.ajax({
1119 method: "POST", 1120 url: "/api/basket/add/" + encodeURIComponent(product) + "/quantity/" + qty + uom + itemVariantId, 1121 cache: false, 1122 dataType: "json", 1123 contentType: "application/json", 1124 }) 1125 .done(function (data) { 1126 parseBasket(data); 1127 onProductAdded(); 1128 }) 1129 .fail(function (data, jqXHR, textStatus) { 1130 let response = data.responseText; 1131 let errorOverlay = $("<div>").addClass("overlay errorOverlay").html(response); 1132 $(elm).after(errorOverlay); 1133 $(errorOverlay) 1134 .find(":after") 1135 .on("click", function () { 1136 $(errorOverlay).remove(); 1137 }); 1138 classToAdd = "error"; 1139 }) 1140 .always(function () { 1141 $(elm).removeClass("loading").addClass(classToAdd); 1142 $(elm).attr("last-class", classToAdd); 1143 setTimeout(function () { 1144 $(elm).removeClass(classToAdd); 1145 if (spanContent) $(elm).find("span").html(spanContent); 1146 $(elm).css({ width: "", height: "" }); 1147 setTimeout(function () { 1148 $(elm).blur(); 1149 }, 350); 1150 }, 3000); 1151 $("body").removeClass("loading"); 1152 }); 1153} 1154function replacer(name, val) { 1155 // convert Number to string 1156 if (val && val.constructor === Number) { 1157 return val.toString(); 1158 } else { 1159 return val; // return as is 1160 } 1161} 1162function addProductLotsToBasket(elm, product, uom, variantcode) { 1163 if ($(elm).closest(".interactDisabled").length) { 1164 return; 1165 } 1166 if (window.self !== window.top) return; 1167 let qty = 0; 1168 let data = []; 1169 $(".lotContainer input").each(function () { 1170 if (this.value != "" && this.value != undefined && this.value > 0) { 1171 data.push({ 1172 ItemNo: product, 1173 Uom: uom, 1174 VariantCode: variantcode, 1175 Amount: this.value, 1176 LotNo: $(this).data("lot").toString(), 1177 LotInventoryLocationCode: $(this).data("inv"), 1178 }); 1179 qty += parseFloat(this.value); 1180 } 1181 }); 1182 1183 //Anything to add? 1184 if (qty > 0) { 1185 $("body").addClass("loading"); 1186 $(elm).addClass("loading"); 1187 1188 $.ajax({
1189 method: "POST", 1190 url: "/api/basket/addmanylot", 1191 cache: false, 1192 dataType: "json", 1193 contentType: "application/json", 1194 data: JSON.stringify(data, replacer), 1195 }) 1196 .done(function (data) { 1197 $(elm).addClass("success").attr("last-class", "success"); 1198 parseBasket(data); 1199 setTimeout(function () { 1200 $(".lotDropdown").addClass("hiding"); 1201 setTimeout(function () { 1202 $(".lotDropdown").remove(); 1203 }, 700); 1204 }, 700); 1205 onProductAdded(); 1206 }) 1207 .fail(function (jqXHR, response) { 1208 $(elm).addClass("error").attr("last-class", "error"); 1209 }) 1210 .always(function () { 1211 $("body").removeClass("loading"); 1212 $(elm).removeClass("loading"); 1213 setTimeout(function () { 1214 $(elm).removeClass("success error"); 1215 }, 3000); 1216 }); 1217 } 1218} 1219function addMulitpleProducts(elm, bUpdate) { 1220 if (window.self !== window.top) return; 1221 let qty = 0; 1222 elm.removeClass("scError"); 1223 //elm.children.find('') 1224 let data = []; 1225 $("body").addClass("loading"); 1226 1227 $(".ProductMatrixAmountInput").each(function (index) { 1228 let VariantId = $("input.ProductMatrixVariantId"); 1229 let itemNo = $("input.ProductMatrixItemNo"); 1230 let itemUOM = $("input.ProductMatrixUOM"); 1231 let oldQty = $(this).parent().siblings("input.ProductMatrixOldQty").attr("value"); 1232 if (this.value != oldQty) { 1233 if (VariantId == null || VariantId[index] == null) { 1234 data.push({ 1235 ItemNo: itemNo[index].value, 1236 Uom: itemUOM[index].value, 1237 Amount: this.value, 1238 }); 1239 } else { 1240 data.push({ 1241 ItemNo: itemNo[index].value, 1242 Uom: itemUOM[index].value, 1243 Amount: this.value, 1244 VariantId: VariantId[index].value, 1245 }); 1246 } 1247 qty += parseFloat(this.value); 1248 $(this).addClass("valueChanged"); 1249 } 1250 }); 1251 1252 // new variant renderer 1253 1254 $(".variantItem").each(function () { 1255 const itemNo = $(this).data("itemno"); 1256 const variantCode = $(this).data("variantid"); 1257 const itemUOM = $(this).data("uom"); 1258 const oldQty = $(this).data("oldqty"); 1259 const newQty = $(this).find("input").val(); 1260 1261 if (newQty == oldQty) return; 1262 $(this).data("oldqty", newQty); 1263 data.push({ 1264 ItemNo: String(itemNo), 1265 VariantId: String(variantCode), 1266 Uom: itemUOM, 1267 Amount: newQty, 1268 }); 1269 $(this).addClass("valueChanged"); 1270 }); 1271 1272 strUrl = "/api/basket/addmany"; 1273 1274 if (bUpdate) { 1275 strUrl = "/api/basket/updatemany"; 1276 } 1277 1278 let spanContent = $(elm).find("span").html(); 1279 $(elm).css({ 1280 width: $(elm).outerWidth() + "px", 1281 height: $(elm).outerHeight() + "px", 1282 }); 1283 $(elm).find("span").html(""); 1284 $(elm).addClass("loading"); 1285 1286 let classToAdd = "upToDate"; 1287 1288 if (!$(".valueChanged").length) { 1289 $(elm).removeClass("loading").addClass(classToAdd); 1290 $(elm).attr("last-class", classToAdd); 1291 setTimeout(function () { 1292 $(elm).removeClass(classToAdd); 1293 if (spanContent) $(elm).find("span").html(spanContent); 1294 setTimeout(function () { 1295 $(elm).blur(); 1296 }, 350); 1297 }, 3000); 1298 $("body").removeClass("loading"); 1299 return; 1300 } 1301 1302 classToAdd = "updated"; 1303 $.ajax({
1304 method: "POST", 1305 url: strUrl, 1306 cache: false, 1307 dataType: "json", 1308 contentType: "application/json", 1309 data: JSON.stringify(data), 1310 }) 1311 .done(function (data) { 1312 $(".valueChanged").each(function () { 1313 $(this).removeClass("valueChanged"); 1314 $(this).parent().siblings("input.ProductMatrixOldQty").attr("value", this.value); 1315 }); 1316 parseBasket(data); 1317 onProductAdded(); 1318 }) 1319 .fail(function (jqXHR, response) { 1320 msg = response; 1321 if (jqXHR.responseJSON) { 1322 msg = jqXHR.responseJSON.message + " (" + jqXHR.responseJSON.errorcode + ")"; 1323 elm.find(".addedToCartText").text(msg); 1324 } else if (jqXHR.responseText) { 1325 msg = jqXHR.responseText; 1326 elm.find(".addedToCartText").text(msg); 1327 } else { 1328 msg = "An unknonw error ocurred, please contact the shop administrator."; 1329 elm.find(".addedToCartText").text(msg); 1330 } 1331 let errorOverlay = $("<div>").addClass("overlay errorOverlay").html(msg); 1332 $(elm).after(errorOverlay); 1333 $(errorOverlay) 1334 .find(":after") 1335 .on("click", function () { 1336 $(errorOverlay).remove(); 1337 }); 1338 classToAdd = "error"; 1339 }) 1340 .always(function () { 1341 $(elm).removeClass("loading").addClass(classToAdd); 1342 $(elm).attr("last-class", classToAdd); 1343 setTimeout(function () { 1344 $(elm).removeClass(classToAdd); 1345 if (spanContent) $(elm).find("span").html(spanContent); 1346 }, 3000); 1347 $("body").removeClass("loading"); 1348 }); 1349} 1350function disableRemoveButtons() { 1351 if (window.self !== window.top) return; 1352 removeBtns.forEach((removeBtn) => { 1353 removeBtn.style.pointerEvents = "none"; 1354 1355 setTimeout(() => { 1356 removeBtn.style.pointerEvents = "all"; 1357 }, 5000); 1358 }); 1359} 1360function addVariantListToCart(elm) { 1361 if (window.self !== window.top) return; 1362 let qty = 0; 1363 let data = []; 1364 $(".line").each(function () { 1365 let itemNo = $(this).data("sku").toString(); 1366 let itemUOM = $(this).data("uomcode"); 1367 let itemVariantId = $(this).data("varid"); 1368 let itemQty = $(this).find(".incDecInput").val(); 1369 qty += parseFloat(itemQty); 1370 data.push({ 1371 ItemNo: itemNo, 1372 Uom: itemUOM, 1373 Amount: itemQty, 1374 VariantId: itemVariantId, 1375 }); 1376 }); 1377 1378 $("body").addClass("loading"); 1379 strUrl = "/api/basket/addmany/force"; 1380 1381 let spanContent = $(elm).find("span").html(); 1382 $(elm).css("width", $(elm).outerWidth() + "px"); 1383 $(elm).css("height", $(elm).outerHeight() + "px"); 1384 $(elm).find("span").html(""); 1385 $(elm).addClass("loading"); 1386 1387 $.ajax({
1388 method: "POST", 1389 url: strUrl, 1390 cache: false, 1391 dataType: "json", 1392 contentType: "application/json", 1393 data: JSON.stringify(data), 1394 }) 1395 .done(function (data) { 1396 $(elm).addClass("success").attr("last-class", "success"); 1397 parseBasket(data); 1398 onProductAdded(); 1399 }) 1400 .fail(function (jqXHR, textStatus) { 1401 $(elm).addClass("error").attr("last-class", "error"); 1402 //todo error handling 1403 }) 1404 .always(function () { 1405 $("body").removeClass("loading"); 1406 $(elm).removeClass("loading"); 1407 setTimeout(function () { 1408 $(elm).removeClass("success error"); 1409 }, 3000); 1410 if (spanContent) $(elm).find("span").html(spanContent); 1411 $(elm).css({ width: "", height: "" }); 1412 }); 1413} 1414function updateProductDiscount(elm, basketGuid, lineGuid) { 1415 if (window.self !== window.top) return; 1416 clearTimeout(clearTimeout_updateProduct); 1417 1418 clearTimeout_updateProduct = setTimeout(function () { 1419 elm.removeClass("scError"); 1420 1421 var discount = elm.val().replaceAll(",", "."); 1422 1423 let doubleRegex = /^(100(\.0{1,2})?|[0-9]{1,2}(\.[0-9]{1,4})?)$/; 1424 if (!doubleRegex.test(discount)) { 1425 alert("Discount must be a number between 0 and 100 with up to 4 decimal points"); 1426 return false; 1427 } 1428 1429 let parsedDiscount = parseFloat(discount); 1430 1431 if (isNaN(parsedDiscount)) { 1432 alert("An error occured in the conversion to a double"); 1433 return false; 1434 } 1435 1436 $("body").addClass("loading"); 1437 1438 $.ajax({
1439 method: "POST", 1440 url: "/api/basket/edit/" + basketGuid + "/lines/" + lineGuid + "/discount/" + parsedDiscount, 1441 cache: false, 1442 }) 1443 .done(function (data) { 1444 parseBasket(data); 1445 }) 1446 .fail(function (xhr, status, error) { 1447 alert(xhr.responseText); 1448 1449 document.location.href = document.location.href.split("#")[0]; 1450 }) 1451 .always(function () { 1452 $("body").removeClass("loading"); 1453 }); 1454 }, 600); 1455} 1456function createLotDialog(elm, product, uom, variantcode, qty) { 1457 $.ajax({ 1458 method: "GET", 1459 url: 1460 "/shop/api/productlothtml?productno=" + 1461 encodeURIComponent(product) + 1462 "&variantcode=" + 1463 encodeURIComponent(variantcode) + 1464 "&quantity=" + 1465 encodeURIComponent(qty) + 1466 "&uom=" + 1467 encodeURIComponent(uom), 1468 cache: false, 1469 }).done(function (data) { 1470 let totalQty = parseInt(qty); 1471 $(elm).parent().addClass("lotSection"); 1472 let lotDropdown = $("<div class='dropdown lotDropdown'>"); 1473 let lotContainer = $("<div>").addClass("lotContainer"); 1474 let closeButton = $("<i>") 1475 .addClass("fas fa-times closeButton") 1476 .on("click", function () { 1477 const dropdown = $(this).parent(); 1478 dropdown.addClass("hiding"); 1479 setTimeout(function () { 1480 const lotSection = dropdown.parent(); 1481 dropdown.remove(); 1482 lotSection.removeClass("lotSection"); 1483 }, 500); 1484 }); 1485 $(data).each(function () { 1486 let lotNo = this.lotNo; 1487 let invNo = this.locationNo; 1488 let quantity = this.quantity; 1489 let currentQty = 0; 1490 1491 if (totalQty > quantity) { 1492 currentQty = quantity; 1493 totalQty -= quantity; 1494 } else { 1495 currentQty = totalQty < 0 ? 0 : totalQty; 1496 totalQty = 0; 1497 } 1498 1499 let title = $("<span>").html(lotNo); 1500 let input = $("<input>") 1501 .attr({ 1502 type: "text", 1503 min: 0, 1504 max: quantity, 1505 "data-inv": invNo, 1506 "data-lot": lotNo, 1507 value: currentQty, 1508 }) 1509 .val(currentQty); 1510 let inventory = $("<span>") 1511 .addClass("lotInv") 1512 .html("/" + quantity); 1513 lotContainer.append(title, input, inventory); 1514 }); 1515 let title = $("<span>").html("No LOT"); 1516 let input = $("<input>").attr({ type: "number", min: 0, "data-lot": "", value: totalQty }).val(totalQty); 1517 let inventory = $("<span>").addClass("lotInv").html("/â"); 1518 if (AddToCartIfOutOfStock) { 1519 lotContainer.append(title, input, inventory); 1520 } 1521 1522 const addButton = $("<button>") 1523 .addClass("btn btn-sm btn-success addLot addToCartAni") 1524 .append($("<span>").html(elm.find("span").html())); 1525 addButton.on("click", function () { 1526 addProductLotsToBasket(this, product, uom, variantcode); 1527 }); 1528 lotDropdown.append(closeButton, lotContainer, addButton); 1529 $(elm).after(lotDropdown); 1530 $(lotDropdown).css("max-height", $(lotDropdown).outerHeight()); 1531 1532 //This is used to make sure you do not enter more than max-value in the lot numbers 1533 $(".lotContainer input").blur(function () { 1534 if (parseInt($(this).val()) > parseInt($(this).attr("max"))) { 1535 $(this).val($(this).attr("max")); 1536 } else if (parseInt($(this).val()) < parseInt($(this).attr("min"))) { 1537 $(this).val($(this).attr("min")); 1538 } 1539 }); 1540 }); 1541} 1542function sideControlsToggle() { 1543 if ($(".showSideControls").length) $(".showSideControls").removeClass("showSideControls"); 1544 else $("body").addClass("showSideControls"); 1545} 1546function selectVariant(event) { 1547 let elm; 1548 1549 if (event.type === "click") { 1550 elm = $(event.currentTarget); 1551 } else if (event.type === "change" && $(event.target).is("select")) { 1552 // For select change events, get the selected option which has the data attributes 1553 elm = $(event.target).find("option:selected"); 1554 } else { 1555 elm = $(event.target); 1556 } 1557 if (elm.hasClass("selected")) return; 1558 $("body").addClass("loading"); 1559 $(".variantItem.selected").removeClass("selected"); 1560 $(elm).addClass("selected").siblings().removeClass("selected"); 1561 1562 //Set the new image 1563 const image = $(elm).data("image"); 1564 if (image) { 1565 $("#mainimage").attr("src", image); 1566 } else if (typeof VariantStayOnNoImage === "undefined" || !VariantStayOnNoImage) { 1567 $("#mainimage").attr("src", $("#mainimage").data("original-src")); 1568 } 1569 1570 //Load data 1571 let url = decodeURIComponent(elm.data("newloc")); 1572 1573 if (!url || url === "undefined") { 1574 $("body").removeClass("loading"); 1575 return; 1576 } 1577 1578 $("<div>").load(url, function (newVariant) { 1579 if (event.type === "change") { 1580 //replace the interact section 1581 $(".ProductInteract").replaceWith($(newVariant).find(".ProductInteract")); 1582 incDecInit(); 1583 AddToCartBtn();
1584 favoritesButtonInit(); 1585 } 1586 1587 //Replace the information section 1588 $(".ProductInfo").replaceWith($(newVariant).find(".ProductInfo")); 1589 1590 //Replace Product Name 1591 $(".ProductNameHeader .header").html($(newVariant).find(".ProductNameHeader").html()); 1592 $("body").removeClass("loading"); 1593 $(".InventoryLocation").addClass(inventoryLocDisplay); 1594 }); 1595} 1596 1597// Filtering & Sorting 1598function setUrlParams() { 1599 //empty params 1600 let query = searchParams.get("query"); 1601 window.history.replaceState({}, "", window.location.href.split("?")[0]); 1602 if (query) pushUrlParam("query", query); 1603 1604 //filtering 1605 $(".sectionFilter") 1606 .find(".filterSection:has(input:checked)") 1607 .each(function () { 1608 let section = this; 1609 let selected = ""; 1610 $(section) 1611 .find("input:checked") 1612 .each(function () { 1613 if (selected != "") selected += URIJoiner; 1614 selected += $(this).attr("id").split(URIEquals)[1]; 1615 }); 1616 let sectionName = $(this).find("input").first().attr("id").split(URIEquals)[0]; 1617 pushUrlParam("filter", sectionName + URIEquals + selected); 1618 }); 1619 //range filtering (numeric value sliders) 1620 $(".sectionFilter double-slider").each(function () { 1621 if (this.isDefault) return; 1622 pushUrlParam("range", $(this).attr("data-code") + URIEquals + this.valueMin + ".." + this.valueMax); 1623 }); 1624 //sorting 1625 let newSorting = $("#sortDropdown").val(); 1626 if (newSorting == defaultSortingMethod) pushUrlParam("sort", null); 1627 else pushUrlParam("sort", newSorting); 1628 //view 1629 let newGalleryClass = $(".btn-layout:has(:checked) input").attr("value"); 1630 if (newGalleryClass == defaultGalleryClass) pushUrlParam("view", null); 1631 else pushUrlParam("view", newGalleryClass); 1632 checkUrlParams(); 1633} 1634function checkUrlParams() { 1635 searchParams = new URLSearchParams(window.location.search); 1636 let filters = searchParams.getAll("filter"); 1637 let ranges = searchParams.getAll("range"); 1638 if (filters) { 1639 $(filters).each(function () { 1640 let type = this.split(URIEquals)[0]; 1641 let content = this.split(URIEquals)[1].split(URIJoiner); 1642 $(content).each(function () { 1643 let newId = type + URIEquals + this; 1644 $(".filterSpecifications") 1645 .filter(function () { 1646 const currentId = decodeURIComponent($(this).attr("id")); 1647 return currentId == newId; 1648 }) 1649 .attr("checked", "checked"); 1650 }); 1651 }); 1652 } 1653 if (ranges) { 1654 $(ranges).each(function () { 1655 let code = this.split(URIEquals)[0]; 1656 let rangeStr = this.split(URIEquals)[1]; 1657 if (!rangeStr || rangeStr.indexOf("..") === -1) return; 1658 let parts = rangeStr.split(".."); 1659 let minVal = parts[0]; 1660 let maxVal = parts[1]; 1661 let target = $("double-slider").filter(function () { 1662 return $(this).attr("data-code") == code; 1663 }); 1664 if (minVal !== "") target.attr("value-min", minVal); 1665 if (maxVal !== "") target.attr("value-max", maxVal); 1666 }); 1667 } 1668 productsFilter(); 1669 let sort = searchParams.get("sort"); 1670 if (sort) { 1671 sortingMethod = sort; 1672 $("#sortDropdown").val(sortingMethod); 1673 } else { 1674 sortingMethod = defaultSortingMethod; 1675 } 1676 productsSort(); 1677 let view = searchParams.get("view"); 1678 if (view) currentGalleryClass = view; 1679 galleryViewInit(); 1680 CategoryPagerV2.refreshIfChanged(); 1681} 1682function pushUrlParam(param, val) { 1683 var url = window.location.href; 1684 var newParam = param + "=" + val; 1685 if (url.indexOf("?") !== -1) { 1686 newParam = "&" + newParam; 1687 } else { 1688 newParam = "?" + newParam; 1689 } 1690 if (val == null) { 1691 newParam = ""; 1692 } 1693 url += newParam; 1694 window.history.replaceState({}, "", url); 1695} 1696function productsFilter() { 1697 if (document.getElementById("categoryIndexV2")) return; 1698 searchParams = new URLSearchParams(window.location.search); 1699 let filters = searchParams.getAll("filter"); 1700 let ranges = searchParams.getAll("range"); 1701 let productItems = $(".ProductItem"); 1702 $(".ProductItem.hide").removeClass("hide"); 1703 productItems.each(function () { 1704 for (let i = 0; i < filters.length; i++) { 1705 let filterBy = filters[i].split(URIEquals)[0].toLowerCase().replaceAll(" ", "-"); 1706 let specContent = decodeURIComponent($(this).attr("spec-" + filterBy)); 1707 if (!specContent) { 1708 $(this).addClass("hide"); 1709 continue; 1710 } 1711 specContent = specContent.split(URISplitter); 1712 let filterContent = filters[i].split(URIEquals)[1].split(URIJoiner); 1713 if (!filterContent.some((item) => specContent.includes(item))) $(this).addClass("hide"); 1714 } 1715 for (let j = 0; j < ranges.length; j++) { 1716 let rangeBy = ranges[j].split(URIEquals)[0].toLowerCase().replaceAll(" ", "-"); 1717 let rangeStr = ranges[j].split(URIEquals)[1]; 1718 if (!rangeStr || rangeStr.indexOf("..") === -1) continue; 1719 let parts = rangeStr.split(".."); 1720 let minVal = parts[0] === "" ? Number.NEGATIVE_INFINITY : parseFloat(parts[0]); 1721 let maxVal = parts[1] === "" ? Number.POSITIVE_INFINITY : parseFloat(parts[1]); 1722 if (isNaN(minVal)) minVal = Number.NEGATIVE_INFINITY; 1723 if (isNaN(maxVal)) maxVal = Number.POSITIVE_INFINITY; 1724 let specnumAttr = $(this).attr("specnum-" + rangeBy); 1725 if (!specnumAttr) { 1726 $(this).addClass("hide"); 1727 continue; 1728 } 1729 let nums = specnumAttr 1730 .split(URISplitter) 1731 .map(parseFloat) 1732 .filter((n) => !isNaN(n)); 1733 if (!nums.some((n) => n >= minVal && n <= maxVal)) $(this).addClass("hide"); 1734 } 1735 });
1736 if ($(".TotalProducts").length) { 1737 let totalProductsTitle = $(".TotalProducts").html().split(" ")[1]; 1738 let filteredProductsCount = $(".ProductItem:not(.hide)").length; 1739 //$(".TotalProducts").html(filteredProductsCount + " " + totalProductsTitle); 1740 } 1741 refreshProductGroupCounts(); 1742 FilterFacets.refresh(FilterFacets.domEntries()); 1743} 1744function refreshProductGroupCounts() { 1745 $(".productGroup").each(function () { 1746 const visibleCount = $(this).find(".ProductItem:not(.hide)").length; 1747 $(this).find(".productGroupTotal").text(visibleCount); 1748 $(this).prop("hidden", visibleCount === 0); 1749 }); 1750} 1751function productsSort() { 1752 if (document.getElementById("categoryIndexV2")) return; 1753 let activeProducts = $(".ProductItem:not(.hide)"); 1754 $(activeProducts).css("order", "0"); 1755 activeProducts.sort(compareAttributes); 1756 for (let i = 0; i < activeProducts.length; i++) { 1757 $(activeProducts[i]).css("order", i); 1758 } 1759} 1760function compareAttributes(a, b) { 1761 let method = "name"; 1762 let direction = 1; 1763 switch (sortingMethod) { 1764 case "name-za": 1765 direction = -1; 1766 break; 1767 case "price-09": 1768 method = "price"; 1769 break; 1770 case "price-90": 1771 method = "price"; 1772 direction = -1; 1773 break; 1774 case "sku-az": 1775 method = "sku"; 1776 break; 1777 case "sku-za": 1778 method = "sku"; 1779 direction = -1; 1780 break; 1781 case "relevance": 1782 method = "relevance"; 1783 break; 1784 default: 1785 break; 1786 } 1787 1788 let aVal = $(a) 1789 .attr("data-" + method) 1790 .toLowerCase(); 1791 let bVal = $(b) 1792 .attr("data-" + method) 1793 .toLowerCase(); 1794 if (method == "price") { 1795 aVal = parseFloat(aVal); 1796 bVal = parseFloat(bVal); 1797 if (aVal == "") aVal = direction > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; 1798 if (bVal == "") bVal = direction > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; 1799 } 1800 1801 if (aVal < bVal) return -1 * direction; 1802 else if (bVal < aVal) return direction; 1803 else return 0; 1804} 1805function collapseFilter(btn) { 1806 $(btn).toggleClass("show"); 1807} 1808// Initializazions 1809function filterInit() { 1810 if (sideFilter) { 1811 if (!$(".galleryControls").length) return; 1812 $("body").addClass("sideControls"); 1813 $($(".galleryControls")[1]).addClass("d-none"); 1814 } 1815 $(".sectionFilter").on("change", "input, double-slider", function (e) { 1816 setUrlParams(); 1817 // Check if we need to load more products after filtering 1818 endlessScrollCheckVisibleProducts(); 1819 e.stopPropagation(); 1820 }); 1821 $(".sectionFilter").on("click", ".sliderReset", function (e) { 1822 let slider = $(this).closest(".filterRange").find("double-slider")[0]; 1823 if (!slider || slider.isDefault) return; 1824 slider.reset(); 1825 setUrlParams(); 1826 // Check if we need to load more products after clearing the range 1827 endlessScrollCheckVisibleProducts(); 1828 }); 1829 $(".sectionFilter .clearFilters").on("click", function (e) { 1830 $(".sectionFilter input:checked").prop("checked", false); 1831 $(".sectionFilter double-slider").each(function () { 1832 this.reset(); 1833 }); 1834 setUrlParams(); 1835 // Check if we need to load more products after clearing filters 1836 endlessScrollCheckVisibleProducts(); 1837 }); 1838} 1839 1840// Endless Scroll for Subcategory Products 1841function endlessScrollInit() { 1842 let container = $("#subCatContainer"); 1843 if (!container.length) return; 1844 1845 // Check if pagination is enabled 1846 let paginationEnabled = container.data("pagination-enabled") === true || container.data("pagination-enabled") === "true"; 1847 if (!paginationEnabled) return; 1848 1849 // Read initial state from data attributes 1850 endlessScrollCategoryNo = container.data("category"); 1851 endlessScrollCurrentPage = parseInt(container.data("page")) || 1; 1852 endlessScrollPageSize = parseInt(container.data("pagesize")) || 48; 1853 endlessScrollTotalCount = parseInt(container.data("total")) || 0; 1854 endlessScrollHasMore = container.data("hasmore") === true || container.data("hasmore") === "true"; 1855 1856 // Set up scroll event listener 1857 $(window).on("scroll.endlessScroll", endlessScrollOnScroll); 1858 1859 // Initial check - in case the page is short and we need to load more 1860 setTimeout(function () { 1861 endlessScrollCheckVisibleProducts(); 1862 }, 500); 1863} 1864 1865function endlessScrollOnScroll() { 1866 if (endlessScrollLoading || !endlessScrollHasMore) return; 1867 1868 let loader = $("#subProdsLoader"); 1869 if (!loader.length) return; 1870 1871 // Check if the loader is visible in viewport (with some buffer for early loading) 1872 let loaderTop = loader.offset().top; 1873 let windowBottom = $(window).scrollTop() + $(window).height(); 1874 let buffer = 300; // Load 300px before the loader is visible 1875 1876 if (windowBottom + buffer >= loaderTop) { 1877 endlessScrollLoadMore(); 1878 } 1879} 1880 1881function endlessScrollLoadMore() { 1882 if (endlessScrollLoading || !endlessScrollHasMore) return; 1883 1884 endlessScrollLoading = true; 1885 let loader = $("#subProdsLoader"); 1886 loader.addClass("loading"); 1887 1888 let nextPage = endlessScrollCurrentPage + 1; 1889 1890 $.ajax({
1891 method: "GET", 1892 url: "/api/category/" + encodeURIComponent(endlessScrollCategoryNo) + "/subproducts", 1893 data: { 1894 page: nextPage, 1895 pageSize: endlessScrollPageSize, 1896 }, 1897 cache: false, 1898 dataType: "json", 1899 }) 1900 .done(function (response) { 1901 if (response.html && response.html.length > 0) { 1902 // Append new products to the list 1903 let productList = $("#subProductList"); 1904 productList.append(response.html); 1905 1906 // Update state 1907 endlessScrollCurrentPage = response.currentPage; 1908 endlessScrollHasMore = response.hasMore; 1909 1910 // Update container data attributes 1911 $("#subCatContainer").data("page", endlessScrollCurrentPage); 1912 $("#subCatContainer").data("hasmore", endlessScrollHasMore); 1913 1914 // Initialize lazy loading for new images 1915 if (typeof lazyload === "function") { 1916 lazyload(); 1917 } 1918 1919 // Re-initialize add to cart buttons for new products 1920 AddToCartBtn(); 1921 1922 // Re-initialize increment/decrement inputs for new products 1923 incDecInit(); 1924 1925 // Apply current sorting to all products including new ones 1926 productsSort(); 1927 1928 // Apply current filters to new products 1929 productsFilter(); 1930 1931 // Re-initialize favorites buttons 1932 if (typeof favoritesButtonInit === "function") { 1933 favoritesButtonInit(); 1934 } 1935 } 1936 1937 // Hide loader if no more products 1938 if (!endlessScrollHasMore) { 1939 loader.removeClass("loading").hide(); 1940 } 1941 1942 // Check if we need to load more (filtering might have hidden the new products) 1943 setTimeout(function () { 1944 endlessScrollCheckVisibleProducts(); 1945 }, 100); 1946 }) 1947 .fail(function (xhr, status, error) { 1948 console.error("Failed to load more products:", error); 1949 // Show error state on loader 1950 loader.removeClass("loading").addClass("error"); 1951 setTimeout(function () { 1952 loader.removeClass("error"); 1953 }, 3000); 1954 }) 1955 .always(function () { 1956 endlessScrollLoading = false; 1957 loader.removeClass("loading"); 1958 }); 1959} 1960 1961function endlessScrollCheckVisibleProducts() { 1962 if (!endlessScrollHasMore || endlessScrollLoading) return; 1963 if (!$("#subProductList").length) return; 1964 1965 // Count visible products in subcategory list (not hidden by filters) 1966 let visibleCount = $("#subProductList .ProductItem:not(.hide)").length; 1967 1968 // If we have fewer visible products than the minimum threshold, load more 1969 if (visibleCount < endlessScrollMinVisibleProducts) { 1970 endlessScrollLoadMore(); 1971 } 1972} 1973 1974const FilterFacets = { 1975 refresh(entries) { 1976 const inputs = $(".sectionFilter .filterSpecifications"); 1977 if (!inputs.length) return; 1978 1979 const counts = this.countOptions(entries, this.activeFilters()); 1980 inputs.each((i, input) => { 1981 const option = this.parseOption(input.id); 1982 const count = counts.get(this.optionKey(option.code, option.value)) || 0; 1983 input.disabled = count === 0 && !input.checked; 1984 $(input).siblings(".filterCount").text(count); 1985 }); 1986 }, 1987 1988 codeKey(code) { 1989 return code.toLowerCase().replaceAll(" ", "-"); 1990 }, 1991 1992 optionKey(code, value) { 1993 return code + URIEquals + value; 1994 }, 1995 1996 parseOption(id) { 1997 const split = id.indexOf(URIEquals); 1998 return { 1999 code: this.codeKey(id.substring(0, split)), 2000 value: decodeURIComponent(id.substring(split + URIEquals.length)), 2001 }; 2002 }, 2003 2004 activeFilters() { 2005 const params = new URLSearchParams(window.location.search); 2006 const filters = params.getAll("filter").map((raw) => ({
2007 code: this.codeKey(raw.split(URIEquals)[0]), 2008 wanted: raw.split(URIEquals)[1].split(URIJoiner), 2009 })); 2010 2011 const ranges = []; 2012 params.getAll("range").forEach((raw) => { 2013 const bounds = raw.split(URIEquals)[1]; 2014 if (!bounds || bounds.indexOf("..") === -1) return; 2015 const parts = bounds.split(".."); 2016 const min = parseFloat(parts[0]); 2017 const max = parseFloat(parts[1]); 2018 ranges.push({ 2019 code: this.codeKey(raw.split(URIEquals)[0]), 2020 min: isNaN(min) ? Number.NEGATIVE_INFINITY : min, 2021 max: isNaN(max) ? Number.POSITIVE_INFINITY : max, 2022 }); 2023 }); 2024 2025 return { filters, ranges }; 2026 }, 2027 2028 domEntries() { 2029 return $(".ProductItem") 2030 .map((i, node) => { 2031 const specs = {}; 2032 const nums = {}; 2033 for (const attr of node.attributes) { 2034 if (attr.name.startsWith("specnum-")) { 2035 nums[attr.name.substring(8)] = attr.value 2036 .split(URISplitter) 2037 .map(parseFloat) 2038 .filter((n) => !isNaN(n)); 2039 } else if (attr.name.startsWith("spec-")) { 2040 specs[attr.name.substring(5)] = decodeURIComponent(attr.value).split(URISplitter); 2041 } 2042 } 2043 return { specs, nums }; 2044 }) 2045 .get(); 2046 }, 2047 2048 passesFilter(entry, filter) { 2049 const own = entry.specs[filter.code]; 2050 return !!own && filter.wanted.some((value) => own.includes(value)); 2051 }, 2052 2053 passesRanges(entry, ranges) { 2054 return ranges.every((range) => { 2055 const own = entry.nums[range.code]; 2056 return !!own && own.some((value) => value >= range.min && value <= range.max); 2057 }); 2058 }, 2059 2060 countOptions(entries, active) { 2061 const counts = new Map(); 2062 entries.forEach((entry) => { 2063 if (!this.passesRanges(entry, active.ranges)) return; 2064 2065 const failed = active.filters.filter((filter) => !this.passesFilter(entry, filter)); 2066 if (failed.length > 1) return; 2067 2068 const codes = failed.length ? [failed[0].code] : Object.keys(entry.specs); 2069 codes.forEach((code) => { 2070 new Set(entry.specs[code] || []).forEach((value) => { 2071 const key = this.optionKey(code, value); 2072 counts.set(key, (counts.get(key) || 0) + 1); 2073 }); 2074 }); 2075 }); 2076 return counts; 2077 }, 2078}; 2079 2080const CategoryPagerV2 = { 2081 active: false, 2082 loading: false, 2083 index: null, 2084 entries: [], 2085 facetEntries: [], 2086 matched: [], 2087 cards: new Map(), 2088 codeIndex: {}, 2089 valueIndex: [], 2090 numCodeIndex: {}, 2091 groupTemplate: null, 2092 groupByNo: {}, 2093 groupCounts: {}, 2094 pageSize: 48, 2095 offset: 0, 2096 rendered: 0, 2097 lastSignature: "", 2098 2099 init() { 2100 const source = document.getElementById("categoryIndexV2"); 2101 if (!source) return; 2102 2103 this.index = JSON.parse(source.textContent); 2104 this.pageSize = this.index.pageSize || 48; 2105 this.groupTemplate = document.getElementById("productGroupsV2"); 2106 this.buildLookups(); 2107 this.adoptRenderedCards(); 2108 this.active = true; 2109 this.lastSignature = this.signature(); 2110 $(window).on("scroll.categoryPagerV2", () => this.onScroll()); 2111 2112 if (this.hasListParams()) { 2113 this.refresh(); 2114 return; 2115 } 2116 2117 this.matched = this.matchedNos(); 2118 this.offset = (this.currentPageFromUrl() - 1) * this.pageSize; 2119 this.rendered = $("#productList .ProductItem").length; 2120 FilterFacets.refresh(this.facetEntries); 2121 this.updateTotal(); 2122 this.updateState(); 2123 this.onScroll(); 2124 }, 2125 2126 buildLookups() { 2127 this.index.codes.forEach((code, i) => (this.codeIndex[code] = i));
2128 this.index.numCodes.forEach((code, i) => (this.numCodeIndex[code] = i)); 2129 2130 this.valueIndex = this.index.values.map((list) => { 2131 const lookup = {}; 2132 list.forEach((value, i) => (lookup[value] = i)); 2133 return lookup; 2134 }); 2135 2136 this.entries = this.index.items.map((item) => ({ 2137 no: item[0], 2138 specs: this.unpack(item[1], (text) => parseInt(text, 10)), 2139 nums: this.unpack(item[2], (text) => parseFloat(text)), 2140 ranks: item[3], 2141 group: item[4], 2142 })); 2143 2144 this.entries.forEach((entry) => (this.groupByNo[entry.no] = entry.group)); 2145 this.facetEntries = this.entries.map((entry) => this.toFacetEntry(entry)); 2146 }, 2147 2148 toFacetEntry(entry) { 2149 const specs = {}; 2150 const nums = {}; 2151 Object.keys(entry.specs).forEach((codeSlot) => { 2152 specs[FilterFacets.codeKey(this.index.codes[codeSlot])] = entry.specs[codeSlot].map((slot) => this.index.values[codeSlot][slot]); 2153 }); 2154 Object.keys(entry.nums).forEach((codeSlot) => { 2155 nums[FilterFacets.codeKey(this.index.numCodes[codeSlot])] = entry.nums[codeSlot]; 2156 }); 2157 return { specs, nums }; 2158 }, 2159 2160 unpack(packed, parse) { 2161 const groups = {}; 2162 if (!packed) return groups; 2163 packed.split(";").forEach((group) => { 2164 const parts = group.split(":"); 2165 groups[parseInt(parts[0], 10)] = parts[1].split(",").map(parse); 2166 }); 2167 return groups; 2168 }, 2169 2170 adoptRenderedCards() { 2171 $("#productList .ProductItem").each((i, node) => this.cards.set($(node).attr("data-sku"), $(node))); 2172 }, 2173 2174 currentPageFromUrl() { 2175 const page = parseInt(new URLSearchParams(window.location.search).get("page"), 10); 2176 return page > 1 ? page : 1; 2177 }, 2178 2179 hasListParams() { 2180 const params = new URLSearchParams(window.location.search); 2181 return params.getAll("filter").length > 0 || params.getAll("range").length > 0 || !!params.get("sort"); 2182 }, 2183 2184 signature() { 2185 const params = new URLSearchParams(window.location.search); 2186 return JSON.stringify([params.getAll("filter"), params.getAll("range"), params.get("sort")]); 2187 }, 2188 2189 sortSlot() { 2190 const method = sortingMethod || defaultSortingMethod || "name-az"; 2191 return { 2192 slot: this.index.sorts.indexOf(method.split("-")[0]), 2193 descending: method === "name-za" || method === "sku-za" || method === "price-90", 2194 }; 2195 }, 2196 2197 matchedNos() { 2198 const params = new URLSearchParams(window.location.search); 2199 const filters = []; 2200 let impossible = false; 2201 2202 params.getAll("filter").forEach((raw) => { 2203 const codeSlot = this.codeIndex[raw.split(URIEquals)[0]]; 2204 if (codeSlot === undefined) { 2205 impossible = true; 2206 return; 2207 } 2208 filters.push({ 2209 codeSlot: codeSlot, 2210 wanted: raw 2211 .split(URIEquals)[1] 2212 .split(URIJoiner) 2213 .map((value) => this.valueIndex[codeSlot][value]) 2214 .filter((slot) => slot !== undefined), 2215 }); 2216 }); 2217 2218 const ranges = []; 2219 2220 params.getAll("range").forEach((raw) => { 2221 const bounds = raw.split(URIEquals)[1]; 2222 if (!bounds || bounds.indexOf("..") === -1) return; 2223 const parts = bounds.split(".."); 2224 ranges.push({ 2225 codeSlot: this.numCodeIndex[raw.split(URIEquals)[0]], 2226 min: parts[0] === "" ? Number.NEGATIVE_INFINITY : parseFloat(parts[0]), 2227 max: parts[1] === "" ? Number.POSITIVE_INFINITY : parseFloat(parts[1]), 2228 }); 2229 }); 2230 2231 if (impossible) return []; 2232 2233 const matches = this.entries.filter((entry) => { 2234 for (const filter of filters) { 2235 const own = entry.specs[filter.codeSlot]; 2236 if (!own || !filter.wanted.some((slot) => own.includes(slot))) return false; 2237 } 2238 for (const range of ranges) { 2239 const own = entry.nums[range.codeSlot]; 2240 if (!own || !own.some((value) => value >= range.min && value <= range.max)) return false; 2241 } 2242 return true; 2243 }); 2244
2245 const sort = this.sortSlot(); 2246 if (sort.slot >= 0) { 2247 const direction = sort.descending ? -1 : 1; 2248 matches.sort((a, b) => this.compareGroups(a, b) || (a.ranks[sort.slot] - b.ranks[sort.slot]) * direction); 2249 } 2250 2251 return matches.map((entry) => entry.no); 2252 }, 2253 2254 compareGroups(a, b) { 2255 return this.groupTemplate ? a.group - b.group : 0; 2256 }, 2257 2258 countGroups() { 2259 this.groupCounts = {}; 2260 this.matched.forEach((no) => { 2261 const group = this.groupByNo[no]; 2262 this.groupCounts[group] = (this.groupCounts[group] || 0) + 1; 2263 }); 2264 }, 2265 2266 listFor(no) { 2267 const list = $("#productList"); 2268 if (!this.groupTemplate) return list; 2269 2270 const group = this.groupByNo[no]; 2271 let section = list.children(`.productGroup[data-group="${group}"]`); 2272 if (!section.length) { 2273 section = $(this.groupTemplate.content.querySelector(`.productGroup[data-group="${group}"]`).cloneNode(true)); 2274 section.find(".productGroupTotal").text(this.groupCounts[group]); 2275 section.find(".GalleryBox").attr("class", "GalleryBox " + currentGalleryClass); 2276 list.append(section); 2277 } 2278 return section.children(".GalleryBox"); 2279 }, 2280 2281 refreshIfChanged() { 2282 if (!this.active) return; 2283 const signature = this.signature(); 2284 if (signature === this.lastSignature) return; 2285 this.lastSignature = signature; 2286 this.refresh(); 2287 }, 2288 2289 refresh() { 2290 this.matched = this.matchedNos(); 2291 this.offset = 0; 2292 this.rendered = 0; 2293 $("#productList").children().detach(); 2294 FilterFacets.refresh(this.facetEntries); 2295 this.updateTotal(); 2296 this.updateState(); 2297 this.renderNext(); 2298 }, 2299 2300 renderNext() { 2301 if (this.loading) return; 2302 2303 const from = this.offset + this.rendered; 2304 const next = this.matched.slice(from, from + this.pageSize); 2305 if (!next.length) return; 2306 2307 const missing = next.filter((no) => !this.cards.has(no)); 2308 if (!missing.length) { 2309 this.appendCards(next); 2310 return; 2311 } 2312 2313 this.loading = true; 2314 const loader = $("#productListLoader"); 2315 loader.addClass("loading"); 2316 2317 $.ajax({
2318 method: "POST", 2319 url: "/api/categoryv2/products", 2320 contentType: "application/json", 2321 dataType: "json", 2322 data: JSON.stringify({ nos: missing }), 2323 }) 2324 .done((response) => { 2325 this.storeCards(response.html); 2326 this.appendCards(next); 2327 }) 2328 .fail(() => { 2329 loader.addClass("error"); 2330 setTimeout(() => loader.removeClass("error"), 3000); 2331 }) 2332 .always(() => { 2333 this.loading = false; 2334 loader.removeClass("loading"); 2335 this.onScroll(); 2336 }); 2337 }, 2338 2339 storeCards(html) { 2340 if (!html) return; 2341 $($.parseHTML(html)).each((i, node) => { 2342 const card = $(node); 2343 if (!card.hasClass("ProductItem")) return; 2344 this.cards.set(card.attr("data-sku"), card); 2345 }); 2346 }, 2347 2348 appendCards(nos) { 2349 nos.forEach((no) => { 2350 const card = this.cards.get(no); 2351 if (card) this.listFor(no).append(card); 2352 }); 2353 this.rendered += nos.length; 2354 2355 if (typeof lazyload === "function") lazyload(); 2356 AddToCartBtn(); 2357 incDecInit(); 2358 if (typeof favoritesButtonInit === "function") favoritesButtonInit(); 2359 2360 this.updateState(); 2361 }, 2362 2363 updateTotal() { 2364 if (this.groupTemplate) this.countGroups(); 2365 const total = $(".TotalProducts"); 2366 if (!total.length) return; 2367 total.html(this.matched.length + " " + total.data("label")); 2368 }, 2369 2370 updateState() { 2371 $("#productListLoader").toggleClass("active", this.offset + this.rendered < this.matched.length); 2372 $(".emptyFilterResult").toggleClass("active", this.matched.length === 0); 2373 }, 2374 2375 onScroll() { 2376 if (!this.active || this.loading) return; 2377 if (this.offset + this.rendered >= this.matched.length) return; 2378 2379 const loader = $("#productListLoader"); 2380 if (!loader.length) return; 2381 2382 if ($(window).scrollTop() + $(window).height() + 300 >= loader.offset().top) { 2383 this.renderNext(); 2384 } 2385 }, 2386}; 2387 2388function sortDropdownInit() { 2389 defaultSortingMethod = $(sortDropdown).attr("default"); 2390 if (defaultSortingMethod == "") defaultSortingMethod = "name-az"; 2391 if (!sortingMethod) { 2392 sortingMethod = defaultSortingMethod; 2393 } 2394 $(sortDropdown).val(sortingMethod); 2395 $(sortDropdown).on("change", function () { 2396 setUrlParams(); 2397 }); 2398} 2399function galleryViewInit() { 2400 $(galleryClasses).each(function () { 2401 $(`.btn-layout:has([value="${this}"])`).css("display", "flex"); 2402 }); 2403 $(".GalleryBox").attr("class", "GalleryBox " + currentGalleryClass); 2404 $(".btn-layout:has([value=" + currentGalleryClass + "]) input").attr("checked", ""); 2405} 2406function niceSelectInit() { 2407 !(function (e) { 2408 e.fn.niceSelect = function (t) { 2409 var listHeight; 2410 function s(t) { 2411 t.after( 2412 e("<div></div>") 2413 .addClass("nice-select") 2414 .addClass(t.attr("class") || "") 2415 .addClass(t.attr("disabled") ? "disabled" : "") 2416 .attr("tabindex", t.attr("disabled") ? null : "0") 2417 .html('<span class="current"></span><ul class="list"></ul>'), 2418 ); 2419 let s = t.next(), 2420 n = t.find("option"), 2421 i = t.find("option:selected"); 2422 (s.find(".current").html(i.data("display") || i.text()), 2423 n.each(function (t) { 2424 let n = e(this), 2425 i = n.data("display"); 2426 let stock = n.data("stock") != null ? n.data("stock") : ""; 2427 let pricetag = n.data("pricetag") != null ? n.data("pricetag") : ""; 2428 let stockText = n.data("stocktext") != null ? n.data("stocktext") : ""; 2429 let text = n.text(); 2430 s.find("ul").append( 2431 e("<li></li>") 2432 .attr("data-value", n.val()) 2433 .attr("pricetag", n.data("pricetag")) 2434 .attr("stock", n.data("stock")) 2435 .attr("stocktext", n.data("stocktext")) 2436 .attr("data-display", i || null) 2437 .addClass( 2438 "option" + 2439 (n.is(":selected") ? " selected" : "") + 2440 (n.is(":disabled") ? " disabled" : "") + 2441 (n.attr("class") ? " " + n.attr("class") : ""), 2442 ) 2443 .html( 2444 "<span class='valuetext'>" + 2445 text + 2446 "</span>" + 2447 "<span class='stock'>" + 2448 stock + 2449 "</span><span class='pricetag'>" + 2450 pricetag + 2451 " </span><span class='stocktext'>" + 2452 stockText + 2453 "</span>", 2454 ), 2455 ); 2456 })); 2457 listHeight = s.find("ul").outerHeight(); 2458 } 2459 if ("string" == typeof t) 2460 return ( 2461 "update" == t 2462 ? this.each(function () { 2463 let t = e(this), 2464 n = e(this).next(".nice-select"), 2465 i = n.hasClass("open"); 2466 n.length && (n.remove(), s(t), i && t.next().trigger("click")); 2467 }) 2468 : "destroy" == t 2469 ? (this.each(function () { 2470 let t = e(this), 2471 s = e(this).next(".nice-select"); 2472 s.length && (s.remove(), t.css("display", "")); 2473 }), 2474 0 == e(".nice-select").length && e(document).off(".nice_select")) 2475 : console.log('Method "' + t + '" does not exist.'), 2476 this 2477 ); 2478 (this.hide(), 2479 this.each(function () { 2480 let t = e(this); 2481 t.next().hasClass("nice-select") || s(t); 2482 }), 2483 e(document).off(".nice_select"), 2484 e(document).on("click.nice_select", ".nice-select", function (t) { 2485 let s = e(this); 2486 e(".nice-select").not(s).removeClass("open"); 2487 s.toggleClass("open"); 2488 if (s.hasClass("open")) { 2489 (s.find(".option"), s.find(".focus").removeClass("focus"), s.find(".selected").addClass("focus")); 2490 2491 if ($(".mobileTrue").length) { 2492 return; 2493 } 2494 const navbarIntrusion = Math.max( 2495 0, 2496 $("#primary_nav").outerHeight() - ($(window).scrollTop() - $("#primary_nav").offset().top), 2497 ); 2498 const spaceAbove = s.offset().top - $(window).scrollTop() - navbarIntrusion; 2499 const footerIntrusion = Math.max(0, $(window).scrollTop() + $(window).height() - $("footer").offset().top); 2500 const spaceBelow = 2501 $(window).height() - (s.offset().top - $(window).scrollTop() + s.outerHeight()) - footerIntrusion; 2502 s.find("ul").removeClass("open-upwards"); 2503 if (listHeight < spaceBelow - 20) return; 2504 if (spaceAbove > spaceBelow) { 2505 s.find("ul").addClass("open-upwards"); 2506 } 2507 if (listHeight < spaceAbove - 20) { 2508 return; 2509 } 2510 const maxHeight = Math.max(spaceBelow, spaceAbove) - 20; 2511 s.find("ul").css("max-height", maxHeight + "px"); 2512 } else { 2513 s.trigger("focus"); 2514 } 2515 }), 2516 e(document).on("click.nice_select", function (t) {
2517 0 === e(t.target).closest(".nice-select").length && e(".nice-select").removeClass("open").find(".option"); 2518 }), 2519 e(document).on("click.nice_select", ".nice-select .option:not(.disabled)", function (t) { 2520 let s = e(this), 2521 n = s.closest(".nice-select"); 2522 (n.find(".selected").removeClass("selected"), s.addClass("selected")); 2523 let i = s.data("display") || s.find(".valuetext").text(); 2524 (n.find(".current").text(i), n.prev("select").val(s.data("value")).trigger("change")); 2525 }), 2526 e(document).on("keydown.nice_select", ".nice-select", function (t) { 2527 let s = e(this), 2528 n = e(s.find(".focus") || s.find(".list .option.selected")); 2529 if (t.key === "Enter") return (s.hasClass("open") ? n.trigger("click") : s.trigger("click"), !1); 2530 if (t.key === "ArrowDown") { 2531 if (s.hasClass("open")) { 2532 let i = n.nextAll(".option:not(.disabled)").first(); 2533 i.length > 0 && (s.find(".focus").removeClass("focus"), i.addClass("focus")); 2534 } else { 2535 s.trigger("click"); 2536 } 2537 return !1; 2538 } 2539 if (t.key === "ArrowUp") { 2540 if (s.hasClass("open")) { 2541 let l = n.prevAll(".option:not(.disabled)").first(); 2542 l.length > 0 && (s.find(".focus").removeClass("focus"), l.addClass("focus")); 2543 } else { 2544 s.trigger("click"); 2545 } 2546 return !1; 2547 } 2548 if (t.key === "Escape") s.hasClass("open") && s.trigger("click"); 2549 else if (t.key === "Tab" && s.hasClass("open")) return !1; 2550 })); 2551 2; 2552 let n = document.createElement("a").style; 2553 return ((n.cssText = "pointer-events:auto"), "auto" !== n.pointerEvents && e("html").addClass("no-csspointerevents"), this); 2554 }; 2555 })(jQuery); 2556} 2557function searchSelectInit() { 2558 !(function (e) { 2559 e.fn.searchSelect = function () { 2560 const select = $(this); 2561 const input = $( 2562 `<input type="text" class="searchSelectInput" placeholder="${select.find(".option.selected .valuetext").text()}">`, 2563 ); 2564 $(this).append(input); 2565 $(input) 2566 .on("focus", function (e) { 2567 $(this).val("").trigger("input"); 2568 $(this).data("placehodler", $(this).attr("placeholder")); 2569 $(this).attr("placeholder", "Type to search..."); 2570 }) 2571 .on("keydown", function (e) { 2572 if (e.key === "Escape") { 2573 $(this).trigger("blur"); 2574 return; 2575 } 2576 if (e.key === "ArrowDown" || e.key === "ArrowUp") { 2577 const selected = select.find(".option:visible").filter(".active"); 2578 let next = null; 2579 if (e.key === "ArrowDown") { 2580 next = selected.nextAll(".option:visible").first(); 2581 } else { 2582 next = selected.prevAll(".option:visible").first(); 2583 } 2584 if (!next.length) { 2585 if (e.key === "ArrowDown") { 2586 next = select.find(".option:visible").first(); 2587 } else { 2588 next = select.find(".option:visible").last(); 2589 } 2590 } 2591 selected.removeClass("active"); 2592 next.addClass("active"); 2593 const list = select.find(".list"); 2594 if (next.offset().top < list.offset().top) { 2595 list.scrollTop(list.scrollTop() + next.offset().top - list.offset().top); 2596 } else if (next.offset().top + next.outerHeight() > list.offset().top + list.outerHeight()) { 2597 list.scrollTop( 2598 list.scrollTop() + next.offset().top + next.outerHeight() - (list.offset().top + list.outerHeight()), 2599 ); 2600 } 2601 return; 2602 } 2603 if (e.key === "Enter") { 2604 e.preventDefault(); 2605 e.stopPropagation(); 2606 const selected = select.find(".option:visible").filter(".active"); 2607 if (selected.length) { 2608 selected.trigger("click"); 2609 } 2610 return; 2611 } 2612 }) 2613 .on("input", function (e) { 2614 const searchTerm = $(this).val().toLowerCase(); 2615 if (searchTerm === "") { 2616 select.removeClass("filter"); 2617 select.find(".option").show(); 2618 return; 2619 } 2620 2621 select.addClass("filter"); 2622 const shown = select.find(".option").filter(function () { 2623 const show = 2624 $(this).text().toLowerCase().indexOf(searchTerm) > -1 || 2625 $(this).attr("data-value")?.toLowerCase().indexOf(searchTerm) > -1; 2626 $(this).toggle(show); 2627 return show; 2628 }); 2629 shown.first().addClass("active").siblings().removeClass("active"); 2630 }) 2631 .on("blur", function () { 2632 $(this).attr("placeholder", $(this).data("placehodler")); 2633 }) 2634 }; 2635 })(jQuery); 2636} 2637function ajaxQueueInit() { 2638 (function ($) { 2639 // jQuery on an empty object, we are going to use this as our Queue 2640 let ajaxQueue = $({}); 2641 $.ajaxQueue = function (ajaxOpts) { 2642 let jqXHR, 2643 dfd = $.Deferred(), 2644 promise = dfd.promise(); 2645 2646 // queue our ajax request 2647 ajaxQueue.queue(doRequest); 2648 2649 // add the abort method 2650 promise.abort = function (statusText) { 2651 // proxy abort to the jqXHR if it is active 2652 if (jqXHR) { 2653 return jqXHR.abort(statusText); 2654 } 2655 // if there wasn't already a jqXHR we need to remove from queue 2656 let queue = ajaxQueue.queue(), 2657 index = $.inArray(doRequest, queue); 2658 2659 if (index > -1) { 2660 queue.splice(index, 1); 2661 } 2662 // and then reject the deferred 2663 dfd.rejectWith(ajaxOpts.context || ajaxOpts, [promise, statusText, ""]); 2664 return promise; 2665 }; 2666 2667 // run the actual query 2668 function doRequest(next) { 2669 jqXHR = $.ajax(ajaxOpts).done(dfd.resolve).fail(dfd.reject).then(next, next); 2670 } 2671 return promise; 2672 }; 2673 })(jQuery); 2674} 2675function favoritesButtonInit() { 2676 if ($(".favoriteStarButton").length) { 2677 $(".favoriteStarButton").on("click", function () { 2678 toggleFavoritesDropdown($(this)); 2679 }); 2680 $("body").on("click", function (event) { 2681 //Hide the favorites dropdown if clicked outside 2682 if (!$(event.target).closest(".favoriteStarButton").length && !$(event.target).closest(".favoritesDropdown").length) { 2683 $(".showDropdown").removeClass("showDropdown"); 2684 } 2685 }); 2686 } 2687} 2688 2689// --- BASKET --- // 2690function parseBasket(data) { 2691 if (data == "") { 2692 $("body").removeClass("loading"); 2693 return; 2694 } 2695 2696 let decimalnum = (0.1).toString(); 2697 let decmialstr = decimalnum.substring(1, 2); 2698 2699 $(".dynBasketQuantity").text( 2700 data.salesQuoteLines.reduce(function (_this, val) { 2701 if (showBasketQtySum == "0") { 2702 if (val.lineObjectNumber != freightitem) { 2703 $("body").removeClass("loading"); 2704 return Math.round((parseFloat(_this) + 1 + Number.EPSILON) * 100) / 100; 2705 } else { 2706 $("body").removeClass("loading"); 2707 return _this; 2708 } 2709 } else { 2710 if (val.lineObjectNumber != freightitem) { 2711 $("body").removeClass("loading"); 2712 return Math.round((parseFloat(_this) + val.quantity + Number.EPSILON) * 100) / 100; 2713 } else { 2714 $("body").removeClass("loading"); 2715 return Math.round((_this + Number.EPSILON) * 100) / 100; 2716 } 2717 } 2718 }, 0), 2719 ); 2720 2721 $(".dynBasketQuantity").text($(".dynBasketQuantity").text().replace(decmialstr, DecimalSeparator)); 2722 2723 $(".topBarBasketSection").addClass("changed"); 2724 setTimeout(function () { 2725 $(".topBarBasketSection").removeClass("changed"); 2726 }, basketChangedTimeout); 2727 2728 let AllLines = []; 2729 2730 if ($("body").hasClass("pageViewBasket")) { 2731 $(".pageViewBasket .RowQuickAddToCartLink").on("click", function (e) { 2732 $(this).animate( 2733 { 2734 opacity: "0", 2735 }, 2736 200, 2737 );
2738 if (!quickAddInitialized) quickAddInit(); 2739 $(".basketRowLines_QuickAddToCart *").val(""); 2740 $("html, body").animate( 2741 { 2742 scrollTop: "1px", 2743 }, 2744 400, 2745 ); 2746 2747 $(".basketRowLines_QuickAddToCart").animate( 2748 { 2749 maxHeight: "100px", 2750 }, 2751 400, 2752 ).css('margin-block', 'var(--gapS) var(--gapSM)'); 2753 setTimeout(function () { 2754 $(".basketRowLines_QuickAddToCart").css("overflow", "visible"); 2755 }, 600); 2756 $("#quicksku").focus(); 2757 }); 2758 2759 let reloadBasket = false; 2760 2761 if (data.salesQuoteLines.length > 0) { 2762 $(data.salesQuoteLines).each(function (k, v) { 2763 if ($('.syncItem[itemNo="' + v.id + '"]').length == 0) { 2764 reloadBasket = true; 2765 } 2766 AllLines.push(v.id); 2767 }); 2768 } else if ($(".syncItem").length == 0) { 2769 $("body.loading").removeClass("loading"); 2770 } 2771 2772 $(".syncItem").each(function () { 2773 if (!AllLines.includes($(this).attr("itemNo"))) { 2774 reloadBasket = true; 2775 } 2776 }); 2777 2778 if (reloadBasket) { 2779 reloadBasketPage(function () { 2780 $("body").removeClass("loading"); 2781 }); 2782 } 2783 } 2784 2785 //Update basket lines 2786 if (!$(".dynBasketLine.showFieldTrue").length) { 2787 $(data.salesQuoteLines).each(function (k, v) { 2788 const elm = $('.basketRowLine[itemNo="' + v.id + '"]'); 2789 elm.find(".Text_Name").html(v.description); 2790 $(".dynBasketUnitPrice" + v.id).text(v.unitPrice); 2791 if (v.discountPercent > 0) { 2792 //Here used to be the line below, but this do not work when the discount is a percentage... 2793 //$('.dynBasketDiscountPercent' + v.id).text(v.discountPercent + '%'); 2794 $(".dynBasketDiscountPercent" + v.id).text(v.discountPercent + "%"); 2795 } else { 2796 $(".dynBasketDiscountPercent" + v.id).text(""); 2797 } 2798 $(".dynBasketTotalPrice" + v.id).text(v.amountExcludingTax); 2799 }); 2800 } 2801 2802 $(".dynBasketTotal_ExcludeTax").text(data.totalAmountExcludingTax); 2803 $(".dynBasketTotal_DiscountAmount").text(data.discountAmount); 2804 $(".dynBasketTotal_PureTax").text(data.totalTaxAmount); 2805 $(".dynBasketTotal_IncludeTax").text(data.totalAmountIncludingTax); 2806 2807 onBasketParsed(data); 2808} 2809function reloadBasketPage(callback) { 2810 $.ajax({
2811 url: "/basket/", 2812 success: function (data) { 2813 $(".basketRowLines").html($(data).find(".basketRowLines").html()); 2814 $(".basketlinehash").val($(data).find(".basketlinehash").val()); 2815 2816 lazyload(); 2817 incDecInit(true); 2818 if (callback != null) { 2819 callback(); 2820 } 2821 onBasketUpdated(); 2822 }, 2823 }); 2824} 2825function setBasketInfo() { 2826 $.ajax({ 2827 method: "POST", 2828 url: "/api/basket/info/set", 2829 cache: false, 2830 data: $(".RenderBody *:visible").serialize(), 2831 }) 2832 .done(function (data) {}) 2833 .fail(function () {}) 2834 .always(function () {}); 2835} 2836function getBasketInfo() { 2837 $.ajax({ 2838 method: "GET", 2839 url: "/api/basket/info/get", 2840 cache: false, 2841 }) 2842 .done(function (data) { 2843 $.each(JSON.parse(data), function (k, v) { 2844 if (v == "true") 2845 $('*[name="' + k + '"]') 2846 .attr("checked", "checked") 2847 .trigger("change"); 2848 else 2849 $('*[name="' + k + '"]') 2850 .val(v) 2851 .trigger("change"); 2852 }); 2853 }) 2854 .fail(function () {}) 2855 .always(function () { 2856 $(".scShopCheckout input, .scShopCheckout select, .scShopCheckout textarea").on("change", function (e) { 2857 setBasketInfo(); 2858 }); 2859 }); 2860} 2861function incDecInit(reload = false, selector = null) { 2862 selector = selector || (reload ? $(".basketRowLines input.addIncDec") : $(".addIncDec")); 2863 2864 // Replace input elements with increment/decrement buttons 2865 $(selector).each(function () { 2866 const decBtn = $('<div class="input-group-text decButton">-</div>'); 2867 const incBtn = $('<div class="input-group-text incButton">+</div>'); 2868 2869 $(this) 2870 .removeClass("addIncDec") 2871 .addClass("incDecInput") 2872 .attr("onblur", "checkAmountValidity(this)") 2873 .wrap('<div class="input-group input-group-sm IncDecSection flex-nowrap"></div>') 2874 .before(decBtn) 2875 .after(incBtn); 2876 2877 $(this).on("blur", function () { 2878 checkMinMaxDecimals(this); 2879 }); 2880 2881 // Handle click events for increment/decrement buttons 2882 $(decBtn) 2883 .off() 2884 .on("click", function (e) { 2885 e.preventDefault(); 2886 adjustValue(this, false); 2887 }); 2888 $(incBtn) 2889 .off() 2890 .on("click", function (e) { 2891 e.preventDefault(); 2892 adjustValue(this, true); 2893 }); 2894 $(this) 2895 .on("click", function (e) { 2896 e.preventDefault(); 2897 }) 2898 .on("keydown", function (e) { 2899 if (e.key === "ArrowUp") { 2900 e.preventDefault(); 2901 adjustValue(this, true); 2902 } else if (e.key === "ArrowDown") { 2903 e.preventDefault(); 2904 adjustValue(this, false); 2905 } 2906 }); 2907 }); 2908 2909 // to be deprecated 2910 $(".StairStepPricing") 2911 .closest("div:has(.ProductAmountInput)") 2912 .find(".ProductAmountInput") 2913 .each(function () { 2914 $(this).off("change keyup"); 2915 $(this).on("change keyup", function () { 2916 let stairseps = $(this).closest("div:has(.StairStepPricing)").find(".StairStepPricing"); 2917 const form = $(this); 2918 $(stairseps).each(function () { 2919 let tbody = $(this).find("tbody"); 2920 let rows = $(tbody).find("tr"); 2921 let qtyPricePairs = []; 2922 let amount = $(form).val(); 2923 if (!amount || parseInt(amount) < 1) return; 2924 $(rows).each(function () { 2925 let qty = $(this).find(".qty").text().trim(); 2926 let price = $(this).find(".price").text().trim(); 2927 qtyPricePairs.push({ qty: qty, price: price }); 2928 }); 2929 let i = rows.length - 1; 2930 let qtyRow = null; 2931 while (qtyRow == null) { 2932 let qty = qtyPricePairs[i].qty; 2933 if (parseInt(amount) >= parseInt(qty)) { 2934 qtyRow = i; 2935 break; 2936 } 2937 i--; 2938 } 2939 $(tbody).attr("data-active", qtyRow); 2940 2941 $(rows[qtyRow]).siblings().removeClass("active"); 2942 2943 if (qtyRow > 0) { 2944 $(rows[qtyRow]).addClass("active"); 2945 } 2946 if (!$(rows[qtyRow]).find(".price").length) return; 2947 let priceSpan = $(form).parents(".InteractRow").siblings(".PriceRow").find('[replaceKey="GetPriceTag"]'); 2948 if (!priceSpan.length) { 2949 priceSpan = $(form).parents(".ProductBuy").find('[replaceKey="GetPriceTag"]'); 2950 } 2951 let newPrice = $(rows[qtyRow]).find(".price").html(); 2952 priceSpan.html(newPrice); 2953 }); 2954 }); 2955 }); 2956}
2957function deleteProductFromBasket(elm, basketGuid, lineGuid) { 2958 elm.removeClass("scError"); 2959 2960 let qty = elm.val(); 2961 2962 let line = $(elm).parents(".basketRowLine"); 2963 let height = $(line).outerHeight(); 2964 $(line) 2965 .addClass("deleting") 2966 .css({ height: height + "px", "max-height": height + "px" }); 2967 setTimeout(function () { 2968 $(line).html("").removeClass("deleting").addClass("deleted"); 2969 setTimeout(function () { 2970 $(line).remove(); 2971 }, 350); 2972 }, 350); 2973 2974 $.ajax({ 2975 method: "POST", 2976 url: "/api/basket/edit/" + basketGuid + "/lines/" + lineGuid + "/remove/", 2977 cache: false, 2978 }) 2979 .done(function (data) { 2980 parseBasket(data); 2981 if (!data.salesQuoteLines.length) { 2982 window.location.reload(); 2983 } 2984 onProductRemoved(); 2985 }) 2986 .fail(function () { 2987 elm.addClass("scError"); 2988 }) 2989 .always(function () {}); 2990} 2991function UpdateOrderDate(elm) { 2992 $("#completeOrder").prop("disabled", true); 2993 $("#completeOrder").text(WaitMessage); 2994 $("body").addClass("loading"); 2995 $.ajax({
2996 method: "POST", 2997 url: "/api/basket/updateorderdate/" + $(elm).val(), 2998 cache: false, 2999 dataType: "json", 3000 contentType: "application/json", 3001 }) 3002 .done(function (data) {}) 3003 .fail(function (jqXHR, textStatus) {}) 3004 .always(function () { 3005 document.location.href = document.location.href.split("#")[0]; 3006 }); 3007} 3008// Comments 3009function openComment(elm) { 3010 let sourceRow = $(elm).find('.row[class*="CommentLine_"]'); 3011 let modalElm = $(".modal-commentline").first(); 3012 modalElm.find(".cloneRow:not(.d-none)").remove(); 3013 let cloneRow = modalElm.find(".cloneRow"); 3014 new bootstrap.Modal(modalElm, { 3015 keyboard: true, 3016 }); 3017 sourceRow.each(function (k, v) { 3018 let cloneEditRow = cloneRow.clone(); 3019 let input = cloneEditRow.find("input"); 3020 let label = cloneEditRow.find("label"); 3021 /*CONTENT*/ 3022 // cleanup remove all old data tags 3023 $.each(input.data(), function (i) { 3024 input.removeAttr("data-" + i); 3025 }); 3026 // copy all data tags from source to target 3027 $.each($(v).data(), function (i, v) { 3028 input.attr("data-" + i, v); 3029 }); 3030 3031 let MDValue = $(v).data("masterdatavalue"); 3032 label.text($(v).data("key")).attr("for", MDValue); 3033 input.val($(v).data("value")).attr("id", MDValue); 3034 3035 if (MDValue.endsWith("DATE")) { 3036 input.attr("type", "DATE"); 3037 } else if (MDValue.endsWith("SELECT")) { 3038 let id = $(v).data("lineid"); 3039 let selecthtml = $(".LineSelectLineId" + MDValue + id).prop("outerHTML"); 3040 selecthtml = selecthtml.replace("d-none", ""); 3041 input.replaceWith(selecthtml); 3042 } else if (MDValue.endsWith("FILE") || MDValue.endsWith("IMAGE")) { 3043 input.attr("type", "FILE"); 3044 input.on("change", function () { 3045 cartCommentFileChanged(input); 3046 }); 3047 let wrapper = $("<div>").addClass("fileInputWrapper"); 3048 $(input).wrap($(wrapper)); 3049 let orgValue = $(input).data("orgvalue"); 3050 if (orgValue) { 3051 $(input).after($("<span>").html(orgValue.split("\\").pop())); 3052 } 3053 } else { 3054 input.attr("type", "TEXT"); 3055 } 3056 /*CONTENT*/ 3057 cloneEditRow.removeClass("d-none"); 3058 cloneEditRow.insertBefore(cloneRow); 3059 }); 3060 $(".modal-commentline").first().modal("show"); 3061} 3062function saveHeaderComment(elm) { 3063 if (!hasFired) { 3064 hasFired = true; 3065 let postData = new Array(); 3066 let bAnyNewLines = false; 3067 3068 $("#completeOrder").prop("disabled", true); 3069 $("#completeOrder").text(WaitMessage); 3070 3071 $(".HeaderComment").each(function () { 3072 let value = $(this).val(); 3073 let orgvalue = $(this).data("orgvalue"); 3074 3075 if (value != orgvalue) { 3076 $(this).data("orgvalue", value); 3077 3078 cl = new CommentLine(); 3079 cl.salesquoteid = $(this).data("salesquoteid"); 3080 cl.lineid = 0; 3081 cl.lineno = $(this).data("lineno").toString(); 3082 cl.masterdatacode = $(this).data("masterdatacode"); 3083 cl.masterdatavalue = $(this).data("masterdatavalue"); 3084 cl.value = encodeURIComponent(value); 3085 cl.isfile = ""; 3086 cl.filedata = ""; 3087 postData.push(cl); 3088 bAnyNewLines = true; 3089 } 3090 }); 3091 3092 if (bAnyNewLines) { 3093 $("body").addClass("loading"); 3094 $.ajax({
3095 method: "POST", 3096 url: "/api/basket/updateheadercomments/", 3097 cache: false, 3098 data: JSON.stringify(postData), 3099 contentType: "application/json", 3100 }) 3101 .done(function (data) {}) 3102 .fail(function (jqXHR, textStatus) {}) 3103 .always(function (data) { 3104 if (data == "1" || data == "reload") { 3105 document.location.href = document.location.href.split("#")[0]; 3106 } else { 3107 $("body").removeClass("loading"); 3108 $("#completeOrder").prop("disabled", false); 3109 $("#completeOrder").text(CompleteMessage); 3110 hasFired = false; 3111 } 3112 }); 3113 } else { 3114 $("body").removeClass("loading"); 3115 $("#completeOrder").prop("disabled", false); 3116 $("#completeOrder").text(CompleteMessage); 3117 hasFired = false; 3118 } 3119 } 3120} 3121function saveCommentLines() { 3122 let postData = new Array(); 3123 let bAnyNewLines = false; 3124 3125 $(".modal-commentline .cloneRow:not(.d-none) input.basketLineComments").each(function () { 3126 let value = $(this).val(); 3127 let orgvalue = $(this).data("orgvalue"); 3128 let hasFile = $(this).data("hasfile"); 3129 let fileData = $(this).data("filedata"); 3130 let type = $(this).attr("type"); 3131 3132 if (hasFile || (value != orgvalue && type != "FILE")) { 3133 let cl = new CommentLine(); 3134 cl.salesquoteid = $(this).data("salesquoteid"); 3135 cl.lineid = $(this).data("lineid"); 3136 cl.lineno = $(this).data("lineno").toString(); 3137 cl.masterdatacode = $(this).data("masterdatacode"); 3138 cl.masterdatavalue = $(this).data("masterdatavalue"); 3139 cl.value = encodeURIComponent(value.split("\\").pop()); 3140 cl.isfile = hasFile ? "true" : ""; 3141 cl.filedata = fileData || ""; 3142 postData.push(cl); 3143 bAnyNewLines = true; 3144 } 3145 }); 3146 $(".modal-commentline .cloneRow:not(.d-none) select.basketLineComments").each(function () { 3147 let value = $(this).find(":selected").val(); 3148 let orgvalue = $(this).data("orgvalue"); 3149 let type = $(this).attr("type"); 3150 3151 if (value != orgvalue) { 3152 let cl = new CommentLine(); 3153 cl.salesquoteid = $(this).data("salesquoteid"); 3154 cl.lineid = $(this).data("lineid"); 3155 cl.lineno = $(this).data("lineno").toString(); 3156 cl.masterdatacode = $(this).data("masterdatacode"); 3157 cl.masterdatavalue = $(this).data("masterdatavalue"); 3158 cl.value = encodeURIComponent(value.split("\\").pop()); 3159 cl.isfile = ""; 3160 cl.filedata = ""; 3161 postData.push(cl); 3162 bAnyNewLines = true; 3163 } 3164 }); 3165 3166 if (bAnyNewLines) { 3167 $("body").addClass("loading"); 3168 $.ajax({
3169 method: "POST", 3170 url: "/api/basket/updatelinecomments/" + $(".basketlinehash").val(), 3171 cache: false, 3172 dataType: "json", 3173 data: JSON.stringify(postData), 3174 contentType: "application/json", 3175 }).always(function (jqXHR) { 3176 if (jqXHR.status == 200) { 3177 $(".modal-commentline").first().modal("hide"); 3178 setTimeout(function () { 3179 reloadBasketPage(function () { 3180 $("body").removeClass("loading"); 3181 }); 3182 }, 800); 3183 } 3184 }); 3185 } else { 3186 $(".modal-commentline").first().modal("hide"); 3187 } 3188} 3189// Quick add by SKU 3190function quickAddInit() { 3191 $(".basketRowLines_QuickAddToCart .incDecInput").on("keydown", function (e) { 3192 if (e.keyCode == 13) { 3193 if ($(this).val() > 0) $(".basketRowLines_QuickAddToCart .addToCartAni").trigger("mouseup"); 3194 } 3195 }); 3196 $("#quicksku").on("keyup", function (e) { 3197 if (e.keyCode == 13) { 3198 $(this).trigger("blur"); 3199 quickskucheck($(this)); 3200 } else { 3201 quickskucheck($(this), false); 3202 } 3203 }); 3204 $(document).on("click", function () { 3205 $(".quickadderror").html("").hide(); 3206 }); 3207 quickAddInitialized = true; 3208} 3209function quickskucheck(elm, finalCheck = true) { 3210 const sku = $(elm).val(); 3211 if (sku == "") return; 3212 if (!finalCheck) $(".quickadderror").html("").hide(); 3213 $.ajax({
3214 method: "GET", 3215 url: "/shop/api/GetProduct/?productId=" + encodeURIComponent(sku) + "&$fields=variantsBc,variantDescription,no,uom,minQty,colliQty", 3216 cache: false, 3217 dataType: "json", 3218 }) 3219 .done(function (data) { 3220 $(elm).addClass("success"); 3221 if (data.value.variantsBc.length > 0) { 3222 $(".basketRowLines_QuickAddToCart:not(#quicksku)").val(""); 3223 $(".RowQuickAddToCart_quickvariant").show(); 3224 let loopOption = ""; 3225 $.each(data.value.variantsBc, function (i, variant) { 3226 loopOption += 3227 "<option value='" + variant.no + "' data-min-qty='" + variant.minQty + "' data-colli-qty='" + variant.colliQty + "'>" + variant.variantDescription + "</option>"; 3228 }); 3229 3230 //$('.dropdownVariant').html('<select name="quickvariant" id="quickvariant" onselect="return quickvariantcheck()" class="form-control form-control-sm">' + loopOption + '</select>'); 3231 $(".dropdownVariant").html( 3232 '<select name="quickvariant" id="quickvariant" class="form-control form-control-sm">' + loopOption + "</select>", 3233 ); 3234 3235 $("#quickvariant").on("change", applySelectedVariantQtyLimits); 3236 applySelectedVariantQtyLimits(); 3237 3238 $('*[name="quickvariant"]').select2({ 3239 tags: true, 3240 }); 3241 3242 if (!finalCheck) return; 3243 3244 $(document).on("select2:open", () => { 3245 document.querySelector(".select2-search__field").focus(); 3246 }); 3247 3248 $(document).on("select2:select", () => { 3249 $('*[name="quickqty"]').trigger("focus"); 3250 }); 3251 3252 $('*[name="quickvariant"]').select2("open"); 3253 } else { 3254 setQuickQtyLimits(data.value.minQty, data.value.colliQty); 3255 if (finalCheck) { 3256 $(".basketRowLines_QuickAddToCart .amount input").focus(); 3257 } 3258 } 3259 $(".quickadderror").html("").hide(); 3260 $('input[name="quickuom"]').val(data.value.uom); 3261 }) 3262 .fail(function () { 3263 $(elm).removeClass("success"); 3264 $("#quickvariant").prop("disabled", true).html(""); 3265 setQuickQtyLimits(); 3266 if (!finalCheck) return; 3267 $(".quickadderror").html(SKUNotFound).show(); 3268 $('input[name="quicksku"]').val(""); 3269 $('input[name="quicksku"]').trigger("focus"); 3270 }); 3271} 3272function setQuickQtyLimits(minQty = 0, colliQty = 1) { 3273 $('input[name="quickqty"]').attr({ min: minQty, step: colliQty }); 3274} 3275function applySelectedVariantQtyLimits() { 3276 const selectedVariant = $("#quickvariant option:selected"); 3277 setQuickQtyLimits(selectedVariant.data("min-qty"), selectedVariant.data("colli-qty")); 3278} 3279function quickaddtocart(elm) { 3280 let qtyInput = $('input[name = "quickqty"]'); 3281 checkMinMaxDecimals(qtyInput); 3282 3283 let sku = $('input[name="quicksku"]').val(); 3284 const variant = $('*[name="quickvariant"]').val(); 3285 const qty = qtyInput.val(); 3286 const variantDescription = $("#select2-quickvariant-container").text(); 3287 3288 let uom = $('input[name="quickuom"]').val(); 3289 let error = ""; 3290 3291 if (!(sku && qty) || qty <= 0) { 3292 error = "Fill in all fields"; 3293 } 3294 if (uom) { 3295 uom = "/uom/" + encodeURIComponent(uom); 3296 } 3297 3298 let itemVariantId = ""; 3299 3300 if (variant) itemVariantId = "/itemvariantid/" + encodeURIComponent(variant); 3301 3302 $(elm).find(".addedToCartText").removeClass("d-none"); 3303 3304 let spanContent = $(elm).find("span").html(); 3305 $(elm).css({ 3306 width: $(elm).outerWidth() + "px", 3307 height: $(elm).outerHeight() + "px", 3308 }); 3309 $(elm).find("span").html(""); 3310 $(elm).addClass("loading"); 3311 3312 if (sku.indexOf("/") > 0) { 3313 sku = sku.replace("/", "%2F"); 3314 } 3315 3316 const oldRowLines = $(".basketRowLines .basketRowLine"); 3317 $.ajax({
3318 method: "POST", 3319 url: "/api/basket/add/" + encodeURIComponent(sku) + "/quantity/" + qty + uom + itemVariantId, 3320 cache: false, 3321 dataType: "json", 3322 contentType: "application/json", 3323 }) 3324 .done(function (data) { 3325 //reset the quick add form 3326 $("#quickvariant").prop("disabled", true).html(""); 3327 $(".quickadderror").html("").hide(); 3328 $("#quicksku").val("").removeClass("success").focus(); 3329 $(".basketRowLines_QuickAddToCart *").val(""); 3330 $(elm).addClass("success").attr("last-class", "success"); 3331 3332 //reload basket partial and define callback function 3333 reloadBasketPage(function () { 3334 for (i = 0; i < $(".basketRowLines .basketRowLine").length; i++) { 3335 const line = $(".basketRowLines .basketRowLine").eq(i); 3336 const oldLine = $(oldRowLines).eq(i); 3337 if (line.attr("itemno") != oldLine.attr("itemno")) break; 3338 if (line.find(".incDecInput").val() != oldLine.find(".incDecInput").val()) break; 3339 } 3340 $(".newLineAlert").removeClass("newLineAlert"); 3341 $(".lineAdded").removeClass("lineAdded"); 3342 const newLine = $(".basketRowLines .basketRowLine").eq(i); 3343 if (oldRowLines.length != $(".basketRowLines .basketRowLine").length) { 3344 newLine.addClass("lineAdded"); 3345 let maxHeight = $(":root").css("--basketRowHeight"); 3346 if (!$(".screenW--xs--s--m--l").length) { 3347 const interactH = $(":root").css("--interactHeight"); 3348 const basketPadding = $(":root").css("--basketRowPadding"); 3349 maxHeight = `calc(${maxHeight} + ${interactH} + 2 * ${basketPadding})`; 3350 } 3351 setTimeout(function () { 3352 newLine.css("max-height", maxHeight); 3353 newLine.removeClass("newLineAlert"); 3354 }, 300); 3355 } 3356 newLine.addClass("newLineAlert"); 3357 }); 3358 }) 3359 .fail(function () { 3360 if (error == "") error = "Error adding item"; 3361 $(".quickadderror").html(error).show(); 3362 $(".basketRowLines_QuickAddToCart *").val(""); 3363 $("#quicksku").focus(); 3364 $(elm).addClass("error").attr("last-class", "error"); 3365 }) 3366 .always(function () { 3367 $(elm).removeClass("loading"); 3368 setTimeout(function () { 3369 $(elm).removeClass("error success"); 3370 if (spanContent) $(elm).find("span").html(spanContent); 3371 $(elm).css({ width: "", height: "" }); 3372 }, 3000); 3373 }); 3374} hideQuickAdd 3375function hideQuickAdd() { 3376 $(".RowQuickAddToCart").show(); 3377 $(".basketRowLines_QuickAddToCart").css("overflow", "clip"); 3378 $(".basketRowLines_QuickAddToCart").animate( 3379 { 3380 maxHeight: "0", 3381 }, 3382 400, 3383 ).css('margin-block', ""); 3384 $(".RowQuickAddToCartLink").animate( 3385 { 3386 opacity: "1", 3387 }, 3388 400, 3389 ); 3390} 3391function adjustValue(button, isIncrement) { 3392 if ($(button).closest(".interactDisabled").length) { 3393 return; 3394 } 3395 const input = $(button).parent().find("input"); 3396 const step = parseInt($(input).attr("step")) || 1; 3397 const min = parseInt($(input).attr("min")) || 0; 3398 const max = parseInt($(input).attr("max")); 3399 const type = $(input).attr("type"); 3400 const allowZero = $(input).attr("allow-zero") === "allow"; 3401 3402 let containsComma = false; 3403 let decimalPointMultiplier = Math.pow(10, $(input).data("decimals")); 3404 if (!decimalPointMultiplier) decimalPointMultiplier = 1; 3405 3406 let value = $(input).val() || 0; 3407 if (type == "number") value = parseInt(value); 3408 if (type == "text") { 3409 containsComma = value.includes(","); 3410 value = parseFloat(value.replaceAll(",", ".")); 3411 } 3412 value += isIncrement ? step : -step; 3413 value = Math.round(value * decimalPointMultiplier) / decimalPointMultiplier; 3414 3415 if (!isNaN(min) && value < min) { 3416 value = allowZero && !isIncrement ? 0 : min; 3417 } 3418 if (!isNaN(max) && value > max) value = max; 3419 if (type == "text") { 3420 if (containsComma) value = value.toString().replace(".", ","); 3421 else value = value.toString(); 3422 } 3423 input.val(value).change(); 3424} 3425// Salesquotes 3426function EmailQuotePdf() { 3427 let modalElm = $(".modal-quotepdf"); 3428 modalElm.modal("show"); 3429} 3430function SendQuotePdfEmail(quoteid) { 3431 $("body").addClass("loading"); 3432 $.ajax({
3433 method: "POST", 3434 url: "/shop/api/emailquote/" + quoteid, 3435 cache: false, 3436 contentType: "application/x-www-form-urlencoded", 3437 data: $(".modal-quotepdf *:visible").serialize(), 3438 }) 3439 .fail(function () { 3440 $("body").removeClass("loading"); 3441 3442 alert("Failed to send email!"); 3443 }) 3444 .done(function (response) { 3445 $("body").removeClass("loading"); 3446 let modalElm = $(".modal-quotepdf"); 3447 modalElm.modal("hide"); 3448 }); 3449} 3450function mutlipleSalesQuotesInit() { 3451 if ($("#selectUserSalesQuote").val() == "new") { 3452 $("#createUserSalesQuote").removeClass("d-none"); 3453 $("#salesQuoteDescription").removeClass("d-none").val(""); 3454 $("#deleteUserSalesQuote").addClass("d-none"); 3455 $("#renameUserSalesQuote").addClass("d-none"); 3456 } 3457 $("#selectUserSalesQuote").on("change", function () { 3458 selectSalesQuote($(this).val()); 3459 }); 3460 $("#deleteUserSalesQuote").on("click", function () { 3461 deleteSalesQuote($("#selectUserSalesQuote").attr("selectedId")); 3462 }); 3463 $("#createUserSalesQuote").on("click", function () { 3464 createUserQuote(); 3465 }); 3466 $("#renameUserSalesQuote").on("click", function () { 3467 renameButtonClick(); 3468 }); 3469 $("#salesQuoteDescription").on("keydown", function (e) { 3470 if (e.keyCode == 13) { 3471 if ($(".renameMode").length > 0) $("#renameUserSalesQuote").click(); 3472 else $("#createUserSalesQuote").click(); 3473 } else if (e.keyCode == 27) { 3474 $("#deleteUserSalesQuote").trigger("click"); 3475 } 3476 }); 3477} 3478function selectSalesQuote(val) { 3479 if (val == "new") { 3480 $("#selectUserSalesQuote").attr("lastSelected", $("#selectUserSalesQuote").attr("selectedId")); 3481 $("#createUserSalesQuote").removeClass("d-none"); 3482 $("#salesQuoteDescription").removeClass("d-none").val(""); 3483 $("#deleteUserSalesQuote").addClass("d-none"); 3484 $("#renameUserSalesQuote").addClass("d-none"); 3485 } else { 3486 if ($("#deleteUserSalesQuote").hasClass("d-none")) { 3487 $("#createUserSalesQuote").addClass("d-none"); 3488 $("#salesQuoteDescription").addClass("d-none"); 3489 $("#deleteUserSalesQuote").removeClass("d-none"); 3490 $("#renameUserSalesQuote").removeClass("d-none"); 3491 } 3492 if (val == $("#selectUserSalesQuote").attr("lastSelected")) return; 3493 $("body").addClass("loading"); 3494 3495 $.ajax({
3496 method: "GET", 3497 url: "/api/basket/setselectedsalesquote?id=" + val, 3498 cache: false, 3499 dataType: "json", 3500 contentType: "application/json", 3501 }) 3502 .done(function (data) {}) 3503 .always(function (data) { 3504 parseBasket(data); 3505 let selected = $("#selectUserSalesQuote").find("option:selected"); 3506 let newDesc = $(selected).attr("data-description"); 3507 $("#salesQuoteDescription").val(newDesc).attr("value", newDesc); 3508 $("#selectUserSalesQuote").attr("selectedId", $(selected).attr("value")); 3509 $("#selectUserSalesQuote").attr("selectedNo", $(selected).data("quoteno")); 3510 }); 3511 } 3512} 3513function createUserQuote() { 3514 let url = "/api/basket/createsalesquote?description=" + encodeURIComponent($("#salesQuoteDescription").val()); 3515 3516 $("body").addClass("loading"); 3517 3518 $.ajax({ 3519 method: "POST", 3520 url: url, 3521 cache: false, 3522 dataType: "json", 3523 contentType: "application/json", 3524 }) 3525 .done(function () {}) 3526 .always(function () { 3527 window.location.href = url; 3528 }); 3529} 3530function renameButtonClick() { 3531 $(".salesQuoteSection").addClass("renameMode"); 3532 $("#salesQuoteDescription").trigger("focus").trigger("select"); 3533 $("#renameUserSalesQuote") 3534 .off("click") 3535 .on("click", function () { 3536 renameSalesQuote(); 3537 }); 3538 $("#deleteUserSalesQuote") 3539 .off("click") 3540 .on("click", function () { 3541 $(".salesQuoteSection").attr("class", "salesQuoteSection"); 3542 $("#renameUserSalesQuote") 3543 .off("click") 3544 .on("click", function () { 3545 renameButtonClick(); 3546 }); 3547 $(this) 3548 .off("click") 3549 .on("click", function () { 3550 document.location.href = "/api/basket/deleteselectedsalesquote?id=" + $("#selectUserSalesQuote").attr("selectedId"); 3551 }); 3552 }); 3553} 3554function createSalesQuoteAsSalesPerson(customerNo, email) { 3555 if (email != null && email != "" && email.length > 0) { 3556 email = "/" + email; 3557 } 3558 3559 $.ajax({
3560 method: "GET", 3561 url: "/shop/api/SalesPersonCreateQuote/" + customerNo + email, 3562 cache: false, 3563 }) 3564 .fail(function (data) { 3565 alert(data.responseText); 3566 }) 3567 .done(function (data) { 3568 location.href = "/shop/api/SalesPersonImpersonate/" + customerNo + "/" + data; 3569 }); 3570} 3571function deleteSalesQuote(id) { 3572 $("body").addClass("loading"); 3573 $.ajax({ 3574 method: "GET", 3575 url: "/api/basket/deleteselectedsalesquote?id=" + id, 3576 cache: false, 3577 dataType: "json", 3578 contentType: "application/json", 3579 }) 3580 .done(function () {}) 3581 .always(function () { 3582 window.location.href = "/basket"; 3583 }); 3584} 3585function renameSalesQuote() { 3586 let url = 3587 "/api/basket/renamesalesquote?quoteno=" + 3588 $("#selectUserSalesQuote").attr("selectedNo") + 3589 "&id=" + 3590 $("#selectUserSalesQuote").attr("selectedId") + 3591 "&description=" + 3592 encodeURIComponent($("#salesQuoteDescription").val()); 3593 $("body").addClass("loading"); 3594 3595 $.ajax({
3596 method: "POST", 3597 url: url, 3598 cache: false, 3599 dataType: "json", 3600 contentType: "application/json", 3601 }) 3602 .done(function () {}) 3603 .always(function () { 3604 window.location.href = url; 3605 }); 3606} 3607 3608// --- CHECKOUT --- // 3609function completeOrder(elem_) { 3610 let object = {}; 3611 3612 if (!isOrderReadyForCompletion(elem_)) { 3613 return; 3614 } 3615 3616 $(".checkoutDiv input").each(function () { 3617 object[$(this).attr("name")] = $(this).val(); 3618 }); 3619 3620 if ($("#addShippingAddress")[0] != null) { 3621 object["newShippingAddress"] = $("#addShippingAddress")[0].checked; 3622 } 3623 if ($("#shippingAddressSelector") != null) { 3624 object["shippingAddressSelector"] = $("#shippingAddressSelector").val(); 3625 } 3626 3627 if ($("[name=shipToCountry]") != null) { 3628 object["shipToCountry"] = $("[name=shipToCountry]").find(":selected").val(); 3629 } 3630 3631 if ($("[name=shipToSaveAddress]") != null) { 3632 object["shipToSaveAddress"] = $("[name=shipToSaveAddress]").is(":checked") ? "true" : ""; 3633 } 3634 3635 let data_ = JSON.stringify(object); 3636 3637 $("body").addClass("loading"); 3638 3639 $.ajax({
3640 url: "/api/basket/edit/complete/", 3641 method: "POST", 3642 contentType: "application/json", 3643 data: data_, 3644 cache: false, 3645 }) 3646 .fail(function (response) { 3647 $("body").removeClass("loading"); 3648 $(elem_).removeClass("loading").addClass("error").attr("last-class", "error"); 3649 let errorOverlay = $("<div>").addClass("overlay errorOverlay").html(response.responseText); 3650 $(errorOverlay) 3651 .find(":after") 3652 .on("click", function () { 3653 $(errorOverlay).remove(); 3654 }); 3655 $(elem_).after($(errorOverlay)); 3656 setTimeout(function () { 3657 $(elem_).removeClass("error"); 3658 }, 3000); 3659 }) 3660 .done(function (response) { 3661 $("body").removeClass("loading"); 3662 3663 $(".checkoutDiv").hide(); 3664 3665 if (response != undefined && response != "" && response != null) { 3666 $("#orderNo").html(response); 3667 $(".orderCompleted").removeClass("d-none"); 3668 $(".dynBasketQuantity").text("0"); 3669 onOrderCompleted(); 3670 } 3671 }); 3672} 3673function isOrderReadyForCompletion(elem_) { 3674 $(elem_).addClass("loading"); 3675 3676 let anyErrors = false; 3677 let errorList = []; 3678 3679 const checkRequiredFields = () => { 3680 if (requiredShippingFieldsFilled()) { 3681 return false; 3682 } 3683 errorList.push($(errorTexts).attr("fillInNecessaryFields")); 3684 return true; 3685 }; 3686 3687 anyErrors = checkRequiredFields(); 3688 3689 if (anyErrors) { 3690 $(elem_).removeClass("loading").addClass("error").attr("last-class", "error"); 3691 setTimeout(function () { 3692 let errorOverlay = $("<div>").addClass("overlay errorOverlay").html(errorList.join("<br>")); 3693 $(errorOverlay) 3694 .find(":after") 3695 .on("click", function () { 3696 $(errorOverlay).remove(); 3697 }); 3698 $(elem_).after($(errorOverlay)); 3699 }, 100); 3700 setTimeout(function () { 3701 $(elem_).removeClass("error"); 3702 }, 3000); 3703 return false; 3704 } 3705 return true; 3706} 3707function saveShipmentMethod(elem_) { 3708 let sq = $(elem_).data("salesquote"); 3709 let code = $(elem_).find(":selected").val(); 3710 $("body").addClass("loading"); 3711 3712 if (sq != null && sq != "" && code != null) { 3713 $("#completeOrder").prop("disabled", true); 3714 $("#completeOrder").text(WaitMessage); 3715 3716 $.ajax({
3717 method: "POST", 3718 url: "/api/basket/updateshipmentmethod/" + sq + "/" + code, 3719 cache: false, 3720 dataType: "json", 3721 contentType: "application/json", 3722 }) 3723 .fail(function (jqXHR) {}) 3724 .always(function () { 3725 $("#completeOrder").prop("disabled", false); 3726 $("#completeOrder").text(CompleteMessage); 3727 $("body").removeClass("loading"); 3728 }); 3729 } 3730} 3731function saveShippingAgent(elem_) { 3732 let sq = $(elem_).data("salesquote"); 3733 let code = $(elem_).find(":selected").val(); 3734 $("body").addClass("loading"); 3735 $("#completeOrder").prop("disabled", true); 3736 $("#completeOrder").text(WaitMessage); 3737 3738 $.ajax({ 3739 method: "POST", 3740 url: "/api/basket/updateshippingagent/" + sq + "/" + encodeURIComponent(code), 3741 cache: false, 3742 contentType: "application/json", 3743 }) 3744 .fail(function (jqXHR) {}) 3745 .always(function () { 3746 document.location.href = document.location.href.split("#")[0]; 3747 }); 3748} 3749function saveShippingAgentService(elem_) { 3750 let sq = $(elem_).data("salesquote"); 3751 let code = $(elem_).find(":selected").val(); 3752 $("body").addClass("loading"); 3753 $("#completeOrder").prop("disabled", true); 3754 $("#completeOrder").text(WaitMessage); 3755 3756 $.ajax({
3757 method: "POST", 3758 url: "/api/basket/updateshippingagentservice/" + sq + "/" + encodeURIComponent(code), 3759 cache: false, 3760 contentType: "application/json", 3761 }) 3762 .fail(function (jqXHR) {}) 3763 .always(function () { 3764 document.location.href = document.location.href.split("#")[0]; 3765 }); 3766} 3767function saveHeaderExtDocNo(elem_) { 3768 $("#completeOrder").prop("disabled", true); 3769 $("#completeOrder").text(WaitMessage); 3770 $("body").addClass("loading"); 3771 3772 $.ajax({ 3773 method: "POST", 3774 url: "/api/basket/edit/externaldocumentno/" + encodeURIComponent($(elem_).val()), 3775 cache: false, 3776 contentType: "application/json", 3777 }) 3778 .done(function (data) {}) 3779 .fail(function (jqXHR, textStatus) {}) 3780 .always(function (data) { 3781 if (data == "1") { 3782 document.location.href = document.location.href.split("#")[0]; 3783 } else { 3784 $("body").removeClass("loading"); 3785 $("#completeOrder").prop("disabled", false); 3786 $("#completeOrder").text(CompleteMessage); 3787 } 3788 }); 3789} 3790function saveHeaderYourRef(elm, sqNo) { 3791 var oldbtnText = $("#completeOrder").text(); 3792 3793 $("#completeOrder").prop("disabled", true); 3794 $("#completeOrder").text(WaitMessage); 3795 $("body").addClass("loading"); 3796 3797 postData = '{"YourReference":"' + $(elm).val() + '"}'; 3798 3799 $.ajax({
3800 method: "POST", 3801 url: "/api/basket/edit/salesorderinfo/" + sqNo, 3802 cache: false, 3803 data: postData, 3804 contentType: "application/json", 3805 }) 3806 .done(function (data) {}) 3807 .fail(function (jqXHR, textStatus) {}) 3808 .always(function (data) { 3809 if (data == "1") { 3810 document.location.href = document.location.href.split("#")[0]; 3811 } else { 3812 $("body").removeClass("loading"); 3813 $("#completeOrder").prop("disabled", false); 3814 $("#completeOrder").text(CompleteMessage); 3815 $("#completeOrder").text(oldbtnText); 3816 } 3817 }); 3818} 3819function updateSellToContact(elem_) { 3820 var contactNo = $(elem_).val(); 3821 if (!contactNo) return; 3822 3823 $("body").addClass("loading"); 3824 3825 $.ajax({ 3826 method: "POST", 3827 url: "/api/basket/edit/selltocontactno/" + encodeURIComponent(contactNo), 3828 cache: false, 3829 contentType: "application/json", 3830 }) 3831 .done(function () { 3832 document.location.href = document.location.href.split("#")[0]; 3833 }) 3834 .fail(function () { 3835 $("body").removeClass("loading"); 3836 }); 3837} 3838function UpdateRequestedDeliveryDate(elm) { 3839 $("#completeOrder").prop("disabled", true); 3840 $("#completeOrder").text(WaitMessage); 3841 $("body").addClass("loading"); 3842 $.ajax({
3843 method: "POST", 3844 url: "/api/basket/updatedeliverydate/" + $(elm).val(), 3845 cache: false, 3846 dataType: "json", 3847 contentType: "application/json", 3848 }) 3849 .done(function (data) {}) 3850 .fail(function (jqXHR, textStatus) {}) 3851 .always(function () { 3852 document.location.href = document.location.href.split("#")[0]; 3853 }); 3854} 3855function requiredShippingFieldsFilled() { 3856 let allFilled = true; 3857 $(".orderInfoRow.required").each(function () { 3858 let input = $(this).find("input, select"); 3859 input.removeClass("form-error"); 3860 input.each(function () { 3861 if ( 3862 ($(this).is("input") && ($(this).val() == undefined || $($(this)).val() == null || $($(this)).val() == "")) || 3863 ($(this).is("select") && $(this).find("option:selected[null]").length) 3864 ) { 3865 if ($(this).parents(".optional.unrequire").length) { 3866 return; 3867 } 3868 $(this).addClass("form-error"); 3869 allFilled = false; 3870 } 3871 }); 3872 }); 3873 return allFilled; 3874} 3875 3876// Filter shipping addresses in the dropdown based on search input 3877function filterShippingAddresses(searchInput) { 3878 let searchTerm = searchInput.value.toLowerCase().trim(); 3879 let select = document.getElementById("shippingAddressSelector"); 3880 3881 if (!select) return; 3882 3883 let options = select.options; 3884 let firstVisibleIndex = -1; 3885 let visibleCount = 0; 3886 let isSearching = searchTerm !== ""; 3887 3888 for (let i = 0; i < options.length; i++) { 3889 let option = options[i]; 3890 let optionText = option.text.toLowerCase(); 3891 let optionValue = option.value; 3892 3893 // Hide placeholder options when searching, show when not searching 3894 if (optionValue === "" || optionValue === "-1") { 3895 if (isSearching) { 3896 // Hide placeholder options while searching 3897 option.style.display = "none"; 3898 option.hidden = true; 3899 } else { 3900 // Show placeholder options when not searching 3901 option.style.display = ""; 3902 option.hidden = false; 3903 if (firstVisibleIndex === -1) firstVisibleIndex = i; 3904 visibleCount++; 3905 } 3906 continue; 3907 } 3908 3909 // Filter based on search term 3910 if (!isSearching || optionText.includes(searchTerm)) { 3911 option.style.display = ""; 3912 option.hidden = false; 3913 if (firstVisibleIndex === -1) firstVisibleIndex = i; 3914 visibleCount++; 3915 } else { 3916 option.style.display = "none"; 3917 option.hidden = true; 3918 } 3919 } 3920 3921 // Expand select to show options when searching, collapse when search is cleared 3922 if (isSearching) { 3923 // Show up to 8 visible options (or less if fewer match) 3924 select.size = Math.min(visibleCount, 8); 3925 select.classList.add("shipping-address-expanded"); 3926 } else { 3927 // Collapse back to normal dropdown 3928 select.size = 0; 3929 select.removeAttribute("size"); 3930 select.classList.remove("shipping-address-expanded"); 3931 } 3932 3933 // If current selection is hidden, reset to first visible option 3934 if (select.selectedIndex >= 0 && options[select.selectedIndex].hidden) { 3935 select.selectedIndex = firstVisibleIndex >= 0 ? firstVisibleIndex : 0; 3936 } 3937} 3938 3939function shippingAddressSelectorChange(salesquoteId, _elm) { 3940 // Skip if we're just navigating with keyboard (not actually selecting) 3941 let select = document.getElementById("shippingAddressSelector"); 3942 if (select) { 3943 select.size = 0; 3944 select.removeAttribute("size"); 3945 select.classList.remove("shipping-address-expanded"); 3946 // Reset all options to visible 3947 for (let i = 0; i < select.options.length; i++) { 3948 select.options[i].style.display = ""; 3949 select.options[i].hidden = false; 3950 } 3951 } 3952 3953 setOrDeleteCookie(salesquoteId, _elm); 3954 if ($(_elm).attr("type") == "checkbox") { 3955 if ($(_elm).is(":checked")) { 3956 $(".newShippingAddress").show().removeClass("unrequire"); 3957 } else { 3958 $(".newShippingAddress").hide().addClass("unrequire"); 3959 } 3960 return; 3961 } 3962 let selval = $("#shippingAddressSelector option:selected").val(); 3963 //None selected 3964 if (selval == "") { 3965 $(".newShippingAddress").hide(); 3966 $(".existingShippingAddress").hide(); 3967 3968 $("body").addClass("loading"); 3969 3970 $.ajax({
3971 method: "POST", 3972 url: "/api/basket/updateshipto/", 3973 cache: false, 3974 dataType: "json", 3975 contentType: "application/json", 3976 }) 3977 .done(function (data) {}) 3978 .always(function (data) { 3979 if (data.responseText == "reload") { 3980 document.location.href = document.location.href.split("#")[0]; 3981 } else { 3982 $("body").removeClass("loading"); 3983 } 3984 }); 3985 3986 $(".newShippingAddress :input").attr("disabled", false); 3987 $(".newShippingAddress").hide(); 3988 $(".existingShippingAddress").hide(); 3989 } else if (selval == "-1") { 3990 //Write new address 3991 $("body").addClass("loading"); 3992 3993 $.ajax({ 3994 method: "POST", 3995 url: "/api/basket/updateshipto/", 3996 cache: false, 3997 dataType: "json", 3998 contentType: "application/json", 3999 }).always(function (data) { 4000 if (data.responseText == "reload") { 4001 document.location.href = document.location.href.split("#")[0]; 4002 } else { 4003 $("body").removeClass("loading"); 4004 } 4005 }); 4006 4007 $(".newShippingAddress :input").attr("disabled", false); 4008 $(".newShippingAddress").show(); 4009 $(".existingShippingAddress").hide(); 4010 } 4011 //Select address 4012 else { 4013 $(".newShippingAddress").hide(); 4014 $("body").addClass("loading"); 4015 4016 $.ajax({
4017 method: "POST", 4018 url: "/api/basket/updateshipto?shiptocode=" + encodeURIComponent(selval), 4019 cache: false, 4020 dataType: "json", 4021 contentType: "application/json", 4022 }) 4023 .fail(function (jqXHR) {}) 4024 .always(function (data) { 4025 if (data.responseText == "reload") { 4026 document.location.href = document.location.href.split("#")[0]; 4027 } else { 4028 $("body").removeClass("loading"); 4029 4030 $.ajax({ 4031 method: "GET", 4032 url: "/shop/api/GetCustomerShippingAddressList?selectedCode=" + encodeURIComponent(selval), 4033 cache: false, 4034 }).done(function (data) { 4035 $('input[name="existingShipToName"]').val(data[0].name); 4036 $('input[name="existingShipToContact"]').val(data[0].contact); 4037 $('input[name="existingShipToAddressLine1"]').val(data[0].address); 4038 $('input[name="existingShipToAddressLine2"]').val(data[0].address2); 4039 $('input[name="existingShipToPostCode"]').val(data[0].zipcode); 4040 $('input[name="existingShipToCity"]').val(data[0].city); 4041 $('input[name="existingShipToCountry"]').val(data[0].country); 4042 $(".existingShippingAddress").show(); 4043 }); 4044 } 4045 }); 4046 } 4047} 4048 4049// --- SALESPERSON --- // 4050function resetSalesPersonLogin() { 4051 deleteCookie("_scSPID"); 4052 deleteCookie("_scID"); 4053 deleteCookie("_scIDV2"); 4054 location.reload(); 4055} 4056function isoDateReviver(value) { 4057 if (typeof value === "string") { 4058 var a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(?:([\+-])(\d{2})\:(\d{2}))?Z?$/.exec(value); 4059 if (a) { 4060 var utcMilliseconds = Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]); 4061 return new Date(utcMilliseconds); 4062 } 4063 } 4064 return value; 4065} 4066function closeQuoteDrawer() { 4067 $("body").removeClass("quoteActive"); 4068 $("tr.active").removeClass("active"); 4069 $('.drawer-salesquotes [class^="contentSection"]').html(""); 4070} 4071function updatePage(search = false, bottom = false) { 4072 $(".salesCustomerList").addClass("loading"); 4073 visibleCustomersList = $(".salesCustomerList tbody tr:not(.hidden)"); 4074 customerCount = visibleCustomersList.length; 4075 if (!totalCustomerPages) { 4076 totalCustomerPages = Math.ceil(customerCount / customersPerPage); 4077 } 4078 if (search) { 4079 totalCustomerPages = Math.ceil(customerCount / Math.max(customersPerPage, 100)); 4080 currentCustomerPage = 1; 4081 } 4082 4083 $(".pageSelect :is(.first-page, .prev-page)").attr("disabled", currentCustomerPage == 1); 4084 $(".pageSelect :is(.last-page, .next-page)").attr("disabled", currentCustomerPage == totalCustomerPages); 4085 4086 $(".pageSelect").find("input").val(currentCustomerPage).attr("max", totalCustomerPages); 4087 $(".pageSelect") 4088 .find("span") 4089 .html("/" + totalCustomerPages); 4090 $(".pageSelect") 4091 .find("span") 4092 .html("/" + totalCustomerPages); 4093 if (bottom) { 4094 $(".customersContainer").animate( 4095 { 4096 scrollTop: 0, 4097 }, 4098 300, 4099 "myEasing", 4100 ); 4101 } 4102 debounce(calculateHidden(), 300); 4103} 4104function calculateHidden() { 4105 let counter = (currentCustomerPage - 1) * customersPerPage; 4106 let newPage = []; 4107 let chunkSize = 5; 4108 function partialWhile() { 4109 let chunkLimit = Math.min(counter + chunkSize, currentCustomerPage * customersPerPage); 4110 while (counter < chunkLimit) { 4111 newPage.push(visibleCustomersList[counter]); 4112 counter++; 4113 } 4114 if (chunkLimit == currentCustomerPage * customersPerPage) { 4115 $(".inPage").removeClass("inPage"); 4116 $(newPage).addClass("inPage"); 4117 $(".salesCustomerList").removeClass("loading"); 4118 return; 4119 } 4120 requestAnimationFrame(partialWhile); 4121 } 4122 requestAnimationFrame(partialWhile); 4123} 4124function showDrawer(elm) { 4125 let drawer = $(".drawer-salesquotes");
4126 let customerNo = $(elm).data("customer"); 4127 let customerName = $(elm).find(".customerName span").html(); 4128 $("tr.active").removeClass("active"); 4129 $(elm).addClass("active"); 4130 4131 $(".drawer-salesquotes").addClass("loading"); 4132 $(drawer).find('.header, [class^="contentSection"]').html(""); 4133 4134 $.ajax({ 4135 method: "GET", 4136 url: "/shop/api/SalesPersonCustomerQuotesV2/" + customerNo, 4137 cache: false, 4138 }).done(function (data) { 4139 showSalesQuotes(data, drawer, customerNo, customerName); 4140 }); 4141 4142 if ($(".tab-control").length) { 4143 let quoteCount = $(elm).find(".quoteCount").text(); 4144 $("#dynQuotesAmt").text("(" + quoteCount + ")"); 4145 let orderCount = $(elm).find(".orderCount").text(); 4146 $("#dynOrdersAmt").text("(" + orderCount + ")"); 4147 if (!$(".tab-control .active").length) { 4148 $(".quotesTab").addClass("active"); 4149 } 4150 4151 $.ajax({ 4152 method: "GET", 4153 url: "/shop/api/SalesPersonCustomerOrders/" + customerNo, 4154 cache: false, 4155 }).done(function (data) { 4156 showSalesOrders(data, drawer); 4157 }); 4158 } 4159} 4160function showSalesQuotes(data, drawer, customerNo, customerName) { 4161 $(drawer).find(".header").html(customerName); 4162 let contentSection = $(drawer).find(".contentSectionQuotes"); 4163 $(data).each(function () { 4164 $(drawer).removeClass("loading"); 4165 let userName = this.name; 4166 if (userName == "Your quotes") { 4167 $(contentSection).append($("<h6>").html(salesperson_label_you).addClass("divider")); 4168 } else if ($(contentSection).find(".linesSection").length == 1) { 4169 $(contentSection).append($("<h6>").html(salesperson_label_users).addClass("divider")); 4170 } 4171 let userEmail = this.eCommCartUserEmail; 4172 //USER SECTION 4173 let userSection = $("<div>").addClass("linesSection").attr("user-email", userEmail); 4174 // header 4175 let userHeader = $("<div>").addClass("sectionHeader"); 4176 let userHeaderName = $("<h6>").html(userEmail); 4177 4178 userHeader.append(userHeaderName); 4179 if (this.canCreateSalesQuote) { 4180 let createQuote = $("<button>") 4181 .addClass("btn btn-sm btn-secondary") 4182 .html($(".CreateQuoteTxt").html()) 4183 .on("click", function () { 4184 createSalesQuoteAsSalesPerson(customerNo, userEmail); 4185 }); 4186 userHeader.append(createQuote); 4187 } 4188 //body 4189 let userBody = $("<div>").addClass("linesBody"); 4190 let quotes = this.userSalesQuoteDataItems; 4191 $(quotes).each(function () { 4192 let quote = $("<div>") 4193 .addClass("salesQuote") 4194 .attr("data-href", "/shop/api/SalesPersonImpersonate/" + customerNo + "/" + this.quoteId); 4195 4196 let quoteDate = $("<span>").html(this.description).addClass("quoteDate"); 4197 let quoteNo = $("<span>").html(this.quoteNo).addClass("quoteNo"); 4198 let quoteLines = $("<span>").html(this.lineCount).addClass("quoteLines"); 4199 let quoteTotal = $("<span>").html(this.totalInclTax).addClass("quoteTotal"); 4200 4201 quote.append(quoteDate, quoteNo, quoteLines, quoteTotal); 4202 userBody.append(quote); 4203 }); 4204 userSection.append(userHeader, userBody); 4205 $(contentSection).append(userSection); 4206 }); 4207} 4208function showSalesOrders(data, drawer) { 4209 let ordersHeader = $("<div>").addClass("ordersHeader").append($("<h6>").html(salesperson_label_Open_Sales_Orders).addClass("divider")); 4210 let ordersBody = $("<div>").addClass("linesBody"); 4211 $(data).each(function () { 4212 let order = $("<div>").addClass("salesOrder"); 4213 let od = new Date(this.orderDate); 4214 let rdd = new Date(this.requestedDeliveryDate); 4215 let now = new Date(); 4216 let isLate = false; 4217 let hasRdd = od < rdd; 4218 if (hasRdd) { 4219 if (rdd < now) { 4220 isLate = true; 4221 } 4222 } 4223 let rddClass = isLate ? " isLate" : ""; 4224 rddClass += hasRdd ? "" : " noRDD"; 4225 let orderDate = $("<span>").html(od.toLocaleDateString()).addClass("orderDate"); 4226 let orderRDD = $("<span>") 4227 .html(hasRdd ? rdd.toLocaleDateString() : "") 4228 .addClass("orderRDD" + rddClass) 4229 .attr("title", "Requested Delivery Date"); 4230 let orderNo = $("<span>").html(this.number).addClass("orderNo"); 4231 let orderTotal = $("<span>") 4232 .html(this.totalAmountIncludingTax + " " + this.currencyCode) 4233 .addClass("orderTotal"); 4234 4235 order.append(orderDate, orderNo, orderRDD, orderTotal);
4236 ordersBody.append(order); 4237 }); 4238 let ordersSection = $("<div>").addClass("linesSection").append(ordersBody); 4239 $(drawer).find(".contentSectionOrders").append(ordersHeader, ordersSection); 4240} 4241function customerSearch() { 4242 const form = $(".salesCustomerSearch"); 4243 const value = $(form).val(); 4244 $(".salesCustomerList tr").each(function (index) { 4245 if (index !== 0) { 4246 const $row = $(this); 4247 var matchedIndex = $row.find("td").text().toLowerCase().indexOf(value.toLowerCase()); 4248 if (matchedIndex == -1) { 4249 $row.addClass("hidden"); 4250 } else { 4251 $row.removeClass("hidden"); 4252 } 4253 } 4254 }); 4255 updatePage(true); 4256} 4257function cancelSearch() { 4258 if ($("#search").val() == "") return; 4259 $("#search").val("").trigger("input"); 4260} 4261 4262// --- FAVORITES PAGES --- // 4263function showFavsListForm(btn) { 4264 let newFavList = $(btn).siblings(".newFavList"); 4265 $(newFavList).removeClass("d-none").show(); 4266 $(btn).hide(); 4267 newFavList 4268 .find("#newFavoriteListName") 4269 .focus() 4270 .on("keydown", function (e) { 4271 favsListCreate; 4272 if (e.keyCode == 13) { 4273 $("#favsListCreate").trigger("click"); 4274 } 4275 }); 4276 newFavList 4277 .find("#favsListCancel") 4278 .off() 4279 .on("click", function () { 4280 newFavList.hide(); 4281 $(btn).show(); 4282 }); 4283 newFavList 4284 .find("#favsListCreate") 4285 .off() 4286 .on("click", function () { 4287 createFavoriteList($(this)); 4288 }); 4289} 4290function createFavoriteList(btn) { 4291 var listName = $("#newFavoriteListName").val(); 4292 $(btn).addClass("loading"); 4293 $("body").addClass("loading"); 4294 4295 $.post("/favorites/add", { 4296 listName: listName, 4297 }) 4298 .done(function () { 4299 $(btn).addClass("success").attr("last-class", "success"); 4300 location.reload(); 4301 }) 4302 .fail(function (xhr) { 4303 btn.addClass("error").attr("last-class", "error"); 4304 }) 4305 .always(function () { 4306 $(btn).removeClass("loading"); 4307 $("body").removeClass("loading"); 4308 setTimeout(function () { 4309 $(btn).removeClass("success error"); 4310 }, 3000); 4311 }); 4312} 4313function renameFavoriteList(btn, favoriteCode, headerId) { 4314 let name = $(btn).parent(); 4315 if (!$(name).hasClass("editing")) { 4316 $(".name.editing").removeClass("editing"); 4317 $(name).addClass("editing"); 4318 let input = $(name).find("input"); 4319 let newVal = $(name).find(".favoriteName").html(); 4320 input.val(newVal); 4321 input.select(); 4322 input.off().on("keydown", function (e) { 4323 if (e.keyCode == 13) { 4324 renameFavoriteList(btn, favoriteCode, headerId); 4325 } 4326 }); 4327 return; 4328 } 4329 4330 let newListName = $(name).find(".favoriteNameEdit").val(); 4331 $(btn).addClass("loading"); 4332 $.post("/favorites/edit", { 4333 favoriteCode: favoriteCode, 4334 newListName: newListName, 4335 headerId: headerId, 4336 }) 4337 .done(function () { 4338 $(btn).siblings(".favoriteName").html(newListName); 4339 $(btn).removeClass("loading").addClass("success"); 4340 $(".name.editing").removeClass("editing"); 4341 }) 4342 .fail(function (xhr) { 4343 $(btn).removeClass("loading").addClass("error"); 4344 alert("Failed to update favorite list name: " + xhr.responseJSON.message); 4345 }) 4346 .always(function () { 4347 setTimeout(function () { 4348 $(btn).removeClass("success error"); 4349 }, 3000); 4350 }); 4351} 4352function cancelRename(btn) { 4353 let name = $(btn).parent(); 4354 $(name).removeClass("editing"); 4355} 4356function editFavoriteList(button) { 4357 var row = button.closest("tr"); 4358 row.querySelector(".favoriteName").classList.add("d-none"); 4359 row.querySelector(".favoriteNameEdit").classList.remove("d-none"); 4360 button.classList.add("d-none"); 4361 row.querySelector(".btn-primary").classList.remove("d-none"); 4362} 4363function toggleConfirm(btn, show = false) { 4364 let initDelete = $(btn).parent(); 4365 if (!show) { 4366 initDelete.removeClass("show"); 4367 return; 4368 } 4369 if (!initDelete.hasClass("show")) { 4370 initDelete.addClass("show"); 4371 return; 4372 } 4373 deletetFavoriteList(initDelete.data("headerid"), btn); 4374} 4375function deletetFavoriteList(headerId, btn) { 4376 $(btn).addClass("loading"); 4377 let thisRow = $(btn).closest("tr"); 4378 thisRow.css("max-height", thisRow.outerHeight()); 4379 $.post("/favorites/removeList", { 4380 headerId: headerId, 4381 }) 4382 .done(function () { 4383 $(thisRow).addClass("deleting"); 4384 setTimeout(function () { 4385 $(thisRow).remove(); 4386 }, 500); 4387 }) 4388 .fail(function (xhr) { 4389 $(btn).removeClass("loading").addClass("error"); 4390 setTimeout(function () { 4391 $(btn).removeClass("error"); 4392 }, 3000); 4393 alert("Failed to remove favorite list: " + xhr.responseJSON); 4394 }); 4395} 4396 4397// --- COMPARE ITEMS --- // 4398function compareScroll(btn, directionForward = true) { 4399 if ($(btn).attr("disabled")) return; 4400 const containerW = $(".tableContainer").outerWidth(); 4401 const itemW = $('.compareTable thead th[scope="col"]').first().outerWidth(); 4402 let scrollLeft = $(".tableContainer").scrollLeft(); 4403 4404 if ($(".compareTable").hasClass("sideScroll") || $('body:not([class*="--m"])').length) { 4405 const gutter = Number($(".compareTable tr").first().css("column-gap").replace("px", "")); 4406 const multiplier = directionForward ? 1 : -1; 4407 const toScroll = (itemW + gutter) * multiplier; 4408 4409 scrollLeft += toScroll; 4410 4411 $(".tableContainer").animate( 4412 { 4413 scrollLeft: scrollLeft, 4414 }, 4415 200, 4416 ); 4417 } 4418 $(".compareTable:not(.sideScroll)").addClass("sideScroll"); 4419 4420 setTimeout(() => { 4421 const limitLeft = scrollLeft <= 4; 4422 const limitRight = scrollLeft + containerW >= $(".compareTable").width() - itemW / 10; 4423 $(".tableControls > div").removeAttr("disabled"); 4424 if (limitLeft) { 4425 $(".tableControls .left").attr("disabled", "disabled"); 4426 } 4427 if (limitRight) { 4428 $(".tableControls .right").attr("disabled", "disabled"); 4429 } 4430 }, 200); 4431} 4432function addToCompareList(e, btn, productNo) { 4433 e.preventDefault(); 4434 if ($(btn).find(".fas").hasClass("fa-check")) { 4435 $.ajax({
4436 method: "POST", 4437 url: "/api/itemcompareremove/" + productNo, 4438 cache: false, 4439 }) 4440 .done(function () { 4441 decrementQuantity(); 4442 }) 4443 .always(function () { 4444 $(btn).find(".fas").removeClass("fa-check").addClass("fa-right-left"); 4445 }); 4446 return; 4447 } 4448 $.ajax({ 4449 method: "POST", 4450 url: "/api/itemcompareadd/" + productNo, 4451 cache: false, 4452 }) 4453 .done(function () { 4454 incrementQuantity(); 4455 }) 4456 .always(function () { 4457 $(btn).find(".fas").removeClass("fa-right-left").addClass("fa-check"); 4458 }); 4459} 4460function removeFromCompareList(elm) { 4461 let $elm = $(elm); 4462 let index = $elm.parents("th").data("index"); 4463 $(".deleting").each(function () { 4464 $(this).parent().addClass("noTransitions"); 4465 $(this).remove(); 4466 }); 4467 4468 setTimeout(function () { 4469 $(".compareTable tr").each(function () { 4470 $(this).removeClass("noTransitions"); 4471 $($(this).find(`:is(td, th)[data-index="${index}"]`)).addClass("deleting"); 4472 }); 4473 productNo = $elm.parent().data("productno"); 4474 $.ajax({
4475 method: "POST", 4476 url: "/api/itemcompareremove/" + productNo, 4477 cache: false, 4478 }) 4479 .done(function () { 4480 decrementQuantity(); 4481 }) 4482 .fail(function (jqXHR, response) {}); 4483 }, 100); 4484} 4485function incrementQuantity() { 4486 const quantityElement = document.querySelector(".dynCompareQuantity"); 4487 4488 if (quantityElement) { 4489 const currentValue = parseInt(quantityElement.textContent.trim(), 10); 4490 4491 if (!isNaN(currentValue)) { 4492 quantityElement.textContent = currentValue + 1; 4493 } else { 4494 console.error("The content is not a valid number."); 4495 } 4496 } else { 4497 console.error('Element with class "dynCompareQuantity" not found.'); 4498 } 4499} 4500function decrementQuantity() { 4501 const quantityElement = document.querySelector(".dynCompareQuantity"); 4502 4503 if (quantityElement) { 4504 const currentValue = parseInt(quantityElement.textContent.trim(), 10); 4505 4506 if (!isNaN(currentValue)) { 4507 quantityElement.textContent = currentValue - 1; 4508 } else { 4509 console.error("The content is not a valid number."); 4510 } 4511 } else { 4512 console.error('Element with class "dynCompareQuantity" not found.'); 4513 } 4514} 4515function itemCompareOverflow() { 4516 $(".tableContainer").addClass("noTransitions"); 4517 setTimeout(function () { 4518 $(".tableContainer").removeClass("noTransitions"); 4519 }, 500); 4520 if ($(".compareTable").width() <= $(".tableContainer").width()) { 4521 $(".compareSection").removeClass("overflow"); 4522 $(".compareTable").removeClass("sideScroll"); 4523 return; 4524 } 4525 $(".compareSection").addClass("overflow"); 4526 $(".tableContainer").scrollLeft(0); 4527 $(".tableControls > div").removeAttr("disabled"); 4528} 4529 4530// --- DOM MANIPULATION --- // 4531function appendLogos() { 4532 let dir = $(".logoSection").find("img").attr("src"); 4533 let lastIndex = dir.lastIndexOf("/"); 4534 let logoSection = $(".logoSection"); 4535 let logoTemplate = $(logoSection).find("img"); 4536 $(logoSection).html(""); 4537 for (let i = 0; i < multiLogos.length; i++) { 4538 let newDir = dir.substring(0, lastIndex) + "/" + multiLogos[i]; 4539 let newLogo = $(logoTemplate).clone().attr("src", newDir); 4540 $(logoSection).append($(newLogo)); 4541 } 4542} 4543function resizeNavbar(scrollTop) { 4544 if (scrollTop) $("body:not(.miniNav)").addClass("miniNav"); 4545 else $(".miniNav").removeClass("miniNav"); 4546} 4547function showVideo(htmldata) { 4548 $(".videoplayer").html(htmldata); 4549 $(".videoplayer").show(); 4550 $(".imagediv").hide(); 4551} 4552function reElastic(elm) { 4553 elm.show().css("height", "unset"); 4554 let paddingTop = parseInt(elm.css("padding-top")); 4555 let paddingBottom = parseInt(elm.css("padding-bottom")); 4556 let scrollHeight = parseInt(elm.prop("scrollHeight")); 4557 elm.css("height", paddingTop + paddingBottom + scrollHeight + "px"); 4558 $("body.loading").removeClass("loading"); 4559} 4560function checkWidthDependantElements() { 4561 setWidthClass(); 4562 if (sideFilter) { 4563 let collapseSideFilter = collapseMainNav; 4564 if (sideFilterCollapseWidth) collapseSideFilter = parseFloat($("body").width()) < sideFilterCollapseWidth; 4565 if (collapseSideFilter && !$("body").hasClass("sideFilterCollapse")) $("body").addClass("sideFilterCollapse"); 4566 else if (!collapseSideFilter && $("body").hasClass("sideFilterCollapse")) $("body").removeClass("sideFilterCollapse"); 4567 } 4568 if (!sidebarEnabled) { 4569 if (!collapseMainNav) { 4570 logoSectionWidth = $(".logoSection").outerWidth(); 4571 extendedMainNavWidth = $("#main_nav").outerWidth(); 4572 } 4573 collapseMainNav = 4574 screenWidth < navCollapseWidth || 4575 (navCollapseWidth == 0 && $(".navbar > .container").width() - logoSectionWidth < extendedMainNavWidth); 4576 } else collapseMainNav = screenWidth < sidebarCollapseWidth; 4577 if (autoCollapseNav && collapseMainNav) $("body:not(.collapseMainNav)").addClass("collapseMainNav").removeClass("displayMainNav"); 4578 else { 4579 $("body:not(.displayMainNav)").addClass("displayMainNav").removeClass("collapseMainNav"); 4580 }
4581 if (page == "itemcompare") { 4582 itemCompareOverflow(); 4583 } 4584 $("body").addClass("bodyReady"); 4585} 4586function calculateScrollbarWidth() { 4587 const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; 4588 $(":root").css("--scrollbarWidth", scrollbarWidth + "px"); 4589} 4590function setWidthClass() { 4591 let widthClass = "screenW--xs"; 4592 if (screenWidth >= 460) widthClass += "--s"; 4593 if (screenWidth >= 720) widthClass += "--m"; 4594 if (screenWidth >= 1024) widthClass += "--l"; 4595 if (screenWidth >= 1366) widthClass += "--xl"; 4596 if (screenWidth >= 1600) widthClass += "--xxl"; 4597 let prevClass = $("body") 4598 .attr("class") 4599 .split(" ") 4600 .find((x) => x.includes("screenW")); 4601 if (prevClass) { 4602 if (prevClass == widthClass) return; 4603 $("body").removeClass(prevClass); 4604 } 4605 $("body").addClass(widthClass); 4606} 4607function changeGalleryClass(btn) { 4608 currentGalleryClass = $(btn).attr("value"); 4609 $(".GalleryBox").attr("class", "GalleryBox"); 4610 setUrlParams(); 4611} 4612function changeAltDelivery() { 4613 if ($("#AltDelivery").is(":checked")) { 4614 $("#AltDeliveryDiv input, #AltDeliveryDiv textarea, #AltDeliveryDiv select").removeAttr("disabeld"); 4615 $("#AltDeliveryDiv").show(); 4616 } else { 4617 $("#AltDeliveryDiv input, #AltDeliveryDiv textarea, #AltDeliveryDiv select").attr("disabeld", "disabeld"); 4618 $("#AltDeliveryDiv").hide(); 4619 } 4620} 4621// menus 4622function MakeMenuStructure(callback = null) { 4623 $.ajax({
4624 url: "/api/categories", 4625 method: "GET", 4626 contentType: "application/json", 4627 cache: false, 4628 }) 4629 .fail(function () { 4630 console.error("Failed to load master data"); 4631 }) 4632 .done(function (response) { 4633 let html_ = ""; 4634 4635 if (manyCategories || sidebarEnabled) { 4636 let productsContent = AppendChildren(response, 1); 4637 if (sidebarEnabled) { 4638 $(".user-navbar").after( 4639 $("<div>") 4640 .addClass("side-navbar") 4641 .append($("<div>").addClass("container").append("<ul>").attr("role", "menu").html(productsContent)), 4642 ); 4643 $("body").addClass("sidebarEnabled"); 4644 } 4645 if (manyCategories) { 4646 html_ = `<li class="nav-item dropdown has-megamenu category_zero" role="none"><a class="nav-link dropdown-toggle" role="menuitem" href="#" ${ 4647 hoverNavItems && !mobile ? ">" : 'data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">' 4648 }${$(".navbar-nav").attr("productHeader")}</a> 4649 <div class="dropdown-menu singular-menu megamenu" role="none"><div class="container" role="none">${productsContent}</div></div></li><li class="nav-item TemplateMenu_AboutUs" role="none"><a class="nav-link" role="menuitem" href="/about-us">About us</a></li>`; 4650 4651 $(".ulLoop_1 a").each(function () { 4652 if ($(this).next("ul").length && $(this).next("ul").css("display") == "none") { 4653 $(this).next("ul").addClass("visibleToggle"); 4654 $(this).next("ul").parent().addClass("visibleToggleSub"); 4655 } 4656 }); 4657 $('.ulLoop_1 a[href="' + location.pathname + '"]').addClass("active"); 4658 $(".ulLoop_1 .active").parents("ul").show(); 4659 } 4660 } else { 4661 html_ = ""; 4662 for (i in response) { 4663 let catId = response[i].syncID; 4664 let activeCategory = catId == currentCategory ? " activeCat" : ""; 4665 if (response[i].children.length) { 4666 html_ += `<li class="nav-item dropdown has-megamenu category_${catId}" role="none"> 4667 <a class="nav-link dropdown-toggle${activeCategory}" role="menuitem" data-category="${catId}" href="${ 4668 response[i].url 4669 }" ${hoverNavItems && !mobile ? ">" : ' data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">'}${ 4670 response[i].name 4671 }</a> 4672 <div class="dropdown-menu megamenu" role="none"><div class="container" role="none">${AppendChildren( 4673 response[i].children, 4674 1, 4675 )}</div></div></li>`; 4676 } else { 4677 html_ += `<li class="nav-item category_${catId}" role="none"><a class="nav-link${activeCategory}" data-category="${catId}" href="${response[i].url}" role="menuitem">${response[i].name}</a></li>`; 4678 } 4679 } 4680 } 4681 4682 $("#scMenu").replaceWith(html_); 4683 screenWidth = $(window).width(); 4684 checkWidthDependantElements(); 4685 //callback 4686 onNavbarCreated(); 4687 }); 4688} 4689function AppendChildren(menu, level) { 4690 let html_ = ""; 4691 for (i in menu) { 4692 activeCategory = menu[i].syncID == currentCategory ? " activeCat" : ""; 4693 html_ += `<li class="level${level} category_${menu[i].syncID}" role="none"><a href="${ 4694 menu[i].url 4695 }" class="dropdown-link${activeCategory}" data-category="${ 4696 menu[i].syncID 4697 }" role="menuitem" aria-haspopup=\"true\" aria-expanded=\"true\"">${menu[i].name}</a>${AppendChildren( 4698 menu[i].children,
4699 level + 1, 4700 )}</li>`; 4701 } 4702 4703 switch (level) { 4704 case 1: 4705 return html_.length > 0 4706 ? '<ul role="menu" class="d-lg-flex ulLoop_' + level + '">' + html_ + "</ul>" 4707 : ""; 4708 default: 4709 return html_.length > 0 ? '<ul role="menu" class="ulLoop_' + level + '">' + html_ + "</ul>" : ""; 4710 } 4711} 4712function adjustSideControlsHeight() { 4713 let scrollTop = $(window).scrollTop(); 4714 let windowHeight = $(window).height(); 4715 let bodyHeight = $("body").outerHeight(); 4716 let footerProtrusion = windowHeight + scrollTop - bodyHeight; 4717 $(".galleryControls").css("padding-bottom", footerProtrusion + "px"); 4718 $(".sidebarEnabled .side-navbar").css("padding-bottom`", footerProtrusion + "px"); 4719} 4720 4721// --- UTILITIES --- // 4722 4723function changeAccount(elm) { 4724 const guid = $(elm).attr("value"); 4725 if (guid != "") { 4726 document.location.href = "/accountselect/" + guid; 4727 } 4728} 4729 4730function addCommas(nStr) { 4731 if (bAddComma) { 4732 nStr += ""; 4733 x = nStr.split("."); 4734 x1 = x[0]; 4735 x2 = x.length > 1 ? "," + x[1] : ""; 4736 let rgx = /(\d+)(\d{3})/; 4737 while (rgx.test(x1)) { 4738 x1 = x1.replace(rgx, "$1" + "." + "$2"); 4739 } 4740 return x1 + x2; 4741 } else { 4742 nStr += ""; 4743 x = nStr.split("."); 4744 x1 = x[0]; 4745 x2 = x.length > 1 ? "." + x[1] : ""; 4746 let rgx = /(\d+)(\d{3})/; 4747 while (rgx.test(x1)) { 4748 x1 = x1.replace(rgx, "$1" + "," + "$2"); 4749 } 4750 return x1 + x2; 4751 } 4752} 4753function isEmail(email) { 4754 let regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/; 4755 return regex.test(email); 4756} 4757function sendEmail() { 4758 $("#email").css("border", "var(--inputBorder)"); 4759 $(".emailSent").hide(); 4760 4761 let email = $("#email").val(); 4762 let data_ = JSON.stringify({ email: email }); 4763 4764 if (email == "") { 4765 $("#email").css("border", "red solid 1px"); 4766 } 4767 if (!email.includes("@") || !email.split("@")[1].includes(".")) { 4768 //QQX Needs label 4769 alert("Please enter a valid email address."); 4770 } else { 4771 $.ajax({
4772 url: "/api/login/resetPassword/", 4773 method: "POST", 4774 contentType: "application/json", 4775 data: data_, 4776 cache: false, 4777 }) 4778 .fail(function () { 4779 //QQX Needs label 4780 alert("Failed to login"); 4781 }) 4782 .done(function (response) { 4783 if (response.success) { 4784 $(".emailSent").show(); 4785 } else { 4786 alert(response.error_msg); 4787 } 4788 }); 4789 } 4790} 4791function AddToCartBtn() { 4792 $("*[onAdd]").each(function () { 4793 $(this).addClass("addToCartAni"); 4794 }); 4795} 4796function validateForm() { 4797 $(".loginError").addClass("d-none"); 4798 4799 let username = $("#username").val(); 4800 let password = $("#password").val(); 4801 let security_code = ""; 4802 let data_ = JSON.stringify({ username: username, password: password }); 4803 4804 if (username == "" || password == "") { 4805 $(".loginError").removeClass("d-none"); 4806 } else { 4807 $("body").addClass("loading"); 4808 let button = $("button[type=submit]"); 4809 let spanContent = $(button).find("span").html(); 4810 4811 $(button).css({ 4812 width: $(button).outerWidth() + "px", 4813 height: $(button).outerHeight() + "px", 4814 }); 4815 $(button).find("span").html(""); 4816 $(button).addClass("loading"); 4817 $.ajax({
4818 url: "/api/login", 4819 method: "POST", 4820 contentType: "application/json", 4821 data: data_, 4822 cache: false, 4823 }) 4824 .fail(function () { 4825 //QQX Needs label 4826 alert("Failed to login"); 4827 $("body").removeClass("loading"); 4828 }) 4829 .done(function (response) { 4830 $("body").removeClass("loading"); 4831 if (response.success) { 4832 if (response.salesperson) { 4833 location.href = "/salesperson"; 4834 return true; 4835 } 4836 4837 if (response.redirect_url != null && response.redirect_url != "") { 4838 location.href = response.redirect_url; 4839 return true; 4840 } 4841 4842 if (response.change_password) { 4843 location.href = 4844 "/changepassword?user=" + encodeURIComponent(username) + "&security_code=" + encodeURIComponent(security_code); 4845 return true; 4846 } 4847 if (response.is_salesperson) { 4848 } 4849 // redirect to homepage 4850 else { 4851 $("#username").val(""); 4852 $("#password").val(""); 4853 4854 if (location.href.indexOf("login") != -1 || location.href.indexOf("logout") != -1) location.href = "/"; 4855 else location.reload(); 4856 } 4857 } else { 4858 $(button).find("span").html(spanContent); 4859 $(button).css({ width: "", height: "" }); 4860 $(button).removeClass("loading"); 4861 $("#password").val(""); 4862 $(".loginError").removeClass("d-none"); 4863 if (response.error_msg != "") { 4864 $(".loginError").html(response.error_msg); 4865 } 4866 $(".resetPassword").attr("href", "/resetpassword/?user=" + encodeURIComponent(username)); 4867 } 4868 }); 4869 } 4870 return false; 4871} 4872function checkMinMaxDecimals(input) { 4873 //input variables 4874 let value = $(input).val(); 4875 const step = parseFloat($(input).attr("step")); 4876 const min = parseFloat($(input).attr("min")); 4877 const max = parseFloat($(input).attr("max")); 4878 const type = $(input).attr("type"); 4879 let containsComma = false; 4880 4881 //correct comma 4882 if (type == "text") { 4883 containsComma = value.includes(","); 4884 value = parseFloat(value.replaceAll(",", ".")); 4885 } 4886 4887 //correct decimals 4888 let decimalPointMultiplier = Math.pow(10, $(input).data("decimals")); 4889 if (!decimalPointMultiplier) decimalPointMultiplier = 1; 4890 value = Math.round(value * decimalPointMultiplier) / decimalPointMultiplier; 4891 4892 //correct min and step 4893 if (!isNaN(min) && value < min) value = min; 4894 if (!isNaN(max) && value > max) value = max; 4895 if (!isNaN(step) && value) value = Math.floor(value / step) * step; 4896 4897 //re-correct comma 4898 if (type == "text") { 4899 if (containsComma) value = value.toString().replace(".", ","); 4900 else value = value.toString(); 4901 } 4902 4903 //set value 4904 $(input).val(value).change(); 4905} 4906// Cookies 4907function setCookie(cname, cvalue, exdays) { 4908 const d = new Date(); 4909 d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); 4910 let expires = "expires=" + d.toUTCString(); 4911 document.cookie = cname + "=" + encodeURIComponent(cvalue) + ";" + expires + ";path=/"; 4912} 4913function deleteCookie(cname) { 4914 const d = new Date(); 4915 d.setTime(d.getTime() + 24 * 60 * 60 * 1000); 4916 let expires = "expires=" + d.toUTCString(); 4917 document.cookie = cname + "=;" + expires + ";path=/"; 4918} 4919function getCookie(cname) { 4920 let name = cname + "="; 4921 let decodedCookie = decodeURIComponent(document.cookie); 4922 let ca = decodedCookie.split(";"); 4923 for (let i = 0; i < ca.length; i++) { 4924 let c = ca[i];
4925 while (c.charAt(0) == " ") { 4926 c = c.substring(1); 4927 } 4928 if (c.indexOf(name) == 0) { 4929 return c.substring(name.length, c.length); 4930 } 4931 } 4932 return ""; 4933} 4934function acceptCookieConsent() { 4935 deleteCookie("user_cookie_consent"); 4936 setCookie("user_cookie_consent", 1, 30); 4937 document.getElementById("cookieNotice").style.display = "none"; 4938} 4939function AcceptTerms(elm) { 4940 if ($(".AcceptTerms").is(":checked")) { 4941 $("#completeOrderCreditCard").removeAttr("disabled"); 4942 } else { 4943 $("#completeOrderCreditCard").attr("disabled", "disabled"); 4944 } 4945} 4946function setOrDeleteCookie(id, elm) { 4947 if ((elm.type == "checkbox" && elm.checked) || (elm.type != "checkbox" && elm.value.trim().length)) { 4948 setCookie(id + "-" + elm.name, elm.value); 4949 } else { 4950 deleteCookie(id + "-" + elm.name); 4951 } 4952} 4953// Helpers 4954function isPasswordComplexEnough(password) { 4955 if (!password || password.length < 6) { 4956 return false; 4957 } 4958 let hasUpper = /[A-Z]/.test(password); 4959 let hasNumberOrSpecial = /[^A-Za-z]/.test(password); 4960 return hasUpper && hasNumberOrSpecial; 4961} 4962function resetPassword() { 4963 $("#new_password").css("border", "1px solid black"); 4964 $("#confirm_password").css("border", "1px solid black"); 4965 $(".passNotMatching").hide(); 4966 $(".passwordUpdated").hide(); 4967 $(".passNotUpdated").hide(); 4968 $(".passwordNotComplex").hide(); 4969 $(".resetLinkInvalid").hide(); 4970 4971 let newPass = $("#new_password").val(); 4972 let confirmPass = $("#confirm_password").val(); 4973 let cs = $("#cs").val(); 4974 let data_ = JSON.stringify({ newPass: newPass, cs: cs }); 4975 4976 if (newPass == "") { 4977 $("#new_password").css("border", "red solid 1px"); 4978 } 4979 if (confirmPass == "") { 4980 $("#confirm_password").css("border", "red solid 1px"); 4981 } 4982 let elm = $("button[type=submit]"); 4983 // better check: use a regular expression 4984 if (newPass != confirmPass) { 4985 $(".passNotMatching").show(); 4986 buttonAlert(elm, "error"); 4987 } else if (newPass != "") { 4988 if ($("#enforceComplexPassword").val() == "1" && !isPasswordComplexEnough(newPass)) { 4989 $(".passwordNotComplex").show(); 4990 $("#new_password").css("border", "red solid 1px"); 4991 buttonAlert(elm, "error"); 4992 return; 4993 } 4994 let spanContent = $(elm).find("span").html(); 4995 $(elm).css({ 4996 width: $(elm).outerWidth() + "px", 4997 height: $(elm).outerHeight() + "px", 4998 }); 4999 $(elm).find("span").html(""); 5000 $(elm).addClass("loading"); 5001 let classToAdd = "success"; 5002 $.ajax({
5003 url: "/api/login/resetPasswordCompleted", 5004 method: "POST", 5005 contentType: "application/json", 5006 data: data_, 5007 cache: false, 5008 }) 5009 .fail(function () { 5010 alert("Failed to update password"); 5011 classToAdd = "error"; 5012 }) 5013 .done(function (response) { 5014 if (response.success) { 5015 $("#new_password").val(); 5016 $("#confirm_password").val(); 5017 $(".passwordUpdated").show(); 5018 //Redirect to login after 3 seconds. 5019 setTimeout(function () { 5020 window.location.href='/login'; 5021 }, 3000); 5022 } else { 5023 classToAdd = "error"; 5024 if (response.status) { 5025 $(".resetLinkInvalid").show(); 5026 } else { 5027 $(".passNotUpdated").show(); 5028 } 5029 } 5030 }) 5031 .always(function () { 5032 buttonAlert($(elm), classToAdd, spanContent); 5033 }); 5034 } 5035} 5036function buttonAlert(elm, classToAdd, spanContent = null) { 5037 $(elm).removeClass("loading").addClass(classToAdd); 5038 $(elm).attr("last-class", classToAdd); 5039 setTimeout(function () { 5040 $(elm).removeClass(classToAdd); 5041 if (spanContent) $(elm).find("span").html(spanContent); 5042 $(elm).css({ width: "", height: "" }); 5043 setTimeout(function () { 5044 $(elm).blur(); 5045 }, 350); 5046 }, 3000); 5047} 5048function selectFinder(elm, specCode, level) { 5049 let val = $(elm).val(); 5050 5051 if (val == "_") { 5052 deleteFinderLevel(level); 5053 return; 5054 } 5055 setCookie("_scFinderFilter" + level, specCode + "!" + val); 5056 deleteCookie("_scFinderFilter" + (level + 1)); 5057 location.reload(); 5058} 5059function deleteFinderLevel(level) { 5060 $(".productFinderItem").eq(level).find("select").val("_"); 5061 for (let c = level; c < 20; c++) { 5062 deleteCookie("_scFinderFilter" + c); 5063 } 5064 location.reload(); 5065} 5066function validateChangePassForm() { 5067 $(".loginError").addClass("d-none"); 5068 $("#old_password").css("border", "1px solid black"); 5069 $("#new_password").css("border", "1px solid black"); 5070 $("#confirm_password").css("border", "1px solid black"); 5071 5072 let old_password = $("#old_password").val(); 5073 let new_password = $("#new_password").val(); 5074 let confirm_password = $("#confirm_password").val(); 5075 const queryString = window.location.search; 5076 5077 let user = new URLSearchParams(queryString).get("user"); 5078 let data_ = JSON.stringify({ 5079 username: user, 5080 old_password: old_password, 5081 new_password: new_password, 5082 confirm_password: confirm_password, 5083 }); 5084 let authenticated = false; 5085 let button = $("button[type=submit]"); 5086 let spanContent = $(button).find("span").html(); 5087 if (old_password == "" || new_password == "" || confirm_password == "") { 5088 if (old_password == "") { 5089 $("#old_password").css("border", "red solid 1px"); 5090 } 5091 if (new_password == "") { 5092 $("#new_password").css("border", "red solid 1px"); 5093 } 5094 if (confirm_password == "") { 5095 $("#confirm_password").css("border", "red solid 1px"); 5096 } 5097 } else if (new_password == old_password) { 5098 $("#new_password").css("border", "red solid 1px"); 5099 $(".loginErrorMessage").html($(".passwordSameAsOldMsg").html()); 5100 $(".loginError").removeClass("d-none"); 5101 } else if ($("#enforceComplexPassword").val() == "1" && !isPasswordComplexEnough(new_password)) { 5102 $("#new_password").css("border", "red solid 1px"); 5103 $(".loginErrorMessage").html($(".passwordNotComplexMsg").html()); 5104 $(".loginError").removeClass("d-none"); 5105 } else { 5106 $(button).css({ 5107 width: $(button).outerWidth() + "px", 5108 height: $(button).outerHeight() + "px", 5109 }); 5110 $(button).find("span").html(""); 5111 $(button).addClass("loading"); 5112 $.ajax({
5113 url: "/api/login/changepassword", 5114 method: "POST", 5115 contentType: "application/json", 5116 data: data_, 5117 cache: false, 5118 }) 5119 .fail(function () { 5120 alert("failed"); 5121 }) 5122 .done(function (response) { 5123 if (response.success) { 5124 location.href = "/"; 5125 } else { 5126 $(button).find("span").html(spanContent); 5127 $(button).css({ width: "", height: "" }); 5128 $(button).removeClass("loading"); 5129 5130 $("#old_password").val(""); 5131 $("#new_password").val(""); 5132 $("#confirm_password").val(""); 5133 5134 $(".loginError").removeClass("d-none"); 5135 if (response.error_msg != "") { 5136 $(".loginErrorMessage").html(response.error_msg); 5137 } 5138 } 5139 }); 5140 } 5141 return false; 5142} 5143function requiredCreateCustomerFieldsFilled() { 5144 let allFilled = true; 5145 let focusSet = false; 5146 5147 $(".createAccount") 5148 .find("form") 5149 .find("input") 5150 .each(function () { 5151 $(this).css("border", ""); 5152 }); 5153 5154 $(".createAccount") 5155 .find("form") 5156 .find("input") 5157 .each(function () { 5158 if ($(this).prop("required")) { 5159 if ($(this).val() == undefined || $(this).val() == null || $(this).val() == "") { 5160 $(this).css("border", "1px red solid"); 5161 if (!focusSet) { 5162 focusSet = true; 5163 $(this).trigger("focus"); 5164 } 5165 allFilled = false; 5166 $("#error_fields").show(); 5167 } 5168 } 5169 }); 5170 5171 if (!isEmail($("#email").val())) { 5172 $("#email").css("border", "1px red solid"); 5173 allFilled = false; 5174 } 5175 return allFilled; 5176} 5177function changeCustomerType() { 5178 if ($(".scShopCheckout").length) { 5179 switch ($("#CustomerType").val()) { 5180 case "PRIVATE": 5181 if ($("#" + $("#CustomerType").val()).not(":visible")) { 5182 $("#" + $("#CustomerType").val()).show(); 5183 $("#COMPANY").hide(); 5184 $("#PRIVATE input, #PRIVATE textarea, #PRIVATE select").removeAttr("disabeld"); 5185 $("#COMPANY input, #COMPANY textarea, #COMPANY select").attr("disabeld", "disabeld"); 5186 } 5187 break; 5188 case "COMPANY": 5189 if ($("#" + $("#CustomerType").val()).not(":visible")) { 5190 $("#" + $("#CustomerType").val()).show(); 5191 $("#PRIVATE").hide(); 5192 $("#COMPANY input, #COMPANY textarea, #COMPANY select").removeAttr("disabeld"); 5193 $("#PRIVATE input, #PRIVATE textarea, #PRIVATE select").attr("disabeld", "disabeld"); 5194 } 5195 break; 5196 } 5197 } 5198} 5199function debounce(func, delay) { 5200 let timeout; 5201 return function (...args) { 5202 const context = this; 5203 clearTimeout(timeout); 5204 timeout = setTimeout(() => func.apply(context, args), delay); 5205 }; 5206} 5207function checkAmountValidity(elm) { 5208 const value = parseInt($(elm).val()); 5209 if ($(elm).attr("allow-zero") === "allow" && value == 0) { 5210 return; 5211 } 5212 if (value === "" || value < $(elm).attr("min") || value < 0 || isNaN(value)) { 5213 $(elm).val($(elm).attr("min")); 5214 return; 5215 } 5216 if (value > $(elm).attr("max")) { 5217 $(elm).val($(elm).attr("max")); 5218 return; 5219 } 5220 let step = parseFloat($(elm).attr("step")); 5221 if (step) { 5222 let roundedValue = Math.round(value / step) * step; 5223 while (roundedValue < $(elm).attr("min")) { 5224 roundedValue += step; 5225 } 5226 $(elm).val(roundedValue.toString()); 5227 } 5228} 5229// Favorites 5230function toggleFavoritesDropdown(button) { 5231 if ( 5232 $(".ProductMatrixAmountInput, .ProductAmountInput").length && 5233 !$(".ProductMatrixAmountInput, .ProductAmountInput").filter(function () { 5234 return $(this).val() != 0; 5235 }).length 5236 ) { 5237 $.toast({ 5238 heading: "Missing quantity", 5239 text: "Please set an amount for at least one variant", 5240 icon: "error", 5241 stack: false, 5242 hideAfter: 5000, 5243 position: "bottom-right", 5244 }); 5245 return; 5246 } 5247 if ($(".pageViewBasket").length && !$(".basketRowLines > .basketRowLine").length) { 5248 $.toast({ 5249 heading: "Missing quantity", 5250 text: "No items in the basket", 5251 icon: "error", 5252 stack: false, 5253 hideAfter: 5000, 5254 position: "bottom-right", 5255 }); 5256 return; 5257 } 5258 $(button).toggleClass("showDropdown"); 5259 if ($(button).hasClass("showDropdown")) { 5260 // Load favorite lists 5261 let dropdown = $(button).siblings(".favoritesDropdown"); 5262 5263 // Reset the dropdown to its initial state 5264 dropdown.empty().removeClass("listFetched"); 5265 $(button).addClass("loading"); 5266 $.get("/favorites/getlists", function (data) { 5267 let content = $("<ul>"); 5268 5269 // Display existing favorite lists 5270 if (data.length > 0) { 5271 data.forEach(function (list) { 5272 let desc = $("<span>").html(list.description); 5273 let btn = $("<button>") 5274 .addClass("btn btn-icon btn-success submitBtn") 5275 .append($("<span>").append($("<i>").addClass("fas fa-plus"))); 5276 let starBtn = $(dropdown).siblings(".favoriteStarButton"); 5277 $(btn).on("click", function () { 5278 addToFavorite(list.code, starBtn, $(btn).siblings("span").html()); 5279 }); 5280 let option = $("<li>").append(desc, btn); 5281 content.append(option); 5282 }); 5283 } 5284 let createListBtn = $("<button>").addClass("btn btn-primary").append($("<span>").html("Create New List")); 5285 $(createListBtn).on("click", function () { 5286 $(this).addClass("editName"); 5287 $(newListInput).trigger("focus"); 5288 }); 5289 let newFavListForm = $("<form>").addClass("newFavListForm"); 5290 let newListInput = $("<input>") 5291 .attr("type", "text") 5292 .attr("placeholder", "Enter new list name") 5293 .addClass("newListInput form-control"); 5294 let submitBtn = $("<button>").addClass("btn btn-primary").attr("type", "submit").append($("<span>").html("Create List")); 5295 $(newFavListForm).on("submit", function (e) { 5296 e.preventDefault(); 5297 let listName = $(newListInput).val(); 5298 if (listName.trim() !== "") { 5299 $.post("/favorites/add", { listName: listName }, function () { 5300 $.get("/favorites/getlists", function (newData) { 5301 let newList = newData.find((list) => list.description === listName); 5302 if (newList) { 5303 let starBtn = $(dropdown).siblings(".favoriteStarButton"); 5304 addToFavorite(newList.code, starBtn, listName); // Add item to the new list 5305 $(dropdown).removeClass("listFetched"); // Force reload on next dropdown open 5306 toggleFavoritesDropdown(starBtn); // Close and reopen to refresh the list 5307 } else { 5308 console.error("Failed to find new list in response data"); 5309 } 5310 }).fail(function () { 5311 console.error("Failed to fetch new list data"); 5312 }); 5313 }).fail(function () { 5314 console.error("Failed to create new list"); 5315 }); 5316 } else { 5317 $(this).find(".newListInput").addClass("form-error").trigger("focus"); 5318 } 5319 }); 5320 5321 $(newListInput).on("input", function () { 5322 $(this).removeClass("form-error"); 5323 }); 5324
5325 newFavListForm.append(newListInput, submitBtn); 5326 let createListSection = $("<li>").addClass("createListSection").append(createListBtn, newFavListForm); 5327 $(content).append(createListSection); 5328 dropdown.append(content).addClass("listFetched"); 5329 $(button).removeClass("loading"); 5330 }).fail(function () { 5331 $(btn).removeClass("success error"); 5332 }); 5333 return; 5334 } 5335} 5336function addToFavorite(favoriteCode, btn, favListName) { 5337 $(btn).addClass("loading").removeClass("showDropdown"); 5338 5339 if ($(".pageViewBasket").length) { 5340 postBasketToFavoriteList(favoriteCode, btn, favListName); 5341 return; 5342 } 5343 if ($(".ProductMatrixTable, .ProductBCVariantDropDown, .variantTypeList, .variantTypeMatrix").length) { 5344 addMultipleProductsToFavorites(favoriteCode, btn, favListName); 5345 return; 5346 } 5347 postProductToFavoriteList(favoriteCode, btn, favListName); 5348 return; 5349} 5350function postProductToFavoriteList(favoriteCode, btn, favListName) { 5351 let thisBuy = $(btn).closest(".buyBox"); 5352 let itemNo = $(thisBuy).data("sku"); 5353 let variantCode = $(thisBuy).data("variant"); 5354 let uomCode = $(thisBuy).data("uom"); 5355 let qtyInput = $(thisBuy).find(".ProductAmountInput"); 5356 let qty = 1; 5357 if (qtyInput.length) { 5358 qty = $(qtyInput).val(); 5359 } 5360 $.post("/favorites/lines/add", { 5361 favoriteCode: favoriteCode, 5362 itemNo: itemNo, 5363 variantCode: variantCode, 5364 unitOfMeasureCode: uomCode, 5365 quantity: qty, 5366 }) 5367 .done(function () { 5368 $(btn).addClass("success").attr("last-class", "success"); 5369 showAddedToFavsToast(favoriteCode, favListName); 5370 }) 5371 .fail(function (xhr) { 5372 $(btn).addClass("error").attr("last-class", "error"); 5373 }) 5374 .always(function (textStatus) { 5375 $(btn).removeClass("loading"); 5376 setTimeout(function () { 5377 $(btn).removeClass("success error"); 5378 }, 3000); 5379 }); 5380} 5381function postBasketToFavoriteList(favoriteCode, btn, favListName) { 5382 $.ajax({
5383 url: "favorites/savebasket/?favoriteCode=" + encodeURIComponent(favoriteCode), 5384 method: "POST", 5385 cache: false, 5386 contentType: "application/json", 5387 }) 5388 .done(function () { 5389 $(btn).addClass("success").attr("last-class", "success"); 5390 showAddedToFavsToast(favoriteCode, favListName); 5391 }) 5392 .fail(function (xhr) { 5393 $(btn).addClass("error").attr("last-class", "error"); 5394 }) 5395 .always(function () { 5396 $(btn).removeClass("loading"); 5397 setTimeout(function () { 5398 $(btn).removeClass("success error"); 5399 }, 3000); 5400 }); 5401} 5402function addMultipleProductsToFavorites(favoriteCode, btn, favListName) { 5403 let selectedVariants = []; 5404 5405 //product matrix 5406 $(".ProductMatrixAmountInput").each(function (index) { 5407 if (this.value == 0) return; 5408 const variantId = $("input.ProductMatrixVariantId")[index]; 5409 const itemNo = $("input.ProductMatrixItemNo")[index]; 5410 const itemUOM = $("input.ProductMatrixUOM")[index]; 5411 5412 const variantData = { 5413 ItemNo: itemNo.value, 5414 VariantCode: VariantId ? variantId.value : null, 5415 UnitOfMeasureCode: itemUOM.value, 5416 Quantity: parseInt(this.value), 5417 }; 5418 5419 selectedVariants.push(variantData); 5420 }); 5421 5422 //NewPriceAndVariantRender matrix and list 5423 $(".variantContainer .variantItem").each(function () { 5424 let itemQty = parseInt($(this).find(".incDecInput").val()); 5425 if (itemQty == 0) return; 5426 let itemNo = String($(this).data("itemno")); 5427 let variantId = String($(this).data("variant")); 5428 let itemUOM = $(this).data("uom"); 5429 let variantData = { 5430 ItemNo: itemNo, 5431 VariantCode: variantId ? variantId : null, 5432 UnitOfMeasureCode: itemUOM, 5433 Quantity: itemQty, 5434 }; 5435 5436 selectedVariants.push(variantData); 5437 $(this).addClass("valueChanged"); 5438 }); 5439 5440 // bc variants list 5441 $(".favItem").each(function () { 5442 let itemQty = parseInt($(this).find(".incDecInput").val()); 5443 if (itemQty == 0) return; 5444 let itemNo = String($(this).data("itemno")); 5445 let variantId = String($(this).data("variant")); 5446 let itemUOM = $(this).data("uom"); 5447 let variantData = { 5448 ItemNo: itemNo, 5449 VariantCode: variantId ? variantId : null, 5450 UnitOfMeasureCode: itemUOM, 5451 Quantity: itemQty, 5452 }; 5453 5454 selectedVariants.push(variantData); 5455 $(this).addClass("valueChanged"); 5456 }); 5457 5458 if (selectedVariants.length > 0) { 5459 $(btn).addClass("loading"); 5460 5461 $.ajax({
5462 url: "/favorites/lines/addmultiple", 5463 method: "POST", 5464 contentType: "application/json", 5465 data: JSON.stringify({ 5466 favoriteCode: favoriteCode, 5467 lines: selectedVariants, 5468 }), 5469 }) 5470 .done(function () { 5471 $(btn).addClass("success").attr("last-class", "success"); 5472 showAddedToFavsToast(favoriteCode, favListName); 5473 }) 5474 .fail(function () { 5475 $(btn).addClass("error").attr("last-class", "error"); 5476 }) 5477 .always(function () { 5478 $(btn).removeClass("loading"); 5479 setTimeout(function () { 5480 $(btn).removeClass("success error"); 5481 }, 3000); 5482 }); 5483 } else { 5484 console.error("No variants selected to add to favorites."); 5485 $(btn).removeClass("loading"); 5486 $(btn).addClass("error").attr("last-class", "error"); 5487 setTimeout(function () { 5488 $(btn).removeClass("success error"); 5489 }, 1500); 5490 } 5491} 5492function removeProduct(lineId = null, btn = null, cancel = false) { 5493 let thisDelete = $(btn).parent(); 5494 let thisRow = $(thisDelete).closest("tr"); 5495 if (cancel) { 5496 $(thisDelete).removeClass("show"); 5497 return; 5498 } 5499 if (!$(thisDelete).hasClass("show")) { 5500 $(thisDelete).addClass("show"); 5501 return; 5502 } 5503 $(btn).addClass("loading"); 5504 $("body").addClass("loading"); 5505 $(thisRow).css("max-height", $(thisRow).outerHeight() + "px"); 5506 $.post("/favorites/lines/remove", { 5507 lineId: lineId, 5508 }) 5509 .done(function (data) { 5510 $(btn).addClass("success").attr("last-class", "success"); 5511 $(thisRow).addClass("deleting"); 5512 setTimeout(function () { 5513 $(thisRow).remove(); 5514 }, 500); 5515 }) 5516 .fail(function (xhr) { 5517 $(btn).addClass("error").attr("last-class", "error"); 5518 }) 5519 .always(function () { 5520 $("body").removeClass("loading"); 5521 }) 5522 .always(function () { 5523 setTimeout(function () { 5524 $(btn).removeClass("success error"); 5525 }, 3000); 5526 }); 5527} 5528function showAddedToFavsToast(favoriteCode, name) { 5529 $.toast({ 5530 text: `Added to favorite list:<br><a href="/favorites/lines/${favoriteCode}">${name}</a>`, 5531 icon: "success", 5532 stack: false, 5533 hideAfter: 5000, 5534 position: "bottom-right", 5535 }); 5536} 5537// ASYNC Functions 5538async function cartCommentFileChanged(elm) { 5539 let file = $(elm).prop("files")[0]; 5540 5541 if (file == null || elm == "") { 5542 return; 5543 } 5544 5545 let filedata = await getBase64(file); 5546 5547 $(".modal-commentline .cloneRow") 5548 .not(".d-none") 5549 .find("input.basketLineComments") 5550 .each(function () { 5551 if ($(this).data("masterdatavalue") === $(elm).data("masterdatavalue")) { 5552 $(this).attr("data-hasfile", "true"); 5553 $(this).attr("data-filedata", filedata); 5554 $(this).siblings("span").html(file.name); 5555 } 5556 }); 5557} 5558async function getBase64(file) { 5559 return new Promise((resolve, reject) => { 5560 let reader = new FileReader(); 5561 reader.onload = () => resolve(reader.result); 5562 reader.onerror = (e) => reject(e); 5563 reader.readAsDataURL(file); 5564 }); 5565} 5566// ---- CUSTOM ELEMENTS ---- // 5567customElements.define( 5568 "double-slider", 5569 class DoubleSlider extends HTMLElement { 5570 static get observedAttributes() { 5571 return ["min", "max", "step", "value-min", "value-max"]; 5572 } 5573 5574 constructor() { 5575 super(); 5576 this._built = false; 5577 this._reflecting = false; 5578 this._activeNode = null; 5579 this._dragStart = null; 5580 this._dragFraction = 0; 5581 this._rect = null; 5582 this._size = null; 5583 this._commitTimer = null; 5584 } 5585 5586 connectedCallback() { 5587 if (!this._built) { 5588 this._build(); 5589 } 5590 this._sync(); 5591 } 5592 5593 attributeChangedCallback() { 5594 if (this._built && !this._reflecting) { 5595 this._sync(); 5596 } 5597 } 5598 5599 get min() { 5600 return this._parse(this.getAttribute("min"), 0); 5601 } 5602 5603 get max() { 5604 const max = this._parse(this.getAttribute("max"), 100); 5605 return max > this.min ? max : this.min; 5606 } 5607 5608 //Zero means continuous, matching step="any" on a native range input. 5609 get step() { 5610 const raw = this.getAttribute("step"); 5611 if (raw === null || raw === "any") { 5612 return 0; 5613 } 5614 const step = parseFloat(raw); 5615 return isNaN(step) || step <= 0 ? 0 : step; 5616 } 5617 5618 get nudge() { 5619 return this.step || (this.max - this.min) / 100; 5620 } 5621 5622 get valueMin() { 5623 return this._limit(this._parse(this.getAttribute("value-min"), this.min)); 5624 } 5625 5626 set valueMin(value) { 5627 this._write(value, this.valueMax); 5628 } 5629 5630 get valueMax() { 5631 return this._limit(this._parse(this.getAttribute("value-max"), this.max)); 5632 } 5633 5634 set valueMax(value) { 5635 this._write(this.valueMin, value); 5636 } 5637 5638 get isDefault() { 5639 return this.valueMin <= this.min && this.valueMax >= this.max; 5640 } 5641 5642 //Silent, so a caller clearing several sliders can submit once for all of them. 5643 reset() { 5644 clearTimeout(this._commitTimer); 5645 this._write(this.min, this.max); 5646 } 5647 5648 _build() { 5649 this.innerHTML = ` 5650 <input type="number" class="sliderValue min" inputmode="decimal"> 5651 <div class="sliderTrack"> 5652 <div class="sliderRange"></div> 5653 <div class="sliderNode min" role="slider" tabindex="0" aria-orientation="horizontal"></div> 5654 <div class="sliderNode max" role="slider" tabindex="0" aria-orientation="horizontal"></div> 5655 </div> 5656 <input type="number" class="sliderValue max" inputmode="decimal">`; 5657 this._track = this.querySelector(".sliderTrack"); 5658 this._nodes = { 5659 min: this.querySelector(".sliderNode.min"), 5660 max: this.querySelector(".sliderNode.max"), 5661 }; 5662 this._inputs = { 5663 min: this.querySelector(".sliderValue.min"), 5664 max: this.querySelector(".sliderValue.max"), 5665 }; 5666 ["min", "max"].forEach((which) => { 5667 const label = this.getAttribute("label-" + which) || (which === "min" ? "Minimum" : "Maximum"); 5668 this._nodes[which].setAttribute("aria-label", label); 5669 this._inputs[which].setAttribute("aria-label", label); 5670 this._inputs[which].addEventListener("input", () => this._onFieldInput(which)); 5671 this._inputs[which].addEventListener("blur", () => this._commitField(which, true)); 5672 this._inputs[which].addEventListener("keydown", (e) => this._onFieldKeyDown(e, which)); 5673 //The fields are internal, so their native change never reaches the page. 5674 this._inputs[which].addEventListener("change", (e) => e.stopPropagation()); 5675 }); 5676 this._track.addEventListener("pointerdown", (e) => this._onPointerDown(e)); 5677 this._track.addEventListener("pointermove", (e) => this._onPointerMove(e)); 5678 this._track.addEventListener("pointerup", (e) => this._onPointerUp(e)); 5679 this._track.addEventListener("pointercancel", () => this._onPointerCancel()); 5680 this.addEventListener("keydown", (e) => this._onKeyDown(e)); 5681 this._built = true; 5682 } 5683 5684 _parse(raw, fallback) { 5685 const value = parseFloat(raw); 5686 return isNaN(value) ? fallback : value; 5687 } 5688 5689 _limit(value) { 5690 if (value < this.min) { 5691 return this.min; 5692 } 5693 if (value > this.max) { 5694 return this.max; 5695 } 5696 return value; 5697 } 5698 5699 _snap(value) { 5700 const step = this.step; 5701 if (!step) { 5702 return this._limit(value); 5703 } 5704 const steps = Math.round((value - this.min) / step); 5705 return this._limit(parseFloat((this.min + steps * step).toFixed(10))); 5706 } 5707 5708 //Single point of truth: normalises, reflects to attributes and repaints. 5709 _write(low, high) { 5710 let newLow = this._snap(low);
5711 let newHigh = this._snap(high); 5712 if (newLow > newHigh) { 5713 const swap = newLow; 5714 newLow = newHigh; 5715 newHigh = swap; 5716 } 5717 this._reflecting = true; 5718 this.setAttribute("value-min", String(newLow)); 5719 this.setAttribute("value-max", String(newHigh)); 5720 if (newLow <= this.min && newHigh >= this.max) { 5721 this.removeAttribute("modified"); 5722 } else { 5723 this.setAttribute("modified", ""); 5724 } 5725 this._reflecting = false; 5726 this._paint(); 5727 } 5728 5729 _sync() { 5730 this._reserveLabels(); 5731 this._write(this.valueMin, this.valueMax); 5732 } 5733 5734 //Widest value this slider can ever show, so live values during a drag can never 5735 //resize the field and pull the node out from under the pointer. Counted in digit 5736 //widths, with the separator and sign costing well under a digit each. 5737 _reserveLabels() { 5738 const decimals = this.step ? Math.max(this._decimals(this.getAttribute("step")), this._decimals(this.getAttribute("min"))) : 1; 5739 const digits = Math.max(String(Math.floor(Math.abs(this.min))).length, String(Math.floor(Math.abs(this.max))).length); 5740 const width = digits + (decimals ? decimals + 0.5 : 0) + (this.min < 0 ? 0.6 : 0); 5741 this.style.setProperty("--_labelChars", Math.round(width * 10) / 10); 5742 } 5743 5744 _decimals(raw) { 5745 const text = String(raw); 5746 const dot = text.indexOf("."); 5747 return dot === -1 ? 0 : text.length - dot - 1; 5748 } 5749 5750 _paint() { 5751 const low = this.valueMin; 5752 const high = this.valueMax; 5753 this.style.setProperty("--_min", this._fractionOf(low)); 5754 this.style.setProperty("--_max", this._fractionOf(high)); 5755 this._readout("min", low); 5756 this._readout("max", high); 5757 } 5758 5759 //Field plus ARIA for one node. Used both by _paint and live during a drag. 5760 //The field's own bounds are the other node's position, so native validation 5761 //agrees with the clamping applied on blur. 5762 _readout(which, value) { 5763 const low = which === "min" ? this.min : this.valueMin; 5764 const high = which === "min" ? this.valueMax : this.max; 5765 const input = this._inputs[which]; 5766 //Never rewrite a field that is being typed into: a half-typed "0." parses to 5767 //0, and echoing that back would delete the separator from under the caret. 5768 if (input !== document.activeElement) { 5769 input.value = this._format(value); 5770 } 5771 input.min = String(low); 5772 input.max = String(high); 5773 input.step = this.step ? String(this.step) : "any"; 5774 const node = this._nodes[which]; 5775 node.setAttribute("aria-valuenow", String(value)); 5776 node.setAttribute("aria-valuemin", String(low)); 5777 node.setAttribute("aria-valuemax", String(high)); 5778 node.setAttribute("aria-valuetext", this._format(value)); 5779 } 5780 5781 //Typing repositions the node straight away and submits once typing settles. 5782 _onFieldInput(which) { 5783 this._scheduleCommit(which); 5784 const typed = parseFloat(this._inputs[which].value); 5785 if (isNaN(typed)) { 5786 return; 5787 } 5788 const bounded = which === "min" ? Math.min(this._limit(typed), this.valueMax) : Math.max(this._limit(typed), this.valueMin); 5789 this.style.setProperty(which === "min" ? "--_min" : "--_max", this._fractionOf(this._snap(bounded))); 5790 } 5791 5792 _scheduleCommit(which) { 5793 clearTimeout(this._commitTimer); 5794 this._commitTimer = setTimeout(() => this._commitField(which, false), 200); 5795 } 5796 5797 _onFieldKeyDown(event, which) { 5798 if (event.key === "Enter") { 5799 event.preventDefault(); 5800 this._commitField(which, true); 5801 } else if (event.key === "Escape") { 5802 event.preventDefault(); 5803 clearTimeout(this._commitTimer);
5804 this._paint(); 5805 this._fill(which); 5806 } else if (event.key === "ArrowUp" || event.key === "ArrowDown") { 5807 event.preventDefault(); 5808 this._nudgeField(which, event.key === "ArrowUp" ? 1 : -1); 5809 } 5810 } 5811 5812 //Stepped from the field's own value. Native stepping aligns to the field's min, 5813 //which is the other node, so 50 against a min of 27.5 would step to 49.5 and 5814 //could never get back to 50. 5815 _nudgeField(which, direction) { 5816 const input = this._inputs[which]; 5817 const current = this._parse(input.value, which === "min" ? this.valueMin : this.valueMax); 5818 const stepped = current + direction * (this.step || 1); 5819 const bounded = which === "min" ? Math.min(this._limit(stepped), this.valueMax) : Math.max(this._limit(stepped), this.valueMin); 5820 input.value = this._format(this._snap(bounded)); 5821 this._onFieldInput(which); 5822 } 5823 5824 _fill(which) { 5825 this._inputs[which].value = this._format(which === "min" ? this.valueMin : this.valueMax); 5826 } 5827 5828 //Validates a typed value: unusable input falls back to the value already 5829 //committed, and anything out of range lands on the nearest valid value. Only an 5830 //explicit commit rewrites the field, so a debounced one leaves the caret alone. 5831 _commitField(which, normalize) { 5832 clearTimeout(this._commitTimer); 5833 const typed = parseFloat(this._inputs[which].value); 5834 const before = [this.valueMin, this.valueMax]; 5835 this._move(which, isNaN(typed) ? before[which === "min" ? 0 : 1] : typed); 5836 if (normalize) { 5837 this._fill(which); 5838 } 5839 if (before[0] !== this.valueMin || before[1] !== this.valueMax) { 5840 this._notify(); 5841 } 5842 } 5843 5844 //Stepped values are shown exactly; a continuous slider is rounded for display. 5845 _format(value) { 5846 return String(parseFloat(value.toFixed(this.step ? 10 : 1))); 5847 } 5848 5849 _fractionOf(value) { 5850 return (value - this.min) / (this.max - this.min || 1); 5851 } 5852 5853 _valueOf(fraction) { 5854 return this.min + fraction * (this.max - this.min); 5855 } 5856 5857 //Geometry is read from what was cached at pointerdown, so a drag can never be 5858 //disturbed by the labels resizing the track underneath it. 5859 _geometry() { 5860 const rect = this._rect || this._track.getBoundingClientRect(); 5861 const size = this._size || this._nodes.min.getBoundingClientRect().width; 5862 return { left: rect.left, size: size, travel: Math.max(0, rect.width - size) }; 5863 } 5864 5865 //Both centres travel the track minus one node, with the max node's centre a full 5866 //node further along. So each centre reaches its end of the track, and equal values 5867 //leave the two nodes exactly touching instead of overlapping. 5868 _centerOf(which, fraction) { 5869 const geometry = this._geometry(); 5870 return fraction * geometry.travel + (which === "max" ? geometry.size : 0); 5871 } 5872 5873 _fractionAt(which, clientX) { 5874 const geometry = this._geometry(); 5875 if (!geometry.travel) { 5876 return 0; 5877 } 5878 const offset = clientX - geometry.left - (which === "max" ? geometry.size : 0); 5879 return Math.min(1, Math.max(0, offset / geometry.travel)); 5880 } 5881 5882 //A press anywhere on the track hands control to whichever centre is nearest. 5883 _closestNode(clientX, target) { 5884 const node = target && target.closest ? target.closest(".sliderNode") : null; 5885 if (node) { 5886 return node.classList.contains("max") ? "max" : "min"; 5887 } 5888 const x = clientX - this._geometry().left; 5889 const toMin = Math.abs(x - this._centerOf("min", this._fractionOf(this.valueMin))); 5890 const toMax = Math.abs(x - this._centerOf("max", this._fractionOf(this.valueMax))); 5891 return toMin <= toMax ? "min" : "max"; 5892 } 5893 5894 _move(which, value) { 5895 if (which === "min") { 5896 this._write(Math.min(value, this.valueMax), this.valueMax); 5897 } else { 5898 this._write(this.valueMin, Math.max(value, this.valueMin)); 5899 } 5900 } 5901 5902 //Moves and reads out the node without writing a value: the label shows what 5903 //would be committed if released here, but nothing is stored or submitted yet. 5904 _drag(clientX) { 5905 let fraction = this._fractionAt(this._activeNode, clientX); 5906 if (this._activeNode === "min") { 5907 fraction = Math.min(fraction, this._fractionOf(this.valueMax)); 5908 this.style.setProperty("--_min", fraction); 5909 } else { 5910 fraction = Math.max(fraction, this._fractionOf(this.valueMin)); 5911 this.style.setProperty("--_max", fraction); 5912 } 5913 this._dragFraction = fraction; 5914 this._readout(this._activeNode, this._snap(this._valueOf(fraction))); 5915 } 5916 5917 _onPointerDown(event) { 5918 if (event.button > 0) { 5919 return; 5920 } 5921 this._rect = this._track.getBoundingClientRect(); 5922 this._size = this._nodes.min.getBoundingClientRect().width; 5923 this._activeNode = this._closestNode(event.clientX, event.target); 5924 this._dragStart = [this.valueMin, this.valueMax]; 5925 this._nodes[this._activeNode].focus({ preventScroll: true });
5926 this._track.setPointerCapture(event.pointerId); 5927 this.setAttribute("dragging", ""); 5928 this._drag(event.clientX); 5929 event.preventDefault(); 5930 } 5931 5932 _onPointerMove(event) { 5933 if (!this._activeNode) { 5934 return; 5935 } 5936 this._drag(event.clientX); 5937 } 5938 5939 //Release is the only point where the drag turns into a value. 5940 _onPointerUp(event) { 5941 if (!this._activeNode) { 5942 return; 5943 } 5944 const which = this._activeNode; 5945 const value = this._valueOf(this._dragFraction); 5946 this._activeNode = null; 5947 this._rect = null; 5948 this._size = null; 5949 this.removeAttribute("dragging"); 5950 this._move(which, value); 5951 if (this._dragStart[0] !== this.valueMin || this._dragStart[1] !== this.valueMax) { 5952 this._notify(); 5953 } 5954 } 5955 5956 //An interrupted drag never became a value, so just put the node back. 5957 _onPointerCancel() { 5958 if (!this._activeNode) { 5959 return; 5960 } 5961 this._activeNode = null; 5962 this._rect = null; 5963 this._size = null; 5964 this.removeAttribute("dragging"); 5965 this._paint(); 5966 } 5967 5968 _onKeyDown(event) { 5969 const node = event.target.closest ? event.target.closest(".sliderNode") : null; 5970 if (!node) { 5971 return; 5972 } 5973 const which = node.classList.contains("max") ? "max" : "min"; 5974 const current = which === "max" ? this.valueMax : this.valueMin; 5975 let value = current; 5976 switch (event.key) { 5977 case "ArrowLeft": 5978 case "ArrowDown": 5979 value = current - this.nudge; 5980 break; 5981 case "ArrowRight": 5982 case "ArrowUp": 5983 value = current + this.nudge; 5984 break; 5985 case "Home": 5986 value = this.min; 5987 break; 5988 case "End": 5989 value = this.max; 5990 break; 5991 default: 5992 return; 5993 } 5994 event.preventDefault(); 5995 const before = [this.valueMin, this.valueMax]; 5996 this._move(which, value); 5997 if (before[0] !== this.valueMin || before[1] !== this.valueMax) { 5998 this._notify(); 5999 } 6000 } 6001 6002 _notify() { 6003 this.dispatchEvent(new CustomEvent("change", { bubbles: true })); 6004 } 6005 }, 6006); 6007 6008// Empty callbacks 6009function onNavbarCreated() {} 6010function onProductAdded() {} 6011function onProductRemoved() {} 6012function onOrderCompleted() {} 6013function onBasketParsed(data) {} 6014function onBasketUpdated() {} 6015function onScroll(scroll) {} 6016function onWindowResize(width) {} 6017function onPageInit(page) {}
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.