fb23bce308b7ce29749b4f6a0347bada22e8c885 chmalee Thu Mar 14 15:56:53 2024 -0700 Set the value and label for the autocomplete data to the correct variables so the bolded item stays in the drop down list but then when you select the item the bold tags go away in the input box, refs #29611 diff --git src/hg/js/hgSearch.js src/hg/js/hgSearch.js index 34f71f1..281ecb0 100644 --- src/hg/js/hgSearch.js +++ src/hg/js/hgSearch.js @@ -1,1031 +1,1051 @@ // Utility JavaScript // "use strict"; // Don't complain about line break before '||' etc: /* jshint -W014 */ /* jshint -W087 */ /* jshint esnext: true */ var db; /* A global variable for the current assembly. Needed when we may not have sent a cartJson request yet */ var hgSearch = (function() { // this object contains everything needed to build current state of the page var uiState = { db: "", /* The assembly for which all this business belongs to */ categs: {}, /* all possible categories for this database, this includes all possible * searchable tracks */ currentCategs: {}, /* the categories (filters) for the current search results */ positionMatches: [], /* an array of search result objects, one for each category, * created by hgPositionsJson in cartJson.c */ search: "", /* what is currently in the search box */ trackGroups: {}, /* the track groups available for this assembly */ resultHash: {}, /* positionMatches but each objects' category name is a key */ genomes: {}, /* Hash of organism name: [{assembly 1}, {assembly 2}, ...] */ }; // if true, log to the console anything that comes back from the server (for debug) var debugCartJson = false; // This object is the parent for all tracks currently hidden on hgTracks var hiddenTrackGroup = { id: "Currently Hidden Tracks", name: "Currently Hidden Tracks", label: "Currently Hidden Tracks", text: "Currently Hidden Tracks", numMatches: 0, searchTime: -1, children: [], state: {opened: true, loaded: true}, li_attr: {title: "Search for track items in currently hidden tracks"} }; // This object that is the parent for all tracks currently visible on hgTracks var visibleTrackGroup = { id: "Visible Tracks", name: "Visible Tracks", label: "Visible Tracks", text: "Visible Tracks", numMatches: 0, searchTime: -1, li_attr: {title: "Search for track items in all the currently visible searchable tracks"}, children: [], state: {opened: true, loaded: true}, priority: 0.0 }; // variables to parse url arguments correctly var digitTest = /^\d+$/, keyBreaker = /([^\[\]]+)|(\[\])/g, plus = /\+/g, paramTest = /([^?#]*)(#.*)?$/; function deparam(params) { /* From https://github.com/jupiterjs/jquerymx/blob/master/lang/string/deparam/deparam.js * Used by the cell browser */ if(! params || ! paramTest.test(params) ) { return {}; } var data = {}, pairs = params.split('&'), current; for (var i=0; i < pairs.length; i++){ current = data; var pair = pairs[i].split('='); // if we find foo=1+1=2 if(pair.length !== 2) { pair = [pair[0], pair.slice(1).join("=")]; } var key = decodeURIComponent(pair[0].replace(plus, " ")), value = decodeURIComponent(pair[1].replace(plus, " ")), parts = key.match(keyBreaker); for ( var j = 0; j < parts.length - 1; j++ ) { var part = parts[j]; if (!current[part] ) { //if what we are pointing to looks like an array current[part] = digitTest.test(parts[j+1]) || parts[j+1] === "[]" ? [] : {}; } current = current[part]; } var lastPart = parts[parts.length - 1]; if (lastPart === "[]"){ current.push(value); } else{ current[lastPart] = value; } } return data; } function changeUrl(vars, oldVars) { /* Save the users search string to the url so web browser can easily * cache search results into the browser history * vars: object of new key: val pairs like CGI arguments * oldVars: arguments we want to keep between calls */ var myUrl = window.location.href; myUrl = myUrl.replace('#',''); var urlParts = myUrl.split("?"); var baseUrl; if (urlParts.length > 1) baseUrl = urlParts[0]; else baseUrl = myUrl; var urlVars; if (typeof oldVars === "undefined") { var queryStr = urlParts[1]; urlVars = deparam(queryStr); } else { urlVars = oldVars; } for (var key in vars) { var val = vars[key]; if (val === null || val === "") { if (key in urlVars) { delete urlVars[key]; } } else { urlVars[key] = val; } } var argStr = jQuery.param(urlVars); argStr = argStr.replace(/%20/g, "+"); return {"baseUrl": baseUrl, "args": argStr, "urlVars": urlVars}; } function saveHistory(obj, urlParts, replace) { /* Save an object to the web browser's history stack. When we visit the page for the * first time, we replace the basic state after we are done making the initial UI */ if (replace) { history.replaceState(obj, "", urlParts.baseUrl + (urlParts.args.length !== 0 ? "?" + urlParts.args : "")); } else { history.pushState(obj, "", urlParts.baseUrl + (urlParts.args.length !== 0 ? "?" + urlParts.args : "")); } } function compareTrack(trackA, trackB) { /* comparator function for sorting tracks, lowest priority wins, * followed by short label */ priorityA = trackA.priority; priorityB = trackB.priority; // if both priorities are undefined or equal to each other, sort // on shortlabel alphabetically if (priorityA === priorityB) { if (trackA.name < trackB.name) { return -1; } else if (trackA.name > trackB.name) { return 1; } else { return 0; } } else { if (typeof priorityA === "undefined") { return 1; } else if (typeof priorityB === "undefined") { return -1; } else if (priorityA < priorityB) { return -1; } else if (priorityA > priorityA) { return 1; } else { return 0; } } } function compareGroups(a, b) { /* Compare function for track group sorting */ return uiState.trackGroups[a.name].priority - uiState.trackGroups[b.name].priority; } function sortByTrackGroups(groupList) { return groupList.sort(compareGroups); } function sortTrackCategories(trackList) { /* Sort the nested track list structure such that within each group * the leaves of the tree are sorted by priority */ if (typeof trackList.children !== "undefined") { trackList.children.sort(compareTrack); for (var i = 0; i < trackList.children.length; i++) { trackList.children[i] = sortTrackCategories(trackList.children[i]); } } return trackList; } function categoryComp(category) { if (typeof category.priority !== "undefined") return category.priority; return 1000.0; } function sortCategories(categList) { return _.sortBy(categList, categoryComp); } function addCountAndTimeToLabel(categ) { /* Change the text label of the node */ categ.text = categ.label + " (<span id='" + categ.id + "count'><b>" + categ.numMatches + " results</b></span>"; if (typeof categ.searchTime !== "undefined" && categ.searchTime >= 0) { categ.text += ", <span id='" + categ.id + "searchTime'><b>" + categ.searchTime + "ms searchTime</b></span>"; } categ.text += ")"; } function tracksToTree(trackList) { /* Go through the list of all tracks for this assembly, filling * out the necessary information for jstree to be able to work. * Only include categories that have results. * The groups object will get filled out along the way. */ trackGroups = uiState.trackGroups; var ret = []; var parentsHash = {}; var groups = {}; visibleTrackGroup.children = []; visibleTrackGroup.numMatches = 0; visibleTrackGroup.searchTime = -1; hiddenTrackGroup.children = []; hiddenTrackGroup.numMatches = 0; hiddenTrackGroup.searchTime = -1; _.each(trackList, function(track) { if (!(track.id in uiState.resultHash)) { return; } var newCateg = {}; _.assign(newCateg, track); newCateg.text = track.longLabel; newCateg.text = track.label; var group = track.group; newCateg.state = {checked: true, opened: true}; newCateg.text = track.label; newCateg.li_attr = {title: track.description}; newCateg.numMatches = uiState.resultHash[newCateg.id].matches.length; newCateg.searchTime = uiState.resultHash[newCateg.id].searchTime; addCountAndTimeToLabel(newCateg); if (track.visibility > 0) { if (!groups.visible) { groups.visible = visibleTrackGroup; } groups.visible.children.push(newCateg); if (typeof newCateg.searchTime !== "undefined") { if (groups.visible.searchTime < 0) groups.visible.searchTime = 0; groups.visible.searchTime += newCateg.searchTime; } groups.visible.numMatches += newCateg.numMatches; } else { var last = newCateg; var doNewComp = true; if (track.parents) { var tracksAndLabels = track.parents.split(','); var l = tracksAndLabels.length; for (var i = 0; i < l; i+=2) { var parentTrack= tracksAndLabels[i]; var parentLabel = tracksAndLabels[i+1]; if (!(parentTrack in parentsHash)) { parent = {}; parent.id = parentTrack; parent.label = parentLabel; parent.children = [last]; parent.li_attr = {title: "Search for track items in all of the searchable subtracks of the " + parentLabel + " track"}; parent.numMatches = last.numMatches; parent.searchTime = last.searchTime; parentsHash[parentTrack] = parent; addCountAndTimeToLabel(parent); last = parent; doNewComp = true; } else if (typeof last !== "undefined") { // if we are processing the first parent, we need to add ourself (last) // as a child so the subtrack list is correct, but we still need // to go up through the parent list and update the summarized counts if (doNewComp) { parentsHash[parentTrack].children.push(last); } doNewComp = false; parentsHash[parentTrack].numMatches += last.numMatches; if (typeof last.searchTime !== "undefined") { parentsHash[parentTrack].searchTime += last.searchTime; } addCountAndTimeToLabel(parent); } } } if (typeof groups[group] !== "undefined" && typeof last !== "undefined") { groups[group].numMatches += last.numMatches; if (typeof last.searchTime !== "undefined") { groups[group].searchTime += last.searchTime; } addCountAndTimeToLabel(groups[group]); if (doNewComp) { groups[group].children.push(last); } } else if (doNewComp) { groups[group] = {}; groups[group].id = group; groups[group].name = group; groups[group].label = group; addCountAndTimeToLabel(groups[group]); groups[group].numMatches = last.numMatches; groups[group].searchTime = last.searchTime; groups[group].children = [last]; if (typeof trackGroups !== "undefined" && group in trackGroups) { groups[group].priority = trackGroups[group].priority; groups[group].label = trackGroups[group].label; addCountAndTimeToLabel(groups[group]); } else { trackGroups[group] = groups[group]; } } } }); if ("visible" in groups) { groups.visible.children = sortTrackCategories(groups.visible.children); addCountAndTimeToLabel(groups.visible); ret.push(groups.visible); } hiddenTrackChildren = []; _.each(groups, function(group) { if (group.id !== "Visible Tracks") { group.li_attr = {title: "Search for track items in the " + group.label+ " set of tracks"}; group.state = {checked: true, opened: true}; group.children = sortTrackCategories(group.children); hiddenTrackChildren.push(group); } }); if (hiddenTrackChildren.length > 0) { hiddenTrackChildren = sortByTrackGroups(hiddenTrackChildren); _.each(hiddenTrackChildren, function(group) { hiddenTrackGroup.children.push(group); if (typeof group.searchTime !== "undefined") { if (hiddenTrackGroup.searchTime < 0) { hiddenTrackGroup.searchTime = 0; } hiddenTrackGroup.searchTime += group.searchTime; } hiddenTrackGroup.numMatches += group.numMatches; }); addCountAndTimeToLabel(hiddenTrackGroup); ret.push(hiddenTrackGroup); } return ret; } function filtersToJstree() { /* Turns uiState.categs into uiState.currentCategs, which populates the * tree of filters. We only make a leaf node in the tree if that leaf * has a search result */ thisCategs = {}; _.each(uiState.categs, function(categ) { var newCateg = {}; if (categ.id === "trackData") { // this id will never be in the results since we only get a result // per leaf node, so handle this case separately _.assign(newCateg, categ); newCateg.text = categ.label; newCateg.li_attr = {title: newCateg.description}; newCateg.state = {opened: true, loaded: true, checked: true}; newCateg.children = tracksToTree(categ.tracks); newCateg.searchTime = 0; newCateg.numMatches = 0; if (_.isEmpty(newCateg.children)) { return true; // goes to next instance of _.each() } _.each(newCateg.children, function(track) { newCateg.searchTime += track.searchTime; newCateg.numMatches += track.numMatches; }); addCountAndTimeToLabel(newCateg); } else if (categ.id in uiState.resultHash) { _.assign(newCateg, categ); newCateg.numMatches = uiState.resultHash[categ.id].matches.length; newCateg.searchTime = uiState.resultHash[categ.id].searchTime; addCountAndTimeToLabel(newCateg); newCateg.li_attr = {title: "Show/hide hits to " + newCateg.description}; newCateg.state = {opened: true, loaded: true, checked: true}; } if (!_.isEmpty(newCateg)) thisCategs[categ.id] = newCateg; }); // all of the currentCategs need to be children of the root node uiState.currentCategs['#'] = { id: '#', children: sortCategories(Object.keys(thisCategs).map(function(ele) { return thisCategs[ele]; })) }; } function showOrHideResults(event, node) { /* When a checkbox is checked/uncheck in the tree, show/hide the corresponding * result section in the list of results */ var state = node.state.checked; if (node.children.length > 0) { _.each(node.children, function(n) { showOrHideResults(event, $("#searchCategories").jstree().get_node(n)); }); } else { resultLi = $('[id="' + node.id + 'Results"'); if (typeof resultLi !== "undefined") // if we don't have any results for this track resultLi is undefined _.each(resultLi, function(li) { li.style = state ? "display" : "display: none"; }); } } function buildTree(node, cb) { cb.call(this, uiState.currentCategs[node.id]); } function makeCategoryTree() { var parentDiv = $("#searchCategories"); $.jstree.defaults.core.themes.icons = false; $.jstree.defaults.core.themes.dots = true; $.jstree.defaults.contextmenu.show_at_node = false; parentDiv.jstree({ 'plugins' : ['contextmenu', 'checkbox'], 'core': { 'data': buildTree, 'check_callback': true }, 'checkbox': { 'tie_selection': false } }); parentDiv.css('height', "auto"); } function updateFilters(uiState) { if (typeof uiState.categs !== "undefined") { filtersToJstree(); makeCategoryTree(); } } function clearOldFacetCounts() { $("[id*='extraInfo']").remove(); } function printMatches(list, matches, title, searchDesc) { var printCount = 0; _.each(matches, function(match, printCount) { var position = match.position.split(':'); var url, matchTitle; if (title === "helpDocs") { url = position[0]; matchTitle = "<b>" + position[1].replace(/_/g, " ") + "</b>"; } else if (title === "publicHubs") { var hubUrl = position[0] + ":" + position[1]; var dbName = position[2]; var track = position[3]; var hubShortLabel = position[4]; var hubLongLabel = position[5]; url = "hgTrackUi?hubUrl=" + hubUrl + "&g=" + track + "&db=" + dbName; matchTitle = "<b>" + hubShortLabel + "</b>"; } else if (title === "trackDb") { var trackName = position[0]; var shortLabel = position[1]; var longLabel = position[2]; url = "hgTrackUi?g=" + trackName; matchTitle = "<b>" + shortLabel + " - " + longLabel + "</b>"; } else { // unaligned mrnas and ests can still be searched but all you can get // to is the hgc page, no hgTracks for them goToHgTracks = true; hgTracksTitle = hgcTitle = title; if (["all_mrna", "all_est", "xenoMrna", "xenoEst", "intronEst"].includes(title)) { hgTracksTitle = title.replace(/all_/, ""); if (searchDesc.includes("Unaligned")) goToHgTracks = false; } if (goToHgTracks) { url = "hgTracks?db=" + db + "&" + hgTracksTitle + "=pack&position=" + match.position; if (match.hgFindMatches) { url += "&hgFind.matches=" + match.hgFindMatches; } if (match.extraSel) { url += "&" + match.extraSel; } if (match.highlight) { url += url[url.length-1] !== '&' ? '&' : ''; url += "addHighlight=" + encodeURIComponent(match.highlight); } } else { url = "hgc?db=" + db + "&g=" + hgcTitle + "&i=" + match.position + "&c=0&o=0&l=0&r=0" ; } matchTitle = match.posName; //if (match.canonical === true) matchTitle = "<b>" + matchTitle + "</b>"; } var newListObj; if (printCount < 500) { if (printCount + 1 > 10) { newListObj = "<li class='searchResult " + title + "_hidden' style='display: none'><a href=\"" + url + "\">" + matchTitle + "</a> - "; } else { newListObj = "<li class='searchResult'><a href=\"" + url + "\">" + matchTitle + "</a> - "; } printedPos = false; if (!(["helpDocs", "publicHubs", "trackDb"].includes(title))) { newListObj += match.position; printedPos = true; } if (match.description) { if (printedPos) {newListObj += " - ";} newListObj += match.description; } newListObj += "</li>"; list.innerHTML += newListObj; printCount += 1; } }); } function showMoreResults() { let trackName = this.id.replace(/Results_.*/, ""); let isHidden = $("." + trackName + "_hidden")[0].style.display === "none"; let btnId = this.id.replace(/Link/, "Button"); _.each($("." + trackName + "_hidden"), function(hiddenLi) { if (isHidden) { hiddenLi.style = "display:"; } else { hiddenLi.style = "display: none"; } }); let isIconClick = this.nodeName !== "A"; let linkEl = null; if (isIconClick) {linkEl = this.nextSibling.children[0];} if (isHidden) { if (isIconClick) { // click on the '+' icon newText = linkEl.textContent.replace(/Show/,"Hide"); linkEl.textContent = newText; this.src = "../images/remove_sm.gif"; } else { // click on the link text this.textContent = this.textContent.replace(/Show/,"Hide"); let img = document.getElementById(btnId); img.src = "../images/remove_sm.gif"; } } else { if (isIconClick) { // click on the '-' icon newText = linkEl.textContent.replace(/Hide/,"Show"); linkEl.textContent = newText; this.src = "../images/add_sm.gif"; } else { this.textContent = this.textContent.replace(/Hide/,"Show"); let img = document.getElementById(btnId); img.src = "../images/add_sm.gif"; } } } function collapseNode() { var toCollapse = this.parentNode.childNodes[3]; var isHidden = toCollapse.style.display === "none"; if (isHidden) { toCollapse.style = 'display:'; this.src = "../images/remove_sm.gif"; } else { toCollapse.style = 'display: none'; this.src = "../images/add_sm.gif"; } } function updateSearchResults(uiState) { var parentDiv = $("#searchResults"); if (uiState && typeof uiState.search !== "undefined") { $("#searchBarSearchString").val(uiState.search); } else { // back button all the way to the beginning $("#searchBarSearchString").val(""); } if (uiState && uiState.positionMatches && uiState.positionMatches.length > 0) { // clear the old search results if there were any: parentDiv.empty(); // create the elements that will hold results: var newList = document.createElement("ul"); var noUlStyle = document.createAttribute("class"); noUlStyle.value = "ulNoStyle"; newList.setAttributeNode(noUlStyle); parentDiv.append(newList); clearOldFacetCounts(); var categoryCount = 0; // Loop through categories of match (public hubs, help docs, a single track, ... _.each(uiState.positionMatches, function(categ) { var title = categ.name; var searchDesc = categ.description; var matches = categ.matches; var numMatches = matches.length; var newListObj = document.createElement("li"); var idAttr = document.createAttribute("id"); idAttr.value = title + 'Results'; newListObj.setAttributeNode(idAttr); var noLiStyle = document.createAttribute("class"); noLiStyle.value = "liNoStyle"; newListObj.setAttributeNode(noLiStyle); newListObj.innerHTML += "<input type='hidden' id='" + idAttr.value + categoryCount + "' value='0'>"; newListObj.innerHTML += "<img height='18' width='18' id='" + idAttr.value + categoryCount + "_button' src='../images/remove_sm.gif'>"; newListObj.innerHTML += " " + searchDesc + ":"; //printOneFullMatch(newList, matches[0], title, searchDesc); // Now loop through each actual hit on this table and unpack onto list var subList = document.createElement("ul"); printMatches(subList, matches, title, searchDesc); if (matches.length > 10) { idStr = idAttr.value + "_" + categoryCount; subList.innerHTML += "<li class='liNoStyle'>"; subList.innerHTML += "<input type='hidden' id='" + idStr + "showMore' value='0'>"; subList.innerHTML += "<img height='18' width='18' id='" + idStr + "_showMoreButton' src='../images/add_sm.gif'>"; if (matches.length > 500) { let newText = "<div class='showMoreDiv' id='" + idStr +"_showMoreDiv'>"; newText += " <a id='"+ idStr + "_showMoreLink'>"; newText += "Show 490 (out of " + (matches.length) + " total) more matches for " + searchDesc; newText += "</a></div></li>"; subList.innerHTML += newText; } else { let newText = "<div class='showMoreDiv' id='" + idStr + "_showMoreDiv'>"; newText += " <a id='"+ idStr + "_showMoreLink'>"; newText += "Show " + (matches.length - 10) + " more matches for " + searchDesc; newText += "</a></div></li>"; subList.innerHTML += newText; } } newListObj.append(subList); newList.append(newListObj); // make result list collapsible: $('#'+idAttr.value+categoryCount+"_button").click(collapseNode); $('#'+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; } } else { if (assembly.name === db) { return true; } } })) { newOpt.selected = true; } $("#speciesSelect").append(newOpt); }); } function changeSearchResultsLabel() { // change the title to indicate what assembly was search: $("#dbPlaceholder").empty(); $("#dbPlaceholder").append("on " + db + " (" + $("#dbSelect")[0].selectedOptions[0].label+ ")"); } 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) { console.log('from server:\n', jsonData); } return true; } return false; } function updateStateAndPage(jsonData, doSaveHistory) { // Update uiState with new values and update the page. _.assign(uiState, jsonData); db = uiState.db; if (typeof jsonData === "undefined" || jsonData === null) { // now that the show more text is a link, a popstate event gets fired because the url changes // we can safely return because there is no state to change 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(); 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(); } function handleErrorState(jqXHR, textStatus) { cart.defaultErrorCallback(jqXHR, textStatus); $("#spinner").remove(); } function sendUserSearch() { // User has clicked the search button, if they also entered a search // term, fire off a search cart.debug(debugCartJson); var searchTerm = $("#searchBarSearchString").val().replaceAll("\"",""); if (typeof searchTerm !== 'undefined' && searchTerm.length > 0) { // put up a loading image $("#searchBarSearchButton").after("<i id='spinner' class='fa fa-spinner fa-spin'></i>"); // redirect to hgBlat if the input looks like a DNA sequence // minimum length=19 so we do not accidentally redirect to hgBlat for a gene identifier // like ATG5 var dnaRe = new RegExp("^(>[^\n\r ]+[\n\r ]+)?(\\s*[actgnACTGN \n\r]{19,}\\s*)$"); if (dnaRe.test(searchTerm)) { var blatUrl = "hgBlat?type=BLAT%27s+guess&userSeq="+searchTerm; window.location.href = blatUrl; return false; } // if the user entered a plain position string like chr1:blah-blah, just // go to the old cgi/hgTracks var canonMatch = searchTerm.match(canonicalRangeExp); var gbrowserMatch = searchTerm.match(gbrowserRangeExp); var lengthMatch = searchTerm.match(lengthRangeExp); var bedMatch = searchTerm.match(bedRangeExp); var sqlMatch = searchTerm.match(sqlRangeExp); var singleMatch = searchTerm.match(singleBaseExp); var positionMatch = canonMatch || gbrowserMatch || lengthMatch || bedMatch || sqlMatch || singleMatch; if (positionMatch !== null) { var prevCgi = uiState.prevCgi !== undefined ? uiState.prevCgi : "hgTracks"; window.location.replace("../cgi-bin/" + prevCgi + "?db=" + db + "position=" + encodeURIComponent(searchTerm)); return; } _.assign(uiState, {"search": searchTerm}); cart.send({ getSearchResults: { db: db, search: searchTerm } }, handleRefreshState, handleErrorState); // always update the results when a search has happened cart.flush(); } } function switchAssemblies(newDb) { // reload the page to attach curated hub (if any) re = /db=[\w,\.]*/; window.location = window.location.href.replace(re,"db="+newDb); } function saveLinkClicks() { // attach the link handlers to save search history document.querySelectorAll(".searchResult>a").forEach(function(i,j,k) { i.addEventListener("click", function(e) { // i is the <a> element, use parent elements and the href // to construct a fake autocomplete option and save it e.preventDefault(); // stops the page from redirecting until this function finishes let callbackData = {}; let trackName = i.parentNode.parentNode.parentNode.id.replace(/Results$/,""); let matchList = uiState.positionMatches.find((matches) => matches.name === trackName); let id, match, matchStr = i.childNodes[0].textContent; // switch lookup depending on the different search categories: let decoder = function(str) { // helper decoder to change the html encoded entities in // uiState.positionMatches.posName to what is actually rendered // in matchStr return $("<textarea/>").html(str).text(); }; let geneSymbol; // a potentially fake geneSymbol for the autoComplete if (trackName === "trackDb") { match = matchList.matches.find((elem) => { let posSplit = elem.posName.split(":"); geneSymbol = decoder(posSplit[1] + " - " + posSplit[2]); id = "hgTrackUi?db=" + uiState.db + "&g=" + posSplit[0]; return geneSymbol === matchStr; }); - callbackData.value = geneSymbol + " " + match.description; + callbackData.label = geneSymbol; + callbackData.value = geneSymbol; + if (typeof match.description !== "undefined") { + callbackData.label += " " + match.description; + // hack to remove the bolding from the item values in the autocomplete + callbackData.value = $("<div>" + match.description + "</div>").text(); + } callbackData.id = id; callbackData.geneSymbol = geneSymbol; callbackData.internalId = ""; } else if (trackName === "publicHubs") { match = matchList.matches.find((elem) => { let posSplit = elem.posName.split(":"); geneSymbol = decoder(posSplit[4] + " - " + trackHubSkipHubName(posSplit[3])); id = "hgTrackUi?hubUrl=" + posSplit[0] + ":" + posSplit[1] + "&g=" + posSplit[3] + "&db=" + posSplit[3]; return decoder(posSplit[4]) === matchStr; }); - callbackData.value = geneSymbol + " " + match.description; + callbackData.label = geneSymbol; + callbackData.value = geneSymbol; + if (typeof match.description !== "undefined") { + callbackData.label += " " + match.description; + // hack to remove the bolding from the item values in the autocomplete + callbackData.value = $("<div>" + match.description + "</div>").text(); + } callbackData.id = id; callbackData.geneSymbol = geneSymbol; callbackData.internalId = ""; } else if (trackName === "helpDocs") { match = matchList.matches.find((elem) => { let posSplit = elem.posName.split(":"); geneSymbol = decoder(posSplit[1].replaceAll("_", " ")); return geneSymbol === matchStr; }); - callbackData.value = geneSymbol + " " + match.description; + callbackData.label = geneSymbol; + callbackData.value = geneSymbol; + if (typeof match.description !== "undefined") { + callbackData.label += " " + match.description; + // hack to remove the bolding from the item values in the autocomplete + callbackData.value = $("<div>" + match.description + "</div>").text(); + } callbackData.id = match.position.split(":")[0]; callbackData.geneSymbol = geneSymbol; callbackData.internalId = ""; } else { // regular track item search result click match = matchList.matches.find((elem) => { geneSymbol = elem.posName.replace(/ .*$/,""); return decoder(elem.posName) === matchStr; }); + callbackData.label = geneSymbol; callbackData.value = geneSymbol; if (typeof match.description !== "undefined") { - callbackData.value += " " + match.description; + callbackData.label += " " + match.description; + // hack to remove the bolding from the item values in the autocomplete + callbackData.value = $("<div>" + match.description + "</div>").text(); } // special case the genbank searches that are supposed to go to hgc // and not hgTracks let parentTitle = i.parentNode.parentNode.parentNode.childNodes[2]; if (["all_mrna", "all_est", "xenoMrna", "xenoEst", "intronEst"].includes(trackName) && parentTitle.textContent.includes("Unaligned")) { id = "hgc?db=" + db + "&g=" + trackName+ "&i=" + match.position + "&c=0&o=0&l=0&r=0" ; } else { id = match.position; } callbackData.id = id; callbackData.geneSymbol = geneSymbol; callbackData.internalId = match.hgFindMatches; } // type for autocomplete select to know where to navigate to callbackData.type = trackName; - callbackData.label = callbackData.value; addRecentSearch(db, callbackData.geneSymbol, callbackData); window.location = i.href; }); }); } function init() { cart.setCgi('hgSearch'); cart.debug(debugCartJson); // If a user clicks search before the page has finished loading // start processing it now: $("#searchBarSearchButton").click(sendUserSearch); if (typeof cartJson !== "undefined") { if (typeof cartJson.db !== "undefined") { db = cartJson.db; } else { alert("Error no database from request"); } if (typeof cartJson.warning !== "undefined") { alert("Warning: " + cartJson.warning); } checkJsonData(cartJson, "init"); // check right away for a special redirect to hgTracks: if (typeof cartJson.warning === "undefined" && typeof cartJson.positionMatches !== "undefined" && cartJson.positionMatches.length == 1 && cartJson.positionMatches[0].matches[0].doRedirect === true) { positionMatch = cartJson.positionMatches[0]; match = positionMatch.matches[0]; position = match.position; newUrl = "../cgi-bin/hgTracks" + "?db=" + db + "&position=" + position; if (match.highlight) { newUrl += "&addHighlight=" + encodeURIComponent(match.highlight); } if (positionMatch.name !== "chromInfo") { newUrl += "&" + positionMatch.name + "=pack"; } window.location.replace(newUrl); } var urlParts = {}; if (debugCartJson) { console.log('from server:\n', cartJson); } if (typeof cartJson.search !== "undefined") { urlParts = changeUrl({"search": cartJson.search}); } else { urlParts = changeUrl({"db": db}); cartJson.search = urlParts.urlVars.search; } _.assign(uiState,cartJson); if (typeof cartJson.categs !== "undefined") { _.each(uiState.positionMatches, function(match) { uiState.resultHash[match.name] = match; }); filtersToJstree(); makeCategoryTree(); } else { cart.send({ getUiState: {db: db} }, handleRefreshState); cart.flush(); } $("#searchCategories").bind('ready.jstree', function(e, data) { // wait for the category jstree to finish loading before showing the results $("#searchBarSearchString").val(uiState.search); updateSearchResults(uiState); // when a category is checked/unchecked we show/hide that result // from the result list $("#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); }); 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 if ($("#searchBarSearchString").val() !== undefined) { $('#searchBarSearchButton').focus().click(); // clicks search button button } } }); }); // when a user reaches this page from the back button we can display our saved state // instead of sending another network request window.onpopstate = function(event) { event.preventDefault(); hgSearch.updateStateAndPage(event.state, false); };