← back to Designer Wallcoverings
Fix spec-sheet Download PDF: render via hidden iframe (fetch+DOMParser never ran the page JS), add layout none to spec-sheet template
a727c0942285607251971c42994ccdcd8977e999 · 2026-06-23 10:37:14 -0700 · Steve
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M shopify/_cwGRID/snippets/spec-sheet-button.liquidM shopify/_cwGRID/templates/page.spec-sheet.liquid
Diff
commit a727c0942285607251971c42994ccdcd8977e999
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Jun 23 10:37:14 2026 -0700
Fix spec-sheet Download PDF: render via hidden iframe (fetch+DOMParser never ran the page JS), add layout none to spec-sheet template
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
shopify/_cwGRID/snippets/spec-sheet-button.liquid | 133 +++++++++++-----------
shopify/_cwGRID/templates/page.spec-sheet.liquid | 7 ++
2 files changed, 74 insertions(+), 66 deletions(-)
diff --git a/shopify/_cwGRID/snippets/spec-sheet-button.liquid b/shopify/_cwGRID/snippets/spec-sheet-button.liquid
index c6030f30..22f6c7b2 100644
--- a/shopify/_cwGRID/snippets/spec-sheet-button.liquid
+++ b/shopify/_cwGRID/snippets/spec-sheet-button.liquid
@@ -1,97 +1,98 @@
{% comment %}
- Spec Sheet Button - With inline PDF download (no page navigation)
+ Spec Sheet Button - Inline PDF download (no page navigation)
+
+ FIX 2026-06-23: the previous version did fetch() + DOMParser on
+ /pages/spec-sheet, then ran html2canvas on the parsed #pdf-content. That
+ never worked: the spec-sheet page renders its product data via JavaScript
+ (loadProduct()), and DOMParser does NOT execute scripts, so #pdf-content was
+ always captured as the "Loading Product..." placeholder -> 0-dimension canvas
+ -> jsPDF.addImage() threw -> "Error" -> nothing saved.
+
+ This version spins up a hidden SAME-ORIGIN iframe pointed at the spec-sheet
+ page. Inside the iframe the page's OWN scripts run and render the product
+ (styles + image + specs all present), then we invoke the iframe's already-
+ proven downloadPDF(). One click, reuses the working generator.
{% endcomment %}
<div class="spec-sheet-controls">
<button class="spec-sheet-btn pdf-download-btn"
- onclick="downloadSpecSheetPDF(event, '{{ product.handle }}', '{{ product.title }}', '{{ product.featured_image.src }}');"
+ onclick="downloadSpecSheetPDF(event, '{{ product.handle }}', '{{ product.title }}');"
style="vertical-align: top;">
🖨️ Download PDF
</button>
</div>
<script>
-async function downloadSpecSheetPDF(e, handle, title, imageUrl) {
+async function downloadSpecSheetPDF(e, handle, title) {
e?.preventDefault?.();
e?.stopPropagation?.();
- const btn = e?.target;
- if (btn) {
- btn.textContent = '⏳ Generating...';
- btn.disabled = true;
- }
+ // currentTarget is the button itself (target can be a child node/emoji span)
+ const btn = e?.currentTarget || e?.target;
+ const reset = () => { if (btn) { btn.textContent = '🖨️ Download PDF'; btn.disabled = false; } };
+ if (btn) { btn.textContent = '⏳ Generating...'; btn.disabled = true; }
+ let iframe;
try {
- // Fetch the spec sheet HTML
- const response = await fetch(`/pages/spec-sheet?product=${encodeURIComponent(handle)}`);
- const html = await response.text();
-
- // Extract the printable content
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
- const pdfContent = doc.getElementById('pdf-content');
-
- if (!pdfContent) {
- throw new Error('Could not load spec sheet');
- }
-
- // Use html2pdf library (already loaded on spec sheet page)
- const script1 = document.createElement('script');
- script1.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
- document.head.appendChild(script1);
-
- const script2 = document.createElement('script');
- script2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
- document.head.appendChild(script2);
-
- // Wait for scripts to load
- await new Promise(resolve => {
- script1.onload = () => {
- script2.onload = resolve;
- };
- });
-
- // Generate PDF
- const canvas = await html2canvas(pdfContent, {
- scale: 2,
- useCORS: true,
- allowTaint: false,
- backgroundColor: '#ffffff',
- logging: false
+ // 1. Hidden, same-origin iframe so the spec-sheet page's scripts execute.
+ iframe = document.createElement('iframe');
+ iframe.setAttribute('aria-hidden', 'true');
+ iframe.style.cssText = 'position:fixed;left:-10000px;top:0;width:900px;height:1400px;border:0;visibility:hidden;';
+ iframe.src = '/pages/spec-sheet?product=' + encodeURIComponent(handle);
+ document.body.appendChild(iframe);
+
+ // 2. Wait for the iframe to render the product (its JS reveals #pdfBtn when ready).
+ await new Promise(function (resolve, reject) {
+ var t0 = Date.now();
+ iframe.addEventListener('load', function () {
+ var poll = setInterval(function () {
+ var idoc = iframe.contentDocument;
+ if (!idoc) return; // not accessible yet
+ var pdfBtn = idoc.getElementById('pdfBtn');
+ var content = idoc.getElementById('content');
+ var failed = content && /Product Not Found|No Product Specified/i.test(content.textContent || '');
+ var ready = pdfBtn && pdfBtn.style.display !== 'none';
+ if (ready) { clearInterval(poll); resolve(); }
+ else if (failed) { clearInterval(poll); reject(new Error('Product not found in spec sheet')); }
+ else if (Date.now() - t0 > 20000) { clearInterval(poll); reject(new Error('Spec sheet render timed out')); }
+ }, 200);
+ });
+ iframe.addEventListener('error', function () { reject(new Error('Could not load spec sheet')); });
});
- const { jsPDF } = window.jspdf;
- const imgData = canvas.toDataURL('image/png');
- const pdf = new jsPDF({
- orientation: 'portrait',
- unit: 'mm',
- format: 'letter'
+ // 3. Invoke the iframe's own proven generator (libs + image + styles all in its context).
+ var iwin = iframe.contentWindow;
+ var idoc = iframe.contentDocument;
+ if (typeof iwin.downloadPDF !== 'function') {
+ throw new Error('Spec sheet generator unavailable');
+ }
+ iwin.downloadPDF();
+
+ // 4. Wait for the iframe's button to report success/failure before tearing it down.
+ await new Promise(function (resolve, reject) {
+ var t0 = Date.now();
+ var poll = setInterval(function () {
+ var pdfBtn = idoc.getElementById('pdfBtn');
+ var txt = pdfBtn ? pdfBtn.textContent : '';
+ if (/Downloaded/i.test(txt)) { clearInterval(poll); resolve(); }
+ else if (/Error/i.test(txt)) { clearInterval(poll); reject(new Error('Generator failed')); }
+ else if (Date.now() - t0 > 30000) { clearInterval(poll); reject(new Error('PDF generation timed out')); }
+ }, 250);
});
- const imgWidth = 216;
- const imgHeight = (canvas.height * imgWidth) / canvas.width;
- pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight);
-
- // Download with product SKU in filename
- const sku = title.replace(/[^a-z0-9]/gi, '-').toLowerCase();
- pdf.save(`Designer-Wallcoverings-${sku}.pdf`);
-
if (btn) {
btn.textContent = '✓ Downloaded!';
- setTimeout(() => {
- btn.textContent = '🖨️ Download PDF';
- btn.disabled = false;
- }, 2000);
+ setTimeout(reset, 2000);
}
} catch (err) {
console.error('PDF generation failed:', err);
if (btn) {
btn.textContent = '✗ Error - Try Again';
- setTimeout(() => {
- btn.textContent = '🖨️ Download PDF';
- btn.disabled = false;
- }, 2000);
+ setTimeout(reset, 2500);
}
+ } finally {
+ // Give the browser a beat to commit the download, then remove the iframe.
+ if (iframe) setTimeout(function () { iframe.remove(); }, 1500);
}
}
</script>
diff --git a/shopify/_cwGRID/templates/page.spec-sheet.liquid b/shopify/_cwGRID/templates/page.spec-sheet.liquid
index dfc7fef1..4fd5cb1d 100644
--- a/shopify/_cwGRID/templates/page.spec-sheet.liquid
+++ b/shopify/_cwGRID/templates/page.spec-sheet.liquid
@@ -1,8 +1,15 @@
+{% layout none %}
{% comment %}
Simple Spec Sheet - Refined version
- Larger image (500x500px)
- Single column if space available
- Title case, not bold
+
+ layout none (2026-06-23): this template outputs its own complete <!DOCTYPE html>
+ document, so it must NOT be wrapped in theme.liquid. Without this, the rendered
+ page was ~900KB of nested/duplicate markup (theme chrome wrapping a full HTML
+ doc). Skipping the layout yields a lean standalone spec sheet -> faster, valid,
+ and ideal for the hidden-iframe PDF generator in spec-sheet-button.liquid.
{% endcomment %}
<!DOCTYPE html>
← f59d4ade Add Tres Tintas spec recrawl — fills width/repeat/length/wei
·
back to Designer Wallcoverings
·
cadence: honor newwall_catalog dedup_skip so Tres Tintas imp 0b67db27 →