// 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 = `
- Assessed: ${entry.entry_date}
${entry.expiration ? `- Expires: ${entry.expiration}
` : ""}
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();
});