63e3449141555055de52e908c50fed88cb5782ae jcasper Wed May 27 07:43:25 2026 -0700 Resolving request to not impose title case on facet titles, refs #36320 diff --git src/hg/js/facetedComposite.js src/hg/js/facetedComposite.js index 3b3c88af412..fa46e91eac9 100644 --- src/hg/js/facetedComposite.js +++ src/hg/js/facetedComposite.js @@ -21,34 +21,32 @@ } const fetchUrl = `/cgi-bin/hgTrackUi?${fetchBody}`; const req = (fetchUrl.length > 2048 || embeddedData.udcTimeout) ? fetch("/cgi-bin/hgTrackUi", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: fetchBody, }) : fetch(fetchUrl, { method: "GET", headers: { "Content-Type": "application/x-www-form-urlencoded" }, }); return req.then(r => r.ok ? r.json() : null).catch(() => null); }; - const toTitleCase = str => - str.toLowerCase() - .split(/[_\s-]+/) // Split on underscore, space, or hyphen - .map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" "); + const toTitleStyle = str => + str.replace(/_+/g, " "); const escapeRegex = str => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // For primaryKey values that use the 'id|label' form, return just the id. // The label is for display only; the cart and rowToIdx need the bare id. const primaryKeyId = v => { if (v == null) return v; const s = String(v); const bar = s.indexOf("|"); return bar >= 0 ? s.slice(0, bar) : s; }; const embeddedData = (() => { // get data that was embedded in the HTML here to use them as globals const dataTag = document.getElementById("app-data"); @@ -138,31 +136,31 @@ const { metadata, rowToIdx, colNames } = allData; // Match subtrackUrls trackDb keys against metadata column names // ignoring leading underscores on either side, so authors can toggle // facet visibility by adding/removing a '_' prefix in the metadata // file without having to re-edit trackDb. const stripUnderscores = s => s.replace(/^_+/, ""); const subtrackUrls = Object.fromEntries( Object.entries(embeddedData.subtrackUrls || {}) .map(([k, v]) => [stripUnderscores(k), v]) ); const ordinaryColumns = colNames.map(key => { const col = { data: key, - title: toTitleCase(key.replace(/^_/, "")), + title: toTitleStyle(key.replace(/^_+/, "")), }; const urlTemplate = subtrackUrls[stripUnderscores(key)]; if (urlTemplate) { // Mirrors hgc/hgc.c:printIdOrLinks(): split cell on ',', each // token may be 'id|label' (id substitutes $$, label is shown). // urlTemplate is html-encoded server-side (htmlEncode in // hgTrackUi.c), so it's safe to interpolate into an href. col.render = (data, type) => { if (type !== "display") return data; if (data == null || data === "") return ""; const parts = String(data).split(",") .map(s => s.trim()) .filter(Boolean); return parts.map(tok => { let idForUrl = tok, label = tok, encode = true; @@ -506,31 +504,31 @@ }); if (otherValue !== null) { topToShow.push([otherKey, otherValue]); } if (topToShow.length <= 1) { // no point if there's only one group excludeCheckboxes.push(key); return; } // --- Build the facet group with collapsible structure --- const facetDiv = document.createElement("div"); facetDiv.classList.add("facet-group"); // Clickable heading that toggles collapse const heading = Object.assign(document.createElement("strong"), { - textContent: toTitleCase(key), + textContent: toTitleStyle(key), className: "facet-heading", }); facetDiv.appendChild(heading); // Collapsible body: holds Clear button + all checkboxes const facetBody = document.createElement("div"); facetBody.classList.add("facet-body"); // Clear button — built here so it lives inside the collapsible body const clearBtn = document.createElement("button"); clearBtn.textContent = "Clear"; clearBtn.type = "button"; facetBody.appendChild(clearBtn); // Build checkbox labels