<script defer>
<script defer>
Script to link product option dropdowns on a shopify site.
If red t-shirts are only available in size medium, all other size options will be disabled when customer selects red.
Written by David Mynors. Based on a script by carolineschnapp.
Since this script may not be the only JavaScript on the page, the code is encapuslated
within a function on the global Shopify
object, creating it if necessary.
This means we don’t have to worry about naming collisions with variables from other scripts.
var Shopify = Shopify || {};
Shopify.linkOptionDropdowns = function (product) {
First we need to grab all the dropdowns on the page and create some variables. This is done by finding the ‘add to card’ form and grabbing all the select elements within it.
‘Select’ is the proper name for dropdown elements in HTML but I’ve referred to ‘dropdowns’ throughout the script to make it easier to follow.
I’ve written this script for a theme where the relevant dropdowns have an
HTML class of single-option-selector
but this might not be true for
you. You can check using the inspector feature in your browser developer
tools.
const addToCartForm = document.querySelector('form[action="/cart/add"]');
const dropdowns = addToCartForm.querySelectorAll("select.single-option-selector");
Since this script aims to properly reflect the state of your product options in your dropdowns, it’s handy to set up a mapping between options and dropdowns. We’ll populate these variables when we iterate over our dropdowns further down.
This script assumes your product has multiple variants where each variant is a different combination of options. Here’s an example:
product = {
variants: [
{ option1: "red", option2: "medium" },
{ option1: "red", option2: "large" }
}
Once filled, the variables should like this:
optionNumbers = [ "option1", "option2" ]
optionNumberToDropdownMap = {
option1: <dropdown1>,
option2: <dropdown2>
}
const optionNumbers = [];
const optionNumberToDropdownMap = {};
Before iterating over the dropdowns and adding event listeners to change them when the user interacts with them, I’ve declared some handy helper functions.
We don’t want to show options in the dropdowns if that option isn’t in any available variants, so we’ll need a function to remove values from dropdowns.
function removeValuesFromDropdown(dropdown, values) {
for (const value of values) {
const elementToRemove = dropdown.querySelector(
`option[value="${value}"]`
);
elementToRemove.remove();
}
}
If the user selects an option from a dropdown which is only available with one of the options from another dropdown, we’ll want to disable the unavailable options. Disabling means the customer can’t select them but it indicates that they would be selectable in a different option combination.
function disableValuesInDropdown(dropdown, values) {
for (const value of values) {
const elementToDisable = dropdown.querySelector(
`option[value="${value}"]`
);
if (elementToDisable) {
elementToDisable.disabled = true;
}
}
}
It’s also handy to have a function to enable all the values in our dropdowns, for the situation where the customer selects one option, sees that it disables other options, and deselects that option to explore other combinations.
function enableAllValuesInDropdowns(dropdowns) {
for (const dropdown of dropdowns) {
for (const option of dropdown.children) {
option.disabled = false;
}
}
}
Finally, we begin to iterate over our dropdowns…
for (const dropdown of dropdowns) {
First we grab the option number of the current dropdown and use it to populate those empty variables from earlier.
const dropdownOptionNumber = dropdown.dataset.index;
optionNumbers.push(dropdownOptionNumber);
optionNumberToDropdownMap[dropdownOptionNumber] = dropdown;
Next, we remove any dropdown options which do not appear in any available combination. I’ve done this by building a list of all possible values for the current dropdown and another list of all the values for the dropdown which have a combination which is in stock, and then finding the different between those lists.
const allDropdownValues = new Set();
const inStockDropdownValues = new Set();
for (const variant of product.variants) {
const value = variant[dropdownOptionNumber];
allDropdownValues.add(value);
variant.available && inStockDropdownValues.add(value);
}
const impossibleDropdownValues = [...allDropdownValues].filter(
(value) => !inStockDropdownValues.has(value)
);
removeValuesFromDropdown(dropdown, impossibleDropdownValues);
Here we define a function which will be called every time our dropdown value changes.
dropdown.addEventListener("change", () => {
We first make sure all options in all dropdowns are available.
enableAllValuesInDropdowns(dropdowns);
Then we check whether the selected option is the placeholder value for that dropdown
e.g. “Please select a size”. If it is a placeholder, we return
to leave all options enabled.
const isPlaceholderValue = !dropdown.value;
if (isPlaceholderValue) return;
If we’ve got this far, we might want to disable values on other dropdowns so it’s useful to have a list of the option numbers excluding the current dropdown.
const otherOptionNumbers = optionNumbers.filter(
(optionNumber) => optionNumber !== dropdownOptionNumber
);
Next, we get the newly selected value from the dropdown which has changed and find unavailable variants containing it.
const newValue = dropdown.value;
const unavailableVariantsWithNewValue = product.variants.filter(
(variant) =>
variant[dropdownOptionNumber] === newValue && !variant.available
);
We then build up an object grouping values to disable based on their option number.
const valuesToDisablePerOptionNumber = unavailableVariantsWithNewValue.reduce(
(accumulator, variant) => {
for (const optionNumber of otherOptionNumbers) {
const valueToDisable = variant[optionNumber];
accumulator[optionNumber]
? accumulator[optionNumber].push(valueToDisable)
: (accumulator[optionNumber] = [valueToDisable]);
}
return accumulator;
},
{}
);
And we disable those options.
for (const [optionNumber, valuesToDisable] of Object.entries(
valuesToDisablePerOptionNumber
)) {
const dropdownToChange = optionNumberToDropdownMap[optionNumber];
disableValuesInDropdown(dropdownToChange, valuesToDisable);
}
});
}
};
Finally, we run the above function with the product data for the current page by using Liquid’s json filter.
Shopify.linkOptionDropdowns({{ product | json }})
</script>