| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718 |
- (function () {
- 'use strict';
- // Security: validate any member id before fetch / DOM use.
- var ID_RE = /^[A-Z]\d{6}$|^S\d{3,4}$/;
- var LS_KEY = 'polisci:v119:lastMember';
- var COLORS = {
- R: '#d9534f', D: '#337ab7', I: '#5cb85c',
- helpedR: '#d9534f', helpedD: '#337ab7',
- helpedBoth: '#8e44ad', helpedNeither: '#7f8c8d',
- green: '#27ae60', red: '#c0392b'
- };
- var ALIGN_KEYS = ['Helped Republicans', 'Helped Democrats', 'Helped Both', 'Helped Neither'];
- var ALIGN_COLORS = [COLORS.helpedR, COLORS.helpedD, COLORS.helpedBoth, COLORS.helpedNeither];
- var state = {
- manifest: null,
- membersById: {}, // id -> manifest entry
- BASE: './data/',
- currentId: null,
- currentMember: null,
- sortCol: null,
- sortAsc: true,
- selectedStates: null, // null => all
- skipPush: false,
- urlReplaceTimer: 0
- };
- var els = {};
- var charts = {};
- document.addEventListener('DOMContentLoaded', init);
- function init() {
- var root = document.getElementById('polisci-root');
- if (!root) return;
- state.BASE = root.dataset.base || './data/';
- els.root = root;
- els.note = document.getElementById('member-note');
- els.summary = document.getElementById('member-summary');
- els.kpis = document.getElementById('kpi-cards');
- els.search = document.getElementById('member-search');
- els.results = document.getElementById('member-results');
- els.lastGen = document.getElementById('last-generated');
- els.filterChamber = document.getElementById('filter-chamber');
- els.filterParty = document.getElementById('filter-party');
- els.filterState = document.getElementById('filter-state-select');
- els.filterReset = document.getElementById('filter-reset');
- els.sidebarToggle = document.getElementById('sidebar-toggle');
- els.sidebar = document.getElementById('sidebar');
- els.tableBody = document.querySelector('#vote-table tbody');
- els.tableHead = document.querySelector('#vote-table thead');
- var inline = document.getElementById('polisci-manifest');
- if (inline && inline.textContent) {
- try { onManifest(JSON.parse(inline.textContent)); return; }
- catch (e) { /* fall through to fetch */ }
- }
- var url = state.BASE + 'manifest.json';
- fetch(url, { credentials: 'omit' })
- .then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
- .then(onManifest)
- .catch(function (e) { showFatal('Failed to load manifest: ' + e.message + ' — if you opened this file directly, serve it over HTTP (e.g., `cd results/119 && python3 -m http.server`).'); });
- }
- function showFatal(msg) {
- els.root.replaceChildren();
- var p = document.createElement('p');
- p.style.padding = '20px';
- p.style.color = '#c0392b';
- p.textContent = msg;
- els.root.appendChild(p);
- }
- function onManifest(m) {
- state.manifest = m;
- var members = m.members || [];
- for (var i = 0; i < members.length; i++) {
- state.membersById[members[i].id] = members[i];
- }
- els.lastGen.textContent = m.generated_at || '';
- populateStateOptions(members);
- wireEvents();
- buildCharts();
- initSortHeaders();
- restoreFiltersFromURL();
- initialSelect();
- window.addEventListener('popstate', onPopState);
- }
- function populateStateOptions(members) {
- var states = {};
- for (var i = 0; i < members.length; i++) states[members[i].s] = true;
- var keys = Object.keys(states).sort();
- els.filterState.replaceChildren();
- for (var j = 0; j < keys.length; j++) {
- var opt = document.createElement('option');
- opt.value = keys[j];
- opt.textContent = keys[j];
- opt.selected = true;
- els.filterState.appendChild(opt);
- }
- }
- function wireEvents() {
- var chamberInputs = els.filterChamber.querySelectorAll('input[type=checkbox]');
- var partyInputs = els.filterParty.querySelectorAll('input[type=checkbox]');
- chamberInputs.forEach(function (i) { i.addEventListener('change', onFilterChange); });
- partyInputs.forEach(function (i) { i.addEventListener('change', onFilterChange); });
- els.filterState.addEventListener('change', onFilterChange);
- els.filterReset.addEventListener('click', resetFilters);
- els.search.addEventListener('input', onFilterChange);
- els.search.addEventListener('focus', refreshFilter);
- document.addEventListener('click', function (e) {
- if (!els.results.contains(e.target) && e.target !== els.search) {
- els.results.classList.add('is-hidden');
- els.search.setAttribute('aria-expanded', 'false');
- }
- });
- if (els.sidebarToggle) {
- els.sidebarToggle.addEventListener('click', function () {
- var open = els.sidebar.classList.toggle('is-open');
- els.sidebarToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
- });
- }
- }
- function onFilterChange() {
- refreshFilter();
- debounceUrlReplace();
- }
- function resetFilters() {
- var inputs = els.filterChamber.querySelectorAll('input[type=checkbox]');
- inputs.forEach(function (i) { i.checked = true; });
- inputs = els.filterParty.querySelectorAll('input[type=checkbox]');
- inputs.forEach(function (i) { i.checked = true; });
- for (var j = 0; j < els.filterState.options.length; j++) {
- els.filterState.options[j].selected = true;
- }
- els.search.value = '';
- refreshFilter();
- debounceUrlReplace();
- }
- function getCheckedValues(group) {
- var out = [];
- var inputs = group.querySelectorAll('input[type=checkbox]:checked');
- inputs.forEach(function (i) { out.push(i.value); });
- return out;
- }
- function getSelectedStates() {
- var out = [];
- for (var i = 0; i < els.filterState.options.length; i++) {
- if (els.filterState.options[i].selected) out.push(els.filterState.options[i].value);
- }
- return out;
- }
- function initialsOf(name) {
- var parts = name.split(/\s+/);
- var out = '';
- for (var i = 0; i < parts.length; i++) {
- if (parts[i]) out += parts[i].charAt(0).toUpperCase();
- }
- return out;
- }
- function refreshFilter() {
- var chambers = getCheckedValues(els.filterChamber);
- var parties = getCheckedValues(els.filterParty);
- var states = getSelectedStates();
- var q = (els.search.value || '').trim();
- var qLower = q.toLowerCase();
- var qUpper = q.toUpperCase();
- var initialsMode = q.length > 0 && q.length <= 4 && /^[A-Za-z]+$/.test(q);
- var members = state.manifest.members;
- var matches = [];
- for (var i = 0; i < members.length && matches.length < 50; i++) {
- var m = members[i];
- if (chambers.indexOf(m.c) < 0) continue;
- if (parties.indexOf(m.p) < 0) continue;
- if (states.indexOf(m.s) < 0) continue;
- if (q) {
- var nameLower = m.n.toLowerCase();
- var hit = nameLower.indexOf(qLower) >= 0;
- if (!hit && initialsMode) {
- if (initialsOf(m.n) === qUpper) hit = true;
- }
- if (!hit) continue;
- }
- matches.push(m);
- }
- matches.sort(function (a, b) { return a.n.localeCompare(b.n); });
- renderResults(matches, q);
- }
- function renderResults(matches, q) {
- els.results.replaceChildren();
- if (!q && matches.length === state.manifest.members.length) {
- els.results.classList.add('is-hidden');
- els.search.setAttribute('aria-expanded', 'false');
- return;
- }
- if (!matches.length) {
- var li0 = document.createElement('li');
- li0.textContent = 'No members match.';
- li0.setAttribute('aria-disabled', 'true');
- els.results.appendChild(li0);
- } else {
- for (var i = 0; i < matches.length; i++) {
- var m = matches[i];
- var li = document.createElement('li');
- li.setAttribute('role', 'option');
- li.setAttribute('data-id', m.id);
- li.textContent = m.n + ' (' + m.p + '-' + m.s + ', ' + (m.c === 'H' ? 'House' : 'Senate') + ')';
- li.addEventListener('click', onResultClick);
- els.results.appendChild(li);
- }
- }
- els.results.classList.remove('is-hidden');
- els.search.setAttribute('aria-expanded', 'true');
- }
- function onResultClick(e) {
- var id = e.currentTarget.getAttribute('data-id');
- if (!isValidId(id)) return;
- els.results.classList.add('is-hidden');
- els.search.setAttribute('aria-expanded', 'false');
- selectMember(id);
- }
- function isValidId(id) {
- return typeof id === 'string' && ID_RE.test(id) && Object.prototype.hasOwnProperty.call(state.membersById, id);
- }
- function selectMember(id) {
- if (!isValidId(id)) return;
- var version = state.manifest.version || '';
- var url = state.BASE + 'members/' + encodeURIComponent(id) + '.json?v=' + encodeURIComponent(version);
- fetch(url, { credentials: 'omit' })
- .then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
- .then(function (member) {
- state.currentId = id;
- state.currentMember = member;
- renderMember(member);
- try { localStorage.setItem(LS_KEY, id); } catch (_) {}
- if (!state.skipPush) {
- var u = buildShareUrl(id);
- history.pushState({ id: id }, '', u);
- }
- state.skipPush = false;
- document.title = member.name + ' — 119th Congress Voting Dashboard';
- })
- .catch(function (e) { showError('Failed to load member ' + id + ': ' + e.message); });
- }
- function showError(msg) {
- els.summary.replaceChildren();
- var p = document.createElement('p');
- p.style.color = '#c0392b';
- p.textContent = msg;
- els.summary.appendChild(p);
- }
- function renderMember(member) {
- renderSummary(member);
- renderNote(member);
- renderKPIs(member);
- updateCharts(member);
- renderTable(member.metrics.rows || []);
- }
- function renderSummary(m) {
- els.summary.replaceChildren();
- var h = document.createElement('h2');
- h.textContent = m.name;
- h.style.margin = '0 0 6px';
- els.summary.appendChild(h);
- var sub = document.createElement('div');
- sub.style.color = 'var(--ps-text-muted)';
- sub.style.fontSize = '0.92rem';
- var chamberLabel = m.chamber === 'house' ? 'House' : 'Senate';
- var distSuffix = (m.district && m.chamber === 'house') ? '-' + m.district : '';
- var endDate = m.served_to || 'present';
- sub.textContent = chamberLabel + ' · ' + m.party + '-' + m.state + distSuffix +
- ' · served ' + (m.served_from || '?') + ' – ' + endDate;
- els.summary.appendChild(sub);
- }
- var TERRITORY_NAMES = {
- AS: 'American Samoa', DC: 'the District of Columbia', GU: 'Guam',
- MP: 'the Northern Mariana Islands', PR: 'Puerto Rico', VI: 'the U.S. Virgin Islands'
- };
- function memberLink(bg) {
- // Returns a span (or anchor if the bg is in the manifest) — caller appends to a node.
- var entry = state.membersById[bg];
- if (!entry) {
- var span = document.createElement('span');
- span.textContent = bg;
- return span;
- }
- var a = document.createElement('a');
- a.textContent = entry.n + ' (' + entry.p + '-' + entry.s + ')';
- a.href = 'app.html?id=' + encodeURIComponent(bg);
- return a;
- }
- function appendText(node, text) {
- node.appendChild(document.createTextNode(text));
- }
- function renderNote(m) {
- els.note.replaceChildren();
- els.note.classList.add('is-hidden');
- var voting = (m.metrics && m.metrics.voting) || 0;
- var total = (m.metrics && m.metrics.total) || 0;
- var isDelegate = m.is_delegate === true;
- var partial = m.served_partial === true;
- var term = m.congress_term || {};
- var startYear = term.startYear;
- var endYear = term.endYear;
- var died = !!m.death_year;
- // Never-seated: appears in vote data (total > 0) but cast no Yea/Nay AND
- // was not a delegate, did not die, and was not in the directory as a
- // currently-serving member with prior tenure. Examples: Gaetz (119th).
- var unseated = (
- !isDelegate && !partial && voting === 0 && total > 0 && !died
- );
- if (isDelegate) {
- var terr = TERRITORY_NAMES[m.state] || m.state;
- appendText(els.note,
- 'Note: This member is the non-voting delegate from ' + terr +
- '. House delegates may vote in committees and on amendments in the ' +
- 'Committee of the Whole, but cannot vote on final passage on the ' +
- 'House floor. Their low participation rate is structural, not absenteeism.');
- els.note.classList.remove('is-hidden');
- return;
- }
- if (unseated) {
- appendText(els.note,
- 'Note: This member appears once in the 119th Congress roll-call data ' +
- '(typically on the opening-day quorum call) but cast no recorded ' +
- 'votes — likely a member-elect who resigned, declined the seat, or ' +
- 'was otherwise never seated. The Total Votes denominator (' + total +
- ') reflects all House roll calls, not their attendance.');
- if (m.replaced_by) {
- appendText(els.note, ' The seat was subsequently filled by ');
- els.note.appendChild(memberLink(m.replaced_by));
- appendText(els.note, '.');
- }
- els.note.classList.remove('is-hidden');
- return;
- }
- if (died) {
- appendText(els.note, 'Note: This member died in office in ' + m.death_year + '. ');
- if (startYear) appendText(els.note, 'Their 119th-Congress service ran from ' + startYear + ' until their death. ');
- appendText(els.note, 'KPIs reflect only the votes they cast before then');
- if (m.replaced_by) {
- appendText(els.note, '. The seat was subsequently filled by ');
- els.note.appendChild(memberLink(m.replaced_by));
- }
- appendText(els.note, '.');
- els.note.classList.remove('is-hidden');
- return;
- }
- if (m.replaced_by) {
- appendText(els.note, 'Note: This member left office during the 119th Congress');
- if (startYear && endYear) appendText(els.note, ' (served ' + startYear + '–' + endYear + ')');
- appendText(els.note, '. They were succeeded by ');
- els.note.appendChild(memberLink(m.replaced_by));
- appendText(els.note, '. KPIs reflect only the portion of the term they served.');
- els.note.classList.remove('is-hidden');
- return;
- }
- if (m.replaces) {
- appendText(els.note, 'Note: This member entered the 119th Congress mid-term, succeeding ');
- els.note.appendChild(memberLink(m.replaces));
- appendText(els.note, '. KPIs reflect only the portion of the term they have served so far.');
- els.note.classList.remove('is-hidden');
- return;
- }
- if (partial || total === 0) {
- var endDate = m.served_to || 'present';
- appendText(els.note,
- 'This member did not cast roll-call votes during the period analyzed ' +
- '(served ' + (m.served_from || '?') + ' – ' + endDate + '). ' +
- 'The dashboards below reflect that absence.');
- els.note.classList.remove('is-hidden');
- }
- }
- function pct(n, d) {
- if (!d) return '0%';
- return ((n / d) * 100).toFixed(1) + '%';
- }
- function renderKPIs(m) {
- var x = m.metrics || {};
- var voting = x.voting || 0;
- var kpis = [
- ['Total Votes', String(x.total || 0)],
- ['Yea / Nay', (x.yeas || 0) + ' / ' + (x.nays || 0)],
- ['Participation', pct(voting, x.total || 0)],
- ['Voted With GOP', (x.voted_with_gop || 0) + ' (' + pct(x.voted_with_gop || 0, voting) + ')'],
- ['Voted With Dem', (x.voted_with_dem || 0) + ' (' + pct(x.voted_with_dem || 0, voting) + ')'],
- ['Voted Against GOP', (x.voted_against_gop || 0) + ' (' + pct(x.voted_against_gop || 0, voting) + ')'],
- ['Voted Against Dem', (x.voted_against_dem || 0) + ' (' + pct(x.voted_against_dem || 0, voting) + ')'],
- ['Lone Wolf Votes', String(x.lone_wolf || 0)]
- ];
- var cards = els.kpis.querySelectorAll('.kpi-card');
- for (var i = 0; i < cards.length; i++) {
- var label = cards[i].querySelector('.kpi-label');
- var value = cards[i].querySelector('.kpi-value');
- if (i < kpis.length) {
- label.textContent = kpis[i][0];
- value.textContent = kpis[i][1];
- } else {
- label.textContent = '';
- value.textContent = '';
- }
- }
- }
- function buildCharts() {
- if (typeof Chart === 'undefined') return;
- var common = { responsive: true, maintainAspectRatio: false };
- charts.voteDist = new Chart(document.getElementById('chart-vote-dist'), {
- type: 'bar',
- data: { labels: [], datasets: [{ label: 'Member vote', data: [], backgroundColor: ['#27ae60', '#c0392b', '#f39c12', '#7f8c8d'] }] },
- options: Object.assign({}, common, { plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true } } })
- });
- charts.alignment = new Chart(document.getElementById('chart-alignment'), {
- type: 'doughnut',
- data: { labels: ALIGN_KEYS.slice(), datasets: [{ data: [0, 0, 0, 0], backgroundColor: ALIGN_COLORS.slice() }] },
- options: Object.assign({}, common, {
- plugins: {
- legend: { position: 'bottom' },
- tooltip: {
- callbacks: {
- label: function (ctx) {
- var dataArr = ctx.dataset.data || [];
- var sum = 0;
- for (var i = 0; i < dataArr.length; i++) sum += Number(dataArr[i]) || 0;
- var v = Number(ctx.parsed) || 0;
- var p = sum > 0 ? ((v / sum) * 100).toFixed(1) + '%' : '0%';
- return String(ctx.label) + ': ' + v + ' (' + p + ')';
- }
- }
- }
- }
- })
- });
- charts.blocked = new Chart(document.getElementById('chart-blocked'), {
- type: 'bar',
- data: {
- labels: ['Dem-Backed', 'GOP-Backed'],
- datasets: [{ label: 'Blocked', data: [0, 0], backgroundColor: [COLORS.helpedD, COLORS.helpedR] }]
- },
- options: Object.assign({}, common, { indexAxis: 'y', plugins: { legend: { display: false } }, scales: { x: { beginAtZero: true } } })
- });
- charts.alignmentTime = new Chart(document.getElementById('chart-alignment-time'), {
- type: 'line',
- data: {
- labels: [],
- datasets: ALIGN_KEYS.map(function (k, i) {
- return {
- label: k, data: [], borderColor: ALIGN_COLORS[i], backgroundColor: ALIGN_COLORS[i],
- tension: 0.2, fill: false
- };
- })
- },
- options: Object.assign({}, common, { plugins: { legend: { position: 'bottom' } }, scales: { y: { beginAtZero: true } } })
- });
- charts.withAgainst = new Chart(document.getElementById('chart-with-against'), {
- type: 'bar',
- data: {
- labels: ['GOP majority', 'Dem majority'],
- datasets: [
- { label: 'Voted With', data: [0, 0], backgroundColor: COLORS.green },
- { label: 'Voted Against', data: [0, 0], backgroundColor: COLORS.red }
- ]
- },
- options: Object.assign({}, common, { plugins: { legend: { position: 'bottom' } }, scales: { y: { beginAtZero: true } } })
- });
- }
- function updateCharts(member) {
- if (typeof Chart === 'undefined') return;
- var x = member.metrics || {};
- var mv = x.member || {};
- var mvKeys = ['Yea', 'Nay', 'Present', 'Not Voting'];
- charts.voteDist.data.labels = mvKeys;
- charts.voteDist.data.datasets[0].data = mvKeys.map(function (k) { return Number(mv[k]) || 0; });
- charts.voteDist.update('none');
- var al = x.alignment || {};
- charts.alignment.data.datasets[0].data = ALIGN_KEYS.map(function (k) { return Number(al[k]) || 0; });
- charts.alignment.update('none');
- charts.blocked.data.datasets[0].data = [Number(x.blocked_dem_count) || 0, Number(x.blocked_rep_count) || 0];
- charts.blocked.update('none');
- var months = Array.isArray(x.months) ? x.months : [];
- var monthly = x.monthly || {};
- charts.alignmentTime.data.labels = months.slice();
- for (var i = 0; i < ALIGN_KEYS.length; i++) {
- var arr = monthly[ALIGN_KEYS[i]];
- charts.alignmentTime.data.datasets[i].data = Array.isArray(arr) ? arr.slice() : months.map(function () { return 0; });
- }
- charts.alignmentTime.update('none');
- charts.withAgainst.data.datasets[0].data = [Number(x.voted_with_gop) || 0, Number(x.voted_with_dem) || 0];
- charts.withAgainst.data.datasets[1].data = [Number(x.voted_against_gop) || 0, Number(x.voted_against_dem) || 0];
- charts.withAgainst.update('none');
- }
- // ---- Table ----
- var COL_DEFS = [
- { key: 'y', numeric: true },
- { key: 'r', numeric: true },
- { key: 'd', numeric: false },
- { key: 'ln', numeric: false },
- { key: 'q', numeric: false },
- { key: 'ds', numeric: false },
- { key: 'rs', numeric: false },
- { key: 'm', numeric: false },
- { key: 'ry_rn', numeric: false },
- { key: 'dy_dn', numeric: false },
- { key: 'a', numeric: false },
- { key: 'b', numeric: false }
- ];
- function initSortHeaders() {
- var ths = els.tableHead.querySelectorAll('th');
- for (var i = 0; i < ths.length; i++) {
- ths[i].style.cursor = 'pointer';
- ths[i].setAttribute('data-col-index', String(i));
- ths[i].addEventListener('click', onSortClick);
- }
- }
- function onSortClick(e) {
- var idx = Number(e.currentTarget.getAttribute('data-col-index'));
- if (isNaN(idx)) return;
- if (state.sortCol === idx) {
- state.sortAsc = !state.sortAsc;
- } else {
- state.sortCol = idx;
- state.sortAsc = true;
- }
- updateSortIndicators();
- if (state.currentMember) renderTable(state.currentMember.metrics.rows || []);
- }
- function updateSortIndicators() {
- var ths = els.tableHead.querySelectorAll('th');
- for (var i = 0; i < ths.length; i++) {
- var base = ths[i].textContent.replace(/[▲▼]\s*$/, '').trim();
- ths[i].textContent = base + (i === state.sortCol ? (state.sortAsc ? ' ▲' : ' ▼') : '');
- }
- }
- function renderTable(rows) {
- var sorted = rows.slice();
- if (state.sortCol != null && state.sortCol >= 0 && state.sortCol < COL_DEFS.length) {
- var col = COL_DEFS[state.sortCol];
- var dir = state.sortAsc ? 1 : -1;
- sorted.sort(function (a, b) {
- var va, vb;
- if (col.key === 'ry_rn') { va = (a.ry || 0) - (a.rn || 0); vb = (b.ry || 0) - (b.rn || 0); }
- else if (col.key === 'dy_dn') { va = (a.dy || 0) - (a.dn || 0); vb = (b.dy || 0) - (b.dn || 0); }
- else { va = a[col.key]; vb = b[col.key]; }
- if (col.numeric) { va = Number(va) || 0; vb = Number(vb) || 0; return (va - vb) * dir; }
- va = String(va == null ? '' : va);
- vb = String(vb == null ? '' : vb);
- return va.localeCompare(vb) * dir;
- });
- }
- els.tableBody.replaceChildren();
- var frag = document.createDocumentFragment();
- for (var i = 0; i < sorted.length; i++) {
- var r = sorted[i];
- var tr = document.createElement('tr');
- appendCell(tr, r.y);
- appendCell(tr, r.r);
- appendCell(tr, r.d);
- appendCell(tr, r.ln);
- appendCell(tr, r.q);
- appendCell(tr, r.ds);
- appendCell(tr, r.rs);
- appendCell(tr, r.m);
- appendCell(tr, (r.ry || 0) + ' / ' + (r.rn || 0));
- appendCell(tr, (r.dy || 0) + ' / ' + (r.dn || 0));
- appendCell(tr, r.a);
- appendCell(tr, r.b);
- frag.appendChild(tr);
- }
- els.tableBody.appendChild(frag);
- }
- function appendCell(tr, val) {
- var td = document.createElement('td');
- td.textContent = val == null ? '' : String(val);
- tr.appendChild(td);
- }
- // ---- URL / history ----
- function buildShareUrl(id) {
- var params = new URLSearchParams();
- if (id) params.set('id', id);
- var q = (els.search.value || '').trim();
- if (q) params.set('q', q);
- var parties = getCheckedValues(els.filterParty);
- if (parties.length > 0 && parties.length < 3) params.set('p', parties.join(','));
- var chambers = getCheckedValues(els.filterChamber);
- if (chambers.length > 0 && chambers.length < 2) params.set('c', chambers.join(','));
- var statesSel = getSelectedStates();
- if (statesSel.length > 0 && statesSel.length < els.filterState.options.length) {
- params.set('s', statesSel.join(','));
- }
- var s = params.toString();
- return s ? ('?' + s) : window.location.pathname;
- }
- function debounceUrlReplace() {
- if (state.urlReplaceTimer) clearTimeout(state.urlReplaceTimer);
- // Debounce to avoid hammering history during keystrokes.
- state.urlReplaceTimer = setTimeout(function () {
- var u = buildShareUrl(state.currentId);
- history.replaceState({ id: state.currentId }, '', u);
- }, 250);
- }
- function restoreFiltersFromURL() {
- var params = new URLSearchParams(window.location.search);
- var p = params.get('p');
- var c = params.get('c');
- var s = params.get('s');
- var q = params.get('q');
- if (p) {
- var allowed = p.split(',');
- els.filterParty.querySelectorAll('input[type=checkbox]').forEach(function (i) {
- i.checked = allowed.indexOf(i.value) >= 0;
- });
- }
- if (c) {
- var allowedC = c.split(',');
- els.filterChamber.querySelectorAll('input[type=checkbox]').forEach(function (i) {
- i.checked = allowedC.indexOf(i.value) >= 0;
- });
- }
- if (s) {
- var allowedS = s.split(',');
- for (var i = 0; i < els.filterState.options.length; i++) {
- els.filterState.options[i].selected = allowedS.indexOf(els.filterState.options[i].value) >= 0;
- }
- }
- if (q) els.search.value = q;
- }
- function onPopState(e) {
- var params = new URLSearchParams(window.location.search);
- var id = params.get('id');
- if (id && isValidId(id)) {
- state.skipPush = true;
- selectMember(id);
- }
- }
- function initialSelect() {
- var params = new URLSearchParams(window.location.search);
- var urlId = params.get('id');
- if (urlId && isValidId(urlId)) {
- state.skipPush = true;
- selectMember(urlId);
- return;
- }
- var stored = null;
- try { stored = localStorage.getItem(LS_KEY); } catch (_) {}
- if (stored && isValidId(stored)) {
- state.skipPush = true;
- selectMember(stored);
- return;
- }
- // No selection — leave summary empty with a hint.
- els.summary.replaceChildren();
- var p = document.createElement('p');
- p.style.color = 'var(--ps-text-muted)';
- p.textContent = 'Pick a member to begin — type a name, set of initials (e.g., AOC), or use the filters at left.';
- els.summary.appendChild(p);
- }
- })();
|