7b8b81e0a4869b24cdd8d7d31eaad33aacb87c0a
hiram
  Wed Aug 21 09:03:50 2024 -0700
will now perform the request email properly and the html page can take arguments refs #32596

diff --git src/hg/js/assemblySearch.js src/hg/js/assemblySearch.js
index f555584..461353b 100644
--- src/hg/js/assemblySearch.js
+++ src/hg/js/assemblySearch.js
@@ -1,179 +1,446 @@
+// global variables:
+
+var measureTiming = false;
+var urlParams;
+var searchFor = "";
+var maxItemsOutput = 500;
+var asmIdText = null;
+var betterCommonName = null;
+var comment = null;
+var requestSubmitButton = null;
+
+// This function is called on DOMContentLoaded as the initialization
+//  procedure for first time page draw
 document.addEventListener('DOMContentLoaded', function() {
+    // allow semi colon separators as well as ampersand
+    var queryString = window.location.search.replaceAll(";", "&");
+    urlParams = new URLSearchParams(queryString);
+    if (urlParams.has('measureTiming')) { // accepts no value or other string
+       var measureValue = urlParams.get('measureTiming');
+       if ("0" === measureValue | "off" === measureValue) {
+         measureTiming = false;
+       } else {			// any other string turns it on
+         measureTiming = true;
+       }
+    }
+
     var searchForm = document.getElementById('searchForm');
     var searchInput = document.getElementById('searchBox');
     var clearButton = document.getElementById('clearSearch');
+    asmIdText = document.getElementById("formAsmId");
+    betterCommonName = document.getElementById("betterCommonName");
+    comment = document.getElementById("comment");
+    requestSubmitButton = document.getElementById("submitButton");
+
+    document.getElementById("modalFeedback").addEventListener("submit", checkForm, false);
+    modalInit();
 
     clearButton.addEventListener('click', function() {
         searchInput.value = ''; // Clear the search input field
     });
 
     searchForm.addEventListener('submit', function(event) {
         event.preventDefault(); // Prevent form submission
 
         var searchTerm = document.getElementById('searchBox').value;
-        var resultCountLimit = document.getElementById('limitResultCount');
+        var resultCountLimit = document.getElementById('maxItemsOutput');
         var browserExist = "mustExist";
         var mustExist = document.getElementById('mustExist').checked;
         var notExist = document.getElementById('notExist').checked;
         if (mustExist && notExist) {
            browserExist = "mayExist";
         } else if (notExist) {
            browserExist = "notExist";
         }
         var wordMatch = document.querySelector('input[name="wordMatch"]:checked').value;
         makeRequest(searchTerm, browserExist, resultCountLimit.value, wordMatch);
     });
+
+    var tableHeader = document.getElementById('tableHeader');
+    headerRefresh(tableHeader);
+
+    if (urlParams.has('maxItemsOutput')) {
+       maxItemsOutput = parseInt(urlParams.get('maxItemsOutput'), 10);
+       if (maxItemsOutput < 1) {
+         maxItemsOutput = 1;
+       } else if (maxItemsOutput > 1000) {
+         maxItemsOutput = 1000;
+       }
+       document.getElementById('maxItemsOutput').value = maxItemsOutput;
+    }
+    if (urlParams.has('searchFor')) {
+       searchFor = urlParams.get('searchFor')
+       if (searchFor.length > 0) {
+          searchInput.value = searchFor;
+          document.getElementById('submitSearch').click();
+       }
+    }
+    document.getElementById("measureTiming").style.display = "none";
 });
 
