fb6e28585e71195df1fecbe0b83aa80f5cd39a6d hiram Wed Feb 16 16:25:51 2022 -0800 working code refs #28930 diff --git src/hg/js/gar.js src/hg/js/gar.js new file mode 100644 index 0000000..b6db716 --- /dev/null +++ src/hg/js/gar.js @@ -0,0 +1,634 @@ + + /////////////////////////////////////////////////////////////////////// + //// gar - genArkRequest 2022-02 /// + //// this 'var gar' is the 'namespace' for these functions /// + //// everything can be referenced via gar.name for functions or /// + //// variables /// +/////////////////////////////////////////////////////////////////////// +var gar = { + + modalWrapper: document.getElementById("modalWrapper"), + modalWindow: document.getElementById("modalWindow"), + modalForm: document.getElementById("modalFeedback"), + queryString: '', + // and a query object with keys arg name and value the paired tag + urlParams: null, + submitButton: document.getElementById("submitButton"), + garStatus: document.getElementById("garStatus"), + asmIdText: document.getElementById("asmId"), + commonName: document.getElementById("commonName"), + betterCommonName: document.getElementById("betterCommonName"), + comment: document.getElementById("comment"), + sciName: document.getElementById("sciName"), + onLoadTime: new Date(), + garEndTime: new Date(), + // recent improvement has reduced this to a single table, no longer + // split up by clades + cladeTableList: document.getElementsByClassName("cladeTable"), + columnCheckBoxItems: document.getElementById('columnCheckBoxItems'), + // text box that says: visible rows: 123,456 + counterDisplay: document.getElementById("counterDisplay"), +// perfDisplay: document.getElementById("perfDisplay"), + completedAsmId: new Map(), + columnNames: new Map(), // key is name, value is column number [0..n) + checkBoxNames: new Map(), // key is name, value is the checkbox object + // going to record the original button labels so they can be augmented + // with count information here + checkBoxLabels: new Map(), // key is name, value is the original button label + measureTiming: false, // will turn on with URL argument measureTiming + + millis: function() { + var d = new Date(); + return d.getTime(); + }, + + tableLegend: function(e) { + alert(e.innerText); + }, + + // Given a table cell c, set it to visible/hidden based on 'tf' + // where tf is 'true' == turn on, or 'false' == turn off + setCellVis: function(c,tf) { + if (tf) { + c.style.display = "table-cell"; + } else { + c.style.display = "none"; + } + }, + + // find out the names of the columns, and get all the checkBox + // hideShow button names so they can be managed later + discoverColumnsCheckboxes: function() { + var colGroup = document.getElementById('colDefinitions'); + var i = 0; + for (i = 0; i < colGroup.children.length; i++) { + gar.columnNames.set(colGroup.children[i].id, i); + } + var hideShowList = document.getElementsByClassName('hideShow'); + for (i = 0; i < hideShowList.length; i++) { + var checkBoxId = hideShowList[i].id; + var cbId = checkBoxId.replace("CheckBox", ""); + gar.checkBoxNames.set(cbId, hideShowList[i]); + var labelId = cbId + "Label"; + var labelText = document.getElementById(labelId).innerText; + gar.checkBoxLabels.set(cbId, labelText); + } +// gar.columnNames.forEach(function(value, key) { +// alert(key + " : " + value); +// }); +// for (var [key, value] of gar.columnNames.entries()) { +// alert(key + " : " + value); +// } + gar.measureTiming = gar.urlParams.has('measureTiming'); + // If there are any URL arguments to the page see if any of them + // are column names + var hasAll = gar.urlParams.has('all'); + if (hasAll) { + var columnList = document.getElementsByClassName('columnCheckBox'); + for ( i = 0; i < columnList.length; i++) { + var id = columnList[i].value + "CheckBox"; + var checkBox = document.getElementById(id); + if (checkBox) { + checkBox.checked = true; + var n = gar.columnNames.get(columnList[i].value); + gar.setColumnNvis(n, true); + } + } + } else { + gar.urlParams.forEach(function(val, arg) { + // beware, get('comName') returns zero, fails this if() statement + if (gar.columnNames.has(arg)) { + var checkBoxId = arg + "CheckBox"; + var checkBox = document.getElementById(checkBoxId); + if (checkBox) { + checkBox.checked = true; + var n = gar.columnNames.get(arg); + gar.setColumnNvis(n, true); + } +/* could look at val here when present to turn on/off + if (val) { + alert(checkBoxId + " yes val col: '" + arg + "' val: '" + val + "' n: '" + n + "'"); + } else { + alert(checkBoxId + " no val col: '" + arg + "' val: '" + val + "' n: '" + n + "'"); + } +*/ + } + }); + } + }, // discoverColumnsCheckboxes: function() + +/* column names: +<col id='comName' span='1' class=colGComName> +<col id='sciName' span='1' class=colGSciName> +<col id='asmId' span='1' class=colGAsmId> +<col id='asmSize' span='1' class=colGAsmSize> +<col id='seqCount' span='1' class=colGAsmSeqCount> +<col id='scafN50' span='1' class=colGScafN50> +<col id='ctgN50' span='1' class=colGContigN50> +<col id='IUCN' span='1' class=colGIUCN> +<col id='taxId' span='1' class=colGTaxId> +<col id='asmDate' span='1' class=colGAsmDate> +<col id='submitter' span='1' class=colGSubmitter> +*/ + + // given a category and a counts Map, increment the count for that category + incrementCount: function(category, counts) { + if (counts.get(category)) { + counts.set(category, counts.get(category) + 1); + } else { + counts.set(category, 1); + } + }, + + // given a category and a counts Map, and some flags + countVisHidden: function(category, counts, gca, gcf, canBeReq, ucscDb) { + gar.incrementCount(category, counts); + if (gca) { gar.incrementCount('gca', counts); } + if (gcf) { gar.incrementCount('gcf', counts); } + if (canBeReq) { + gar.incrementCount('gar', counts); + } else { + gar.incrementCount('gak', counts); + } + }, + + // foreach table, for each row in the table, count visible rows + countVisibleRows: function(et) { +// var t0 = gar.millis(); + var comNameRow = gar.columnNames.get('comName'); + var asmIdRow = gar.columnNames.get('asmId'); + var cladeRow = gar.columnNames.get('clade'); + var visRows = 0; + var totalRows = 0; + // key is category name, value is count visible + var categoryVisible = new Map(); + // key is category name, value is count hidden + var categoryHidden = new Map(); + var i = 0; + for (i = 0; i < gar.cladeTableList.length; i++) { + for (var j = 0; j < gar.cladeTableList[i].rows.length; j++) { + var rowId = gar.cladeTableList[i].rows[j]; + var tagN = rowId.parentNode.tagName.toLowerCase(); + // ignore thead and tfoot rows + if (tagN === "thead" || tagN === "tfoot") { continue; } + ++totalRows; + var thisClade = rowId.cells[cladeRow].innerHTML; + var asmId = rowId.cells[asmIdRow].innerHTML; + var isGCA = asmId.includes("GCA"); + var isGCF = asmId.includes("GCF"); + var comName = rowId.cells[comNameRow].innerHTML; + var canBeRequested = comName.includes("button"); + var ucscDb = comName.includes("cgi-bin/hgTracks"); + if ( rowId.style.display !== "none") { + gar.countVisHidden(thisClade, categoryVisible, isGCA, isGCF, canBeRequested, ucscDb); + ++visRows; + } else { + gar.countVisHidden(thisClade, categoryHidden, isGCA, isGCF, canBeRequested, ucscDb); + } + } + } + var notVis = totalRows - visRows; + /* fixup the hideAll checkbox status, if there are any rows visible, + * those hideAll checkboxes should indicate off so they are + * thereby ready to do the function of 'hideAll'. Or if all rows + * are not visible, they should be on to indicate 'hideAll' is in effect + */ + /* reset the checked status on all the other show/hide check boxes */ + gar.checkBoxNames.forEach(function(checkBox, name) { + if (categoryVisible.get(name)) { + checkBox.checked = true; + } else { + checkBox.checked = false; + } + // add the counts (visible/hidden) to the checkBox label text + var visibleCount = 0; + var hiddenCount = 0; + if ( categoryVisible.get(name) ) { + visibleCount = categoryVisible.get(name); + } + if ( categoryHidden.get(name) ) { + hiddenCount = categoryHidden.get(name); + } + var labelId = name + "Label"; + var labelEl = document.getElementById(labelId); + var labelText = gar.checkBoxLabels.get(name); + if (labelEl) { + labelEl.innerText = labelText + " (" + visibleCount.toLocaleString() + "/" + hiddenCount.toLocaleString() + ")"; + } else { +alert("no element for label '" + labelId + "'"); + } + }); + var hideAllList = document.getElementsByClassName('hideAll'); +// var thisEt = gar.millis() - t0; + var thisEt = et; + if (visRows > 0) { +// var pageEt = gar.garEndTime.getTime() - window.garStartTime.getTime(); +// perfDisplay.innerHTML = "countRows " + gar.garEndTime.getTime() + " - " + window.garStartTime.getTime() + " = page load time " + pageEt + " millis : DOMContentLoaded: " + gar.onLoadTime.getTime(); + if (gar.measureTiming) { + counterDisplay.innerHTML = "showing " + visRows.toLocaleString() + " assemblies, " + notVis.toLocaleString() + " hidden : process time: " + thisEt + " millis"; + } else { + counterDisplay.innerHTML = "showing " + visRows.toLocaleString() + " assemblies, " + notVis.toLocaleString() + " hidden"; + } + for (i = 0; i < hideAllList.length; i++) { + hideAllList[i].checked = false; + } + hideAllLabelList = document.getElementsByClassName('hideAllLabel'); + for (i = 0; i < hideAllLabelList.length; i++) { + hideAllLabelList[i].innerHTML = "hide all (" + visRows.toLocaleString() + "/" + notVis.toLocaleString() + ")"; + } + + } else { + if (gar.measureTiming) { + counterDisplay.innerHTML = totalRows.toLocaleString() + " total ssemblies : use the selection menus to select subsets : process time: " + thisEt + " millis"; + } else { + counterDisplay.innerHTML = totalRows.toLocaleString() + " total ssemblies : use the selection menus to select subsets"; + } + for (i = 0; i < hideAllList.length; i++) { + hideAllList[i].checked = true; + } + hideAllLabelList = document.getElementsByClassName('hideAllLabel'); + for (i = 0; i < hideAllLabelList.length; i++) { + hideAllLabelList[i].innerHTML = "hide all (" + visRows.toLocaleString() + "/" + notVis.toLocaleString() + ")"; + } + } + }, + + // given a column number n, and true/false in tf + // set that column visibility + setColumnNvis: function(n, tf) { + for (var i = 0; i < gar.cladeTableList.length; i++) { + for (var j = 0; j < gar.cladeTableList[i].rows.length; j++) { + var c = gar.cladeTableList[i].rows[j].cells[n]; + if (c) { + gar.setCellVis(c, tf); + } + } + } + }, + + // the 'e' is the event object from the checkbox + // the 'e.value' is the column name. + // can find the column number from columnNames Map object + resetColumnVis: function(e) { + var t0 = gar.millis(); + var n = gar.columnNames.get(e.value); + var tf = e.checked; // true - turn column on, false - turn off +// alert("column '" + e.value + "' n '" + n + " : checked: '" + tf + "'"); + gar.setColumnNvis(n, tf); + var et = gar.millis() - t0; + gar.countVisibleRows(et); + }, + + hideTable: function(tableName) { + tId = document.getElementById(tableName); + var buttonName = tableName + "HideShow"; + bId = document.getElementById(buttonName); + if ( tId.style.display === "none") { + tId.style.display = "block"; + bId.innerHTML = "[hide]"; + } else { + tId.style.display = "none"; + bId.innerHTML = "[show]"; + } + }, + + // foreach table, for each row in the table, hide row + hideAll: function(offOn) { + var t0 = gar.millis(); + for (var i = 0; i < gar.cladeTableList.length; i++) { + for (var j = 0; j < gar.cladeTableList[i].rows.length; j++) { + var rowId = gar.cladeTableList[i].rows[j]; + var tagN = rowId.parentNode.tagName.toLowerCase(); + if (tagN === 'thead' || tagN === 'tfoot') { + continue; + } + if (offOn) { // true, turn *off* the row == this is 'hideAll' + rowId.style.display = "none"; + } else { // false, turn *on* the row == this is ! 'hideAll' + rowId.style.display = "table-row"; + } + } + } + /* reset the checked status on all the other show/hide check boxes */ +// var checkBoxList = document.getElementsByClassName('hideShow'); +// for (var i = 0; i < checkBoxList.length; i++) { +// checkBoxList[i].checked = ! offOn; +// } + /* the countVisibleRows will take care of the status on all 'hideAll' + * checkboxes - AND the checkBox 'hideShow' check boxes depending upon + * the counts for that category. + */ + var et = gar.millis() - t0; + gar.countVisibleRows(et); + }, + + // given one of: garList gakList gcaList gcfList, work through + // all the elements to change vis + // list can be any of the class lists for rows in the tables + // offOn - false turn off, true turn on + resetListVis: function(list, offOn) { + var t0 = gar.millis(); + for (var i = 0; i < list.length; i++) { + var rowId = list[i]; + if (offOn) { // true, turn on the row + rowId.style.display = "table-row"; + } else { // false, turn off the row + rowId.style.display = "none"; + } + } + var et = gar.millis() - t0; + gar.countVisibleRows(et); + }, + + // a check box function is working, do not allow any of + // them to be available for a second click while in progress + // tf is 'true' or 'false' to disable (true), or reenable (false) + disableCheckBoxes: function(tf) { + var hideAllList = document.getElementsByClassName('hideAll'); + var i = 0; + for (i = 0; i < hideAllList.length; i++) { + hideAllList[i].disabled = tf; + } + var hideShowList = document.getElementsByClassName('hideShow'); + for (i = 0; i < hideShowList.length; i++) { + hideShowList[i].disabled = tf; + } + }, + + // checked is false when off, true when on + // value comes from the value='string' in the <input value='string'> element + visCheckBox: function(e) { + var offOn = e.checked; // false to turn off, true to turn on + gar.disableCheckBoxes(true); // disable while processing + if (e.value === "all") { + gar.hideAll(offOn); + } else { + var thisList = document.getElementsByClassName(e.value); + gar.resetListVis(thisList, offOn); + if (e.value === "gak") { // implies also ucscDb list + thisList = document.getElementsByClassName("ucscDb"); + gar.resetListVis(thisList, offOn); + } + } + gar.disableCheckBoxes(false); // re-enable as processing done + }, + + columnPullDownClick: function(e) { + if (gar.columnCheckBoxItems.classList.contains('visible')) { + gar.columnCheckBoxItems.classList.remove('visible'); + gar.columnCheckBoxItems.style.display = "none"; + } else { + gar.columnCheckBoxItems.classList.add('visible'); + gar.columnCheckBoxItems.style.display = "block"; + } + }, + + columnPullDownOnblur: function(e) { + gar.columnCheckBoxItems.classList.remove('visible'); + }, + + failedRequest: function(url) { + gar.submitButton.value = "request failed"; + gar.submitButton.disabled = true; + garStatus.innerHTML = "FAILED: '" + url + "'"; + }, + + sendRequest: function(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/gar?name=" + urlComponents; +// 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("'","’"); +// var encoded = encodeURI(cleaner); + var xmlhttp = new XMLHttpRequest(); + xmlhttp.onreadystatechange = function() { + if (4 === this.readyState && 200 === this.status) { + gar.submitButton.value = "request completed"; +// gar.submitButton.disabled = true; + garStatus.innerHTML = "OK: <a href='" + url + "' target=_blank>" + url + "</a>"; +// alert("SUCCESS: '" + url + "'"); + } else { + if (4 === this.readyState && 404 === this.status) { + gar.failedRequest(url); + } + } + }; + xmlhttp.open("GET", url, true); + xmlhttp.send(); +// alert("SENT: '" + url + "'"); + + }, // sendRequest: function(name, email. asmId) + + // work up the chain to find the row this element is in + whichRow: function (e) { + while (e) { + if (e.rowIndex) { + return e.rowIndex; + } + e = e.parentNode; + } + return undefined; + }, + +// obtain table name from +// https://stackoverflow.com/questions/9066792/get-table-parent-of-an-element + parentTable: function (e) { + while (e) { + e = e.parentNode; + if (e.tagName.toLowerCase() === 'table') { + return e; + } + } + return undefined; + }, + + // Original JavaScript code by Chirp Internet: chirpinternet.eu + // Please acknowledge use of this code by including this header. +// const form = document.getElementById("modalFeedback"); + + openModal: function(e) { + if (e.name && e.name !== "specific") { + var pTable = gar.parentTable(e); + var thisRow = gar.whichRow(e); +// var thisCell = pTable.closest('td').cellIndex; + var comName = pTable.rows[thisRow].cells[0].innerText; + var sciName = pTable.rows[thisRow].cells[1].innerText; + gar.commonName.textContent = comName; + gar.betterCommonName.value = comName; + gar.sciName.textContent = sciName; + gar.asmIdText.textContent = e.name; +// gar.modalForm.name.value = "some name"; +// gar.modalForm.email.value = "some@email.com"; + garStatus.innerHTML = " "; + // check if this asmId already submitted + if (gar.completedAsmId.get(e.name)) { + gar.submitButton.value = "request completed"; + gar.submitButton.disabled = false; + gar.modalWrapper.className = "overlay"; + return; + } + gar.completedAsmId.set(e.name, true); + } else { + gar.commonName.textContent = "enter information about desired assembly in the 'Other comments'"; + gar.sciName.textContent = "including the scientific name"; + gar.asmIdText.textContent = "and the GCx accession identifer"; + } + gar.submitButton.value = "Submit request"; + gar.submitButton.disabled = false; + gar.modalWrapper.className = "overlay"; + var overflow = gar.modalWindow.offsetHeight - document.documentElement.clientHeight; + if(overflow > 0) { + gar.modalWindow.style.maxHeight = (parseInt(window.getComputedStyle(gar.modalWindow).height) - overflow) + "px"; + } + gar.modalWindow.style.marginTop = (-gar.modalWindow.offsetHeight)/2 + "px"; + gar.modalWindow.style.marginLeft = (-gar.modalWindow.offsetWidth)/2 + "px"; + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + }, + + closeModal: function(e) + { + gar.modalWrapper.className = ""; + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + }, + + clickHandler: function(e) { + if(!e.target) e.target = e.srcElement; + if(e.target.tagName === "DIV") { + if(e.target.id != "modalWindow") gar.closeModal(e); + } + }, + + keyHandler: function(e) { + if(e.keyCode === 27) gar.closeModal(e); + }, + + modalInit: function() { + gar.onLoadTime = new Date(); +// var pageEt = gar.garEndTime.getTime() - window.garStartTime.getTime(); + var domReady = gar.onLoadTime.getTime() - gar.garEndTime.getTime(); +// perfDisplay.innerHTML = "modalInit " + gar.garEndTime.getTime() + " - " + window.garStartTime.getTime() + " = page load time " + pageEt + " millis : DOMContentLoaded: " + gar.onLoadTime.getTime() + " - " + gar.garEndTime.getTime() + " = " + domReady + " domReady millis"; + if(document.addEventListener) { +// document.getElementById("modalOpen").addEventListener("click", gar.openModal, false); + document.getElementById("modalClose").addEventListener("click", gar.closeModal, false); + document.addEventListener("click", gar.clickHandler, false); + document.addEventListener("keydown", gar.keyHandler, false); + } else { +// document.getElementById("modalOpen").attachEvent("onclick", gar.openModal); + document.getElementById("modalClose").attachEvent("onclick", gar.closeModal); + document.attachEvent("onclick", gar.clickHandler); + document.attachEvent("onkeydown", gar.keyHandler); + } + }, // modalInit: function() + + // Original JavaScript code by Chirp Internet: chirpinternet.eu + // Please acknowledge use of this code by including this header. + + checkForm: function(e) { + // alert("button.value: '" + gar.submitButton.value + "'"); + if (gar.submitButton.value === "request completed") { + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + gar.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; + } + gar.sendRequest(form.name.value, form.email.value, gar.asmIdText.textContent, gar.betterCommonName.value, gar.comment.value); + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + }, // checkForm: function(e) + + unhideTables: function() { + // foreach table, set display to turn on the table + for (var i = 0; i < gar.cladeTableList.length; i++) { + gar.cladeTableList[i].style.display = "table"; + } + document.getElementById('counterDisplay').style.display = "block"; + document.getElementById('loadingStripes').style.display = "none"; + }, // unhide tables + +}; // var gar + + if(document.addEventListener) { + var t0 = gar.millis(); + // allow semi colon separators as well as ampersand + var queryString = window.location.search.replaceAll(";", "&"); + gar.urlParams = new URLSearchParams(queryString); + document.getElementById("modalFeedback").addEventListener("submit", gar.checkForm, false); + document.addEventListener("DOMContentLoaded", gar.modalInit, false); + gar.discoverColumnsCheckboxes(); + gar.unhideTables(); + var et = gar.millis() - t0; + gar.garEndTime = new Date(); + gar.countVisibleRows(et); + } else { + var t0 = gar.millis(); + document.getElementById("modalFeedback").attachEvent("onsubmit", gar.checkForm); + window.attachEvent("onload", gar.modalInit); + gar.discoverColumnsCheckboxes(); + gar.unhideTables(); + var et = gar.millis() - t0; + gar.countVisibleRows(et); + }