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();
})();