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(); });