[object Object]

← back to Designer Wallcoverings

Add inline PDF download to product spec sheet button — no page navigation

39fa12d4b6325027b684dcd1aa1143a94af8fca8 · 2026-06-23 09:42:29 -0700 · Steve Abrams

Files touched

Diff

commit 39fa12d4b6325027b684dcd1aa1143a94af8fca8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 23 09:42:29 2026 -0700

    Add inline PDF download to product spec sheet button — no page navigation
---
 shopify/_cwGRID/snippets/spec-sheet-button.liquid  | 136 +++++++++++++++++++--
 shopify/push-size-pills-hide-readout.sh            |  41 +++++++
 .../assets/dw-pdp-size-pills.js                    |  11 ++
 3 files changed, 180 insertions(+), 8 deletions(-)

diff --git a/shopify/_cwGRID/snippets/spec-sheet-button.liquid b/shopify/_cwGRID/snippets/spec-sheet-button.liquid
index ac201322..c6030f30 100644
--- a/shopify/_cwGRID/snippets/spec-sheet-button.liquid
+++ b/shopify/_cwGRID/snippets/spec-sheet-button.liquid
@@ -1,17 +1,137 @@
 {% comment %}
-  Spec Sheet Button - Dynamic version with URL parameter
+  Spec Sheet Button - With inline PDF download (no page navigation)
 {% endcomment %}
 
-<a href="/pages/spec-sheet?product={{ product.handle }}"
-   target="_blank"
-   class="dl-sample-btn spec-sheet-btn"
-   onclick="event.stopPropagation();"
-   style="vertical-align: top;">
-  Spec Sheet
-</a>
+<div class="spec-sheet-controls">
+  <button class="spec-sheet-btn pdf-download-btn"
+          onclick="downloadSpecSheetPDF(event, '{{ product.handle }}', '{{ product.title }}', '{{ product.featured_image.src }}');"
+          style="vertical-align: top;">
+    🖨️ Download PDF
+  </button>
+</div>
+
+<script>
+async function downloadSpecSheetPDF(e, handle, title, imageUrl) {
+  e?.preventDefault?.();
+  e?.stopPropagation?.();
+
+  const btn = e?.target;
+  if (btn) {
+    btn.textContent = '⏳ Generating...';
+    btn.disabled = true;
+  }
+
+  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
+    });
+
+    const { jsPDF } = window.jspdf;
+    const imgData = canvas.toDataURL('image/png');
+    const pdf = new jsPDF({
+      orientation: 'portrait',
+      unit: 'mm',
+      format: 'letter'
+    });
+
+    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);
+    }
+  } catch (err) {
+    console.error('PDF generation failed:', err);
+    if (btn) {
+      btn.textContent = '✗ Error - Try Again';
+      setTimeout(() => {
+        btn.textContent = '🖨️ Download PDF';
+        btn.disabled = false;
+      }, 2000);
+    }
+  }
+}
+</script>
 
 <style>
+.spec-sheet-controls {
+  display: inline-block;
+  margin: 0;
+}
+
 .spec-sheet-btn {
   display: inline-block;
+  padding: 10px 16px;
+  background: #2c5f7f;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 13px;
+  font-weight: 600;
+  transition: all 0.3s ease;
+  white-space: nowrap;
+}
+
+.spec-sheet-btn:hover {
+  background: #1e4557;
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+}
+
+.spec-sheet-btn:active {
+  transform: translateY(0);
+}
+
+.spec-sheet-btn:disabled {
+  opacity: 0.6;
+  cursor: not-allowed;
+}
+
+.pdf-download-btn {
+  vertical-align: top;
 }
 </style>
