Shopify 2.0 Dawn - How to hide unavailable variants?

Viewed 2476

PROBLEM

Unavailable variant combinations are different from sold-out because customers don't understand it's the selects which make certain combinations 'not possible'.

Shopify's way of handling this is to display 'Unavailable' in the buy button. But customers think this means sold-out when in reality, they've chosen the wrong combination of variants...

enter image description here

The previous JS workarounds to remove unavailable or 'not possible' variants don't work in Shopify 2.0's new default/flagship theme, Dawn because the JS is different.

As far as I can tell, Dawn's variant JS was recently moved from /asstes/variants.js to line 497 in /assets/global.js.


SKILL

My CSS is decent but my JS is lame, I'm a designer sorry.


QUESTIONS

  1. Based on user interaction with the first variant, how do you hide unavailable variants (not sold-out) in Shopify 2.0 Dawn?

  2. How do you make one variant option set a checkbox instead of a radio button or radio?

  3. What's the best way to add custom text as the first option in selects? e.g. 'Choose a size...' or 'Choose a color...' etc. Is it best to hard-code or use JS for this as well?


RESOURCES/EXAMPLES

  1. Here's a pull request which grabs sold-out from the new Dawn JS but I don't understand how to adapt it for 'Unavailable' sorry (which is a different exception from sold-out): https://github.com/Shopify/dawn/pull/105

  2. Here's an example of how to hide unavailable variants in the older Debut theme which doesn’t seem to work in the newer Dawn JS: https://www.youtube.com/watch?v=vspWDu_POYA

  3. Here's a link to the JS gist referenced in that video: https://gist.github.com/jonathanmoore/c0e0e503aa732bf1c05b7a7be4230c61

  4. And finally, here's the new code from Dawn at line 497 in /assets/global.js

    class VariantSelects extends HTMLElement {
      constructor() {
        super();
        this.addEventListener('change', this.onVariantChange);
      }
    
      onVariantChange() {
        this.updateOptions();
        this.updateMasterId();
        this.toggleAddButton(true, '', false);
        this.updatePickupAvailability();
    
        if (!this.currentVariant) {
          this.toggleAddButton(true, '', true);
          this.setUnavailable();
        } else {
          this.updateMedia();
          this.updateURL();
          this.updateVariantInput();
          this.renderProductInfo();
        }
      }
    
      updateOptions() {
        this.options = Array.from(this.querySelectorAll('select'), (select) => select.value);
      }
    
      updateMasterId() {
        this.currentVariant = this.getVariantData().find((variant) => {
          return !variant.options.map((option, index) => {
            return this.options[index] === option;
          }).includes(false);
        });
      }
    
      updateMedia() {
        if (!this.currentVariant || !this.currentVariant?.featured_media) return;
        const newMedia = document.querySelector(
          `[data-media-id="${this.dataset.section}-${this.currentVariant.featured_media.id}"]`
        );
        if (!newMedia) return;
        const parent = newMedia.parentElement;
        parent.prepend(newMedia);
        window.setTimeout(() => { parent.scroll(0, 0) });
      }
    
      updateURL() {
        if (!this.currentVariant) return;
        window.history.replaceState({ }, '', `${this.dataset.url}?variant=${this.currentVariant.id}`);
      }
    
      updateVariantInput() {
        const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment`);
        productForms.forEach((productForm) => {
          const input = productForm.querySelector('input[name="id"]');
          input.value = this.currentVariant.id;
          input.dispatchEvent(new Event('change', { bubbles: true }));
        });
      }
    
      updatePickupAvailability() {
        const pickUpAvailability = document.querySelector('pickup-availability');
        if (!pickUpAvailability) return;
    
        if (this.currentVariant?.available) {
          pickUpAvailability.fetchAvailability(this.currentVariant.id);
        } else {
          pickUpAvailability.removeAttribute('available');
          pickUpAvailability.innerHTML = '';
        }
      }
    
      renderProductInfo() {
        fetch(`${this.dataset.url}?variant=${this.currentVariant.id}&section_id=${this.dataset.section}`)
          .then((response) => response.text())
          .then((responseText) => {
            const id = `price-${this.dataset.section}`;
            const html = new DOMParser().parseFromString(responseText, 'text/html')
            const destination = document.getElementById(id);
            const source = html.getElementById(id);
    
            if (source && destination) destination.innerHTML = source.innerHTML;
    
            document.getElementById(`price-${this.dataset.section}`)?.classList.remove('visibility-hidden');
            this.toggleAddButton(!this.currentVariant.available, window.variantStrings.soldOut);
          });
      }
    
      toggleAddButton(disable = true, text, modifyClass = true) {
        const addButton = document.getElementById(`product-form-${this.dataset.section}`)?.querySelector('[name="add"]');
    
        if (!addButton) return;
    
        if (disable) {
          addButton.setAttribute('disabled', true);
          if (text) addButton.textContent = text;
        } else {
          addButton.removeAttribute('disabled');
          addButton.textContent = window.variantStrings.addToCart;
        }
    
        if (!modifyClass) return;
      }
    
      setUnavailable() {
        const addButton = document.getElementById(`product-form-${this.dataset.section}`)?.querySelector('[name="add"]');
        if (!addButton) return;
        addButton.textContent = window.variantStrings.unavailable;
        document.getElementById(`price-${this.dataset.section}`)?.classList.add('visibility-hidden');
      }
    
      getVariantData() {
        this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent);
        return this.variantData;
      }
    }
    
    customElements.define('variant-selects', VariantSelects);
    
    class VariantRadios extends VariantSelects {
      constructor() {
        super();
      }
    
      updateOptions() {
        const fieldsets = Array.from(this.querySelectorAll('fieldset'));
        this.options = fieldsets.map((fieldset) => {
          return Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value;
        });
      }
    }
    
    customElements.define('variant-radios', VariantRadios);

Any help or pointers in the right direction would be much appreciated. Cheers

0 Answers
Related