6f41c6a196697c175a2cf5abcbd247ba24a2fe8e
jcasper
Mon Jun 15 09:22:37 2026 -0700
Loading spinner for faceted composites (the page must first do the ajax metadata
fetch, then process the result with dataTables). refs #36320
diff --git src/hg/js/facetedComposite.js src/hg/js/facetedComposite.js
index 170169d36b6..4bf78703583 100644
--- src/hg/js/facetedComposite.js
+++ src/hg/js/facetedComposite.js
@@ -21,30 +21,43 @@
}
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 showLoading = () => { // spinner shown during fetch + table build
+ if (document.getElementById("faceted-loading")) return;
+ const el = document.createElement("div");
+ el.id = "faceted-loading";
+ el.innerHTML =
+ `
Loading metadata…
`;
+ document.getElementById("metadata-placeholder").appendChild(el);
+ };
+ const hideLoading = () => {
+ const el = document.getElementById("faceted-loading");
+ if (el) el.remove();
+ };
+
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 = (() => {
@@ -765,30 +778,31 @@
} else {
uriForUpdate.append(`${mdid}.dt_now`, "");
}
}
// No ${mdid}.dt* variables indicates that the composite doesn't use data types
updateVisibilities(uriForUpdate, submitBtnEvent);
});
} // end initSubmit
function initAll(dataForTable) {
initDataTypeSelector();
const table = initTable(dataForTable);
initFilters(table, dataForTable);
initSubmit(table);
+ hideLoading(); // table is built and drawn; remove the spinner
}
function loadDataAndInit() { // load data and call init functions
const { mdid, primaryKey, metadataUrl, colorSettingsUrl, track } = embeddedData;
const paramsFromUrl = new URLSearchParams(window.location.search);
const hgsid = paramsFromUrl.get("hgsid");
let fetchBody = `fileUrl=${metadataUrl}&track=${track}`;
if (hgsid !== null) {
fetchBody = fetchBody + `&hgsid=${hgsid}`;
}
// fetch file dynamically
const fetchUrl = "/cgi-bin/hgTrackUi?" + fetchBody;
const req = (fetchUrl.length > 2048 || embeddedData.udcTimeout) ?
@@ -828,32 +842,34 @@
row[primaryKey] != null && String(row[primaryKey]).includes(","));
if (badPk)
throw new Error(
`primaryKey column '${primaryKey}' contains a comma in value ` +
`'${badPk[primaryKey]}'; commas are not allowed in primaryKey values`);
const rowToIdx = Object.fromEntries(
metadata.map((row, i) => [primaryKeyId(row[primaryKey]), i])
);
colorMap = isValidColorMap(colorMap) ? colorMap : null;
const freshData = { metadata, rowToIdx, colNames, colorMap };
initAll(freshData);
});
})
.catch(err => {
+ hideLoading(); // stop the spinner before showing the error
const table = document.getElementById("theMetaDataTable");
if (table) {
table.innerHTML =
`| ` +
`Error loading metadata: ${err.message} |
`;
}
});
} // end loadDataAndInit
document.addEventListener("keydown", e => { // block accidental submit
if (e.key === "Enter") { e.preventDefault(); e.stopPropagation(); }
}, true);
generateHTML();
+ showLoading(); // show spinner immediately, before the metadata fetch
loadDataAndInit();
});