+// refresh the thead columns in the specified table
+function headerRefresh(tableHead) {
+  // clear existing content
+  tableHead.innerHTML = '';
+  // re-populate header row - the sortable system added a class to
+  //  the last sorted column, need to rebuild the headerRow to get the
+  //  header back to pristine condition for the next sort
+  var headerRow = '<tr>';
+  headerRow += '<th><div class=tooltip>view/<br>request &#9432;<span onclick="event.stopPropagation()" class="tooltiptext"><em>"view"</em> opens the genome browser for an existing assembly, <em>"request"</em> opens an assembly request form.</span></div></th>';
+  headerRow += '<th><div class="tooltip">English common name &#9432;<span onclick="event.stopPropagation()" class="tooltiptext">English common name</span></div></th>';
+  headerRow += '<th><div class="tooltip">scientific name (count) &#9432;<span onclick="event.stopPropagation()" class="tooltiptext">binomial scientific name</span></div></th>';
+  headerRow += '<th><div class="tooltip">NCBI Assembly &#9432;<span onclick="event.stopPropagation()" class="tooltiptext">Links to NCBI resource record.</span></div></th>';
+  headerRow += '<th><div class="tooltip"><em>genark</em> clade &#9432;<span onclick="event.stopPropagation()" class="tooltiptextright">clade specification as found in the GenArk system.</span></div></th>';
+  headerRow += '<th><div class="tooltip">description &#9432;<span onclick="event.stopPropagation()" class="tooltiptextright">other meta data for this assembly.</span></div></th>';
+  headerRow += '</tr>';
+  tableHead.innerHTML = headerRow;
+}
+
 // Function to generate the table and extra information
 function populateTableAndInfo(jsonData) {
     var tableHeader = document.getElementById('tableHeader');
     var tableBody = document.getElementById('tableBody');
     var metaData = document.getElementById('metaData');
-    document.getElementById('searchString').innerHTML = "";
-    document.getElementById('matchCounts').innerHTML = "0";
-    document.getElementById('availableAssemblies').innerHTML = "0";
+    document.getElementById('resultCounts').innerHTML = "";
     document.getElementById('elapsedTime').innerHTML = "0";
 
     // Clear existing table content
     tableHeader.innerHTML = '';
     tableBody.innerHTML = '';
     metaData.innerHTML = '';
 
     // Extract the genomic entries and the extra info
     const genomicEntries = {};
     const extraInfo = {};
 
     for (const key in jsonData) {
         if (jsonData[key].scientificName) {
             genomicEntries[key] = jsonData[key];
         } else {
             extraInfo[key] = jsonData[key];
         }
     }
 
-    // re-populate header row - the sortable system added a class to
-    //  the last sorted column, need to rebuild the headerRow to get the
-    //  header back to pristine condition for the next sort
-    var headerRow = '<tr>';
-    headerRow += '<th>view/<br>request</th>';
-    headerRow += '<th>English common name</th>';
-    headerRow += '<th>scientific name</th>';
-    headerRow += '<th>assembly</th>';
-    headerRow += '<th>clade</th>';
-    headerRow += '<th>description</th>';
-    headerRow += '</tr>';
-    tableHeader.innerHTML = headerRow;
+    headerRefresh(tableHeader);
 
     var count = 0;
     for (const id in genomicEntries) {
         var dataRow = '<tr>';
         var browserUrl = id;
         var ncbiUrl = id;
         if (genomicEntries[id].browserExists) {
           if (id.startsWith("GC")) {
             browserUrl = "<a href='/h/" + id + "?position=lastDbPos' target=_blank>view</a>";
             ncbiUrl = "<a href='https://www.ncbi.nlm.nih.gov/assembly/" + id + "' target=_blank>" + id + "</a>"
           } else {
             browserUrl = "<a href='/cgi-bin/hgTracks?db=" + id + "' target=_blank>view</a>";
           }
           dataRow += "<th>" + browserUrl + "</th>";
         } else {
-          dataRow += "<th>request</th>";
+          dataRow += "<th><button type=button' onclick='asmOpenModal(this)' name=" + id + "'>request</button></th>";
         }
         dataRow += "<td>" + genomicEntries[id].scientificName + "</td>";
         dataRow += "<td>" + genomicEntries[id].commonName + "</td>";
         dataRow += "<th>" + ncbiUrl + "</th>";
         dataRow += "<td>" + genomicEntries[id].clade + "</td>";
         dataRow += "<td>" + genomicEntries[id].description + "</td>";
         dataRow += '</tr>';
         tableBody.innerHTML += dataRow;
     }
     var dataTable = document.getElementById('dataTable');
     sorttable.makeSortable(dataTable);
 
-    document.getElementById('searchString').innerHTML = extraInfo['genomeSearch'];
-    document.getElementById('matchCounts').innerHTML = extraInfo['totalMatchCount'].toLocaleString();
-    document.getElementById('availableAssemblies').innerHTML = extraInfo['availableAssemblies'].toLocaleString();
+    var itemCount = parseInt(extraInfo['itemCount'], 10);
+    var totalMatchCount = parseInt(extraInfo['totalMatchCount'], 10);
+    var availableAssemblies = parseInt(extraInfo['availableAssemblies'], 10);
+
+    var resultCounts = "<em>results for search string: </em><b>'" + extraInfo['genomeSearch'] + "'</b>, ";
+    if ( itemCount === totalMatchCount ) {
+      resultCounts += "<em>showing </em><b>" + itemCount.toLocaleString() + "</b> <em>match results</em>, ";
+    } else {
+      resultCounts += "<em>showing </em><b>" + itemCount.toLocaleString() + "</b> <em>match results</em> ";
+      resultCounts += "<em>from </em><b>" + totalMatchCount.toLocaleString() + "</b> <em>total matches,</em> ";
+    }
+    resultCounts += "<em>out of </em><b>" + availableAssemblies.toLocaleString() + "</b> <em>total number of assemblies</em>";
+    document.getElementById('resultCounts').innerHTML = resultCounts;
+    if (measureTiming) {
       var etMs = extraInfo['elapsedTimeMs'];
-    var elapsedTime = etMs.toLocaleString() + " milliseconds";
+      var elapsedTime = "<b>" + etMs.toLocaleString() + "</b> <em>milliseconds</em>";
       if ( etMs > 1000 ) {
          var etSec = etMs/1000;
-       elapsedTime = etSec.toFixed(2) + " seconds";
+         elapsedTime = "<b>" + etSec.toFixed(2) + "</b> <em>seconds</em>";
       }
       document.getElementById('elapsedTime').innerHTML = elapsedTime.toLocaleString();
+      document.getElementById("measureTiming").style.display = "inline";
+    } else {
+      document.getElementById("measureTiming").style.display = "none";
+    }
 }	//	function populateTableAndInfo(jsonData)
 
 function enableButtons() {
-    document.getElementById('submitButton').disabled = false;
+    document.getElementById('submitSearch').disabled = false;
     document.getElementById('clearSearch').disabled = false;
 }
 
 function disableButtons() {
-    document.getElementById('submitButton').disabled = true;
+    document.getElementById('submitSearch').disabled = true;
     document.getElementById('clearSearch').disabled = true;
 }
 
