www-from-model/static/publications.js
Stephan Heunis f6b8e6d434 html template, code, and styling to render a custom publications page
The markdown _index.md page is empty except for the title. Since publications is now included
as a taxonomy, the publications page will render the standard list of items on taxonomy list
pages. But since the new layout is included, that template will be rendered for the publicationbs
page. This template calls the additional publications.js script and custom css. The script reads
the publications.json data and builds filtering options from the data, and UX then allows filtering
publications based on these filter options. New options can be configured in the script.

TODO: improved styling of all aspects; mobile rendering of filter panel; linkage to internal and
external pids.
2026-03-30 15:38:25 +02:00

133 lines
No EOL
3.6 KiB
JavaScript

// Load data
const rawData = JSON.parse(document.getElementById("pub-data").textContent);
// Normalize data
const publications = rawData.map(p => ({
...p,
year: p.date ? new Date(p.date).getFullYear() : 'unknown',
topics: (p.topic || []).map(t => t.display_label),
authors: (p.author || []).map(a => `${a.given_name} ${a.family_name}`)
}));
// State
let state = {
search: "",
kind: new Set(),
topic: new Set(),
year: new Set()
};
// Build filter options dynamically
function getUniqueValues(field) {
const values = new Set();
publications.forEach(p => {
if (Array.isArray(p[field])) {
p[field].forEach(v => values.add(v));
} else if (p[field]) {
values.add(p[field]);
}
});
return Array.from(values).sort();
}
// Format 'kind' display values
function formatValue(field, value) {
if (value) {
if (field == "kind") {
return value.split(":").at(-1)
}
}
return value
}
// Render checkboxes for filters
function renderFilter(containerId, field, values) {
const container = document.getElementById(containerId);
values.forEach(value => {
const id = `${field}-${value}`;
const label = document.createElement("label");
label.style.display = "block";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.value = value;
checkbox.addEventListener("change", () => {
if (checkbox.checked) {
state[field].add(value);
} else {
state[field].delete(value);
}
render();
});
label.appendChild(checkbox);
label.appendChild(document.createTextNode(" " + formatValue(field, value)));
container.appendChild(label);
});
}
// Filtering logic
function matchesFilters(p) {
// Kind
if (state.kind.size && !state.kind.has(p.kind)) return false;
// Year
if (state.year.size && !state.year.has(String(p.year))) return false;
// Topic (multi-value)
if (state.topic.size) {
const match = p.topics.some(t => state.topic.has(t));
if (!match) return false;
}
return true;
}
// Search
function matchesSearch(p) {
if (!state.search) return true;
const text = (
p.title +
" " +
p.kind +
" " +
p.topics.join(" ") +
" " +
p.authors.join(" ")
).toLowerCase();
return text.includes(state.search);
}
// Render results
function render() {
const container = document.getElementById("results");
container.innerHTML = "";
const filtered = publications.filter(p =>
matchesFilters(p) && matchesSearch(p)
);
if (filtered.length === 0) {
container.innerHTML = "<p>No results</p>";
return;
}
filtered.forEach(p => {
const div = document.createElement("div");
div.className = "pub";
div.innerHTML = `
<h3><em>${p.title} ${p.year ? ' ('+p.year+')' : ''}</em></h3>
<p><strong>Authors:</strong> ${p.authors.join(", ")}</p>
<p><strong>Kind:</strong> ${formatValue("kind", p.kind)}</p>
<p><strong>Topics:</strong> ${p.topics.join(", ")}</p>
`;
container.appendChild(div);
});
}
// On startup:
// Build filters
renderFilter("filter-kind", "kind", getUniqueValues("kind"));
renderFilter("filter-topic", "topic", getUniqueValues("topics"));
renderFilter("filter-year", "year", getUniqueValues("year").map(String));
// Search input
document.getElementById("search").addEventListener("input", e => {
state.search = e.target.value.toLowerCase();
render();
});
// Initial render
render();