b8b56383b460baeded0eb044c5c50998564f4b9d
jcasper
  Sun Jan 25 22:35:48 2026 -0800
Initial pass at making data types optional for the new faceted
composite UI.  refs #36320

diff --git src/hg/js/facetedComposite.js src/hg/js/facetedComposite.js
index e14690b3867..2a2f31e613b 100644
--- src/hg/js/facetedComposite.js
+++ src/hg/js/facetedComposite.js
@@ -71,30 +71,35 @@
             method: "POST",
             headers: { "Content-Type": "application/x-www-form-urlencoded" },
             body: `hgsid=${hgsid}&db=${db}&${uriForUpdate}`,
         }).then(() => {
             // 'disable' any CSS named elements here to them keep out of cart
             const dtLength = submitBtnEvent.
                   target.form.querySelector("select[name$='_length']");
             if (dtLength) {
                 dtLength.disabled = true;
             }
             submitBtnEvent.target.form.submit();  // release submit event
         });
     }
 
     function initDataTypeSelector() {
+        // Skip if no dataTypes defined or empty object
+        if (!embeddedData.dataTypes || Object.keys(embeddedData.dataTypes).length === 0) {
+            return;
+        }
+
         const selector = document.getElementById("dataTypeSelector");
         selector.appendChild(Object.assign(document.createElement("label"), {
             innerHTML: "<b>Data type</b>",
         }));
         Object.keys(embeddedData.dataTypes).forEach(name => {
             const label = document.createElement("label");
             label.innerHTML = `
                 <input type="checkbox" class="cbgroup" value="${name}">${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')
@@ -224,31 +229,32 @@
 
         const filtersDiv = document.getElementById("filters");
         colNames.forEach((key) => {
             // skip attributes if they should be excluded from checkbox sets
             if (excludeCheckboxes.includes(key)) {
                 return;
             }
 
             const sortedPossibleVals = Array.from(possibleValues[key].entries());
             sortedPossibleVals.sort((a, b) => // neg: less-than
                  a[1] !== b[1] ? b[1] - a[1] : a[0].localeCompare(b[0]));
 
             // Use 'maxCheckboxes' most frequent items (if they appear > 1 time)
             let topToShow = sortedPossibleVals
                 .filter(([val, count]) =>
-                    val.trim().toUpperCase() !== "NA" && count > 1)
+                    val.trim().toUpperCase() !== "NA")  // why only > 1?
+                    //val.trim().toUpperCase() !== "NA" && count > 1)
                 .slice(0, maxCheckboxes);
 
             // Any "other/Other/OTHER" entry will be put at the end
             let otherKey = null, otherValue = null;
             topToShow = topToShow.filter(([val, value]) => {
                 if (val.toLowerCase() === "other") {
                     otherKey = val;
                     otherValue = value;
                     return false;
                 }
                 return true;
             });
             if (otherValue !== null) {
                 topToShow.push([otherKey, otherValue]);
             }
@@ -305,45 +311,60 @@
             clearBtn.textContent = "Clear";
             clearBtn.type = "button";  // prevent form submit if inside a form
             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]));
+
+            if (hasDataTypes) {
+                // Collect checked data types
                 const selectedDataTypes = [];
                 document.querySelectorAll("input.cbgroup").forEach(cb => {
                     if (cb.checked) {
                         selectedDataTypes.push(cb.value);
                     }
                 });
-            const uriForUpdate = new URLSearchParams({ "cartDump.metaDataId": mdid, "noDisplay": 1 });
-            selectedRows.forEach(obj =>  // 'de' for data element
-                uriForUpdate.append(`${mdid}.de`, obj[primaryKey]));
+                // Require at least one data type when the selector exists
+                if (selectedDataTypes.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));
+            } else {
+                // No data types configured for this track: send empty-string sentinel
+                uriForUpdate.append(`${mdid}.dt`, "");
+            }
             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;