[object Object]

← back to Astek Landing

astek-landing: load shared /drill.js instead of inline primitives (TK-10093)

7f57ba603cffc6e7dbd66249e11a35ec38ef11cb · 2026-07-31 11:43:00 -0700 · Steve

Migrated to the single-source module (fork B part 1). Removed inline qstr/readURL/
writeURL/drill/setF; added <script src=/drill.js>. Browser-verified: atom drill filters
(book=Residential 6167->2528), 0 pageerrors.

Files touched

Diff

commit 7f57ba603cffc6e7dbd66249e11a35ec38ef11cb
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Jul 31 11:43:00 2026 -0700

    astek-landing: load shared /drill.js instead of inline primitives (TK-10093)
    
    Migrated to the single-source module (fork B part 1). Removed inline qstr/readURL/
    writeURL/drill/setF; added <script src=/drill.js>. Browser-verified: atom drill filters
    (book=Residential 6167->2528), 0 pageerrors.
---
 public/drill.js   | 36 ++++++++++++++++++++++++++++++++++++
 public/index.html | 33 ++++-----------------------------
 2 files changed, 40 insertions(+), 29 deletions(-)

diff --git a/public/drill.js b/public/drill.js
new file mode 100644
index 0000000..e63cdd3
--- /dev/null
+++ b/public/drill.js
@@ -0,0 +1,36 @@
+// ── href-to-deeper-data primitives — SINGLE SOURCE OF TRUTH (HARD rule, 2026-07-31) ──
+// Every displayed data point must be an href to its deeper (filtered) data. These are the
+// reusable atoms; a viewer's index.html loads this file, then defines FIELDS, FIL, $, esc,
+// and applyFilters (which these reference at call time via the shared classic-script global
+// scope). Canonical copy: ~/Projects/_shared/web/drill.js → each viewer's public/drill.js.
+// Memory: all-data-points-href-to-deeper-data. Reference viewers: astek-landing, schumacher-internal.
+
+// Build the URL for a given filter set — any count/atom becomes a real, shareable link.
+function qstr(fil, q, pat) {
+  const u = new URLSearchParams();
+  for (const [k] of FIELDS) { if (fil[k]) u.set(k, fil[k]); }
+  if (q) u.set('q', q); if (pat) u.set('pat', pat);
+  const s = u.toString(); return s ? ('?' + s) : location.pathname;
+}
+// Read the URL's filter state back into FIL + the search inputs (deep-link / back-button).
+function readURL() {
+  const u = new URLSearchParams(location.search);
+  for (const [k] of FIELDS) FIL[k] = u.get(k) || '';
+  $('#q').value = u.get('q') || ''; $('#pattern').value = u.get('pat') || '';
+}
+// Reflect the current filter state into the URL (push = new history entry, else replace).
+function writeURL(push) {
+  const url = qstr(FIL, $('#q').value.trim(), $('#pattern').value.trim());
+  history[push ? 'pushState' : 'replaceState']({}, '', url);
+}
+// drill(field,value,label,cls) → an <a> whose href IS the filtered view for that value.
+// cmd/ctrl/middle-click opens it in a new tab; plain click filters in place (delegated listener).
+function drill(field, val, label, cls) {
+  if (val == null || val === '') return '';
+  const href = qstr({ ...FIL, [field]: String(val) }, $('#q').value.trim(), $('#pattern').value.trim());
+  return `<a class="drill ${cls || ''}" href="${href}" data-filter="${field}" data-v="${esc(val)}" title="Filter to ${esc(val)}">${label != null ? esc(label) : esc(val)}</a>`;
+}
+// Atom drill target: SET the filter to this value (drill in), vs pickF which TOGGLES.
+function setF(k, v) { if (FIL[k] === String(v)) return; FIL[k] = String(v); applyFilters('push'); }
+
+window.qstr = qstr; window.readURL = readURL; window.writeURL = writeURL; window.drill = drill; window.setF = setF;
diff --git a/public/index.html b/public/index.html
index 7831feb..11eae46 100644
--- a/public/index.html
+++ b/public/index.html
@@ -187,6 +187,7 @@ body.listmode #listhead{display:grid}
   <div id="empty" style="display:none">No products match.</div>
 </main>
 </div>
