← 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
M shopify/_cwGRID/sections/collection-grouped.liquid
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 →