47ea57080b515e5dad5f658c58feb8944a7e7d61
chmalee
  Thu Jan 29 15:30:26 2026 -0800
Replace clade/assembly dropdowns with a search bar on most CGIs. Add a recents list to hgGateway and to the species bar and to the 'Genomes' dropdown menu. Track recently selected species in localStorage. Add toGenome and fromGenome arguemnts to hubApi/liftOver in order to find appropriate liftover assemblies, refs #36232

diff --git src/hg/js/hgSearch.js src/hg/js/hgSearch.js
index c013ff5b1a8..7073b0325be 100644
--- src/hg/js/hgSearch.js
+++ src/hg/js/hgSearch.js
@@ -619,78 +619,92 @@
                 $('#'+idAttr.value+"_" +categoryCount+"_showMoreButton").click(showMoreResults);
                 $('#'+idAttr.value + "_" + categoryCount + "_showMoreLink").click(showMoreResults);
                 categoryCount += 1;
             });
         } else if (uiState && typeof uiState.search !== "undefined") {
             // No results from match
             var msg = "<p>No results</p>";
             parentDiv.empty();
             parentDiv.html(msg);
             clearOldFacetCounts();
         } else {
             parentDiv.empty();
         }
     }
 
-    function fillOutAssemblies(e) {
-        organism = $("#speciesSelect")[0].value;
-        select = $("#dbSelect");
-        select.empty();
-        _.each(_.sortBy(uiState.genomes[organism], ['orderKey']), function(assembly) {
-            newOpt = document.createElement("option");
-            newOpt.value = assembly.name;
-            newOpt.label = trackHubSkipHubName(assembly.organism) + " " + assembly.description;
-            if (assembly.name == db) {
-                newOpt.selected = true;
-            }
-            $("#dbSelect").append(newOpt);
-        });
-        // if we are getting here from a change event on the species dropdown
-        // and are switching to the default assembly for a species, we can
-        // automatically send a search for this organism+assembly
-        if (typeof e !== "undefined") {
-            switchAssemblies($("#dbSelect")[0].value);
-        }
-    }
-
-    function buildSpeciesDropdown() {
-        // Process the species select dropdowns
-        _.each(uiState.genomes, function(genome) {
-            newOpt = document.createElement("option");
-            newOpt.value = genome[0].organism;
-            newOpt.label = trackHubSkipHubName(genome[0].organism);
-            if (genome.some(function(assembly) {
-                if (assembly.isCurated) {
-                    if (assembly.name === trackHubSkipHubName(db)) {
-                        return true;
+    function onGenomeSelect(item) {
+        // Called when user selects a genome from autocomplete
+        // item.genome is the db name from hubApi/findGenome
+        // Just update the label and store the selection - don't reload the page
+        if (item.disabled || !item.genome) return;
+        db = item.genome;
+        $("#currentGenome").text(item.commonName + ' - ' + item.genome);
+        $("#genomeSearchInput").val('');
     }
-                } else {
-                    if (assembly.name === db) {
-                        return true;
+
+    function onSearchError(jqXHR, textStatus, errorThrown, term) {
+        return [{label: 'No genomes found', value: '', genome: '', disabled: true}];
     }
+
+    function initGenomeAutocomplete() {
+        // Initialize the genome search autocomplete using the standard function from utils.js
+        initSpeciesAutoCompleteDropdown('genomeSearchInput', onGenomeSelect, null, null, null, onSearchError);
+
+        // Add click handler for the Change Genome button to trigger autocomplete search
+        $("#genomeSearchButton").click(function(e) {
+            e.preventDefault();
+            $("#genomeSearchInput").autocomplete("search", $("#genomeSearchInput").val());
+        });
     }
-            })) {
-                newOpt.selected = true;
+
+    function updateCurrentGenomeLabel() {
+        // Update the label showing current genome
+        var genomeInfo = null;
+        // Find the current db in the genomes data
+        _.each(uiState.genomes, function(genomeList) {
+            _.each(genomeList, function(assembly) {
+                if (assembly.name === db ||
+                    (assembly.isCurated && assembly.name === trackHubSkipHubName(db))) {
+                    genomeInfo = assembly;
                 }
-            $("#speciesSelect").append(newOpt);
             });
+        });
+        if (genomeInfo) {
+            $("#currentGenome").text(genomeInfo.organism + ' - ' + genomeInfo.description + ' (' + db + ')');
+        } else {
+            $("#currentGenome").text(db);
+        }
     }
 
     function changeSearchResultsLabel() {
-        // change the title to indicate what assembly was search:
+        // change the title to indicate what assembly was searched:
         $("#dbPlaceholder").empty();
-        $("#dbPlaceholder").append("on " + db + " (" + $("#dbSelect")[0].selectedOptions[0].label+ ")");
+        var genomeInfo = null;
+        // Find the current db in the genomes data
+        _.each(uiState.genomes, function(genomeList) {
+            _.each(genomeList, function(assembly) {
+                if (assembly.name === db ||
+                    (assembly.isCurated && assembly.name === trackHubSkipHubName(db))) {
+                    genomeInfo = assembly;
+                }
+            });
+        });
+        if (genomeInfo) {
+            $("#dbPlaceholder").append("on " + db + " (" + genomeInfo.organism + " " + genomeInfo.description + ")");
+        } else {
+            $("#dbPlaceholder").append("on " + db);
+        }
     }
 
     function checkJsonData(jsonData, callerName) {
         // Return true if jsonData isn't empty and doesn't contain an error;
         // otherwise complain on behalf of caller.
         if (! jsonData) {
             alert(callerName + ': empty response from server');
         } else if (jsonData.error && !jsonData.error.startsWith("Sorry, couldn't locate")) {
             console.error(jsonData.error);
             alert(callerName + ': error from server: ' + jsonData.error);
         } else if (jsonData.warning) {
             alert("Warning: " + jsonData.warning);
             return true;
         } else {
             if (debugCartJson) {
@@ -711,32 +725,31 @@
             return;
         }
         if (typeof jsonData.positionMatches !== "undefined") {
             // clear the old resultHash
             uiState.resultHash = {};
             _.each(uiState.positionMatches, function(match) {
                 uiState.resultHash[match.name] = match;
             });
         } else {
             // no results for this search
             uiState.resultHash = {};
             uiState.positionMatches = [];
         }
         updateFilters(uiState);
         updateSearchResults(uiState);
-        buildSpeciesDropdown();
-        fillOutAssemblies();
+        updateCurrentGenomeLabel();
         urlVars = {"db": db, "search": uiState.search, "showSearchResults": ""};
         // changing the url allows the history to be associated to a specific url
         var urlParts = changeUrl(urlVars);
         $("#searchCategories").jstree(true).refresh(false,true);
         saveLinkClicks();
         if (doSaveHistory)
             saveHistory(uiState, urlParts);
         changeSearchResultsLabel();
     }
 
     function handleRefreshState(jsonData) {
         if (checkJsonData(jsonData, 'handleRefreshState')) {
             updateStateAndPage(jsonData, true);
         }
         $("#spinner").remove();
@@ -955,38 +968,32 @@
                 $("#searchCategories").on('check_node.jstree uncheck_node.jstree', function(e, data) {
                     if ($("#searchResults")[0].children.length > 0) {
                         showOrHideResults(e,data.node);
                     }
                 });
                 // Reattach event handlers as necessary:
                 saveLinkClicks();
             });
             saveHistory(cartJson, urlParts, true);
         } else {
             // no cartJson object means we are coming to the page for the first time:
             cart.send({ getUiState: {db: db} }, handleRefreshState);
             cart.flush();
         }
 
-        buildSpeciesDropdown(); // make the list of available organisms
-        fillOutAssemblies(); // call once to get the initial state
-        $("#speciesSelect").change(fillOutAssemblies);
-        $("#dbSelect").change(function(e) {
-            e.preventDefault();
-            db = e.currentTarget.value;
-            switchAssemblies(db);
-        });
+        initGenomeAutocomplete(); // initialize the genome search autocomplete
+        updateCurrentGenomeLabel(); // show the current genome
         changeSearchResultsLabel();
     }
 
     return { init: init,
              updateSearchResults: updateSearchResults,
              updateFilters: updateFilters,
              updateStateAndPage: updateStateAndPage
            };
 
 }());
 
 $(document).ready(function() {
     $('#searchBarSearchString').bind('keypress', function(e) {  // binds listener to search button
         if (e.which === 13) {  // listens for return key
             e.preventDefault();   // prevents return from also submitting whole form