skip to Main Content

I’m trying to add css to product options to provide the user with a visual indication of which options are not available (sold out). This works when a product only has one set of option values, however my product has two ("date", and "time"). I have this working most of the way, however the available "Time" options are not updated when I switch between dates. Any help here would be hugely appreciated.

Inside my product-form.liquid file I have the following code to add a class to sold out options. Perhaps I need some js?

In case it’s helpful, I’m using the Prestige theme and the page is here: https://neetfield.co.uk/pizza

                {%- if option.name == "Time" and product.selected_or_first_available_variant.option1 == product.variants[forloop.index0].option1 -%}
                  {%- if product.variants[forloop.index0].available -%}
                    {%- assign opt_sold = false -%}
                  {%- else -%}
                    {%- assign opt_sold = true -%}
                  {%- endif -%}
                {%- else -%}
                  {%- assign opt_sold = false -%}
                {%- endif -%}

And then this is used to add a class to sold out options {% if opt_sold %}opt-sold-out{% endif %}

This is a screenshot of where I got to (now disabled on the live site until I haved it working when switching between dates):

enter image description here

2

Answers


  1. First page render (Liquid)

    For the initial page load you can use something like this:

    {%- liquid
      assign selected_option_1 = product.selected_or_first_available_variant.option1
      assign relevant_variants = product.variants | where: 'option1', selected_option_1
      assign available_timeslots = ''
    
      for variant in relevant_variants
        if variant.available
          assign available_timeslots = available_timeslots | append: ',' | append: variant.option2
        endif
      endfor
    
      assign available_timeslots = available_timeslots | remove_first: ',' | split: ','
    -%}
    

    This snippet iterates over all product variants where option1 equals the option1 value of the currently selected variant (e.g. "Saturday 4th June"). For each variant we’ll check if its available and push it to the available_timeslots array.

    Make sure to place this snippet before iterating over all product options.

    Finally, use this array to check if an option value is available:

    {%- unless available_timeslots contains value -%}
      Disable option input
    {%- endunless -%}
    

    value basically is a value of option.values. I’m sure you’ll find this somewhere in your product-form 🙂

    After user interaction (JavaScript)

    Since Liquid is a server-side rendered template language, you can only achieve this by updating the option input elements on variant change via JavaScript on the client-side. The Shopify Prestige theme has a useful CustomEvent for this scenario: variant:changed

    An example of the implementation would look like this:

    /**
     * Gets the option index that is different between `variant` in `previousVariant`.
     *
     * @param {object} variant
     * @param {object} previousVariant
     * @returns {1 | 2 | 3}
     */
    const getChangedOptionIndex = (variant, previousVariant) => {
      let changedOptionIndex;
    
      for (let i = 0; i < variant.options.length; i++) {
        if (variant.options[i] !== previousVariant.options[i]) {
          changedOptionIndex = i;
          break;
        }
      }
    
      return changedOptionIndex;
    };
    
    /**
     * Find a variant via its options.
     *
     * @param {object[]} variants
     * @param {{ option1: string, option2: string }} options
     * @returns {object | undefined}
     */
    const findVariantByOptions = (variants, { option1, option2 }) => {
      return variants.find((variant) => variant.option1 === option1 && variant.option2 === option2);
    };
    
    const { product } = JSON.parse(document.querySelector('script[data-product-json]').innerHTML);
    
    document.addEventListener('variant:changed', (event) => {
      // We might need a sanity check here. For example, if the current variant is
      // "Saturday 4th June / 18:30" and you switch to "Saturday 11th June" the time option
      // "18:30" might not exist which would result in `event.detail` being `undefined`.
      // if (!event.detail.variant) {
      //
      // }
    
      const { variant, previousVariant } = event.detail;
      const changedOptionIndex = getChangedOptionIndex(variant, previousVariant);
    
      const optionInputEls = document.querySelectorAll('input[name^="option-"]');
    
      // Note: This approach won't work anymore if a product has 3 options
      optionInputEls.forEach((optionInputEl) => {
        const optionIndex = Number(optionInputEl.name.split('-')[1]);
    
        // Only check option inputs that are not of the same group as the changed option
        if (optionIndex !== changedOptionIndex) {
          const foundVariant = findVariantByOptions(product.variants, {
            [`option${changedOptionIndex + 1}`]: variant.options[changedOptionIndex],
            [`option${optionIndex + 1}`]: optionInputEl.value,
          });
    
          if (!foundVariant || !foundVariant.available) {
            optionInputEl.nextElementSibling.classList.add('opt-sold-out');
          } else {
            optionInputEl.nextElementSibling.classList.remove('opt-sold-out');
          }
        }
      });
    });
    

    Keep in mind that you are not really disabling the input. It’s just visually indicating that it’s disabled. If that’s not a problem for you, just ignore this comment.

    Note that this does not handle the following scenario:

    • User chooses date "A"
    • User chooses time "X"
    • User chooses date "B" (where time "X" is not available)

    I’m guessing that the decision which date to chose always comes first so this should not be an important scenario to worry about.

    Login or Signup to reply.
  2. Approach for products with a single option

    This case is pretty easy and does not require any JavaScript since each option value maps to exactly one variant. The way to go here would be to get that variant and check if it’s available:

    {%- for option in product.options_with_values -%}
      {%- for value in option.values -%}
        {%- assign variant = product.variants | where: 'option1', value -%}
    
        {%- if variant.available == false -%}
          Disable option input
        {%- endif -%}
      {%- endfor -%}
    {%- endfor -%}
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search