1if (!customElements.get('variant-selects')) { 2 3 /** 4 * @class 5 * @function VariantSelects 6 */ 7 class VariantSelects extends HTMLElement { 8 constructor() { 9 super(); 10 this.isDisabledFeature = this.dataset.isDisabled; 11 this.addEventListener('change', this.onVariantChange); 12 } 13 14 connectedCallback() { 15 this.updateOptions(); 16 this.updateMasterId(); 17 this.setDisabled(); 18 } 19 20 onVariantChange() { 21 this.updateOptions(); 22 this.updateMasterId(); 23 this.toggleAddButton(true, '', false); 24 this.updatePickupAvailability(); 25 this.removeErrorMessage(); 26 this.updateVariantText(); 27 this.setDisabled(); 28 if (!this.currentVariant) { 29 this.toggleAddButton(true, '', true); 30 this.setUnavailable(); 31 } else { 32 this.updateMedia(); 33 this.updateURL(); 34 this.updateVariantInput(); 35 36 this.renderProductInfo(); 37 //this.updateShareUrl(); 38 } 39 dispatchCustomEvent('product:variant-change', { 40 variant: this.currentVariant, 41 sectionId: this.dataset.section 42 }); 43 } 44 45 updateOptions() { 46 this.fieldsets = Array.from(this.querySelectorAll('fieldset')); 47 this.options = []; 48 this.fieldsets.forEach((fieldset, i) => { 49 if (fieldset.querySelector('select')) { 50 this.options.push(fieldset.querySelector('select').value); 51 } else if (fieldset.querySelectorAll('input').length) { 52 this.options.push(fieldset.querySelector('input:checked').value); 53 } 54 }); 55 } 56 updateVariantText() { 57 const fieldsets = Array.from(this.querySelectorAll('fieldset')); 58 fieldsets.forEach((item, i) => { 59 let label = item.querySelector('.form__label__value'); 60 if (label) { 61 label.innerHTML = this.options[i]; 62 } 63 }); 64 } 65 updateMasterId() { 66 this.currentVariant = this.getVariantData().find((variant) => { 67 return !variant.options.map((option, index) => { 68 return this.options[index] === option; 69 }).includes(false); 70 }); 71 } 72 73 updateMedia() { 74 if (!this.currentVariant) return; 75 if (!this.currentVariant.featured_media) return; 76 77 let productSlider = this.closest('.thb-product-detail').querySelector('.product-images'), 78 thumbnails = this.closest('.thb-product-detail').querySelector('#Product-Thumbnails'); 79 80 if (!productSlider) { 81 productSlider = document.getElementById('Product-Quick-Slider'); 82 } 83 84 this.setActiveMedia(`#Slide-${this.dataset.section}-${this.currentVariant.featured_media.id}`, `#Thumb-${this.dataset.section}-${this.currentVariant.featured_media.id}`, productSlider, thumbnails); 85 } 86 setActiveMedia(mediaId, thumbId, productSlider, thumbnails) { 87 var hide_variants = productSlider.dataset.hideVariants == 'true', 88 flkty = Flickity.data(productSlider), 89 images = productSlider.querySelectorAll('.product-images__slide'); 90 91 if (flkty && hide_variants) { 92 [].forEach.call(images, function(el) { 93 el.classList.remove('is-selected'); 94 }); 95 productSlider.querySelector(mediaId).classList.add('is-selected'); 96 productSlider.reInit(); 97 productSlider.selectCell(mediaId); 98 99 if (thumbnails) { 100 let activeThumb = thumbnails.querySelector(thumbId); 101 102 thumbnails.prepend(activeThumb); 103 } 104 } else if (flkty) { 105 productSlider.selectCell(mediaId); 106 } 107 108 } 109 110 updateURL() { 111 if (!this.currentVariant || this.dataset.updateUrl === 'false') return; 112 window.history.replaceState({}, '', `${this.dataset.url}?variant=${this.currentVariant.id}`); 113 } 114 115 updateShareUrl() { 116 const shareButton = document.getElementById(`Share-${this.dataset.section}`); 117 if (!shareButton) return; 118 shareButton.updateUrl(`${window.shopUrl}${this.dataset.url}?variant=${this.currentVariant.id}`); 119 } 120 121 updateVariantInput() { 122 const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment`);
123 productForms.forEach((productForm) => { 124 const input = productForm.querySelector('input[name="id"]'); 125 input.value = this.currentVariant.id; 126 input.dispatchEvent(new Event('change', { 127 bubbles: true 128 })); 129 }); 130 } 131 132 updatePickupAvailability() { 133 const pickUpAvailability = document.querySelector('.pickup-availability-wrapper'); 134 135 if (!pickUpAvailability) return; 136 137 if (this.currentVariant && this.currentVariant.available) { 138 pickUpAvailability.fetchAvailability(this.currentVariant.id); 139 } else { 140 pickUpAvailability.removeAttribute('available'); 141 pickUpAvailability.innerHTML = ''; 142 } 143 } 144 145 removeErrorMessage() { 146 const section = this.closest('section'); 147 if (!section) return; 148 149 const productForm = section.querySelector('product-form'); 150 if (productForm) productForm.handleErrorMessage(); 151 } 152 153 getSectionsToRender() { 154 return [`price-${this.dataset.section}`, `inventory-${this.dataset.section}`, `sku-${this.dataset.section}`]; 155 } 156 157 renderProductInfo() { 158 let sections = this.getSectionsToRender(); 159 fetch(`${this.dataset.url}?variant=${this.currentVariant.id}§ion_id=${this.dataset.section}`) 160 .then((response) => response.text()) 161 .then((responseText) => { 162 const html = new DOMParser().parseFromString(responseText, 'text/html'); 163 sections.forEach((id) => { 164 const destination = document.getElementById(id); 165 const source = html.getElementById(id); 166 167 if (source && destination) destination.innerHTML = source.innerHTML; 168 169 const price = document.getElementById(id); 170 171 if (price) price.classList.remove('visibility-hidden'); 172 }); 173 this.toggleAddButton(!this.currentVariant.available, window.theme.variantStrings.soldOut); 174 }); 175 } 176 177 toggleAddButton(disable = true, text = false, modifyClass = true) { 178 const productForm = document.getElementById(`product-form-${this.dataset.section}`); 179 if (!productForm) return; 180 const addButton = productForm.querySelector('[name="add"]'); 181 const addButtonText = productForm.querySelector('[name="add"] > span'); 182 183 if (!addButton) return; 184 185 if (disable) { 186 addButton.setAttribute('disabled', 'disabled'); 187 if (text) addButtonText.textContent = text; 188 } else { 189 addButton.removeAttribute('disabled'); 190 addButton.classList.remove('loading'); 191 addButtonText.textContent = window.theme.variantStrings.addToCart; 192 } 193 194 if (!modifyClass) return; 195 } 196 197 setUnavailable() { 198 const button = document.getElementById(`product-form-${this.dataset.section}`); 199 const addButton = button.querySelector('[name="add"]'); 200 const addButtonText = button.querySelector('[name="add"] > span'); 201 const price = document.getElementById(`price-${this.dataset.section}`); 202 if (!addButton) return; 203 addButtonText.textContent = window.theme.variantStrings.unavailable; 204 addButton.classList.add('sold-out'); 205 if (price) price.classList.add('visibility-hidden'); 206 } 207 208 setDisabled() { 209 if (this.isDisabledFeature != 'true') { 210 return; 211 } 212 const variant_data = this.getVariantData(); 213 214 if (variant_data) { 215 const selected_options = this.currentVariant.options.map((value, index) => { 216 return { 217 value, 218 index: `option${index+1}` 219 }; 220 }); 221 const available_options = this.createAvailableOptionsTree(variant_data, selected_options); 222 223 this.fieldsets.forEach((fieldset, i) => { 224 const fieldset_options = Object.values(available_options)[i]; 225 226 if (fieldset_options) { 227 if (fieldset.querySelector('select')) { 228 fieldset_options.forEach((option, option_i) => { 229 if (option.isUnavailable) { 230 fieldset.querySelector('option[value=' + JSON.stringify(option.value) + ']').disabled = true; 231 } else { 232 fieldset.querySelector('option[value=' + JSON.stringify(option.value) + ']').disabled = false;
233 } 234 }); 235 } else if (fieldset.querySelectorAll('input').length) { 236 fieldset.querySelectorAll('input').forEach((input, input_i) => { 237 input.classList.toggle('is-disabled', fieldset_options[input_i].isUnavailable); 238 }); 239 } 240 } 241 }); 242 243 } 244 } 245 createAvailableOptionsTree(variant_data, selected_options) { 246 // Reduce variant array into option availability tree 247 return variant_data.reduce((options, variant) => { 248 249 // Check each option group (e.g. option1, option2, option3) of the variant 250 Object.keys(options).forEach(index => { 251 252 if (variant[index] === null) return; 253 254 let entry = options[index].find(option => option.value === variant[index]); 255 256 if (typeof entry === 'undefined') { 257 // If option has yet to be added to the options tree, add it 258 entry = { 259 value: variant[index], 260 isUnavailable: true 261 }; 262 options[index].push(entry); 263 } 264 265 // Check how many selected option values match a variant 266 const countVariantOptionsThatMatchCurrent = selected_options.reduce((count, { 267 value, 268 index 269 }) => { 270 return variant[index] === value ? count + 1 : count; 271 }, 0); 272 273 // Only enable an option if an available variant matches all but one current selected value 274 if (countVariantOptionsThatMatchCurrent >= selected_options.length - 1) { 275 entry.isUnavailable = entry.isUnavailable && variant.available ? false : entry.isUnavailable; 276 } 277 278 // Make sure if a variant is unavailable, disable currently selected option 279 if ((!this.currentVariant || !this.currentVariant.available) && selected_options.find((option) => option.value === entry.value && index === option.index)) { 280 entry.isUnavailable = true; 281 } 282 283 // First option is always enabled 284 if (index === 'option1') { 285 entry.isUnavailable = entry.isUnavailable && variant.available ? false : entry.isUnavailable; 286 } 287 }); 288 289 return options; 290 }, { 291 option1: [], 292 option2: [], 293 option3: [] 294 }); 295 } 296 297 getVariantData() { 298 this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent); 299 return this.variantData; 300 } 301 } 302 customElements.define('variant-selects', VariantSelects); 303 304 /** 305 * @class 306 * @function VariantRadios 307 */ 308 class VariantRadios extends VariantSelects { 309 constructor() { 310 super(); 311 } 312 313 updateOptions() { 314 const fieldsets = Array.from(this.querySelectorAll('fieldset')); 315 this.options = fieldsets.map((fieldset) => { 316 return Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value; 317 }); 318 } 319 updateVariantText() { 320 321 322 } 323 } 324 325 customElements.define('variant-radios', VariantRadios); 326} 327if (!customElements.get('product-slider')) { 328 /** 329 * @class 330 * @function ProductSlider 331 */ 332 class ProductSlider extends HTMLElement { 333 constructor() { 334 super(); 335 336 this.addEventListener('change', this.setupProductGallery); 337 } 338 connectedCallback() { 339 const product_container = this.closest('.thb-product-detail'); 340 const thumbnail_container = product_container.querySelector('.product-thumbnail-container'); 341 342 this.hide_variants = this.dataset.hideVariants == 'true'; 343 this.selectedIndex = 0; 344 this.thumbnails = thumbnail_container.querySelectorAll('.product-thumbnail'); 345 this.prev_button = this.querySelector('.flickity-prev'); 346 this.next_button = this.querySelector('.flickity-next'); 347 this.options = { 348 wrapAround: true, 349 pageDots: true, 350 contain: true, 351 adaptiveHeight: true, 352 initialIndex: '.is-initial-selected', 353 prevNextButtons: false, 354 fade: false, 355 cellSelector: '.product-images__slide' 356 }; 357 358 if (this.hide_variants) { 359 this.options.cellSelector = '.product-images__slide-item, .product-images__slide-item--variant.is-selected'; 360 } 361 362 // Start Slider 363 this.init(); 364 } 365 init() { 366 this.flkty = new Flickity(this, this.options); 367 368 this.selectedIndex = this.flkty.selectedIndex; 369 370 // Setup Events 371 this.setupEvents(); 372 373 // Start Gallery 374 this.setupProductGallery(); 375 } 376 reInit() { 377 this.flkty.destroy(); 378 379 this.flkty = new Flickity(this, this.options); 380 381 // Setup Events 382 this.setupEvents(); 383 384 this.selectedIndex = this.flkty.selectedIndex; 385 } 386 setupEvents() { 387 const _this = this; 388 if (this.prev_button) { 389 this.prev_button.addEventListener('click', (event) => { 390 this.flkty.previous(); 391 }); 392 this.prev_button.addEventListener('keyup', (event) => { 393 this.flkty.previous(); 394 event.preventDefault(); 395 }); 396 } 397 if (this.next_button) { 398 this.next_button.addEventListener('click', (event) => { 399 this.flkty.next(); 400 }); 401 this.next_button.addEventListener('keyup', (event) => { 402 this.flkty.next(); 403 event.preventDefault(); 404 }); 405 } 406 this.flkty.on('settle', function(index) { 407 this.selectedIndex = index; 408 }); 409 this.flkty.on('change', (index) => { 410 let previous_slide = this.flkty.cells[this.selectedIndex].element, 411 previous_media = previous_slide.querySelector('.product-single__media'), 412 slide = this.flkty.cells[index].element, 413 media = slide.querySelector('.product-single__media'), 414 active_thumb = this.thumbnails[index]; 415
416 this.thumbnails.forEach((item, i) => { 417 item.classList.remove('is-initial-selected'); 418 }); 419 active_thumb.classList.add('is-initial-selected'); 420 requestAnimationFrame(() => { 421 if (active_thumb.offsetParent === null) { 422 return; 423 } 424 const windowHalfHeight = active_thumb.offsetParent.clientHeight / 2, 425 windowHalfWidth = active_thumb.offsetParent.clientWidth / 2; 426 active_thumb.parentElement.scrollTo({ 427 left: 0, 428 top: active_thumb.offsetTop - windowHalfHeight + active_thumb.clientHeight / 2, 429 behavior: 'smooth' 430 }); 431 }); 432 // Stop previous video 433 if (previous_media.classList.contains('product-single__media-external-video')) { 434 if (previous_media.dataset.provider === 'youtube') { 435 previous_media.querySelector('iframe').contentWindow.postMessage(JSON.stringify({ 436 event: "command", 437 func: "pauseVideo", 438 args: "" 439 }), "*"); 440 } else if (previous_media.dataset.provider === 'vimeo') { 441 previous_media.querySelector('iframe').contentWindow.postMessage(JSON.stringify({ 442 method: "pause" 443 }), "*"); 444 } 445 } else if (previous_media.classList.contains('product-single__media-native-video')) { 446 previous_media.querySelector("video").pause(); 447 } 448 449 // Draggable. 450 if (media.classList.contains('product-single__media-model')) { 451 this.setDraggable(false); 452 } else { 453 this.setDraggable(true); 454 } 455 }); 456 457 this.thumbnails.forEach((thumbnail, index) => { 458 thumbnail.addEventListener('click', () => { 459 this.thumbnailClick(thumbnail, index); 460 }); 461 }); 462 } 463 thumbnailClick(thumbnail, index) { 464 [].forEach.call(this.thumbnails, function(el) { 465 el.classList.remove('is-initial-selected'); 466 }); 467 thumbnail.classList.add('is-initial-selected'); 468 this.flkty.select(index); 469 } 470 setDraggable(draggable) { 471 this.flkty.options.draggable = draggable; 472 this.flkty.updateDraggable(); 473 } 474 selectCell(mediaId) { 475 this.flkty.selectCell(mediaId); 476 } 477 setupProductGallery() { 478 if (!this.querySelectorAll('.product-single__media-zoom').length) { 479 return; 480 } 481 this.setEventListeners(); 482 } 483 buildItems() { 484 this.activeImages = Array.from(this.querySelectorAll('.product-images__slide-item .product-single__media-image, .product-images__slide-item--variant.is-selected .product-single__media-image')); 485 return this.activeImages.map((item) => { 486 let index = [].indexOf.call(item.parentNode.parentNode.children, item.parentNode); 487 488 let activelink = item.querySelector('.product-single__media-zoom'); 489 490 activelink.dataset.index = index; 491 return { 492 src: activelink.getAttribute('href'), 493 msrc: activelink.dataset.msrc, 494 w: activelink.dataset.w, 495 h: activelink.dataset.h 496 }; 497 }); 498 } 499 setEventListeners() { 500 this.links = this.querySelectorAll('.product-single__media-zoom'); 501 this.pswpElement = document.querySelectorAll('.pswp')[0]; 502 this.pswpOptions = { 503 maxSpreadZoom: 2, 504 loop: false, 505 allowPanToNext: false, 506 closeOnScroll: false, 507 showHideOpacity: false, 508 arrowKeys: true, 509 history: false, 510 captionEl: false, 511 fullscreenEl: false, 512 zoomEl: false, 513 shareEl: false, 514 counterEl: false, 515 arrowEl: true, 516 preloaderEl: true, 517 getThumbBoundsFn: () => { 518 const thumbnail = this.querySelector('.product-images__slide.is-selected'), 519 pageYScroll = window.pageYOffset || document.documentElement.scrollTop, 520 rect = thumbnail.getBoundingClientRect(); 521 return { 522 x: rect.left, 523 y: rect.top + pageYScroll, 524 w: rect.width 525 }; 526 } 527 }; 528 529 530 this.links.forEach((link => { 531 link.addEventListener('click', (e) => this.zoomClick(e, link)); 532 })); 533 } 534 zoomClick(e, link) { 535 this.items = this.buildItems(); 536 this.pswpOptions.index = parseInt(link.dataset.index, 10); 537 if (typeof PhotoSwipe !== 'undefined') { 538 let pswp = new PhotoSwipe(this.pswpElement, PhotoSwipeUI_Default, this.items, this.pswpOptions); 539 pswp.listen('firstUpdate', function() { 540 pswp.listen('parseVerticalMargin', function(item) { 541 item.vGap = { 542 top: 50, 543 bottom: 50 544 }; 545 }); 546 }); 547 pswp.init(); 548 } 549 e.preventDefault(); 550 } 551 } 552 customElements.define('product-slider', ProductSlider); 553} 554 555/** 556 * @class 557 * @function ProductForm 558 */ 559if (!customElements.get('product-form')) { 560 customElements.define('product-form', class ProductForm extends HTMLElement { 561 constructor() { 562 super(); 563 564 this.form = this.querySelector('form'); 565 this.form.querySelector('[name=id]').disabled = false;
566 this.form.addEventListener('submit', this.onSubmitHandler.bind(this)); 567 this.cartNotification = document.querySelector('cart-notification'); 568 this.body = document.body; 569 570 this.hideErrors = this.dataset.hideErrors === 'true'; 571 } 572 573 onSubmitHandler(evt) { 574 evt.preventDefault(); 575 const submitButton = this.querySelector('[type="submit"]'); 576 if (submitButton.classList.contains('loading')) return; 577 578 this.handleErrorMessage(); 579 580 submitButton.setAttribute('aria-disabled', true); 581 submitButton.classList.add('loading'); 582 583 const config = { 584 method: 'POST', 585 headers: { 586 'X-Requested-With': 'XMLHttpRequest', 587 'Accept': 'application/javascript' 588 } 589 }; 590 const formData = new FormData(this.form); 591 formData.append('sections', this.getSectionsToRender().map((section) => section.section)); 592 formData.append('sections_url', window.location.pathname); 593 config.body = formData; 594 595 fetch(`${theme.routes.cart_add_url}`, config) 596 .then((response) => response.json()) 597 .then((response) => { 598 if (response.status) { 599 dispatchCustomEvent('product:variant-error', { 600 source: 'product-form', 601 productVariantId: formData.get('id'), 602 errors: response.description, 603 message: response.message 604 }); 605 this.handleErrorMessage(response.description); 606 return; 607 } 608 609 this.renderContents(response); 610 }) 611 .catch((e) => { 612 console.error(e); 613 }) 614 .finally(() => { 615 submitButton.classList.remove('loading'); 616 submitButton.removeAttribute('aria-disabled'); 617 }); 618 } 619 620 getSectionsToRender() { 621 return [{ 622 id: 'Cart', 623 section: 'main-cart', 624 selector: '.thb-cart-form' 625 }, 626 { 627 id: 'Cart-Drawer', 628 section: 'cart-drawer', 629 selector: '.cart-drawer' 630 }, 631 { 632 id: 'cart-drawer-toggle', 633 section: 'cart-bubble', 634 selector: '.thb-item-count' 635 }]; 636 } 637 renderContents(parsedState) {
638 this.getSectionsToRender().forEach((section => { 639 if (!document.getElementById(section.id)) { 640 return; 641 } 642 const elementToReplace = document.getElementById(section.id).querySelector(section.selector) || document.getElementById(section.id); 643 elementToReplace.innerHTML = this.getSectionInnerHTML(parsedState.sections[section.section], section.selector); 644 if (typeof CartDrawer !== 'undefined') { 645 new CartDrawer(); 646 } 647 if (typeof Cart !== 'undefined') { 648 new Cart().renderContents(parsedState); 649 } 650 })); 651 652 653 if (document.getElementById('Cart-Drawer')) { 654 this.body.classList.add('open-cc'); 655 document.getElementById('Cart-Drawer').classList.add('active'); 656 657 dispatchCustomEvent('cart-drawer:open'); 658 } 659 660 let product_drawer = document.getElementById('Product-Drawer'); 661 if (product_drawer && product_drawer.contains(this)) { 662 product_drawer.classList.remove('active'); 663 664 if (!document.getElementById('Cart-Drawer')) { 665 this.body.classList.remove('open-cc'); 666 } 667 } 668 } 669 getSectionInnerHTML(html, selector = '.shopify-section') { 670 return new DOMParser() 671 .parseFromString(html, 'text/html') 672 .querySelector(selector).innerHTML; 673 } 674 handleErrorMessage(errorMessage = false) { 675 if (this.hideErrors) return; 676 this.errorMessageWrapper = this.errorMessageWrapper || this.querySelector('.product-form__error-message-wrapper'); 677 this.errorMessage = this.errorMessage || this.errorMessageWrapper.querySelector('.product-form__error-message'); 678 679 this.errorMessageWrapper.toggleAttribute('hidden', !errorMessage); 680 681 if (errorMessage) { 682 this.errorMessage.textContent = errorMessage; 683 } 684 } 685 }); 686}
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.