in theme.liquid - Reliable: polls /cart.js after add-to-cart, watches #CartDrawer DOM, parses drawer fallback - Debug logs: visible in console (no page output) - Updates cart attribute: has_hulk_option = "true" | "false" */ (function () { const TAG = '[HULK-COD]'; const DEBUG = true; function log(...args){ if(DEBUG) console.log(TAG, ...args); } function err(...args){ console.error(TAG, ...args); } function sleep(ms){ return new Promise(r => setTimeout(r, ms)); } // --- Helpers to read cart and detect Hulk properties --- async function fetchCart() { const r = await fetch('/cart.js', { cache: 'no-cache' }); if (!r.ok) throw new Error('fetch /cart.js failed ' + r.status); return await r.json(); } function cartHasHulkProps(cart) { if (!cart || !Array.isArray(cart.items)) return false; for (const item of cart.items) { const props = item.properties || {}; for (const [k, v] of Object.entries(props)) { if (!k) continue; // ignore internal keys and known system keys if (k.startsWith('_')) continue; if (['has_stitching','stitching_type','stitching_count','estimated_dispatch'].includes(k)) continue; if (v == null) continue; const sval = String(v).trim().toLowerCase(); if (sval === '' || sval === 'none' || sval === '0') continue; // Found a meaningful hulk property return true; } } return false; } // Fallback: parse the drawer DOM to see if rendered line properties exist (Impulse markup) function drawerHasHulkProps() { try { const drawer = document.querySelector('#CartDrawer'); if (!drawer) return false; // look for the rendered properties block that Hulk app injects const propEls = drawer.querySelectorAll('.cart__item--properties, [data-hulkapps-line-properties]'); for (const el of propEls) { const txt = (el.textContent || '').trim(); if (!txt) continue; // ignore trivial "ADD ONS:" header alone const cleaned = txt.replace(/\s+/g,' ').trim(); if (cleaned === 'ADD ONS:' || cleaned.length < 5) continue; return true; } return false; } catch (e) { return false; } } // Update cart attribute only if changed (avoid redundant updates) async function updateCartAttributeIfNeeded(currentCart, newVal) { try { const currentAttr = (currentCart?.attributes && String(currentCart.attributes.has_hulk_option)) || null; if (currentAttr === newVal) { log('No attribute update needed — has_hulk_option already', currentAttr); return; } log('Updating cart attribute has_hulk_option =>', newVal); const upd = await fetch('/cart/update.js', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ attributes: { has_hulk_option: newVal } }) }); if (!upd.ok) log('Warning: /cart/update.js returned', upd.status); else log('Cart update request sent.'); // optional small verify try { const verify = await fetchCart(); log('Cart attributes after update:', verify.attributes || {}); } catch(e) { /* ignore */ } } catch (e) { err('Error updating cart attribute:', e); } } // Poll the cart for Hulk props for a few attempts (useful right after add-to-cart) async function pollCartForHulk(maxAttempts = 8, intervalMs = 400) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { log(`Polling cart (attempt ${attempt}/${maxAttempts})...`); const cart = await fetchCart(); const found = cartHasHulkProps(cart); if (found) { log('Found Hulk props in cart on attempt', attempt); await updateCartAttributeIfNeeded(cart, 'true'); return true; } // If attribute already true (rare), keep it const curAttr = cart?.attributes?.has_hulk_option; if (String(curAttr) === 'true') { log('Cart already had has_hulk_option=true'); return true; } } catch (e) { log('Poll cart error (ignored):', e.message || e); } await sleep(intervalMs); } // timed out log('Polling timed out — will fallback to DOM parse'); return false; } // Main orchestration: try polling, fallback to DOM parse and update accordingly async function ensureHulkAttributeSynced() { try { // First, try polling cart. If poll finds props -> done const foundInCart = await pollCartForHulk(8, 400); if (foundInCart) return; // Fallback: parse drawer DOM (maybe app rendered properties immediately) const foundInDrawer = drawerHasHulkProps(); if (foundInDrawer) { log('Found Hulk props via drawer DOM parse (fallback)'); const cart = await fetchCart(); await updateCartAttributeIfNeeded(cart, 'true'); return; } // No hullk props found -> ensure attribute false log('No Hulk props detected — setting attribute false'); const cart = await fetchCart(); await updateCartAttributeIfNeeded(cart, 'false'); } catch (e) { err('ensureHulkAttributeSynced error:', e); } } // Debounced scheduler to avoid duplicate runs let schTimer = null; function scheduleEnsure(delay = 500) { if (schTimer) clearTimeout(schTimer); schTimer = setTimeout(() => { schTimer = null; ensureHulkAttributeSynced(); }, delay); } // --- Hook points --- // 1) On page load do an initial sync (safe) document.addEventListener('DOMContentLoaded', () => { log('Initial sync scheduled'); scheduleEnsure(400); }); // 2) On Add-to-Cart clicks: schedule a sync with extra delay (gives the app + theme time to push properties) document.body.addEventListener('click', (e) => { const btn = e.target.closest( "form[action*='/cart/add'] [type='submit'], " + "form[action*='/cart/add'] input[type='submit'], " + "button[name='add'], [data-add-to-cart], .add-to-cart, .product-form__submit, .ProductForm__AddToCart" ); if (btn) { log('Add-to-cart click detected -> scheduling sync (long delay)'); scheduleEnsure(1000); // longer delay for add-to-cart flows } }, { passive: true }); // 3) Watch for #CartDrawer being added (Impulse sometimes adds it dynamically) const bodyObserver = new MutationObserver((mutations) => { for (const m of mutations) { for (const n of m.addedNodes) { if (!(n instanceof HTMLElement)) continue; if (n.id === 'CartDrawer' || n.querySelector && n.querySelector('#CartDrawer')) { log('CartDrawer element detected via body mutation -> attaching drawer observer'); attachDrawerObserver(); scheduleEnsure(600); return; } } } }); bodyObserver.observe(document.body, { childList: true, subtree: true }); // 4) Attach drawer observer when drawer exists let drawerObserver = null; function attachDrawerObserver() { try { const drawer = document.querySelector('#CartDrawer'); if (!drawer) return; if (drawerObserver) return; // already attached drawerObserver = new MutationObserver((muts) => { // run a check when drawer content changes log('CartDrawer mutated -> scheduling sync'); scheduleEnsure(450); }); drawerObserver.observe(drawer, { childList: true, subtree: true, attributes: false }); log('Drawer observer attached'); } catch (e) { err('attachDrawerObserver error:', e); } } // Try attach right away if drawer present try { attachDrawerObserver(); } catch (e) { /* ignore */ } // 5) Also listen for some theme events as extra insurance ['ajaxCart:afterAdd', 'cart:refresh', 'cart:updated', 'cart:change'].forEach(evt => { document.addEventListener(evt, () => { log('Theme event:', evt, '-> scheduling sync'); scheduleEnsure(350); }); }); // 6) Expose debug helpers window.__hulk_cod_manual_sync = function (delay=0) { log('Manual sync requested, delay=', delay); if (!delay) return ensureHulkAttributeSynced(); scheduleEnsure(delay); }; window.__hulk_cod_status = async function () { try { const cart = await fetchCart(); log('Manual status:', cart.attributes || {}, 'items:', (cart.items||[]).length); return cart; } catch (e) { err('Status err:', e); throw e; } }; log('Hulk COD watcher initialized'); })();
Skip to content