← back to Ca Donations
ca-donations: href-to-deeper-data — chips are real drill anchors + URL-synced tab/filter state (deep-link/back-button), clears href-drill regression
6af8822a0cb1d4f0a0cc0a96ddef17d82a85c7e7 · 2026-08-23 07:48:35 -0700 · steve
Files touched
Diff
commit 6af8822a0cb1d4f0a0cc0a96ddef17d82a85c7e7
Author: steve <steve@designerwallcoverings.com>
Date: Sun Aug 23 07:48:35 2026 -0700
ca-donations: href-to-deeper-data — chips are real drill anchors + URL-synced tab/filter state (deep-link/back-button), clears href-drill regression
---
public/index.html | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/public/index.html b/public/index.html
index 913596c..81fde10 100644
--- a/public/index.html
+++ b/public/index.html
@@ -85,10 +85,18 @@ const FILTERS = {
function chip(lbl, val, onClick) {
if (val == null || val === '') return '';
const safe = String(val).replace(/</g,'<');
- return `<span class="chip" data-click='${JSON.stringify(onClick)}'><span class="lbl">${lbl}</span>${safe}</span>`;
+ return `<a class="chip drill" data-filter="1" href="${hrefFor(onClick)}" data-click='${JSON.stringify(onClick)}'><span class="lbl">${lbl}</span>${safe}</a>`;
}
const money = (n) => n == null ? '' : '$' + Number(n).toLocaleString(undefined,{maximumFractionDigits:0});
+// ── href-to-deeper-data primitives (page-native over tab+state) — HARD rule 2026-07-31 ──
+// Every chip is a real, shareable href to its refined view (deep-link + back-button).
+function qstr(){ const st=state[tab]; const u=new URLSearchParams(); u.set('tab',tab); if(st.q)u.set('q',st.q); if(st.filter)u.set('filter',st.filter); if(st.sort)u.set('sort',st.sort); return '?'+u.toString(); }
+function hrefFor(o){ const t=(o&&o.t)||tab; const st={...state[t]}; if(o){ if(o.q!==undefined)st.q=o.q; if(o.filter!==undefined)st.filter=o.filter; if(o.filterNtee!==undefined)st.q=o.filterNtee; } const u=new URLSearchParams(); u.set('tab',t); if(st.q)u.set('q',st.q); if(st.filter)u.set('filter',st.filter); if(st.sort)u.set('sort',st.sort); return '?'+u.toString(); }
+function readURL(){ const u=new URLSearchParams(location.search); const t=u.get('tab'); if(t&&state[t])tab=t; const st=state[tab]; if(u.has('q'))st.q=u.get('q'); if(u.has('filter'))st.filter=u.get('filter'); if(u.has('sort'))st.sort=u.get('sort'); }
+function writeURL(push){ history[push?'pushState':'replaceState']({},'',qstr()); }
+window.qstr=qstr; window.readURL=readURL; window.writeURL=writeURL; window.hrefFor=hrefFor;
+
async function loadStats() {
const s = await (await fetch('/api/stats')).json();
$('#stats').innerHTML =
@@ -106,6 +114,7 @@ function syncControls() {
async function render() {
const st = state[tab];
+ try { writeURL(false); } catch(_) {}
let url, cards;
if (tab === 'orgs') {
url = `/api/orgs?q=${encodeURIComponent(st.q)}&status=${st.filter}&sort=${st.sort}&limit=200`;
@@ -114,8 +123,8 @@ async function render() {
<div class="kv">
${chip('EIN', r.ein, {t:'orgs',q:r.ein})}
${chip('City', r.city, {t:'orgs',q:r.city})}
- ${r.ntee_code ? `<span class="chip" data-click='${JSON.stringify({t:'orgs',filterNtee:r.ntee_code})}'><span class="lbl">NTEE</span>${r.ntee_code}</span>`:''}
- ${r.ca_ag_status ? `<span class="chip status-${r.ca_ag_status}" data-click='${JSON.stringify({t:'orgs',filter:r.ca_ag_status})}'><span class="lbl">CA AG</span>${r.ca_ag_status}</span>`:''}
+ ${r.ntee_code ? `<a class="chip drill" data-filter="1" href="${hrefFor({t:'orgs',filterNtee:r.ntee_code})}" data-click='${JSON.stringify({t:'orgs',filterNtee:r.ntee_code})}'><span class="lbl">NTEE</span>${r.ntee_code}</a>`:''}
+ ${r.ca_ag_status ? `<a class="chip drill status-${r.ca_ag_status}" data-filter="1" href="${hrefFor({t:'orgs',filter:r.ca_ag_status})}" data-click='${JSON.stringify({t:'orgs',filter:r.ca_ag_status})}'><span class="lbl">CA AG</span>${r.ca_ag_status}</a>`:''}
${chip('Grants', 'view →', {t:'grants',q:r.name})}
</div></div>`).join('');
view.innerHTML = rows.length ? `<div class="grid">${cards}</div>` : `<div class="empty">No orgs match.</div>`;
@@ -150,6 +159,8 @@ async function render() {
// href-drill: clicking any chip refines the query.
view.addEventListener('click', (e) => {
const el = e.target.closest('.chip'); if (!el || !el.dataset.click) return;
+ if (e.metaKey || e.ctrlKey) return; // let cmd/ctrl-click open the deep-link in a new tab
+ e.preventDefault();
const o = JSON.parse(el.dataset.click);
if (o.t) tab = o.t;
document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
@@ -169,6 +180,8 @@ document.querySelectorAll('.tabs button').forEach(b => b.onclick = () => {
syncControls(); render();
});
+try { readURL(); } catch(_) {}
+window.addEventListener('popstate', () => { try{readURL();}catch(_){} document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('active', b.dataset.tab === tab)); syncControls(); render(); });
syncControls(); loadStats(); render();
</script>
</body>
← b63091b auto-data-snapshot: 2026-08-22T18:14:40 (1 data files) — dat
·
back to Ca Donations
·
auto-data-snapshot: 2026-08-23T09:39:07 (1 data files) — dat 30429c4 →