// CHAD Filter Scripts (FINAL with fixed title, autocomplete, and fast initial render) document.addEventListener("DOMContentLoaded", function () { const xmlPath = "/assessments/all-xml"; const resultsContainer = document.getElementById("results"); const summaryContainer = document.getElementById("filter-summary"); const nameInput = document.getElementById("filter-name"); const functionInput = document.getElementById("filter-function"); const excludeInput = document.getElementById("filter-exclude"); const excludeSubmit = document.getElementById("submit-exclude"); const showExpiredCheckbox = document.getElementById("toggle-expired"); const clearAllButtons = document.querySelectorAll("#clear-all"); const noResultsContainer = document.getElementById("no-results"); const mobileToggleBtn = document.getElementById("toggle-filters-btn"); const filtersPanel = document.getElementById("filters-panel"); const sortBySelect = document.getElementById("sort-by"); const benchmarkSelect = document.getElementById("benchmark-filter"); const functionDatalist = document.getElementById("function-options"); const nameDatalist = document.getElementById("name-options"); let allEntries = []; let filteredEntries = []; function parseDate(dateStr) { return new Date(dateStr).getTime(); } function createTooltipWrapper(content, tooltip) { return ` ${content} `; } function truncateWords(text, wordLimit) { const words = text.split(/,\s*/).filter(w => w); const truncated = words.slice(0, wordLimit).join(", "); if (words.length > wordLimit) { return createTooltipWrapper(`${truncated}…`, text); } return truncated; } function truncateChars(text, maxLength) { const trimmed = text.replace(/[\s,]+$/, ""); if (trimmed.length > maxLength) { return createTooltipWrapper(`${trimmed.substring(0, maxLength).trim()}…`, text); } return trimmed; } function getFunctionTerms(entries) { const terms = new Set(); entries.forEach(entry => { entry.tags.split(/,\s*/).forEach(val => { if (val.trim()) terms.add(val.trim()); }); }); return Array.from(terms).sort(); } function getNameTerms(entries) { const terms = new Set(); entries.forEach(entry => { if (entry.casrn && entry.name) {terms.add(`${entry.casrn} - ${entry.name}`); } }); return Array.from(terms).sort(); } function populateFunctionDropdown(inputValue, dataSet) { functionDatalist.innerHTML = ""; if (inputValue.length < 2) return; const matches = getFunctionTerms(dataSet).filter(term => term.toLowerCase().includes(inputValue)).slice(0, 6); matches.forEach(val => { const opt = document.createElement("option"); opt.value = val; functionDatalist.appendChild(opt); }); } function populateNameDropdown(inputValue, dataSet) { nameDatalist.innerHTML = ""; if (inputValue.length < 2) return; const matches = getNameTerms(dataSet).filter(term => term.toLowerCase().includes(inputValue)).slice(0, 8); matches.forEach(val => { const opt = document.createElement("option"); opt.value = val; nameDatalist.appendChild(opt); }); } function populateBenchmarkDropdown(entries) { const values = new Set(entries.map(e => parseInt(e.benchmark)).filter(v => !isNaN(v))); benchmarkSelect.innerHTML = ``; [1, 2, 3, 4].forEach(bm => { if (values.has(bm)) { const opt = document.createElement("option"); opt.value = bm; opt.textContent = bm < 4 ? `${bm}+` : `4`; benchmarkSelect.appendChild(opt); } }); } function updateSummary(nameVal, funcVal, excludeVal, hideExpired, benchmarkVal) { const total = filteredEntries.length; const filters = []; if (nameVal) filters.push(`matching \"${nameVal}\"`); if (funcVal) filters.push(`with function \"${funcVal}\"`); if (excludeVal) filters.push(`excluding \"${excludeVal}\"`); if (benchmarkVal) filters.push(`Benchmark ${benchmarkVal}`); if (hideExpired) filters.push("excluding expired"); summaryContainer.innerHTML = `

${total} Assessments

${filters.length ? `Selected Entries ${filters.join(", ")}.` : `Selected Entries including expired.`} Sorted by ${sortBySelect.value.replace("_", " ")}

