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 += "&nbsp;" + 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 += "&nbsp;<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 += "&nbsp;<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);