fa0fdadb2fe50750c9a4087c59711039174ba5c2
jcasper
  Mon Jun 1 21:57:25 2026 -0700
Part of making metadata tables in faceted composites get a
horizontal scrollbar, refs #36320

diff --git src/hg/js/facetedComposite.js src/hg/js/facetedComposite.js
index c3e8641ac03..170169d36b6 100644
--- src/hg/js/facetedComposite.js
+++ src/hg/js/facetedComposite.js
@@ -207,31 +207,34 @@
         // otherwise fall back to the first data column.
         let defaultSortCol = 1;  // column 0 is checkboxes, 1 is first data col
         if (embeddedData.defaultSortField) {
             const target = embeddedData.defaultSortField.replace(/^_+/, "").toLowerCase();
             const idx = colNames.findIndex(
                 c => c.replace(/^_+/, "").toLowerCase() === target);
             if (idx >= 0)
                 defaultSortCol = idx + 1;  // +1 for the checkbox column
         }
 
         const table = $("#theMetaDataTable").DataTable({
             data: metadata,
             deferRender: true,    // seems faster
             columns: columns,
             columnDefs: [ { targets:0, render: DataTable.render.select() } ],
-            responsive: true,
+            // 'responsive' (collapsing overflow columns) is intentionally off:
+            // a wide table instead gets an internal horizontal scrollbar via
+            // the .table-xscroll wrapper added at the end of initTable.
+            responsive: false,
             layout: {
                 topStart: 'pageLength',
                 topEnd: null,        // omit global search
                 bottomStart: 'info',
                 bottomEnd: 'paging'
             },
             order: [[defaultSortCol, "asc"]],
             pageLength: 25,       // show 25 rows per page by default
             lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
             language: {
                 lengthMenu: `Show _MENU_ ${itemLabel}`,
                 select: {
                     rows: {
                         0: "",
                         1: `1 ${singularLabel} selected`,
@@ -407,30 +410,42 @@
             const row = table.row(dataIndex);
             return row.select && row.selected();
         });
 
         // implement the 'select all' at the top of the checkbox column
         $("#select-all").closest("label").attr(
             "title", `Select all filtered ${itemLabel}`);
         $("#theMetaDataTable thead").on("click", "#select-all", function () {
             const rowIsChecked = this.checked;
             if (rowIsChecked) {
                 table.rows({ search: "applied" }).select();
             } else {
                 table.rows({ search: "applied" }).deselect();
             }
         });
+
+        // Wrap the table in a horizontally-scrolling box. When the metadata has
+        // many fields the table is wider than the viewport; this gives it its
+        // own internal X scrollbar instead of letting it spill off the right
+        // edge of the screen (which also dragged the "Show N" / paging controls
+        // off-screen). The toolbar rows stay outside this box, so they remain
+        // visible at the wrapper's width regardless of how wide the table gets.
+        const scrollBox = document.createElement("div");
+        scrollBox.className = "table-xscroll";
+        tableEl.parentNode.insertBefore(scrollBox, tableEl);
+        scrollBox.appendChild(tableEl);
+
         return table;
     }  // end initTable
 
 
     // Map of colName -> Map of unescapedValue -> spanElement, for dynamic counts
     const countSpans = new Map();
     // Filter state for cross-facet count computation
     const checkboxFilters = new Map();  // colName -> Set<string> (raw values)
     const textFilters = new Map();      // colName -> lowercase string
 
     function updateFacetCounts(metadata) {
         // For each facet, count values among rows that pass all OTHER filters
         // (excluding this facet's own checkbox filter). This way, unchecked
         // values show how many rows would be added if you checked them.
         for (const [facetCol, valMap] of countSpans) {