Code du thème Base
Le thème de référence, version 1.1.0. Tout télécharger (.zip)
assets/product.js
/* Thème Base : galerie et choix des options sur la fiche produit. */
(function () {
'use strict';
var root = document.querySelector('[data-product]');
if (!root) return;
// Galerie : un clic sur une vignette change la grande photo.
var main = root.querySelector('[data-gallery-main]');
var thumbs = root.querySelectorAll('[data-gallery-thumb]');
function show(url, alt) {
if (!main || !url) return;
main.src = url;
if (alt) main.alt = alt;
thumbs.forEach(function (t) { t.setAttribute('aria-current', t.getAttribute('data-gallery-thumb') === url ? 'true' : 'false'); });
}
thumbs.forEach(function (t) { t.addEventListener('click', function () { show(t.getAttribute('data-gallery-thumb'), t.getAttribute('data-alt')); }); });
// Variantes : les options choisies désignent une variante (prix, stock, photo, référence).
var picker = root.querySelector('[data-variant-picker]');
if (!picker) return;
var data = picker.querySelector('[data-variants]');
var variants = [];
try { variants = JSON.parse(data.textContent) || []; } catch (e) { return; }
var button = root.querySelector('[data-needs-choice]');
var price = root.querySelector('[data-price]'), compare = root.querySelector('[data-compare]'), from = root.querySelector('[data-price-from]');
var avail = root.querySelector('[data-availability]'), sku = root.querySelector('[data-sku]');
var fmt = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' });
var money = function (c) { return c > 0 ? fmt.format(c / 100) : 'Offert'; };
var groups = Array.prototype.map.call(picker.querySelectorAll('fieldset'), function (f) { return f; });
function chosen() {
var c = {};
picker.querySelectorAll('input:checked').forEach(function (i) { c[i.getAttribute('data-option')] = i.value; });
return c;
}
function matches(v, c) {
for (var k in c) if (v.options[k] !== c[k]) return false;
return true;
}
function update() {
var c = chosen();
var complete = Object.keys(c).length === groups.length;
var v = complete ? variants.filter(function (x) { return matches(x, c); })[0] : null;
// Valeurs impossibles avec le reste du choix : grisées (sans les bloquer, pour pouvoir changer d'avis).
picker.querySelectorAll('input').forEach(function (i) {
var t = {}; for (var k in c) t[k] = c[k];
t[i.getAttribute('data-option')] = i.value;
var ok = variants.some(function (x) { return matches(x, t) && x.in_stock; });
i.parentNode.classList.toggle('is-out', !ok);
});
groups.forEach(function (g) {
var i = g.querySelector('input:checked'), s = g.querySelector('[data-opt-chosen]');
if (s) s.textContent = i ? ' : ' + i.value : '';
});
if (!v) {
if (button) { button.disabled = true; button.textContent = complete ? 'Indisponible' : 'Choisissez vos options'; button.setAttribute('data-dc-variant', ''); }
if (avail && complete) { avail.textContent = 'Cette combinaison n’existe pas'; avail.className = 'availability ko'; }
return;
}
if (from) from.hidden = true;
price.textContent = money(v.price);
price.classList.toggle('now', v.on_sale);
compare.hidden = !v.on_sale;
compare.textContent = v.on_sale ? money(v.compare_at) : '';
avail.className = 'availability ' + (v.in_stock ? 'ok' : 'ko');
avail.textContent = !v.in_stock ? 'Épuisé' : (v.low_stock ? 'Plus que ' + v.stock + ' en stock' : 'En stock');
if (sku) { sku.hidden = !v.sku; sku.querySelector('span').textContent = v.sku || ''; }
if (v.image) show(v.image);
if (button) {
button.setAttribute('data-dc-variant', v.id);
button.disabled = !v.in_stock;
button.textContent = v.in_stock ? 'Ajouter au panier' : 'Épuisé';
}
}
picker.addEventListener('change', update);
update();
})();