Affichage dynamique de la dispo des options (product form)
Système permettant de "griser" les options non disponibles selon la sélection actuelle de l'utilisateur dans un formulaire produit. Cela améliore l'expérience utilisateur en évitant les sélections impossibles.
Vue d'ensemble
Ce système vérifie en temps réel la disponibilité des combinaisons de variantes et désactive visuellement les options qui ne sont pas disponibles selon la sélection actuelle.
1. Ajouter updateOptionsStocks() dans onVariantChange()
Dans votre fonction onVariantChange(), ajoutez l'appel à la fonction de mise à jour :
onVariantChange() {
// ... votre code existant ...
this.updateOptionsStocks();
}Explication :
- Appelle la fonction de mise à jour à chaque changement de variante
- Assure que les options sont toujours à jour selon la sélection
2. Ajouter la fonction updateOptionsStocks() dans la classe
Ajoutez cette fonction dans votre classe de gestion des variantes :
updateOptionsStocks() {
const JSONoptions = this.variantData;
const optionsValues = document.querySelectorAll('.mm-product-form-variant-values');
optionsValues.forEach(function(optionValues, index) {
let mainIndex = index;
let optionIndex = index;
let options = optionValues.querySelectorAll('input');
options.forEach(function(option) {
let currentOptionTested = option;
let valuesToTest = [];
valuesToTest.push(option.value);
optionsValues.forEach(function(optionValues, index) {
if (index != optionIndex) {
let options = optionValues.querySelectorAll('input');
options.forEach(function(option) {
if (option.checked) {
valuesToTest.push(option.value);
}
})
}
if (index == (optionsValues.length - 1)) {
if(checkIfAvailable(JSONoptions, valuesToTest) == false) {
currentOptionTested.classList.add('mm-disabled');
} else {
currentOptionTested.classList.remove('mm-disabled');
}
}
})
});
});
}Explication :
- Parcourt toutes les options de variantes
- Pour chaque option, teste si elle est disponible avec les autres sélections actuelles
- Ajoute ou retire la classe
mm-disabledselon la disponibilité
Logique de la fonction
- Récupération des données : Utilise
this.variantDataqui contient toutes les combinaisons de variantes - Parcours des options : Pour chaque groupe d'options (taille, couleur, etc.)
- Test de disponibilité : Pour chaque option, teste si elle est disponible avec les autres sélections
- Mise à jour visuelle : Ajoute/retire la classe CSS
mm-disabled
3. Ajouter la fonction checkIfAvailable() et l'event au chargement
Ajoutez cette fonction à la fin de votre script, ainsi que l'événement de chargement :
function checkIfAvailable(JSONoptions, valuesToTest) {
const valuesToTestSorted = valuesToTest.slice().sort();
for (let option of JSONoptions) {
let optionsSorted = option.options.slice().sort();
if (optionsSorted.every((val, index) => val === valuesToTestSorted[index])) {
return option.available;
}
}
return false;
}
document.addEventListener("DOMContentLoaded", (event) => {
var event = new Event('change');
document.querySelector('variant-selector').dispatchEvent(event);
});Explication :
checkIfAvailable(): Compare les valeurs testées avec les combinaisons disponibles dansvariantData- Tri les valeurs pour une comparaison fiable
- Retourne
truesi la combinaison existe et est disponible,falsesinon - L'événement
DOMContentLoadeddéclenche la mise à jour au chargement de la page
Structure de variantData
Le variantData doit être un tableau d'objets avec cette structure :
[
{
options: ["Small", "Red"],
available: true
},
{
options: ["Small", "Blue"],
available: false
},
{
options: ["Large", "Red"],
available: true
}
]4. Ajouter le CSS
Ajoutez ce style CSS pour griser visuellement les options désactivées :
.mm-product-form-variant-value input.mm-disabled + label {
opacity: .5;
}Explication :
- Cible les labels des inputs avec la classe
mm-disabled - Réduit l'opacité à 50% pour indiquer visuellement que l'option n'est pas disponible
- Utilise le sélecteur adjacent (
+) pour cibler le label suivant l'input
CSS amélioré (optionnel)
Pour une meilleure expérience utilisateur, vous pouvez ajouter :
.mm-product-form-variant-value input.mm-disabled + label {
opacity: .5;
cursor: not-allowed;
pointer-events: none;
}
.mm-product-form-variant-value input.mm-disabled {
pointer-events: none;
}Cela empêche également les clics sur les options désactivées.
Exemple complet
Structure HTML (Liquid)
<div class="mm-product-form-variant-values">
{% for option in product.options_with_values %}
<div class="mm-product-form-variant-option">
<label>{{ option.name }}</label>
<div class="mm-product-form-variant-value">
{% for value in option.values %}
<input
type="radio"
name="option-{{ forloop.parentloop.index }}"
value="{{ value }}"
id="option-{{ forloop.parentloop.index }}-{{ forloop.index }}"
{% if forloop.first %}checked{% endif %}>
<label for="option-{{ forloop.parentloop.index }}-{{ forloop.index }}">
{{ value }}
</label>
{% endfor %}
</div>
</div>
{% endfor %}
</div>Code JavaScript complet
class VariantSelector {
constructor(product) {
this.product = product;
this.variantData = this.buildVariantData();
this.init();
}
buildVariantData() {
// Construire variantData depuis les données du produit
const variants = this.product.variants;
return variants.map(variant => ({
options: variant.options,
available: variant.available
}));
}
init() {
// Écouter les changements sur les options
document.querySelectorAll('.mm-product-form-variant-values input').forEach(input => {
input.addEventListener('change', () => this.onVariantChange());
});
}
onVariantChange() {
// Votre logique existante de changement de variante
const selectedOptions = this.getSelectedOptions();
const variant = this.findVariant(selectedOptions);
if (variant) {
this.updateVariantInfo(variant);
}
// Mettre à jour la disponibilité des options
this.updateOptionsStocks();
}
getSelectedOptions() {
const options = [];
document.querySelectorAll('.mm-product-form-variant-values input:checked').forEach(input => {
options.push(input.value);
});
return options;
}
findVariant(selectedOptions) {
return this.product.variants.find(variant => {
return variant.options.every((option, index) => option === selectedOptions[index]);
});
}
updateVariantInfo(variant) {
// Mettre à jour le prix, l'image, etc.
document.querySelector('.product-price').textContent = variant.price;
}
updateOptionsStocks() {
const JSONoptions = this.variantData;
const optionsValues = document.querySelectorAll('.mm-product-form-variant-values');
optionsValues.forEach(function(optionValues, index) {
let mainIndex = index;
let optionIndex = index;
let options = optionValues.querySelectorAll('input');
options.forEach(function(option) {
let currentOptionTested = option;
let valuesToTest = [];
valuesToTest.push(option.value);
optionsValues.forEach(function(optionValues, index) {
if (index != optionIndex) {
let options = optionValues.querySelectorAll('input');
options.forEach(function(option) {
if (option.checked) {
valuesToTest.push(option.value);
}
})
}
if (index == (optionsValues.length - 1)) {
if(checkIfAvailable(JSONoptions, valuesToTest) == false) {
currentOptionTested.classList.add('mm-disabled');
} else {
currentOptionTested.classList.remove('mm-disabled');
}
}
})
});
});
}
}
function checkIfAvailable(JSONoptions, valuesToTest) {
const valuesToTestSorted = valuesToTest.slice().sort();
for (let option of JSONoptions) {
let optionsSorted = option.options.slice().sort();
if (optionsSorted.every((val, index) => val === valuesToTestSorted[index])) {
return option.available;
}
}
return false;
}
// Initialisation au chargement
document.addEventListener("DOMContentLoaded", (event) => {
// Récupérer les données du produit depuis Liquid
const productData = {{ product | json }};
const variantSelector = new VariantSelector(productData);
// Déclencher la mise à jour initiale
var event = new Event('change');
document.querySelector('.mm-product-form-variant-values')?.dispatchEvent(event);
});Bonnes pratiques
1. Performance
Pour optimiser les performances avec beaucoup de variantes :
updateOptionsStocks() {
// Utiliser requestAnimationFrame pour éviter les calculs inutiles
if (this.updateTimeout) {
cancelAnimationFrame(this.updateTimeout);
}
this.updateTimeout = requestAnimationFrame(() => {
// ... code de mise à jour ...
});
}2. Accessibilité
Ajouter des attributs ARIA pour les lecteurs d'écran :
if(checkIfAvailable(JSONoptions, valuesToTest) == false) {
currentOptionTested.classList.add('mm-disabled');
currentOptionTested.setAttribute('aria-disabled', 'true');
currentOptionTested.setAttribute('tabindex', '-1');
} else {
currentOptionTested.classList.remove('mm-disabled');
currentOptionTested.removeAttribute('aria-disabled');
currentOptionTested.removeAttribute('tabindex');
}3. Feedback visuel amélioré
Ajouter une animation de transition :
.mm-product-form-variant-value input + label {
transition: opacity 0.2s ease;
}
.mm-product-form-variant-value input.mm-disabled + label {
opacity: .5;
cursor: not-allowed;
}Cas particuliers
Produits avec une seule option
Si le produit n'a qu'une seule option, la fonction fonctionne toujours mais peut être simplifiée :
if (optionsValues.length === 1) {
// Logique simplifiée pour une seule option
}Gestion des variantes sans stock
Si vous voulez aussi gérer les variantes en rupture de stock :
function checkIfAvailable(JSONoptions, valuesToTest) {
const valuesToTestSorted = valuesToTest.slice().sort();
for (let option of JSONoptions) {
let optionsSorted = option.options.slice().sort();
if (optionsSorted.every((val, index) => val === valuesToTestSorted[index])) {
// Vérifier aussi le stock si nécessaire
return option.available && option.inventory_quantity > 0;
}
}
return false;
}Dépannage
Les options ne se mettent pas à jour
- Vérifier que
variantDataest correctement construit - Vérifier que les sélecteurs CSS correspondent à votre HTML
- Vérifier que
onVariantChange()est bien appelé
Les options restent grisées
- Vérifier que
checkIfAvailable()retourne bientruepour les combinaisons disponibles - Vérifier la structure de
variantData - Vérifier que les valeurs dans
variantDatacorrespondent aux valeurs dans le HTML
Performance lente
- Limiter le nombre de variantes testées
- Utiliser
requestAnimationFrameoudebounce - Mettre en cache les résultats de
checkIfAvailable()
Ressources
- Shopify Variants : Documentation (opens in a new tab)
- Event Listeners : MDN Documentation (opens in a new tab)