b508259e8ce75a2aa1f919b8152befea99a2e1df chmalee Fri Jan 27 10:31:59 2023 -0800 Prototype download track data button. An hg.conf controlled button that allows users to download all of the track data in the current window without leaving hgTracks, refs #30024 diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index cd11a07..5eb2718 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -5101,30 +5101,233 @@ }); $('#tabs').show(); $("#tabs").tabs('option', 'selected', '#' + val); if (val === 'simpleTab' && $('div#found').length < 1) { $('input#simpleSearch').focus(); } $("#tabs").css('font-family', jQuery('body').css('font-family')); $("#tabs").css('font-size', jQuery('body').css('font-size')); $('.submitOnEnter').keydown(trackSearch.searchKeydown); findTracks.normalize(); findTracks.updateMdbHelp(0); } } }; +//////// +// Download Current Tracks in window Dialog +//////// +var downloadCurrentTrackData = { + downloadData: {}, // container for holding data while it comes in from the api + currentRequests: {}, // pending requests + intervalId: null, // the id of the timer that waits on the api + + failedTrackDataRequest: function(msg) { + msgJson = JSON.parse(msg); + alert("Download failed. Error message: '" + msgJson.error); + }, + + receiveTrackData: function(track, data) { + downloadCurrentTrackData.downloadData[track] = data; + }, + + convertJson: function(data, outType) { + if (outType !== "tsv" && outType !== "csv") { + alert("ERROR: incorrect output format option"); + return null; + } + outSep = outType === "tsv" ? '\t' : ','; + // TODO: someday we will probably want to include some of these fields + // for each track downloaded, perhaps as an option + ignoredKeys = new Set(["chrom", "dataTime", "dataTimeStamp", "downloadTime", "downloadTimeStamp", + "start", "end", "track", "trackType", "genome", "itemsReturned", "columnTypes", + "bigDataUrl", "chromSize"]); + // first get rid of top level non track object keys + _.each(data, function(val, key) { + if (ignoredKeys.has(key)) {delete data[key];} + }); + // now go through each track and format it correctly + str = ""; + _.each(data, function(val, track) { + str += "track name=\"" + track + "\"\n"; + for (var row in val) { + for (var i = 0; i < val[row].length; i++) { + str += JSON.stringify(val[row][i]); + if (i < val[row].length) { str += outSep; } + } + str += "\n"; + } + str += "\n"; // extra new line after each track oh well + }); + return new Blob([str], {type: "text/plain"}); + }, + + makeDownloadFile: function(key) { + if (_.keys(downloadCurrentTrackData.currentRequests).length === 0) { + // first stop the timer so we don't execute again + clearInterval(downloadCurrentTrackData.intervalId); + outType = $("#outputFormat")[0].selectedOptions[0].value; + var blob = null; + if (outType === 'json') { + blob = new Blob([JSON.stringify(downloadCurrentTrackData.downloadData[key])], {type: "text/plain"}); + } else { + blob = downloadCurrentTrackData.convertJson(downloadCurrentTrackData.downloadData[key], outType); + } + if (blob) { + anchor = document.createElement("a"); + anchor.href = URL.createObjectURL(blob); + fname = $("#downloadFileName")[0].value; + if (fname.length === 0) { + fname = "trackDownload.txt"; + } + switch (outType) { + case "tsv": + if (!fname.endsWith(".tsv")) {fname += ".tsv";} + break; + case "csv": + if (!fname.endsWith(".csv")) {fname += ".csv";} + break; + default: + if (!fname.endsWith(".txt")) {fname += ".txt";} + break; + } + anchor.download = fname; + anchor.click(); + window.URL.revokeObjectURL(anchor.href); + downloadCurrentTrackData.downloadData = {}; + } + } + }, + + startDownload: function() { + trackList = []; + $(".downloadTrackName:checked").each(function(i, elem) { + trackList.push(elem.id); + }); + chrom = hgTracks.chromName; + start = hgTracks.winStart; + end = hgTracks.winEnd; + db = getDb(); + apiUrl = "../cgi-bin/hubApi/getData/track?"; + apiUrl += "chrom=" + chrom; + apiUrl += ";start=" + start; + apiUrl += ";end=" + end; + apiUrl += ";genome=" + db; + apiUrl += ";jsonOutputArrays=1"; + apiUrl += ";track=" + trackList.join(','); + // strip off final comma: + var xmlhttp = new XMLHttpRequest(); + downloadCurrentTrackData.currentRequests[apiUrl] = true; + xmlhttp.onreadystatechange = function() { + if (4 === this.readyState && 200 === this.status) { + var mapData = JSON.parse(this.responseText); + downloadCurrentTrackData.receiveTrackData(apiUrl, mapData); + delete downloadCurrentTrackData.currentRequests[apiUrl]; + } else { + if (4 === this.readyState && this.status >= 400) { + clearInterval(downloadCurrentTrackData.intervalId); + downloadCurrentTrackData.failedTrackDataRequest(this.responseText); + delete downloadCurrentTrackData.currentRequests[apiUrl]; + } + } + }; + xmlhttp.open("GET", apiUrl, true); + xmlhttp.send(); // sends request and exits this function + // the onreadystatechange callback above will trigger + // when the data has safely arrived + // wait for the request to complete before making the download file + downloadCurrentTrackData.intervalId = setInterval(downloadCurrentTrackData.makeDownloadFile, 200, apiUrl); + }, + + + showDownloadUi: function() { + // Populate the dialog with the current list of tracks + // and allow the user to select which ones to download + // Grey out tracks that are currently unsupported by the api + // or are protected data + var downloadDialog = $("#downloadDialog")[0]; + if (!downloadDialog) { + downloadDialog = document.createElement("div"); + downloadDialog.id = "downloadDialog"; + downloadDialog.style = "display: none"; + htmlStr = "
Use this selection window to download track data" + + " for the current region (" + genomePos.get() + "). Please note that large regions" + + " may be slow to download.
"; + htmlStr += "