abe6cb020492b82545875d59c570d0a3acc51376
jcasper
Wed Feb 4 14:41:49 2026 -0800
Adjusting faceted composite cart logic to support tracks that are on
by default, refs #36320
diff --git src/hg/js/facetedComposite.js src/hg/js/facetedComposite.js
index 8c2298f5acc..e9da72619a2 100644
--- src/hg/js/facetedComposite.js
+++ src/hg/js/facetedComposite.js
@@ -33,30 +33,36 @@
: callback();
const toTitleCase = str =>
str.toLowerCase()
.split(/[_\s-]+/) // Split on underscore, space, or hyphen
.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
const escapeRegex = str => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const embeddedData = (() => {
// get data that was embedded in the HTML here to use them as globals
const dataTag = document.getElementById("app-data");
return dataTag ? JSON.parse(dataTag.innerText) : "";
})();
+ // Store initial checkbox states for delta computation on server
+ const initialState = {
+ dataElements: new Set(),
+ dataTypes: new Set()
+ };
+
function generateHTML() {
const container = document.createElement("div");
container.id = "myTag";
container.innerHTML = `
`;
// Instead of appending to body, append into the placeholder div
document.getElementById("metadata-placeholder").appendChild(container);
@@ -93,30 +99,33 @@
innerHTML: "Data type",
}));
Object.keys(embeddedData.dataTypes).forEach(name => {
const label = document.createElement("label");
label.innerHTML = `
${name}`;
selector.appendChild(label);
});
const selectedDataTypes = new Set( // get dataTypes selected initially
Object.entries(embeddedData.dataTypes).filter(([_, val]) => val === 1)
.map(([key]) => key)
);
// initialize data type checkboxes (using class instead of 'name')
document.querySelectorAll("input.cbgroup")
.forEach(cb => { cb.checked = selectedDataTypes.has(cb.value); });
+
+ // Capture initial data type state
+ initialState.dataTypes = new Set(selectedDataTypes);
}
function initTable(allData) {
const { metadata, rowToIdx, colNames } = allData;
const ordinaryColumns = colNames.map(key => ({ // all but checkboxes
data: key,
title: toTitleCase(key),
}));
const checkboxColumn = {
data: null,
orderable: false,
className: "select-checkbox",
defaultContent: "",
@@ -133,30 +142,32 @@
columns: columns,
responsive: true,
// autoWidth: true, // might help columns shrink to fit content
order: [[1, "asc"]], // sort by the first data column, not checkbox
pageLength: 50, // show 50 rows per page by default
lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
select: { style: "multi", selector: "td:first-child" },
initComplete: function() { // Check appropriate boxes
const api = this.api();
embeddedData.dataElements.forEach(rowName => {
const rowIndex = rowToIdx[rowName];
if (rowIndex !== undefined) {
api.row(rowIndex).select();
}
});
+ // Capture initial data element state
+ initialState.dataElements = new Set(embeddedData.dataElements);
},
drawCallback: function() { // Reset header "select all" checkbox
$("#select-all")
.prop("checked", false)
.prop("indeterminate", false);
},
});
// define inputs for search functionality for each column in the table
const row = document.querySelector("#theMetaDataTable thead").insertRow();
columns.forEach((col) => {
const cell = row.insertCell();
if (col.className === "select-checkbox") { // show selected items
const label = document.createElement("label");
label.title = "Show only selected rows";
@@ -313,58 +324,89 @@
clearBtn.addEventListener("click", () => {
cboxes.forEach(cb => cb.checked = false); // Uncheck all
// Recalculate the (now cleared) search term and update table
table.column(colIdx + 1).search("", true, false).draw();
});
// Prepend the "clear" button
attrDiv.insertBefore(clearBtn, attrDiv.children[1] || null);
});
return table; // to chain calls
} // end initFilters
function initSubmit(table) { // logic for the submit event
const { mdid, primaryKey } = embeddedData; // mdid: metadata identifier
const hasDataTypes = embeddedData.dataTypes &&
Object.keys(embeddedData.dataTypes).length > 0;
-
document.getElementById("Submit").addEventListener("click", (submitBtnEvent) => {
submitBtnEvent.preventDefault(); // hold the submit button event
- const selectedRows = table.rows({selected: true}).data().toArray();
- const uriForUpdate = new URLSearchParams({ "cartDump.metaDataId": mdid, "noDisplay": 1 });
- selectedRows.forEach(obj => // 'de' for data element
- uriForUpdate.append(`${mdid}.de`, obj[primaryKey]));
-
+ const currentDataTypes = [];
if (hasDataTypes) {
- // Collect checked data types
- const selectedDataTypes = [];
+ // Get current data type selections
document.querySelectorAll("input.cbgroup").forEach(cb => {
if (cb.checked) {
- selectedDataTypes.push(cb.value);
+ currentDataTypes.push(cb.value);
}
});
// Require at least one data type when the selector exists
- if (selectedDataTypes.length === 0) {
+ if (currentDataTypes.length === 0) {
alert("Please select at least one data type.");
return; // abort submission
}
- selectedDataTypes.forEach(dat => // 'dt' for data type
- uriForUpdate.append(`${mdid}.dt`, dat));
+ }
+
+ // Get current data element selections
+ const currentDataElements = table.rows({selected: true}).data().toArray()
+ .map(obj => obj[primaryKey]);
+
+ // Build the parameters for the cart update
+ const uriForUpdate = new URLSearchParams({
+ "cartDump.metaDataId": mdid,
+ "noDisplay": 1
+ });
+
+ // Data elements: was and now
+ if (initialState.dataElements.size > 0) {
+ initialState.dataElements.forEach(de =>
+ uriForUpdate.append(`${mdid}.de_was`, de));
+ } else {
+ uriForUpdate.append(`${mdid}.de_was`, "");
+ }
+ if (currentDataElements.length > 0) {
+ currentDataElements.forEach(de =>
+ uriForUpdate.append(`${mdid}.de_now`, de));
+ } else {
+ uriForUpdate.append(`${mdid}.de_now`, "");
+ }
+
+ if (hasDataTypes) {
+ // Data types: was and now
+ if (initialState.dataTypes.size > 0) {
+ initialState.dataTypes.forEach(dt => {
+ uriForUpdate.append(`${mdid}.dt_was`, dt);});
+ } else {
+ uriForUpdate.append(`${mdid}.dt_was`, "");
+ }
+ if (currentDataTypes.length > 0) {
+ currentDataTypes.forEach(dt => {
+ uriForUpdate.append(`${mdid}.dt_now`, dt);});
} else {
- // No data types configured for this track: send empty-string sentinel
- uriForUpdate.append(`${mdid}.dt`, "");
+ 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);
}
function loadDataAndInit() { // load data and call init functions
const { mdid, primaryKey, metadataUrl, colorSettingsUrl } = embeddedData;
const CACHE_KEY = mdid;