Reputation: 141
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):
Upvotes: 2
Views: 1727
Reputation: 403
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 -%}
Upvotes: 1
Reputation: 403
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 :)
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:
I'm guessing that the decision which date to chose always comes first so this should not be an important scenario to worry about.
Upvotes: 2