1/*=================================== 2= Item Page Top Section App = 3===================================*/ 4 5document.addEventListener('alpine:init', () => { 6 /* Item Page Top Section App */ 7 Alpine.data('itemPageTopSection', () => ({ 8 init() { 9 this.initialized = true; 10 11 // Find Pricing Description embeddable section and check if it has any content 12 const pricingDescParentDiv = document.getElementsByClassName("item-page-15-pricing-desc")[0]; 13 if (pricingDescParentDiv) { 14 this.hasPricingDescription = pricingDescParentDiv.children.length > 0; 15 } 16 17 // Event listener to hide Learn More description when modal is hidden 18 $('#customAddonsModal').on('hidden.bs.modal', () => { 19 this.showLearnMoreDesc = false; 20 this.toggleMobileItemCard(); 21 }); 22 23 // Toggle visibility of slide-up image page card 24 $('#standardAddonsModal').on('hidden.bs.modal', () => { 25 this.toggleMobileItemCard(); 26 }); 27 28 // Retrieve add-on data from URL query string if available 29 const addonDataString = this.getQueryStringValue("addonData"); 30 31 if (addonDataString) { 32 this.addonDataFromUrl = JSON.parse(decodeURIComponent(decodeURIComponent(addonDataString))); 33 } 34 35 if (window.topSectionModel.stripePublicKey) { 36 const stripe = Stripe(window.topSectionModel.stripePublicKey, window.topSectionModel.stripeConnectAccountId ? { stripeAccount: window.topSectionModel.stripeConnectAccountId } : null); 37 const elements = stripe.elements(); 38 const options = { 39 amount: window.topSectionModel.itemPriceCents, 40 currency: 'USD', 41 countryCode: 'US', 42 }; 43 const PaymentMessageElement = 44 elements.create('paymentMethodMessaging', options); 45 PaymentMessageElement.mount('#payment-method-messaging-element'); 46 } 47 }, 48 49 hasPricingDescription: false, 50 isSticky: window.topSectionModel.enableTopSectionStickiness && (window.addOnsModel?.addonGroups ?? []).length == 0, 51 gpid: window.topSectionModel.gpid, // Base product Gpid 52 parentQuantity: (window.topSectionModel.cartQuantity > 0 && !window.topSectionModel.cartQuantityRequired) ? window.topSectionModel.cartQuantity : 1, // Quantity of base product 53 itemIsInCart: window.topSectionModel.itemIsInCart, // Flag to indicate whether base product is in cart 54 addonGroups: window.addOnsModel?.addonGroups ?? [], // Add-on groups associated with base product 55 currentAddon: {}, // Stores current add-on 56 showLearnMoreDesc: false, // Flag to toggle visibility of Learn More description 57 selectedAddonItems: [], // Stores selected add-ons 58 initialized: false, // Flag to trigger skeleton loaders 59 adding: false, // Flag to indicate whether products are in process of being added to cart 60 addonDataFromUrl: "", // Stores add-on data from URL query string 61 inCartAddonGpids: window.addOnsModel?.inCartAddonGpids ?? [], // GPIDs of add-ons already in cart 62 63 // Computed property to check whether current add-on is selected 64 get currentAddonIsSelected() { 65 return this.selectedAddonItems.find((a) => a.Gpid == this.currentAddon.Gpid); 66 }, 67 68 // Computed property to get total count of add-ons to add to cart 69 get addToCartItemCount() { 70 return this.selectedAddonItems.filter(addon => addon.Price > 0).length + 1; 71 }, 72 73 // Computed property to calculate total price including base product price 74 get addToCartPrice() { 75 const baseItemPrice = window.addOnsModel?.parentItemPrice ?? 0; 76 let totalPrice = 0; 77 78 // Calculate total price of selected add-on items
79 this.selectedAddonItems.forEach(item => { 80 for (i = 0; i < item.Qty; i++) { 81 var priceOfAddon = item.Price; 82 totalPrice += priceOfAddon; 83 } 84 }); 85 86 return this.formatPrice(parseFloat(totalPrice) + parseFloat(baseItemPrice)); 87 }, 88 89 // Toggles visibility of slide-up image page card by adding or removing d-none class 90 toggleMobileItemCard() { 91 if ($('.__item-page-card').hasClass('d-none')) { 92 $('.__item-page-card').removeClass('d-none'); 93 } else { 94 $('.__item-page-card').addClass('d-none'); 95 } 96 }, 97 98 // Formats price as currency 99 formatPrice(price) { 100 const formatter = new Intl.NumberFormat('en-US', { 101 style: 'currency', 102 currency: 'USD', 103 }); 104 105 return formatter.format(price); 106 }, 107 108 // Handles selection of add-on for checkbox display 109 handleSelectOption(addon) { 110 // Check whether selectedAddons has another add-on from this group 111 let existingAddon = this.selectedAddonItems.find((selectedAddonItem) => selectedAddonItem.groupId == addon.groupId && selectedAddonItem.Gpid != addon.Gpid); 112 113 if (existingAddon) { 114 // Remove existing add-on 115 let existingAddonInput = document.getElementById('check' + existingAddon.Gpid); 116 existingAddonInput.click(); 117 } 118 119 this.$nextTick(() => { 120 // Select new add-on 121 let selectedAddonInput = document.getElementById('check' + addon.Gpid); 122 selectedAddonInput.click(); 123 }); 124 }, 125 126 // Handles selection of add-on for all displays except checkbox 127 handleSelectAddonStandardPopup(addon) { 128 if (this.currentAddonIsSelected) { 129 // Find first Remove button for existing add-on 130 const existingAddonButton = document.querySelector(`.remove-button[data-gpid="${addon.Gpid}"]`); 131 132 if (existingAddonButton) { 133 existingAddonButton.click(); // Programmatically click first Remove button 134 } 135 } 136 else 137 { 138 const selectedAddonButton = document.querySelector(`.add-button[data-gpid="${addon.Gpid}"]`); 139 140 if (selectedAddonButton) { 141 selectedAddonButton.click(); // Programmatically click first Add button 142 } 143 } 144 }, 145 146 // Handles click event for adding base product and add-ons to cart 147 async addToCartClick() { 148 this.adding = true; 149 150 // Ensure quantity of base product is at least 1 151 if (this.parentQuantity <= 0) { 152 this.parentQuantity = 1; 153 } 154 155 if (!this.gpid) 156 return; 157 158 var addToCartItems = []; 159 160 // only pass priced addons to cart 161 this.selectedAddonItems.filter(addon => addon.Price > 0).forEach((sAddon) => 162 addToCartItems.push({ addonGpid: sAddon.Gpid, qty: sAddon.Qty })); 163 164 try { 165 const payload = { 166 parentGpid: this.gpid, 167 addons: addToCartItems, 168 quantity: this.parentQuantity 169 }; 170 171 const response = await fetch(Utils.baseUrl + "shoppingcart/ShoppingCart/AddMultipleItemsToShoppingCart", 172 { 173 method: "POST", 174 headers: { 175 "Content-Type": "application/json; charset=utf-8" 176 }, 177 body: JSON.stringify(payload) 178 } 179 ); 180 181 if (!response.ok) { 182 throw new Error(`HTTP ${response.status}`); 183 } 184 185 // Hide spinner 186 this.adding = false; 187 this.itemIsInCart = true; 188 189 // Update cart product count 190 const addedCount = this.selectedAddonItems.length + 1; 191 192 const cartCountEl = document.querySelector('.js-cart-count'); 193 if (cartCountEl) { 194 const currentCount = parseInt(cartCountEl.textContent.replace(/\D/g, ""), 10) || 0; 195 196 const cartItemCount = currentCount + addedCount; 197 cartCountEl.textContent = cartItemCount; 198 } 199 200 // Dispatch cart update event 201 document.dispatchEvent( 202 new CustomEvent('update-cart', { 203 detail: { added: addedCount }, 204 bubbles: true 205 }) 206 ); 207 208 var data = response.json(); 209 210 // Track GA4 211 fdEvents.publish("add_to_cart", data.gaEvent); 212 213 // Track Facebook Pixel 214 window.fbPixel.done(() => { 215 fbq( 216 'track', 217 'AddToCart', 218 { content_type: 'product', content_ids: [this.gpid.toString()] }, 219 { eventID: data.eventId } 220 ); 221 }); 222 223 // Track Klaviyo 224 await trackKlaviyoCartAdd(this.gpid ); 225 for (const i of addToCartItems) { 226 await trackKlaviyoCartAdd(i.Gpid); 227 } 228 } catch (error) { 229 this.adding = false; 230 this.itemIsInCart = false; 231 232 console.error(`Error adding items to cart: ${JSON.stringify(this.selectedAddonItems)}`, error); 233 234 const modalBody = document.getElementById('added-to-cart-modal-body'); 235 if (modalBody) 236 modalBody.innerHTML = "Looks like we encountered a problem, try re-adding your products to the cart."; 237 238 const modalEl = document.getElementById('added-to-cart-modal'); 239 if (modalEl && window.bootstrap?.Modal) { 240 const modal = new bootstrap.Modal(modalEl); 241 modal.show(); 242 } 243 } 244 }, 245 246 // Calculates total price of add-on based on its quantity 247 priceBasedOnQty(selectedAddonOption) { 248 let totalPrice = 0; 249 250 // Iterates over quantity and sums up price 251 for (i = 0; i < selectedAddonOption.Qty; i++) { 252 var priceOfAddon = selectedAddonOption.Price; 253 totalPrice += priceOfAddon; 254 } 255 256 return totalPrice; 257 }, 258 checkIfRemovedFromCart(gpid) { 259 if (this.gpid == gpid) { // this item was removed from the cart 260 this.itemIsInCart = false; 261 } 262 263 // Check if removed item was selected addon 264 var addonIndex = this.selectedAddonItems.findIndex(item => item.Gpid == gpid); 265 if (addonIndex !== -1) { 266 this.selectedAddonItems.splice(addonIndex, 1); 267 268 // Dispatch event so add-on group component can reset its selected option 269 window.dispatchEvent(new CustomEvent('addon-removed-from-cart', { detail: { gpid: gpid } })); 270 271 this.updateUrl(); 272 } 273 }, 274 275 // Updates URL with current add-on data 276 updateUrl() { 277 const url = new URL(window.location.href); 278 279 url.searchParams.set("addonData", encodeURIComponent(JSON.stringify(this.generateAddonData()))); 280 window.history.replaceState({ path: url.href }, "", url.href); 281 }, 282 283 // Generates data object representing selected add-ons 284 generateAddonData() { 285 var data = [];
286 this.selectedAddonItems.forEach((selectedAddonItem) => 287 data.push({ addonGpid: selectedAddonItem.Gpid, groupId: selectedAddonItem.groupId })); 288 289 return data; 290 }, 291 292 // Retrieves value of query string parameter by key 293 getQueryStringValue(key) { 294 const query = window.location.search.substring(1); 295 const vars = query.split("&"); 296 297 for (let i = 0; i < vars.length; i++) { 298 const pair = vars[i].split("="); 299 if (pair[0] == key) { 300 return pair[1]; 301 } 302 } 303 } 304 })); 305 306 /* Add-On Group Component */ 307 Alpine.data('addonGroupComponent', (_addonGroup) => ({ 308 init() { 309 // Check whether there is add-on data from URL 310 if (this.addonDataFromUrl) { 311 // Find add-on data that matches current group ID 312 const addonData = this.addonDataFromUrl.find(group => group.groupId == this.groupId); 313 314 if (addonData) { 315 // Find add-on within group that matches Gpid from the URL data 316 const addonFromUrl = _addonGroup.AddonOptions.find(addonOption => addonOption.Gpid == addonData.addonGpid); 317 addonFromUrl.groupId = this.groupId; 318 319 this.handleSelectAddon(addonFromUrl, true); 320 } 321 } 322 323 // If no add-on was selected from URL but item is in cart, check if any add-on 324 // from group is already in cart and auto-select it 325 if (!this.addonDataFromUrl && this.itemIsInCart) { 326 const inCartAddon = _addonGroup.AddonOptions.find(addonOption => 327 this.inCartAddonGpids.includes(addonOption.Gpid)); 328 329 if (inCartAddon) { 330 inCartAddon.groupId = this.groupId; 331 this.handleSelectAddon(inCartAddon, true); 332 } 333 } 334 335 // If selectedOption is not already set and group is of type PrimaryAddon, 336 // check whether primary add-on is preselected 337 if (this.presentationType == 2) { 338 this.primaryAddon = _addonGroup.AddonOptions.find(addonOption => addonOption.IsPrimaryAddon); 339 this.primaryAddon.groupId = this.groupId; 340 341 // Pre-select primary add-on 342 if (this.primaryAddon && this.primaryAddon.IsPreselected && this.selectedOption.Gpid == 0) { 343 this.handleSelectAddon(this.primaryAddon, true); 344 } 345 } else { 346 var preselectedAddon = _addonGroup.AddonOptions.find(addonOption => addonOption.IsPreselected); 347 if (preselectedAddon && this.selectedOption.Gpid == 0) { 348 preselectedAddon.groupId = this.groupId; 349 this.handleSelectAddon(preselectedAddon, true); 350 } 351 } 352 }, 353 354 groupId: _addonGroup.GroupId, 355 groupName: _addonGroup.GroupName, 356 groupNameLeadInText: _addonGroup.CustomLeadInText, // Lead-in text for group name 357 presentationType: _addonGroup.PresentationType, // Stores presentation type (int) of add-on group 358 primaryAddon: {}, 359 showSelectModal: false, // Flag to toggle visibility of add-on selection modal 360 361 // Object to store details of currently selected add-on option 362 selectedOption: { 363 Gpid: 0, // ID of selected add-on option 364 SDesc: '', // Short description of selected add-on option 365 LDesc: '', // Long description of selected add-on option 366 LDescHtml: '', 367 ImageUrl: '', 368 Url: '', 369 WebpImageUrl: '', // WebP image URL of selected add-on option 370 price: 0, 371 PriceBeforeDiscountOrSale: 0, 372 PricingHtml: '', // HTML content for pricing information 373 Qty: 0, 374 IsStandardPopupEnabled: false, 375 Width: "", 376 Depth: "", 377 Height: "" 378 }, 379 380 // Generates list of add-on options with unique IDs and group IDs 381 addonOptions() { 382 var result = []; 383 384 // Iterate over each add-on option and assign unique ID and group ID 385 _addonGroup.AddonOptions.forEach((option) => { 386 const randomId = crypto.randomUUID(); // Generate random unique ID 387 const withId = { ...option, id: randomId, groupId: _addonGroup.GroupId }; 388 389 return result.push(withId); // Add modified option to result list 390 }); 391 392 if (this.presentationType == 2 || this.presentationType == 3) { 393 result = result.sort((a, b) => { 394 if (a.Gpid == this.selectedOption.Gpid) { return -1; } 395 if (b.Gpid == this.selectedOption.Gpid) { return 1; } 396 397 return 0; 398 }); 399 } 400 401 return result; 402 }, 403 404 // Checks position of specific add-on group within filtered list of dropdown groups 405 checkDropDownIndex(last) { 406 // Filter addonGroups to include only those of Dropdown PresentationType 407 // and find index of group with current groupId in filtered list 408 const dropdownGroups = this.addonGroups.filter(addonGroup =>
408 addonGroup.PresentationType == 3); 409 const index = dropdownGroups.findIndex(dropdownGroup => dropdownGroup.GroupId == this.groupId); 410 411 // Check whether group is either first or last item in filtered list 412 if (last) { 413 return index == dropdownGroups.length - 1; 414 } else { 415 return index == 0; 416 } 417 }, 418 419 // Opens custom add-ons modal 420 openCustomAddonsModal(addon) { 421 // Set current addon 422 this.currentAddon = addon; 423 424 // Toggle visibility of slide-up image page card 425 this.toggleMobileItemCard(); 426 $('#customAddonsModal').modal('show'); 427 }, 428 429 // Opens standard add-ons modal 430 openStandardAddonsModal(addon) { 431 this.currentAddon = addon; 432 console.log(addon); 433 434 // Toggle visibility of slide-up image page card 435 this.toggleMobileItemCard(); 436 $('#standardAddonsModal').modal('show'); 437 }, 438 439 // Determines whether given checkbox should be disabled based on its 440 // Gpid (only one option should be selectable) 441 isDisabled(gpid) { 442 if (this.selectedOption.Gpid > 0) { 443 return this.selectedOption.Gpid != gpid ? true : false; 444 } else { 445 return false; 446 } 447 }, 448 449 // Calls function to select or deselect add-on option based on whether checkbox is checked 450 checkCheckboxValue(event, addon, addonGroup) { 451 // Call function for when the checkbox is checked 452 if (event.target.checked) { 453 this.handleSelectAddon(addon); 454 } else { 455 // Call function for when the checkbox is unchecked 456 this.handleNoneOptionSelected(addonGroup); 457 } 458 }, 459 460 // Handles deselection of add-on option 461 handleNoneOptionSelected(addonGroup) { 462 this.showSelectModal = false; // Hide selection modal 463 var index = this.selectedAddonItems.findIndex(item => item.groupId === addonGroup.GroupId); // Find index of add-on in selected items list 464 465 // If add-on is currently selected, remove it from selected items 466 if (index !== -1) { 467 var removedAddon = this.selectedAddonItems[index]; 468 this.selectedAddonItems.splice(index, 1); // Remove add-on from selected items 469 470 // If item is in cart, remove add-on from cart too 471 if (this.itemIsInCart && removedAddon) { 472 this.removeAddonFromCart(removedAddon.Gpid); 473 } 474 475 // Reset details of selectedOption to default values 476 this.selectedOption.Gpid = 0; 477 this.selectedOption.SDesc = ''; 478 this.selectedOption.LDesc = ''; 479 this.selectedOption.LDescHtml = ''; 480 this.selectedOption.ImageUrl = ''; 481 this.selectedOption.Url = ''; 482 this.selectedOption.WebpImageUrl = ''; 483 this.selectedOption.Price = 0; 484 this.selectedOption.PriceBeforeDiscountOrSale = 0; 485 this.selectedOption.PricingHtml = ''; 486 this.selectedOption.Qty = 0; 487 this.selectedOption.IsStandardPopupEnabled = false; 488 this.selectedOption.Width = ''; 489 this.selectedOption.Depth = ''; 490 this.selectedOption.Height = ''; 491 492 // Update URL parameters 493 this.$nextTick(() => { 494 this.updateUrl(); 495 }); 496 } 497 }, 498 499 // Handles selection of add-on option 500 handleSelectAddon(addon, skipUpdateUrl) { 501 // Reset quantity selector and update parent quantity 502 const value = (window.topSectionModel.cartQuantity > 0 && !window.topSectionModel.cartQuantityRequired) ? 503 window.topSectionModel.cartQuantity : 504 1 505 506 $("#quantitySelect").val(value); 507 this.parentQuantity = value; 508 this.showSelectModal = false; // Hide selection modal 509 510 // Remove existing add-on from same group before adding new one 511 var removedAddon = null;
512 this.selectedAddonItems.forEach((currentAddon) => { 513 if (currentAddon.groupId === addon.groupId) { 514 removedAddon = currentAddon; 515 // Find and remove existing add-on from selected items 516 this.selectedAddonItems 517 .splice(this.selectedAddonItems 518 .findIndex((selectedAddon) => selectedAddon.id === currentAddon.id), 1); 519 } 520 }); 521 522 // Add selected add-on to list of selected add-on items and set as new primary add-on 523 this.selectedAddonItems.push(addon); 524 this.primaryAddon = addon; 525 526 // Update details of selectedOption with information of selected add-on 527 this.selectedOption.Gpid = addon.Gpid; 528 this.selectedOption.SDesc = addon.SDesc; 529 this.selectedOption.LDesc = addon.LDesc; 530 this.selectedOption.LDescHtml = addon.LDescHtml; 531 this.selectedOption.ImageUrl = addon.ImageUrl; 532 this.selectedOption.Url = addon.Url; 533 this.selectedOption.WebpImageUrl = addon.WebpImageUrl; 534 this.selectedOption.Price = this.priceBasedOnQty(addon); 535 this.selectedOption.PriceBeforeDiscountOrSale = addon.PriceBeforeDiscountOrSale; 536 this.selectedOption.PricingHtml = addon.PricingHtml; 537 this.selectedOption.Qty = addon.Qty; 538 this.selectedOption.IsStandardPopupEnabled = addon.IsStandardPopupEnabled; 539 this.selectedOption.Width = addon.Width; 540 this.selectedOption.Depth = addon.Depth; 541 this.selectedOption.Height = addon.Height 542 543 if (!skipUpdateUrl) { 544 // Update URL parameters 545 this.$nextTick(() => { 546 this.updateUrl(); 547 }); 548 549 // If item is already in cart, auto-add add-on to cart 550 if (this.itemIsInCart) { 551 // Remove previously selected add-on from same group if it was in cart 552 if (removedAddon) { 553 this.removeAddonFromCart(removedAddon.Gpid); 554 } 555 this.addAddonToCart(addon); 556 } 557 } 558 }, 559 560 // Handles click event for dropdown option 561 handleDropdownClick() { 562 // If add-on is currently selected, call handleNoneOptionSelected to remove add-on 563 if (this.selectedOption.Gpid != 0) { 564 this.handleNoneOptionSelected(_addonGroup); 565 } 566 }, 567 568 // Resets selectedOption when add-on is removed from cart 569 resetSelectedOption(gpid) { 570 if (this.selectedOption.Gpid == gpid) { 571 this.selectedOption.Gpid = 0; 572 this.selectedOption.SDesc = ''; 573 this.selectedOption.LDesc = ''; 574 this.selectedOption.LDescHtml = ''; 575 this.selectedOption.ImageUrl = ''; 576 this.selectedOption.Url = ''; 577 this.selectedOption.WebpImageUrl = ''; 578 this.selectedOption.Price = 0; 579 this.selectedOption.PriceBeforeDiscountOrSale = 0; 580 this.selectedOption.PricingHtml = ''; 581 this.selectedOption.Qty = 0; 582 this.selectedOption.IsStandardPopupEnabled = false; 583 this.selectedOption.Width = ''; 584 this.selectedOption.Depth = ''; 585 this.selectedOption.Height = ''; 586 } 587 }, 588 589 // Adds single add-on to cart when parent item is already in cart 590 addAddonToCart(addon) { 591 var qty = addon.Qty * this.parentQuantity; 592 593 fetch(Utils.baseUrl + "shoppingcart/ShoppingCart/AddToShoppingCart", { 594 method: "POST", 595 headers: { "Content-Type": "application/json; charset=utf-8" }, 596 body: JSON.stringify({ gpid: addon.Gpid, quantity: qty, parentGpid: this.gpid }) 597 }) 598 .then(response => { 599 if (!response.ok) throw new Error(`HTTP ${response.status}`); 600 601 // Update cart product count 602 const cartCountEl = document.querySelector('.js-cart-count'); 603 if (cartCountEl) { 604 const currentCount = parseInt(cartCountEl.textContent.replace(/\D/g, ""), 10) || 0; 605 cartCountEl.textContent = currentCount + 1; 606 } 607 608 document.dispatchEvent(new CustomEvent('update-cart', { detail: { added: 1 }, bubbles: true })); 609 }) 610 .catch(error => { 611 console.log(`Error adding addon GPID ${addon.Gpid} to cart: ${error}`); 612 }); 613 }, 614 615 // Removes single add-on from cart by GPID 616 removeAddonFromCart(gpid) { 617 fetch(Utils.baseUrl + "shoppingcart/ShoppingCart/RemoveCartItemByGpid", { 618 method: "POST", 619 headers: { "Content-Type": "application/json; charset=utf-8" }, 620 body: JSON.stringify({ gpid: gpid }) 621 }) 622 .then(response => { 623 if (!response.ok) throw new Error(`HTTP ${response.status}`); 624 625 // Update cart product count 626 const cartCountEl = document.querySelector('.js-cart-count'); 627 if (cartCountEl) { 628 const currentCount = parseInt(cartCountEl.textContent.replace(/\D/g, ""), 10) || 0; 629 cartCountEl.textContent = currentCount - 1; 630 } 631 632 document.dispatchEvent(new CustomEvent('update-cart', { detail: { added: -1 }, bubbles: true })); 633 }) 634 .catch(error => { 635 console.log(`Error removing addon GPID ${gpid} from cart: ${error}`); 636 }); 637 }, 638 639 // Convert plural noun to its singular form by checking for common pluralization rules 640 convertToSingular(groupName) { 641 let word = groupName.trim(); 642 643 // Primary Rule 644 if (word.endsWith('s') && !word.endsWith('ss')) { 645 // Rule 1: Remove "s" at end of plural word 646 word = word.slice(0, -1); 647 } 648 649 // Additional Rules 650 if (word.endsWith('ies')) { 651 // Rule 2: Replace "ies" with "y" 652 word = word.slice(0, -3) + 'y'; 653 } else if (word.endsWith('es') && word.length > 3) { 654 // Rule 3: Replace "es" with "is" 655 word = word.slice(0, -2); 656 } else if (word.endsWith('ves')) { 657 // Rule 4: Replace "ves"" with "f" or "fe" 658 word = word.slice(0, -3) + 'f'; 659 } 660 661 return word; 662 } 663 })); 664 665}); 666 667$(document).ready(function () { 668 if (window.topSectionModel.cartQuantity != 0 && 669 !window.topSectionModel.cartQuantityRequired && 670 $('#quantitySelect').children().length == 0) { 671 const quantityDropdown = document.getElementById("quantitySelect"); 672 673 for (let i = 1; i < 6; i++) { 674 let option = document.createElement("option"); 675 let optionText = document.createTextNode(window.topSectionModel.cartQuantity * i); 676 677 option.setAttribute('value', window.topSectionModel.cartQuantity * i); 678 option.appendChild(optionText); 679 quantityDropdown.appendChild(option); 680 } 681 } 682});
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.