+function parentTable(e) {
+  while (e) {
+      e = e.parentNode;
+      if (e.tagName.toLowerCase() === 'table') {
+          return e;
+      }
+  }
+  return undefined;
+}
+
+function whichRow(e) {
+  while (e) {
+    if (e.rowIndex) {
+      return e.rowIndex;
+    }
+    e = e.parentNode;
+  }
+  return undefined;
+}
+
+function closeModal(e)
+{
+  document.getElementById("modalWrapper").className = "";
+  if (e.preventDefault) {
+    e.preventDefault();
+  } else {
+    e.returnValue = false;
+  }
+}
+
+function clickHandler(e) {
+  if(!e.target) e.target = e.srcElement;
+    if(e.target.tagName === "DIV") {
+      if(e.target.id != "modalWindow") closeModal(e);
+  }
+} 
+
+function keyHandler(e) {
+  if(e.keyCode === 27) closeModal(e);
+}
+
+function modalInit() {
+  if(document.addEventListener) {
+    document.getElementById("modalClose").addEventListener("click", closeModal, false);
+    document.addEventListener("click", clickHandler, false);
+    document.addEventListener("keydown", keyHandler, false);
+  } else {
+    document.getElementById("modalClose").attachEvent("onclick", closeModal);
+    document.attachEvent("onclick", clickHandler);
+    document.attachEvent("onkeydown", keyHandler);
+  }
+}
+
+function failedRequest(url) {
+  requestSubmitButton.value = "request failed";
+  requestSubmitButton.disabled = true;
+//      garStatus.innerHTML = "FAILED: '" + url + "'";
+}
+
+function sendRequest(name, email, asmId, betterName, comment) {
+    var urlComponents = encodeURIComponent(name) + "&email=" + encodeURIComponent(email) + "&asmId=" + encodeURIComponent(asmId) + "&betterName=" + encodeURIComponent(betterName) + "&comment=" + encodeURIComponent(comment);
+
+    var url = "/cgi-bin/asr?name=" + urlComponents;
+alert("request url: '" + url + "'");
+// information about escaping characters:
+// https://stackoverflow.com/questions/10772066/escaping-special-character-in-a-url/10772079
+// encodeURI() will not encode: ~!@#$&*()=:/,;?+'
+// encodeURIComponent() will not encode: ~!*()'
+
+//    var encoded = encodeURIComponent(url);
+//    encoded = encoded.replace("'","&rsquo;");
+//    var encoded = encodeURI(cleaner);
+    var xmlhttp = new XMLHttpRequest();
+    xmlhttp.onreadystatechange = function() {
+         if (4 === this.readyState && 200 === this.status) {
+            requestSubmitButton.value = "request completed";
+         } else {
+            if (4 === this.readyState && 404 === this.status) {
+               failedRequest(url);
+            }
+         }
+       };
+    xmlhttp.open("GET", url, true);
+    xmlhttp.send();
+
+}  //      sendRequest: function(name, email. asmId)
+
+
+function checkForm(e) {
+alert("checkForm");
+  if (requestSubmitButton.value === "request completed") {
+     if (e.preventDefault) {
+       e.preventDefault();
+     } else {
+       e.returnValue = false;
+     }
+     closeModal(e);
+     return;
+  }
+  var form = (e.target) ? e.target : e.srcElement;
+  if(form.name.value === "") {
+    alert("Please enter your Name");
+    form.name.focus();
+    if (e.preventDefault) {
+      e.preventDefault();
+    } else {
+      e.returnValue = false;
+    }
+    return;
+  }
+  if(form.email.value === "") {
+    alert("Please enter a valid Email address");
+    form.email.focus();
+    if (e.preventDefault) {
+      e.preventDefault();
+    } else {
+      e.returnValue = false;
+    }
+    return;
+  }
+// validation regex from:
+//      https://www.w3resource.com/javascript/form/email-validation.php
+// another example from
+//      https://www.simplilearn.com/tutorials/javascript-tutorial/email-validation-in-javascript
+//   var validRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
+// another example from
+//      https://ui.dev/validate-email-address-javascript/
+//      return /\S+@\S+\.\S+/.test(email)
+//      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
+//      var re = /^[^\s@]+@[^\s@]+$/;
+//  if (re.test(email)) { OK }
+
+//    var validEmail = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
+  var validEmail = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
+  if(! validEmail.test(form.email.value)) {
+    alert("You have entered an invalid email address!");
+    form.email.focus();
+    if (e.preventDefault) {
+      e.preventDefault();
+    } else {
+      e.returnValue = false;
+    }
+    return;
+  }
+  sendRequest(form.name.value, form.email.value, asmIdText.textContent, betterCommonName.value, comment.value);
+  if (e.preventDefault) {
+    e.preventDefault();
+  } else {
+    e.returnValue = false;
+  }
+}    //      checkForm: function(e)
+
+function asmOpenModal(e) {
+  if (e.name) {
+    var modalWindow = document.getElementById("modalWindow");
+    var pTable = parentTable(e);
+    var thisRow = whichRow(e);
+    var colGroup = document.getElementById('colDefinitions');
+    var comName = "n/a";
+    var sciName = "n/a";
+    var descr = "n/a";
+    var i = 0;
+    for (i = 0; i < colGroup.children.length; i++) {
+      if (colGroup.children[i].id === "comName") {
+        comName = pTable.rows[thisRow].cells[i].innerText;
+      } else if (colGroup.children[i].id === "sciName") {
+        sciName = pTable.rows[thisRow].cells[i].innerText;
+      } else if (colGroup.children[i].id === "description") {
+        descr = pTable.rows[thisRow].cells[i].innerText;
+      }
+    }
+    document.getElementById("commonName").textContent = comName;
+    document.getElementById("formSciName").textContent = sciName;
+    document.getElementById("formAsmId").textContent = e.name;
+    document.getElementById("comment").textContent = descr;
+    requestSubmitButton.value = "Submit request";
+    document.getElementById("modalWrapper").className = "overlay";
+    requestSubmitButton.disabled = false;
+    var overflow = modalWindow.offsetHeight - document.documentElement.clientHeight;
+    if (overflow > 0) {
+        modalWindow.style.maxHeight = (parseInt(window.getComputedStyle(modalWindow).height) - overflow) + "px";
+    }
+    modalWindow.style.marginTop = (-modalWindow.offsetHeight)/2 + "px";
+    modalWindow.style.marginLeft = (-modalWindow.offsetWidth)/2 + "px";
+  }
+  if (e.preventDefault) {
+    e.preventDefault();
+  } else {
+    e.returnValue = false;
+  }
+}
+
 function makeRequest(query, browserExist, resultLimit, wordMatch) {
     // Disable the submit button
     disableButtons();
     var queryString = query;
     // for allWords, place + sign in front of each word if not already there
     if (wordMatch === "allWords") {
       const words = query.split(/\s+/);
       if (words.length > 1) {	// not needed on only one word
         var queryPlus = "";	// compose new query string
         words.forEach(word => {
           if (word.startsWith("+")) {
             queryPlus += " " + word;	// space separated each word
           } else {
             queryPlus += " +" + word;
           }
         });
       queryString = queryPlus.trimStart();	// remove first space character
       }
     }
 
     // Show the wait spinner
     document.querySelector(".submitContainer").classList.add("loading");
     document.getElementById("loadingSpinner").style.display = "block";
 
     var xhr = new XMLHttpRequest();
-    var url = "/cgi-bin/hubApi/findGenome?genomeSearch=" + encodeURIComponent(queryString);
+    var urlPrefix = "/cgi-bin/hubApi"
+    var url = "/findGenome?genomeSearch=" + encodeURIComponent(queryString);
     url += ";browser=" + browserExist;
     url += ";maxItemsOutput=" + resultLimit;
 
-    xhr.open('GET', url, true);
+    var apiUrl = "<a href='" + urlPrefix + url + "' target=_blank>" + url + "</a>";
+    document.getElementById("recentAjax").innerHTML = apiUrl;
+
+    xhr.open('GET', urlPrefix + url, true);
 
     xhr.onload = function() {
         if (xhr.status === 200) {
             // Hide the wait spinner once the AJAX request is complete
             document.querySelector(".submitContainer").classList.remove("loading");
             document.getElementById("loadingSpinner").style.display = "none";
             enableButtons();
             var data = JSON.parse(xhr.responseText);
 	    populateTableAndInfo(data);
         } else {
             // Hide the wait spinner once the AJAX request is complete
             document.querySelector(".submitContainer").classList.remove("loading");
             document.getElementById("loadingSpinner").style.display = "none";
             enableButtons();
 	    var tableBody = document.getElementById('tableBody');
             var metaData = document.getElementById('metaData');
             tableBody.innerHTML = '';
             metaData.innerHTML = '';
             metaData.innerHTML = "<b>no results found for query: '" + queryString + "'</b>";
-            document.getElementById('searchString').innerHTML = queryString;
-            document.getElementById('matchCounts').innerHTML = "0";
-            document.getElementById('availableAssemblies').innerHTML = "0";
+            document.getElementById('resultCounts').innerHTML = "";
             document.getElementById('elapsedTime').innerHTML = "0";
         }
     };
 
     xhr.onerror = function() {
         alert('console.log("Request failed")');
         console.log('Request failed');
     };
 
     xhr.send();
 }