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.