[object Object]

← back to Designer Wallcoverings

grouped grid: self-heal stale images from live product.js + hide truly-dead imgs (no broken icons)

c70c7779dba6c16f8b19943bb71dd37a1e920342 · 2026-06-26 07:37:20 -0700 · Steve

Files touched

Diff

commit c70c7779dba6c16f8b19943bb71dd37a1e920342
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Jun 26 07:37:20 2026 -0700

    grouped grid: self-heal stale images from live product.js + hide truly-dead imgs (no broken icons)
---
 shopify/_cwGRID/sections/collection-grouped.liquid | 34 +++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/shopify/_cwGRID/sections/collection-grouped.liquid b/shopify/_cwGRID/sections/collection-grouped.liquid
index 510c1bf1..5c6b55b3 100644
--- a/shopify/_cwGRID/sections/collection-grouped.liquid
+++ b/shopify/_cwGRID/sections/collection-grouped.liquid
@@ -141,6 +141,8 @@
   .dw-grouped__card { display: block; text-decoration: none; color: inherit; }
   .dw-grouped__img-wrap { position: relative; aspect-ratio: 1/1; background: #f4f4f4; border-radius: 4px; overflow: hidden; }
   .dw-grouped__img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .4s ease; }
+  /* truly-dead image (no live fallback) → clean neutral tile, never a broken-icon + alt text */
+  .dw-grouped__img--gone { visibility: hidden; }
   .dw-grouped__card:hover .dw-grouped__img { transform: scale(1.04); }
   .dw-grouped__chip {
     position: absolute; left: 10px; bottom: 10px; z-index: 2;
@@ -230,9 +232,11 @@
     var chip = (t.count > 1)
       ? '<span class="dw-grouped__chip">' + esc(t.colors_label) + ' ' + chevron + '</span>'
       : '';
+    // Mirror image_url is stale for many products → always carry the handle so a
+    // failed/empty image self-heals from the LIVE Shopify product image.
     return '<a class="dw-grouped__card" role="listitem" href="' + esc(href) + '" title="' + esc(t.pattern) + '">'
       +   '<div class="dw-grouped__img-wrap">'
-      +     (img ? '<img class="dw-grouped__img" src="' + img + '" alt="' + esc(t.pattern) + '" loading="lazy" decoding="async">' : '')
+      +     '<img class="dw-grouped__img" src="' + img + '" data-handle="' + esc(t.handle || '') + '" alt="' + esc(t.pattern) + '" loading="lazy" decoding="async">'
       +     chip
       +   '</div>'
       +   '<div class="dw-grouped__meta">'
@@ -243,6 +247,33 @@
       + '</a>';
   }
 
+  // Self-heal stale/missing images from the LIVE Shopify product (current featured_image).
+  function fixImg(img) {
+    if (!img || img.dataset.fb) { if (img) img.classList.add('dw-grouped__img--gone'); return; }
+    img.dataset.fb = '1';
+    var h = img.getAttribute('data-handle');
+    if (!h) { img.classList.add('dw-grouped__img--gone'); return; }
+    fetch('/products/' + encodeURIComponent(h) + '.js', { credentials: 'omit' })
+      .then(function (r) { return r.ok ? r.json() : Promise.reject(); })
+      .then(function (p) {
+        var s = p.featured_image || (p.images && p.images[0]);
+        if (s) { img.src = (s.indexOf('//') === 0 ? 'https:' : '') + s; }   // live url is already valid; no transform (breaks /files/ paths)
+        else { img.classList.add('dw-grouped__img--gone'); }
+      })
+      .catch(function () { img.classList.add('dw-grouped__img--gone'); });
+  }
+  // Delegated: any grid image that fails → try the live product image.
+  grid.addEventListener('error', function (e) {
+    var t = e.target;
+    if (t && t.classList && t.classList.contains('dw-grouped__img')) fixImg(t);
+  }, true);
+  // Empty-src tiles (mirror had no url) → heal immediately.
+  function healEmpty() {
+    grid.querySelectorAll('.dw-grouped__img').forEach(function (img) {
+      if (!img.getAttribute('src')) fixImg(img);
+    });
+  }
+
   function skel(n) { var h=''; for (var i=0;i<n;i++) h+='<div class="dw-grouped__skel"></div>'; grid.insertAdjacentHTML('beforeend', h); }
   function clearSkel() { grid.querySelectorAll('.dw-grouped__skel').forEach(function(e){e.remove()}); }
 
@@ -263,6 +294,7 @@
         var tiles = d.tiles || [];
         if (next === 1 && !tiles.length) { root.hidden = true; document.body.classList.remove('dw-grouped--active'); return; }
         grid.insertAdjacentHTML('beforeend', tiles.map(card).join(''));
+        healEmpty();
         page = next;
         total = d.total_tiles;
         hasNext = !!d.has_next;

← dd2798d1 grouped grid: pass collection_handle for brand-family scopin  ·  back to Designer Wallcoverings  ·  chore: lint clean (drop unused rr) + v1.0.1 (session close — 8d9628b5 →