public/assets/js/evm.js (131 lines of code) (raw):

import { getContentType } from "./utils.js"; const filterTopicsWithAnd = false; export class ExploreViewModel { constructor(templateNode, facetMenuNode, listingNode, lunrResources, latestContent, noResults) { this.templateNode = templateNode; this.facetMenuNode = facetMenuNode; this.listingNode = listingNode; this.lunrResources = lunrResources; this.latestContent = latestContent; this.noResults = noResults; // Set click handler for the whole menu this.facetMenuNode.addEventListener("change", (evt) => { this.handleSelection(evt); }); } getSelectedFacets() { const facetGroups = this.facetMenuNode.querySelectorAll("[data-facet-group]"); const results = { channels: [], topics: [], resources: [] }; let facetGroupKey, selections; let resultCounts = 0; facetGroups.forEach(fg => { facetGroupKey = fg.dataset.facetGroup; // Get the selected items selections = fg.querySelectorAll("input:checked"); results[facetGroupKey] = Array.from(selections).map(input => input.value); resultCounts += results[facetGroupKey].length; }); return resultCounts > 0 ? results : null; } handleSelection() { // Turn off the 3 boxes, then turn on the used one. this.listingNode.style.display = "none"; this.latestContent.style.display = "none"; this.noResults.style.display = "none"; // Get the currently-selected facets const selectedFacets = this.getSelectedFacets(); if (selectedFacets == null) { // No facets selected, restore the default listing this.latestContent.style.display = ""; return; } // Filter the list of resources const selectedResources = this.filterResources(selectedFacets); if (selectedResources.length > 0) { this.listingNode.style.display = ""; // Re-render this.renderCards(selectedResources); return; } // We have selected facets, but there were no results. Show // the no results box. this.noResults.style.display = ""; } filterResources(selectedFacets) { const { channels, resources, topics } = selectedFacets; return this.lunrResources.filter(resource => { if (channels.length && !channels.includes(resource.channel)) { if (!topics.length || (topics.length && resource.channel)) return false; } if (channels.length && !channels.includes(resource.channel)) { if (!(topics.length && !resource.channel)) { return false; } } if (resources.length && !resources.includes(resource.resourceType)) { return false; } if (topics.length) { if (filterTopicsWithAnd) { // Match all selected topics against the resource's topics. for (const topic of topics) { if (!resource.topics?.includes(topic)) { return false; } } } else { // Get intersection of selected topics and this resource's // topics array. If there is any overlap, it's true. return topics.filter(topic => resource.topics?.includes(topic)).length > 0; } } return true; }) .sort((a, b) => { // Sort in reverse date order if (a.datetime > b.datetime) { return -1; } if (a.datetime < b.datetime) { return 1; } return 0; }); } renderCards(filteredResources) { let clone; this.listingNode.replaceChildren(); filteredResources.forEach(resource => { clone = this.templateNode.content.cloneNode(true); // const mergeNodes = clone.querySelectorAll("*"); const merges = [...clone.querySelectorAll("*")] .map(t => [...t.attributes]) .reduce((curr, value) => curr.concat(value)) .filter(({ name }) => name.startsWith("data-template")) .map(attr => { return { operation: attr.name.split("data-template")[1].slice(1), value: attr.value, node: attr.ownerElement }; }); merges.forEach(merge => { const { operation, value, node } = merge; if (value === "contentType") { // We need to call the function with logic for displaying the // "content type" node.textContent = getContentType(resource.resourceType, resource.linkURL); } else if (operation === "") { // Special case, no "suffix", means change the text content node.textContent = resource[value]; } else { node.setAttribute(operation, resource[value]); } }); this.listingNode.appendChild(clone); }); } } // We are in a browser, not in a test. Go get the lunr JSON, and wire up the class. if (!window.happyDOM) { const templateNode = document.querySelector("#cardTemplate"); const facetNode = document.querySelector("#facetMenu"); const listingNode = document.querySelector("#listing"); const latestContent = document.querySelector("#latest-content"); const noResults = document.querySelector("#listing-no-results"); const jsonURL = new URL("/lunr.json", import.meta.url).href; fetch(jsonURL).then(response => { const jsonResources = response.json(); jsonResources.then(rj => { new ExploreViewModel(templateNode, facetNode, listingNode, rj.results, latestContent, noResults); }); }); }