16eee40c920d259c10ee345472708d0cc0cc3393 chmalee Tue Mar 3 11:25:23 2026 -0800 Adds an hg.conf defined 'popular' species list to the new search bar as a 'default' list of results upon focus of the search bar. Combines with 'recents' list. Add a chevron next to the search bar so users know the autocomplete has some default options, refs #36232 diff --git src/hg/js/utils.js src/hg/js/utils.js index 65166d34acb..f08bf39e019 100644 --- src/hg/js/utils.js +++ src/hg/js/utils.js @@ -4470,57 +4470,94 @@ window.localStorage.setItem("recentGenomes", JSON.stringify(recentObj)); } function getRecentGenomes() { // Retrieve recent genome selections from localStorage, formatted for autocomplete display. // Preserves original category for re-selection logic, adds displayCategory for UI. let stored = window.localStorage.getItem("recentGenomes"); if (!stored) return []; let recentObj = JSON.parse(stored); let results = []; for (let genome of recentObj.stack) { if (recentObj.results[genome]) { let item = Object.assign({}, recentObj.results[genome]); // Preserve original category for setDbFromAutocomplete to detect GenArk/hubs - // but also provide a display category for UI + item.originalCategory = item.category; + // Set category for autocomplete grouping header + item.category = "Recent"; item.displayCategory = "Recent"; results.push(item); } } return results; } +function getPopularGenomes($inputEl) { + // Parse the popular assemblies JSON embedded in the page by printGenomeSearchBar(). + // Returns an array of autocomplete-formatted items with category "Popular". + // Items already present in recents are excluded to avoid duplicates. + let inputId = $inputEl.attr('id'); + let dataEl = document.getElementById(inputId + 'PopularData'); + if (!dataEl) return []; + let popularData; + try { + popularData = JSON.parse(dataEl.textContent); + } catch (e) { + return []; + } + // Build a set of db names already in recents to avoid duplicates + let recentDbs = new Set(); + let recents = getRecentGenomes(); + for (let r of recents) { + recentDbs.add(r.db || r.genome); + } + let results = []; + for (let p of popularData) { + if (recentDbs.has(p.db)) continue; + results.push({ + genome: p.db, + db: p.db, + label: p.label, + commonName: p.commonName, + category: "Popular", + displayCategory: "Popular" + }); + } + return results; +} + function removeRecentGenomesByHubUrl(hubUrl) { // Remove all recent genome entries whose hubUrl matches the given URL. // Used when a hub is disconnected to clean up stale entries. let stored = window.localStorage.getItem("recentGenomes"); if (!stored) return; let recentObj = JSON.parse(stored); let newStack = []; for (let key of recentObj.stack) { let item = recentObj.results[key]; if (item && item.hubUrl === hubUrl) { delete recentObj.results[key]; } else { newStack.push(key); } } recentObj.stack = newStack; window.localStorage.setItem("recentGenomes", JSON.stringify(recentObj)); } + function recentGenomeHref(res) { // Build an hgTracks URL for a recent genome entry. GenArk assemblies need // db=, genome=, and a fully qualified hubUrl= so that fixUpDb() and // hubConnectLoadHubs() in cartNew() can connect the hub. UCSC native // databases just need db=. let db = res.db || res.genome; let url = new URL("../cgi-bin/hgTracks", window.location.href); url.searchParams.set("hgsid", getHgsid()); url.searchParams.set("db", db); url.searchParams.set("position", "lastDbPos"); if (res.hubUrl) { // The findGenome API returns a relative hubUrl (e.g. GCA/.../hub.txt) // while hgGateway stores it already prefixed (e.g. /gbdb/genark/GCA/.../hub.txt). let hubUrl = res.hubUrl; if (!hubUrl.startsWith("/gbdb/genark/")) { @@ -4903,45 +4940,66 @@ * labelElementId: ID of the element to update with selected genome label (default: 'genomeLabel') * onSelect: Callback function(item, labelElement) when genome is selected. * Called AFTER standard validation and label update. * item has: {genome, label, commonName, disabled, ...} * apiUrl: Custom API URL (default: null uses standard hubApi/findGenome) * onServerReply: Custom function to process API results (default: null uses processFindGenome) */ function onSearchError(jqXHR, textStatus, errorThrown, term) { return [{label: 'No genomes found', value: '', genome: '', disabled: true}]; } function wrappedSelect(labelElement, item) { // Standard validation - all CGIs check this if (item.disabled || !item.genome) return; // Standard label update - all CGIs do this + if (labelElement) labelElement.innerHTML = item.label; // Call user's custom callback for CGI-specific logic if (typeof config.onSelect === 'function') { config.onSelect(item, labelElement); } } document.addEventListener("DOMContentLoaded", () => { let labelElementId = config.labelElementId || 'genomeLabel'; let labelElement = document.getElementById(labelElementId); let boundSelect = wrappedSelect.bind(null, labelElement); initSpeciesAutoCompleteDropdown(config.inputId, boundSelect, config.apiUrl || null, null, config.onServerReply || null, onSearchError); // Standard search button handler let btn = document.getElementById(config.inputId + "Button"); if (btn) { btn.addEventListener("click", () => { let val = document.getElementById(config.inputId).value; $("[id='" + config.inputId + "']").autocompleteCat("search", val); }); } + + // Dropdown toggle button: opens/closes the autocomplete with recent+popular + let toggle = document.getElementById(config.inputId + "Toggle"); + if (toggle) { + let wasOpen = false; + toggle.addEventListener("mousedown", () => { + let $input = $("[id='" + config.inputId + "']"); + wasOpen = $input.autocompleteCat("widget").is(":visible"); + }); + toggle.addEventListener("click", () => { + let $input = $("[id='" + config.inputId + "']"); + if (wasOpen) { + $input.autocompleteCat("close"); + } else { + $input.val(""); + $input.autocompleteCat("search", ""); + $input.focus(); + } + }); + } }); } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); }