├── README.md
└── source.js
/README.md:
--------------------------------------------------------------------------------
1 | ## Requirements
2 | * Dawn 12/13.
3 |
4 | ## Changelog
5 | * Small change to detect pills/selects in Dawn 13, kept compatibility with 12.
6 |
7 | # Hide Unavailable Combinations
8 |
9 | * Hide unavailable options - Hide or restyle variant buttons/drop down options for variant combinations that do not exist.
10 | * Out of stock items will not be affected, this enhancement only affects _unavailable_ combinations.
11 |
12 | ## Live Demonstration
13 | The following website is using the default Dawn theme, the password is "dynamic":
14 |
15 | https://dynamic-selectors.myshopify.com/products/phone-case
16 |
17 | ## Known Bugs
18 | Screen seems to jump around a bit when you click on different options.
19 |
20 | ## Installation
21 | 1. Customize your theme.
22 | 2. Go to your product template page.
23 | 3. Create a "Custom liquid" block in the "Product information" tree (ensure this is _under_ the variant picker block).
24 | 4. Paste the contents of the [source.js](source.js) file into your recently created "Custom liquid" block **within javascript script tags**. i.e.:
25 | ```
26 |
29 | ```
30 |
31 | 
32 |
33 | ## Say thanks!
34 |
35 | I've spent countless hours working on this solution, I could have made it into a subscription based app, or kept it for myself. But I'd prefer to give back to the Shopify community.
36 |
37 | But if would like to give a small donation, click the button below to say thanks!
38 |
39 |
40 |
41 | ## Hall of Shame! (non-credited usage)
42 |
43 | I set up this repository to help those with similar issues as I was experiencing. With no prior Javascript background I spent weeks trying to figure this out. Such a gut punch to see my work copy and pasted on a business website uncredited. Don't be like that.
44 |
45 | * websensepro.com has stolen this code and using it uncredited on their blog and YouTube (Rs6o1HdsKF8) channel. I've reached out asking them to credit this repository, but they have not responded. Shame.
46 |
--------------------------------------------------------------------------------
/source.js:
--------------------------------------------------------------------------------
1 | const variantSelects = (document.querySelector('variant-selects')) ? document.querySelector('variant-selects') : document.querySelector('variant-radios');
2 | const pickerType = (variantSelects.querySelectorAll('fieldset').length > 0) ? 'radios' : 'selects';
3 | const fieldsets = (pickerType == 'radios') ? Array.from(variantSelects.querySelectorAll('fieldset')) : Array.from(variantSelects.querySelectorAll('.product-form__input--dropdown'));
4 | const productJson = JSON.parse(variantSelects.querySelector('[type="application/json"]').textContent);
5 | let selectedOptions = [];
6 | variantSelects.addEventListener('change', rebuildOptions);
7 | this.rebuildOptions();
8 |
9 | // gather a list of valid combinations of options, check to see if the input passed to it matches in a chain of valid options.
10 | function validCombo(inputValue, optionLevel) {
11 | for(let i = 0; i < productJson.length; i++) {
12 | if(optionLevel == 1){
13 | if (productJson[i].option1 == selectedOptions[0] && productJson[i].option2 == inputValue) { return true; }
14 | } else {
15 | if (productJson[i].option1 == selectedOptions[0] && productJson[i].option2 == selectedOptions[1] && productJson[i].option3 == inputValue) { return true; }
16 | }
17 | }
18 | }
19 |
20 | function rebuildOptions() {
21 | selectedOptions = fieldsets.map((fieldset) => {
22 | return (pickerType == 'radios') ? Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value : Array.from(fieldset.querySelectorAll('select'), (select) => select.value);
23 | });
24 |
25 | //loop through the option sets starting from the 2nd set and disable any invalid options
26 | for(let optionLevel = 1, n = fieldsets.length; optionLevel < n; optionLevel++) {
27 | const inputs = (pickerType == 'radios') ? fieldsets[optionLevel].querySelectorAll('input') : fieldsets[optionLevel].querySelectorAll('option');
28 |
29 | inputs.forEach(input => {
30 | input.disabled = (validCombo(input.value,optionLevel)) ? false : true;
31 | if(pickerType == 'radios'){
32 | //get the label for the current input (this is what the user clicks, the "pill")
33 | const label = fieldsets[optionLevel].querySelector(`label[for="${input.id}"]`);
34 |
35 | label.style.display = (input.disabled) ? "none" : ""; //Hide the option, or comment this line out and use the following lines to style it..
36 | //label.style.opacity = (input.disabled) ? 0.5 : 1;
37 | //label.style.borderStyle = (input.disabled) ? "dashed" : "solid";
38 | //label.style.textDecoration = (input.disabled) ? "none" : "";
39 | } else {
40 | input.hidden = (validCombo(input.value,optionLevel)) ? false : true;
41 | }
42 | });
43 | }
44 |
45 | //if the default selected option is disabled with the function above, select the first available option instead
46 | for (let optionLevel = 1, fieldsetsLength = fieldsets.length, change = false; optionLevel < fieldsetsLength && !change; optionLevel++) {
47 | if(pickerType == 'radios'){
48 | if(fieldsets[optionLevel].querySelector('input:checked').disabled === true) {
49 | change = (fieldsets[optionLevel].querySelector('input:not(:disabled)').checked = true);
50 | }
51 | } else {
52 | if(fieldsets[optionLevel].querySelector('option:checked').disabled === true) {
53 | change = (fieldsets[optionLevel].querySelector('option:not(:disabled)').selected = "selected");
54 | }
55 | }
56 | //if a new option has been selected, restart the whole process
57 | if(change) variantSelects.dispatchEvent(new Event('change', { bubbles: true }));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------