391090d9d977b18dd2f8c2d6817632dab6ed6700 chmalee Mon Apr 8 16:17:54 2024 -0700 hgSearch javascript optimizations: avoid as many innerHTML calls and string += as possible; instead of printing results 11-500 for a category with display none, don't print them at all and just have a javascript function print them when they are asked for, refs Max meeting and code review #32678 diff --git src/hg/js/hgSearch.js src/hg/js/hgSearch.js index 6001dbf..cd38f99 100644 --- src/hg/js/hgSearch.js +++ src/hg/js/hgSearch.js @@ -445,31 +445,31 @@ }); 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) { + function printMatches(list, matches, title, searchDesc, doShowMore) { 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>"; @@ -496,64 +496,92 @@ } 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> - "; - } + let newListObj = document.createElement("li"); + let className = "searchResult "; + if (doShowMore || printCount + 1 > 10) { + className += title + "_hidden"; + if (!doShowMore) { + newListObj.style.display = "none"; + } + } + newListObj.className = className; + let a = document.createElement("a"); + newListObj.appendChild(a); + a.href = url; + a.innerHTML = matchTitle; // need the bold in the title so use innerHTML here + let textStr = " - "; + printedPos = false; if (!(["helpDocs", "publicHubs", "trackDb"].includes(title))) { - newListObj += match.position; + textStr += match.position; printedPos = true; } if (match.description) { - if (printedPos) {newListObj += " - ";} - newListObj += match.description; + if (printedPos) {textStr += " - ";} + textStr += match.description; + } + newListObj.innerHTML += textStr; // the bolded search term can appear anywhere in + // the text string so use innerHTML for now + if (list.nodeName === "LI") { + list.parentNode.insertBefore(newListObj, list); + } else { + list.appendChild(newListObj); } - newListObj += "</li>"; - list.innerHTML += newListObj; printCount += 1; } }); } function showMoreResults() { let trackName = this.id.replace(/Results_.*/, ""); - let isHidden = $("." + trackName + "_hidden")[0].style.display === "none"; + let isHidden = true; + let parentNode; // the li with the control input to hide/show + if (this.nodeName === "IMG") { + isHidden = this.nextSibling.textContent.startsWith(" Show"); + parentNode = this.parentNode; + } else if (this.nodeName === "A") { + isHidden = this.textContent.startsWith(" Show"); + parentNode = this.parentNode.parentNode; + } let btnId = this.id.replace(/Link/, "Button"); - _.each($("." + trackName + "_hidden"), function(hiddenLi) { + let alreadyMadeElems = document.querySelectorAll("." + trackName + "_hidden"); + if (alreadyMadeElems.length > 0) { + _.each(alreadyMadeElems, function(hiddenLi) { if (isHidden) { hiddenLi.style = "display:"; } else { hiddenLi.style = "display: none"; } }); + } else { + // insert more results before parentNode li + printMoreResults(trackName, parentNode); + } 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"; } @@ -574,89 +602,123 @@ 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 printMoreResults(trackName, nodeAfter) { + /* Print the 11-500 result before nodeAfter */ + let results = uiState.resultHash[trackName]; + let title = results.name; + let searchDesc = results.description; + // show the 11-500th elements + // after this, only CSS is used to show hide them, since they are part of the + // page already + printMatches(nodeAfter, results.matches.slice(10), title, searchDesc, true); + } + 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"); + let title = categ.name; + let searchDesc = categ.description; + let matches = categ.matches; + let numMatches = matches.length; + let newListObj = document.createElement("li"); + let idAttr = document.createAttribute("id"); idAttr.value = title + 'Results'; newListObj.setAttributeNode(idAttr); - var noLiStyle = document.createAttribute("class"); + let 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); + let inp = document.createElement("input"); + inp.type = "hidden"; + inp.id = idAttr.value + categoryCount; + inp.value = "0"; + newListObj.appendChild(inp); + let ctrlImg = document.createElement("img"); + ctrlImg.height = "18"; + ctrlImg.width = "18"; + ctrlImg.id = idAttr.value + categoryCount + "_button"; + ctrlImg.src = "../images/remove_sm.gif"; + newListObj.appendChild(ctrlImg); + let descText = document.createTextNode(searchDesc + ":"); + newListObj.appendChild(descText); // Now loop through each actual hit on this table and unpack onto list - var subList = document.createElement("ul"); - printMatches(subList, matches, title, searchDesc); + let subList = document.createElement("ul"); + // only print the first 10 at first + printMatches(subList, matches.slice(0,10), title, searchDesc, false); 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'>"; + let idStr = idAttr.value + "_" + categoryCount; + let showMoreLi = document.createElement("li"); + showMoreLi.id = idStr; + showMoreLi.classList.add("liNoStyle"); + let showMoreInp = document.createElement("input"); + showMoreInp.type = "hidden"; + showMoreInp.value = '0'; + showMoreInp.id = showMoreLi.id + "showMore"; + showMoreLi.appendChild(showMoreInp); + let showMoreImg = document.createElement("img"); + showMoreImg.height = "18"; + showMoreImg.width = "18"; + showMoreImg.id = showMoreLi.id + "_showMoreButton"; + showMoreImg.src = "../images/add_sm.gif"; + showMoreLi.appendChild(showMoreImg); + let showMoreDiv = document.createElement("div"); + showMoreDiv.id = idStr + "_showMoreDiv"; + showMoreDiv.classList.add("showMoreDiv"); + let showMoreA = document.createElement("a"); + showMoreA.id = idStr + "_showMoreLink"; + let newText = ""; 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; + newText = " Show 490 (out of " + (matches.length) + " total) more matches for " + searchDesc; } 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; + newText = " Show " + (matches.length - 10) + " more matches for " + searchDesc; } + showMoreA.textContent = newText; + showMoreDiv.appendChild(showMoreA); + showMoreLi.appendChild(showMoreDiv); + subList.appendChild(showMoreLi); } 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);