706d4aa403071faaf6646c05affbcfe896d427bd jcasper Mon Jun 1 16:32:25 2026 -0700 Moving to a more tab-style for selected/all decision in faceted composite UI, plus some improvements to the docs. refs #36320 diff --git src/hg/js/facetedComposite.js src/hg/js/facetedComposite.js index fa46e91eac9..c3e8641ac03 100644 --- src/hg/js/facetedComposite.js +++ src/hg/js/facetedComposite.js @@ -277,68 +277,84 @@ // Track previous selection count for detecting 0<->nonzero transitions let prevSelCount = table.rows({selected: true}).count(); // Update preferredVis when user manually changes the dropdown if (visDropdown) { visDropdown.addEventListener("change", function() { if (this.value !== "hide") { preferredVis = this.value; } }); } updateSelectAllCheckbox(table); // set initial state after pre-selections - // Create "show only selected" toggle in the toolbar + // Create "All / Selected" segmented tabs in the toolbar. These are a + // re-skin of a simple on/off selection filter: a hidden checkbox holds + // the filter state so the search-filter plug-in and selection handlers + // below can stay unchanged; the tabs just drive that checkbox. const lengthDiv = document.querySelector( "#theMetaDataTable_wrapper .dt-length"); const toggleWrapper = document.createElement("div"); toggleWrapper.id = "selected-filter"; - const toggleLabel = document.createElement("label"); - toggleLabel.classList.add("toggle-switch"); const toggleCheckbox = document.createElement("input"); toggleCheckbox.type = "checkbox"; toggleCheckbox.dataset.selectFilter = "true"; - toggleLabel.appendChild(toggleCheckbox); - toggleLabel.appendChild(Object.assign( - document.createElement("span"), {className: "toggle-slider"})); - toggleWrapper.appendChild(toggleLabel); - const toggleText = Object.assign( - document.createElement("span"), {id: "selected-filter-text"}); - toggleWrapper.appendChild(toggleText); + toggleCheckbox.style.display = "none"; + toggleWrapper.appendChild(toggleCheckbox); + const allTab = Object.assign(document.createElement("button"), + {type: "button", className: "filter-tab"}); + const selectedTab = Object.assign(document.createElement("button"), + {type: "button", className: "filter-tab"}); + toggleWrapper.appendChild(allTab); + toggleWrapper.appendChild(selectedTab); lengthDiv.appendChild(toggleWrapper); - // Disable toggle initially if no rows are selected - toggleCheckbox.disabled = (prevSelCount === 0); - + // Refresh the tab labels and the active-tab highlight. Counts are grand + // totals (default search:'none'), independent of the facet/search + // filters, so "Selected" never misleadingly reads 0 when tracks are + // selected but currently hidden by a facet. How many rows are actually + // visible is reported by DataTables' bottom info line. function updateSelectedText() { const selCount = table.rows({selected: true}).count(); const totalCount = table.rows().count(); - toggleText.textContent = - `Show only selected ${itemLabel} (${selCount} of ${totalCount} selected)`; + allTab.textContent = `All (${totalCount})`; + selectedTab.textContent = `Selected (${selCount})`; + const showSelected = toggleCheckbox.checked; + allTab.classList.toggle("active", !showSelected); + selectedTab.classList.toggle("active", showSelected); } updateSelectedText(); + // Clicking a tab switches the selection filter and redraws. "Selected" + // is always clickable; with nothing selected it just shows an empty list. + function setFilterMode(showSelected) { + toggleCheckbox.checked = showSelected; + table.draw(); + updateSelectedText(); + } + allTab.addEventListener("click", () => setFilterMode(false)); + selectedTab.addEventListener("click", () => setFilterMode(true)); + // Unified handler for selection changes function onSelectionChanged() { const selCount = table.rows({selected: true}).count(); - // Disable toggle when nothing is selected; auto-uncheck if count hits 0 - toggleCheckbox.disabled = (selCount === 0); - if (selCount === 0 && toggleCheckbox.checked) { - toggleCheckbox.checked = false; + // Keep the "Selected" view in sync as rows are (de)selected; no need + // to redraw while showing all rows. + if (toggleCheckbox.checked) { table.draw(); } // Auto-switch Display Mode on 0<->nonzero transitions if (visDropdown) { if (selCount === 0 && prevSelCount > 0) { visDropdown.value = "hide"; } else if (selCount > 0 && prevSelCount === 0) { visDropdown.value = preferredVis; } } updateSelectAllCheckbox(table); updateSelectedText(); prevSelCount = selCount; @@ -379,32 +395,30 @@ } else { textFilters.delete(colName); } table.column(dtColIdx).search(this.value).draw(); }); $.fn.dataTable.ext.search.push(function (_, data, dataIndex) { const filterInput = document.querySelector("input[data-select-filter]"); if (!filterInput?.checked) { // If checkbox not checked, show all rows return true; } // Otherwise, only show selected rows const row = table.row(dataIndex); return row.select && row.selected(); }); - $("#selected-filter input[data-select-filter]") - .on("change", function () { table.draw(); }); // 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(); } }); return table; } // end initTable