`; summaryContainer.focus(); } function renderEntriesInChunks(entries, chunkSize = 40) { resultsContainer.innerHTML = ""; noResultsContainer.style.display = entries.length === 0 ? "block" : "none"; const sortKey = sortBySelect.value; if (sortKey === "assessment_date") { entries.sort((a, b) => parseDate(b.entry_date) - parseDate(a.entry_date)); } else if (sortKey === "chemical_name") { entries.sort((a, b) => a.name.localeCompare(b.name)); } else if (sortKey === "chemical_cas") { entries.sort((a, b) => a.casrn.localeCompare(b.casrn)); } else if (sortKey === "benchmark") { entries.sort((a, b) => { const bmDiff = parseInt(b.benchmark) - parseInt(a.benchmark); return bmDiff !== 0 ? bmDiff : parseDate(b.entry_date) - parseDate(a.entry_date); }); } let index = 0; function renderChunk() { const end = Math.min(index + chunkSize, entries.length); for (; index < end; index++) { const entry = entries[index]; const div = document.createElement("div"); div.className = `col-12 border-bottom mb-3 bench-${entry.benchmark}`; div.innerHTML = `

${entry.name} - ${entry.casrn}

Benchmark: ${entry.benchmark}

Key Functions: ${truncateWords(entry.tags, 6)}${entry.tags}

${entry.synonyms ? `

Chemical Synonyms: ${truncateChars(entry.synonyms, 125)}${entry.synonyms}

` : ""} ${entry.chemical_notes ? `
${entry.chemical_notes}
` : ""} `; resultsContainer.appendChild(div); } if (index < entries.length) requestIdleCallback(renderChunk); } renderChunk(); } function applyFilters() { const nameVal = nameInput.value.toLowerCase(); const funcVal = functionInput.value.toLowerCase(); const excludeVal = excludeInput.value.toLowerCase(); const hideExpired = !showExpiredCheckbox.checked; const benchmarkVal = benchmarkSelect.value; filteredEntries = allEntries.filter(entry => { if (hideExpired && parseDate(entry.expiration) < Date.now()) return false; if (nameVal && !(entry.name + entry.casrn).toLowerCase().includes(nameVal)) return false; if (funcVal && !entry.tags.toLowerCase().includes(funcVal)) return false; if (excludeVal && (entry.name + entry.synonyms + entry.chemical_notes + entry.category + entry.tags).toLowerCase().includes(excludeVal)) return false; if (benchmarkVal && parseInt(entry.benchmark) < parseInt(benchmarkVal)) return false; return true; }); populateFunctionDropdown(functionInput.value.toLowerCase(), filteredEntries); populateNameDropdown(nameInput.value.toLowerCase(), filteredEntries); populateBenchmarkDropdown(filteredEntries); updateSummary(nameVal, funcVal, excludeVal, hideExpired, benchmarkVal); renderEntriesInChunks(filteredEntries); } nameInput.addEventListener("input", () => { populateNameDropdown(nameInput.value.toLowerCase(), filteredEntries); applyFilters(); }); functionInput.addEventListener("input", () => populateFunctionDropdown(functionInput.value.toLowerCase(), filteredEntries)); functionInput.addEventListener("change", applyFilters); showExpiredCheckbox.addEventListener("change", applyFilters); benchmarkSelect.addEventListener("change", applyFilters); sortBySelect.addEventListener("change", applyFilters); excludeSubmit.addEventListener("click", applyFilters); clearAllButtons.forEach(button => { button.addEventListener("click", () => { nameInput.value = ""; functionInput.value = ""; excludeInput.value = ""; showExpiredCheckbox.checked = true; benchmarkSelect.value = ""; sortBySelect.value = "assessment_date"; applyFilters(); }); }); if (mobileToggleBtn && filtersPanel) { mobileToggleBtn.addEventListener("click", () => filtersPanel.classList.toggle("d-none")); } function loadXML() { fetch(xmlPath) .then(response => response.text()) .then(str => new window.DOMParser().parseFromString(str, "text/xml")) .then(data => { const entries = data.querySelectorAll("entry"); allEntries = Array.from(entries).map(entry => { const get = tag => entry.querySelector(tag)?.textContent ?? ""; return { name: get("chemical_name"), casrn: get("casrn"), url: get("url_title"), expiration: get("expiration_date"), entry_date: get("assessment_date"), benchmark: get("benchmark"), synonyms: get("synonyms"), chemical_notes: get("chemical_notes"), category: get("main_category"), tags: get("tags") }; }); applyFilters(); }); } loadXML(); });