+<script src="/drill.js"></script>
 <script>
 const $=s=>document.querySelector(s);
 const grid=$('#grid');
@@ -195,32 +196,7 @@ const PAGE=120;
 const BUCKET_HEX={white:'#f4f4f2',grey:'#9aa0aa',black:'#1c1e24',pink:'#f3a8c0',red:'#d0453e',orange:'#e08a3c',
  brown:'#8a6242',gold:'#c9a75a',green:'#5f8f5c',blue:'#4f7fb5',purple:'#8a6fb5'};
 function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}
-// ── HARD RULE (2026-07-31): every data point is an href to its deeper (filtered) data ──
-// The filter state lives in the URL, so any count/atom becomes a real, shareable,
-// new-tab-able link — not just a JS button. qstr() builds the URL for a given filter set.
-function qstr(fil,q,pat){
-  const u=new URLSearchParams();
-  for(const [k] of FIELDS){ if(fil[k]) u.set(k,fil[k]); }
-  if(q) u.set('q',q); if(pat) u.set('pat',pat);
-  const s=u.toString(); return s?('?'+s):location.pathname;
-}
-function readURL(){
-  const u=new URLSearchParams(location.search);
-  for(const [k] of FIELDS) FIL[k]=u.get(k)||'';
-  $('#q').value=u.get('q')||''; $('#pattern').value=u.get('pat')||'';
-}
-function writeURL(push){
-  const url=qstr(FIL,$('#q').value.trim(),$('#pattern').value.trim());
-  history[push?'pushState':'replaceState']({},'',url);
-}
-// drill(field,value,label,cls) → an <a> whose href IS the filtered view for that value.
-// Real link: cmd/ctrl/middle-click opens the filtered grid in a new tab; plain click
-// filters in place (handled by the delegated listeners below). This is the reusable atom.
-function drill(field,val,label,cls){
-  if(val==null||val==='') return '';
-  const href=qstr({...FIL,[field]:String(val)},$('#q').value.trim(),$('#pattern').value.trim());
-  return `<a class="drill ${cls||''}" href="${href}" data-filter="${field}" data-v="${esc(val)}" title="Filter to ${esc(val)}">${label!=null?esc(label):esc(val)}</a>`;
-}
+// href-to-deeper-data primitives (qstr / readURL / writeURL / drill / setF) load from /drill.js
 // ── every data field is a left-panel table: collapsed on load, click value = filter ──
 const FIELDS=[
  ['book','Books'],['style','Styles'],['color_bucket','Colors'],['series','Patterns'],['color','Colorways'],
@@ -413,9 +389,8 @@ function renderSide(){
   }).join('');
   $('#clearf').style.display=(Object.values(FIL).some(Boolean)||$('#q').value||$('#pattern').value)?'block':'none';
 }
-function pickF(k,v){FIL[k]=(FIL[k]===v?'':v);applyFilters('push');}   // facet row: toggle
-function setF(k,v){if(FIL[k]===String(v))return;FIL[k]=String(v);applyFilters('push');} // atom: set (drill in)
-window.pickF=pickF;window.setF=setF;
+function pickF(k,v){FIL[k]=(FIL[k]===v?'':v);applyFilters('push');}   // facet row: toggle (setF is in /drill.js)
+window.pickF=pickF;
 // facet counts are real <a> hrefs — plain click filters in place, modified click opens the
 // filtered view in a new tab. Delegated on the container so it survives renderSide() rebuilds.
 $('#facetbox').addEventListener('click',e=>{

← 2e85817 auto-save: 2026-07-31T11:27:21 (1 files) — data/products.jso  ·  back to Astek Landing  ·  auto-save: 2026-07-31T11:57:35 (1 files) — data/products.jso 7ad902f →