← 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
M shopify/_cwGRID/snippets/spec-sheet-button.liquidA shopify/push-size-pills-hide-readout.shM shopify/theme-5.0-duplicate/assets/dw-pdp-size-pills.js
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 →