diff --git a/shopify/push-size-pills-hide-readout.sh b/shopify/push-size-pills-hide-readout.sh
new file mode 100644
index 00000000..eec20235
--- /dev/null
+++ b/shopify/push-size-pills-hide-readout.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+# Push dw-pdp-size-pills.js to LIVE theme — hide redundant .last_variant readout
+# (stray duplicate "Sample" beside the SIZE label) so the pills are the single control.
+set -euo pipefail
+cd "$(dirname "$0")"
+STORE="designer-laboratory-sandbox.myshopify.com"; API="2024-10"
+SECRETS="$HOME/Projects/secrets-manager/.env"
+TOK="${THEME_TOKEN:-$(grep -hE '^SHOPIFY_THEME_TOKEN=' "$SECRETS" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'"' ')}"
+[ -z "$TOK" ] && { echo "❌ No theme token."; exit 1; }
+echo "✓ token …${TOK: -4}"; mkdir -p theme-backups
+
+python3 - "$STORE" "$API" "$TOK" <<'PY'
+import sys, json, urllib.request, urllib.error, os.path, datetime
+store, api, tok = sys.argv[1:4]
+H = {"X-Shopify-Access-Token": tok, "Content-Type": "application/json"}
+stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+def api_get(u):
+    try: return json.load(urllib.request.urlopen(urllib.request.Request(u, headers=H)))
+    except urllib.error.HTTPError as e: print(f"❌ GET {e.code} {e.read().decode()[:200]}"); sys.exit(1)
+def api_put(u, d):
+    try:
+        req = urllib.request.Request(u, data=json.dumps(d).encode(), headers=H, method='PUT')
+        return json.load(urllib.request.urlopen(req))
+    except urllib.error.HTTPError as e: print(f"❌ PUT {e.code} {e.read().decode()[:400]}"); sys.exit(1)
+live = [t for t in api_get(f"https://{store}/admin/api/{api}/themes.json")["themes"] if t["role"]=="main"][0]
+tid = live["id"]; print(f"✓ LIVE theme: {live['name']} (ID: {tid})")
+key="assets/dw-pdp-size-pills.js"; local="theme-5.0-duplicate/assets/dw-pdp-size-pills.js"
+assert os.path.isfile(local), local
+content=open(local).read()
+try:
+    cur=api_get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?asset[key]={key}")["asset"]["value"]
+    bk=f"theme-backups/{key.replace('/','__')}.{tid}.{stamp}.bak"; open(bk,"w").write(cur)
+    print(f"  backup → {bk} ({len(cur)} bytes)")
+except Exception as e: print(f"  backup skip: {e}")
+print(f"📝 PUT {key} ({len(content)} bytes)...")
+r=api_put(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json", {"asset":{"key":key,"value":content}})
+assert "asset" in r, r
+chk=api_get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?asset[key]={key}")["asset"]["value"]
+print(f"   ✓ uploaded — hideRedundantReadout present live: {'hideRedundantReadout' in chk}")
+print("\n✅ LIVE: stray .last_variant readout now hidden on enhanced PDPs")
+PY
diff --git a/shopify/theme-5.0-duplicate/assets/dw-pdp-size-pills.js b/shopify/theme-5.0-duplicate/assets/dw-pdp-size-pills.js
index ec1860c3..8236ae0c 100644
--- a/shopify/theme-5.0-duplicate/assets/dw-pdp-size-pills.js
+++ b/shopify/theme-5.0-duplicate/assets/dw-pdp-size-pills.js
@@ -123,9 +123,20 @@
     });
   }
 
+  // The theme also renders a redundant ".last_variant" readout (e.g. a stray
+  // "Sample" floated beside the SIZE label) that duplicates the active pill.
+  // Hide it once the pills exist so the pills are the single source of truth.
+  function hideRedundantReadout() {
+    if (!document.querySelector('.dw-size-pills')) return; // only on enhanced PDPs
+    Array.prototype.forEach.call(document.querySelectorAll('.last_variant'), function (el) {
+      el.style.display = 'none';
+    });
+  }
+
   function init() {
     var selects = document.querySelectorAll(SELECTOR);
     Array.prototype.forEach.call(selects, buildPills);
+    hideRedundantReadout();
   }
 
   if (document.readyState === 'loading') {

← 8ac53559 stage: infinite scroll collection.liquid for dev theme push  ·  back to Designer Wallcoverings  ·  fix: infinite scroll for standard Shopify collection paginat e87f44f9 →