diff options
Diffstat (limited to 'uploader/static')
-rw-r--r-- | uploader/static/css/custom-bootstrap.css | 23 | ||||
-rw-r--r-- | uploader/static/css/styles.css | 187 | ||||
-rw-r--r-- | uploader/static/css/two-column-with-separator.css | 27 | ||||
-rw-r--r-- | uploader/static/images/CITGLogo.png | bin | 0 -> 11962 bytes | |||
-rw-r--r-- | uploader/static/js/datatables.js | 69 | ||||
-rw-r--r-- | uploader/static/js/debug.js | 40 | ||||
-rw-r--r-- | uploader/static/js/files.js | 118 | ||||
-rw-r--r-- | uploader/static/js/populations.js | 36 | ||||
-rw-r--r-- | uploader/static/js/pubmed.js | 113 | ||||
-rw-r--r-- | uploader/static/js/select_platform.js | 70 | ||||
-rw-r--r-- | uploader/static/js/species.js | 34 | ||||
-rw-r--r-- | uploader/static/js/upload_progress.js | 97 | ||||
-rw-r--r-- | uploader/static/js/upload_samples.js | 132 | ||||
-rw-r--r-- | uploader/static/js/utils.js | 37 |
14 files changed, 983 insertions, 0 deletions
diff --git a/uploader/static/css/custom-bootstrap.css b/uploader/static/css/custom-bootstrap.css new file mode 100644 index 0000000..67f1199 --- /dev/null +++ b/uploader/static/css/custom-bootstrap.css @@ -0,0 +1,23 @@ +/** Customize some bootstrap selectors **/ +.btn { + text-transform: capitalize; +} + +.navbar-inverse { + background-color: #336699; + border-color: #080808; + color: #FFFFFF; + background-image: none; +} + +.navbar-inverse .navbar-nav>li>a { + color: #FFFFFF; +} + +.navbar-nav > li > a { + padding: 5px; +} + +.navbar { + min-height: 30px; +} diff --git a/uploader/static/css/styles.css b/uploader/static/css/styles.css new file mode 100644 index 0000000..df50dec --- /dev/null +++ b/uploader/static/css/styles.css @@ -0,0 +1,187 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0.7em; + display: grid; + grid-template-columns: 2fr 8fr; + grid-gap: 20px; + + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-size: 20px; +} + +#header { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Define layout for the children elements */ + display: grid; + grid-template-columns: 8fr 2fr; + + /* Content styling */ + background-color: #336699; + color: #FFFFFF; + border-radius: 3px; + min-height: 30px; +} + +#header #header-text { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 2; + + /* Content styling */ + padding-left: 1em; +} + +#header #header-nav { + /* Place it in the parent element */ + grid-column-start: 2; + grid-column-end: 3; +} + +#header #header-nav .nav li a { + /* Content styling */ + color: #FFFFFF; + background: #4477AA; + border: solid 5px #336699; + border-radius: 5px; + font-size: 0.7em; + text-align: center; + padding: 1px 7px; +} + +#nav-sidebar { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 2; +} + +#nav-sidebar .nav li a:hover { + border-radius: 0.5em; +} + +#nav-sidebar .nav .activemenu { + border-style: solid; + border-radius: 0.5em; + border-color: #AAAAAA; + background-color: #EFEFEF; +} + +#main { + /* Place it in the parent element */ + grid-column-start: 2; + grid-column-end: 3; + + /* Define layout for the children elements */ + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 4em 100%; + grid-gap: 1em; +} + +#main #pagetitle { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Content-styling */ + border-radius: 3px; + background-color: #88BBEE; +} + +#main #pagetitle .title { + font-size: 1.4em; + text-transform: capitalize; + padding-left: 0.5em; +} + +@media screen and (max-width: 20in) { + #main #all-content { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Define layout for the children elements */ + max-width: 80%; + } + + #sidebar-content { + display: none; + } +} + +@media screen and (min-width: 20.1in) { + #main #all-content { + /* Place it in the parent element */ + grid-column-start: 1; + grid-column-end: 3; + + /* Define layout for the children elements */ + display: grid; + grid-template-columns: 7fr 3fr; + grid-gap: 1.5em; + } +} + +#main #all-content .row { + margin: 0 2px; +} + +#main #all-content #main-content { + background: #FFFFFF; + max-width: 950px; +} + +#pagetitle .breadcrumb { + background: none; + text-transform: capitalize; + font-size: 0.75em; +} + +#pagetitle .breadcrumb .active a { + color: #333333; +} + +#pagetitle .breadcrumb a { + color: #666666; +} + +.heading { + border-bottom: solid #EEBB88; + text-transform: capitalize; +} + +.subheading { + padding: 1em 0 0.1em 0.5em; + border-bottom: solid #88BBEE; + text-transform: capitalize; +} + +input[type="search"] { + border-radius: 5px; +} + +.btn { + text-transform: Capitalize; +} + +table.dataTable thead th, table.dataTable tfoot th{ + border-right: 1px solid white; + color: white; + background-color: #369 !important; +} + +table.dataTable tbody tr.selected td { + background-color: #ffee99 !important; +} + +.form-group { + margin-bottom: 2em; + padding-bottom: 0.2em; + border-bottom: solid gray 1px; +} diff --git a/uploader/static/css/two-column-with-separator.css b/uploader/static/css/two-column-with-separator.css new file mode 100644 index 0000000..b6efd46 --- /dev/null +++ b/uploader/static/css/two-column-with-separator.css @@ -0,0 +1,27 @@ +.two-column-with-separator { + display: grid; + grid-template-columns: 9fr 1fr 9fr; +} + +.two-col-sep-col1 { + grid-column: 1 / 2; +} + +.two-col-sep-separator { + grid-column: 2 / 3; + text-align: center; + color: #FE3535; + font-weight: bolder; +} + +.two-col-sep-col2 { + grid-column: 3 / 4; +} + +.two-col-sep-col1, .two-col-sep-col2 { + border-style: solid; + border-color: #FE3535; + border-width: 1px; + border-radius: 2em; + padding: 2em 3em 2em 3em; +} diff --git a/uploader/static/images/CITGLogo.png b/uploader/static/images/CITGLogo.png Binary files differnew file mode 100644 index 0000000..ae99fed --- /dev/null +++ b/uploader/static/images/CITGLogo.png diff --git a/uploader/static/js/datatables.js b/uploader/static/js/datatables.js new file mode 100644 index 0000000..82fd696 --- /dev/null +++ b/uploader/static/js/datatables.js @@ -0,0 +1,69 @@ +/** Handlers for events in datatables **/ + +var addTableLength = (menuList, lengthToAdd, dataLength) => { + if(dataLength >= lengthToAdd) { + newList = structuredClone(menuList);//menuList.slice(0, menuList.length); // shallow copy + newList.push(lengthToAdd); + return newList; + } + return menuList; +}; + +var defaultLengthMenu = (data) => { + menuList = [] + var lengths = [10, 25, 50, 100, 1000, data.length]; + lengths.forEach((len) => { + menuList = addTableLength(menuList, len, data.length); + }); + return menuList; +}; + +var buildDataTable = (tableId, data = [], columns = [], userSettings = {}) => { + var defaultSettings = { + responsive: true, + layout: { + topStart: null, + topEnd: null, + bottomStart: null, + bottomEnd: null, + }, + select: true, + lengthMenu: defaultLengthMenu(data), + language: { + processing: "Processing… Please wait.", + loadingRecords: "Loading table data… Please wait.", + lengthMenu: "", + info: "" + }, + data: data, + columns: columns, + drawCallback: (settings) => { + $(this[0]).find("tbody tr").each((idx, row) => { + var arow = $(row); + var checkboxOrRadio = arow.find(".chk-row-select"); + if (checkboxOrRadio) { + if (arow.hasClass("selected")) { + checkboxOrRadio.prop("checked", true); + } else { + checkboxOrRadio.prop("checked", false); + } + } + }); + } + } + var theDataTable = $(tableId).DataTable({ + ...defaultSettings, + ...userSettings + }); + theDataTable.on("select", (event, datatable, type, cell, originalEvent) => { + datatable.rows({selected: true}).nodes().each((node, index) => { + $(node).find(".chk-row-select").prop("checked", true) + }); + }); + theDataTable.on("deselect", (event, datatable, type, cell, originalEvent) => { + datatable.rows({selected: false}).nodes().each((node, index) => { + $(node).find(".chk-row-select").prop("checked", false) + }); + }); + return theDataTable; +}; diff --git a/uploader/static/js/debug.js b/uploader/static/js/debug.js new file mode 100644 index 0000000..eb01209 --- /dev/null +++ b/uploader/static/js/debug.js @@ -0,0 +1,40 @@ +/** + * The entire purpose of this function is for use to debug values inline + * without changing the flow of the code too much. + * + * This **MUST** be a non-arrow function to allow access to the `arguments` + * object. + * + * This function expects at least one argument. + * + * If more than one argument is provided, then: + * a) the last argument is considered the value, and will be returned + * b) all other arguments will be converted to string and output + * + * If only one argument is provided, it is considered the value, and will be + * returned. + * + * Zero arguments is an error condition. + **/ +function __pk__(val) { + /* Handle zero arguments */ + if (arguments.length < 1) { + throw new Error("Invalid arguments: Expected at least one argument."); + } + + msg = "/********** DEBUG **********/"; + if (arguments.length > 1) { + msg = Array.from( + arguments + ).slice( + 0, + arguments.length - 1 + ).map((val) => { + return String(val); + }).join("; ") + } + + value = arguments[arguments.length - 1]; + console.debug("/********** " + msg + " **********/", value); + return value; +} diff --git a/uploader/static/js/files.js b/uploader/static/js/files.js new file mode 100644 index 0000000..0bde6f7 --- /dev/null +++ b/uploader/static/js/files.js @@ -0,0 +1,118 @@ +var readFirstNLines = (thefile, count, process_content_fns) => { + var reader = new FileReader(); + if(typeof thefile !== "undefined" && thefile !== null) { + reader.addEventListener("load", (event) => { + var content = event + .target + .result + .split("\n") + .slice(0, count) + .map((line) => {return line.trim("\r");}); + process_content_fns.forEach((fn) => {fn(content);}); + }); + reader.readAsText(thefile); + } +}; +var read_first_n_lines = readFirstNLines; + + +var readBinaryFile = (file) => { + return new Promise((resolve, reject) => { + var _reader = new FileReader(); + _reader.onload = (event) => {resolve(_reader.result);}; + _reader.readAsArrayBuffer(file); + }); +}; + + +var Uint8ArrayToHex = (arr) => { + var toHex = (val) => { + _hex = val.toString(16); + if(_hex.length < 2) { + return "0" + val; + } + return _hex; + }; + _hexstr = "" + arr.forEach((val) => {_hexstr += toHex(val)}); + return _hexstr +}; + + +var computeFileChecksum = (file) => { + return readBinaryFile(file) + .then((content) => { + return window.crypto.subtle.digest( + "SHA-256", new Uint8Array(content)); + }).then((digest) => { + return Uint8ArrayToHex(new Uint8Array(digest)) + }); +}; + + +var defaultResumableHandler = (event) => { + throw new Error("Please provide a valid event handler!"); +}; + +var addHandler = (resumable, handlername, handler) => { + if(resumable.support) { + resumable.on(handlername, (handler || defaultResumableHandler)); + } + return resumable; +}; + + +var makeResumableHandler = (handlername) => { + return (resumable, handler) => { + return addHandler(resumable, handlername, handler); + }; +}; + + +var fileSuccessHandler = makeResumableHandler("fileSuccess"); +var fileProgressHandler = makeResumableHandler("fileProgress"); +var fileAddedHandler = makeResumableHandler("fileAdded"); +var filesAddedHandler = makeResumableHandler("filesAdded"); +var filesRetryHandler = makeResumableHandler("filesRetry"); +var filesErrorHandler = makeResumableHandler("filesError"); +var uploadStartHandler = makeResumableHandler("uploadStart"); +var completeHandler = makeResumableHandler("complete"); +var progressHandler = makeResumableHandler("progress"); +var errorHandler = makeResumableHandler("error"); + + +var markResumableDragAndDropElement = (resumable, fileinput, droparea, browsebutton) => { + if(resumable.support) { + //Hide file input element and display drag&drop UI + add_class(fileinput, "visually-hidden"); + remove_class(droparea, "visually-hidden"); + + // Define UI elements for browse and drag&drop + resumable.assignDrop(droparea); + resumable.assignBrowse(browsebutton); + } + + return resumable; +}; + + +var makeResumableElement = (targeturi, fileinput, droparea, uploadbutton, filetype) => { + var resumable = Resumable({ + target: targeturi, + fileType: filetype, + maxFiles: 1, + forceChunkSize: true, + generateUniqueIdentifier: (file, event) => { + return computeFileChecksum(file).then((checksum) => { + var _relativePath = (file.webkitRelativePath + || file.relativePath + || file.fileName + || file.name); + return checksum + "-" + _relativePath.replace( + /[^a-zA-Z0-9_-]/img, ""); + }); + } + }); + + return resumable; +}; diff --git a/uploader/static/js/populations.js b/uploader/static/js/populations.js new file mode 100644 index 0000000..89ededa --- /dev/null +++ b/uploader/static/js/populations.js @@ -0,0 +1,36 @@ +$(() => { + var populationsDataTable = buildDataTable( + "#tbl-select-population", + JSON.parse( + $("#tbl-select-population").attr("data-populations-list")), + [ + { + data: (apopulation) => { + return `<input type="radio" name="population_id"` + + `id="rdo_population_id_${apopulation.InbredSetId}" ` + + `value="${apopulation.InbredSetId}" ` + + `class="chk-row-select">`; + } + }, + { + searchable: true, + data: (apopulation) => { + return `${apopulation.FullName} (${apopulation.InbredSetName})`; + } + } + ], + { + select: "single", + paging: true, + scrollY: 700, + deferRender: true, + scroller: true, + scrollCollapse: true, + layout: { + topStart: "info", + topEnd: "search", + bottomStart: "pageLength", + bottomEnd: false + } + }); +}); diff --git a/uploader/static/js/pubmed.js b/uploader/static/js/pubmed.js new file mode 100644 index 0000000..9afd4c3 --- /dev/null +++ b/uploader/static/js/pubmed.js @@ -0,0 +1,113 @@ +var extract_details = (pubmed_id, details) => { + var months = { + "jan": "January", + "feb": "February", + "mar": "March", + "apr": "April", + "may": "May", + "jun": "June", + "jul": "July", + "aug": "August", + "sep": "September", + "oct": "October", + "nov": "November", + "dec": "December" + }; + var _date = details[pubmed_id].pubdate.split(" "); + return { + "authors": details[pubmed_id].authors.map((authobj) => { + return authobj.name; + }), + "title": details[pubmed_id].title, + "journal": details[pubmed_id].fulljournalname, + "volume": details[pubmed_id].volume, + "pages": details[pubmed_id].pages, + "month": _date.length > 1 ? months[_date[1].toLowerCase()] : "jan", + "year": _date[0], + }; +}; + +var update_publication_details = (details) => { + Object.entries(details).forEach((entry) => {; + switch(entry[0]) { + case "authors": + $("#txt-publication-authors").val(entry[1].join(", ")); + break; + case "month": + $("#select-publication-month") + .children("option") + .each((index, child) => { + console.debug(entry[1].toLowerCase()); + child.selected = child.value == entry[1].toLowerCase(); + }); + default: + $("#txt-publication-" + entry[0]).val(entry[1]); + break; + } + }); +}; + +var fetch_publication_abstract = (pubmed_id, pub_details) => { + $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi", + { + "method": "GET", + "data": { + "db": "pubmed", + "id": pubmed_id, + "rettype": "abstract", + "retmode": "xml" + }, + "success": (data, textStatus, jqXHR) => { + update_publication_details({ + ...pub_details, + ...{ + "abstract": Array.from(data + .getElementsByTagName( + "Abstract")[0] + .children) + .map((elt) => {return elt.textContent.trim();}) + .join("\r\n") + }}); + }, + "error": (jqXHR, textStatus, errorThrown) => {}, + "complete": (jqXHR, textStatus) => {}, + "dataType": "xml" + }); +}; + +var fetch_publication_details = (pubmed_id, complete_thunks) => { + error_display = $("#search-pubmed-id-error"); + error_display.text(""); + add_class(error_display, "visually-hidden"); + $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi", + { + "method": "GET", + "data": {"db": "pubmed", "id": pubmed_id, "format": "json"}, + "success": (data, textStatus, jqXHR) => { + // process and update publication details + hasError = ( + Object.hasOwn(data, "error") || + Object.hasOwn(data.result[pubmed_id], "error")); + if(hasError) { + error_display.text( + "There was an error fetching a publication with " + + "the given PubMed ID! The error received " + + "was: '" + ( + data.error || + data.result[pubmed_id].error) + + "'. Please check ID you provided and try " + + "again."); + remove_class(error_display, "visually-hidden"); + } else { + fetch_publication_abstract( + pubmed_id, + extract_details(pubmed_id, data.result)); + } + }, + "error": (jqXHR, textStatus, errorThrown) => {}, + "complete": () => { + complete_thunks.forEach((thunk) => {thunk()}); + }, + "dataType": "json" + }); +}; diff --git a/uploader/static/js/select_platform.js b/uploader/static/js/select_platform.js new file mode 100644 index 0000000..4fdd865 --- /dev/null +++ b/uploader/static/js/select_platform.js @@ -0,0 +1,70 @@ +function radio_column(chip) { + col = document.createElement("td"); + radio = document.createElement("input"); + radio.setAttribute("type", "radio"); + radio.setAttribute("name", "genechipid"); + radio.setAttribute("value", chip["GeneChipId"]); + radio.setAttribute("required", "required"); + col.appendChild(radio); + return col; +} + +function setup_genechips(genechip_data) { + columns = ["GeneChipId", "GeneChipName"] + submit_button = document.querySelector( + "#select-platform-form button[type='submit']"); + elt = document.getElementById( + "genechips-table").getElementsByTagName("tbody")[0]; + remove_children(elt); + if((genechip_data === undefined) || genechip_data.length === 0) { + row = document.createElement("tr"); + col = document.createElement("td"); + col.setAttribute("colspan", "3"); + text = document.createTextNode("No chips found for selected species"); + col.appendChild(text); + row.appendChild(col); + elt.appendChild(row); + submit_button.setAttribute("disabled", true); + return false; + } + + submit_button.removeAttribute("disabled") + genechip_data.forEach(chip => { + row = document.createElement("tr"); + row.appendChild(radio_column(chip)); + columns.forEach(column => { + col = document.createElement("td"); + content = document.createTextNode(chip[column]); + col.appendChild(content); + row.appendChild(col); + }); + elt.appendChild(row); + }); +} + +function genechips() { + return JSON.parse( + document.getElementById("select-platform-form").getAttribute( + "data-genechips")); +} + +function update_genechips(event) { + genec = genechips(); + + species_elt = document.getElementById("species"); + + if(event.target == species_elt) { + setup_genechips(genec[species_elt.value.toLowerCase()]); + } +} + +function select_row_radio(row) { + radio = row.getElementsByTagName( + "td")[0].getElementsByTagName( + "input")[0]; + if(radio === undefined) { + return false; + } + radio.setAttribute("checked", "checked"); + return true; +} diff --git a/uploader/static/js/species.js b/uploader/static/js/species.js new file mode 100644 index 0000000..d42e081 --- /dev/null +++ b/uploader/static/js/species.js @@ -0,0 +1,34 @@ +$(() => { + var speciesDataTable = buildDataTable( + "#tbl-select-species", + JSON.parse( + $("#tbl-select-species").attr("data-species-list")), + [ + { + data: (aspecies) => { + return `<input type="radio" name="species_id"` + + `id="rdo_species_id_${aspecies.SpeciesId}" ` + + `value="${aspecies.SpeciesId}" class="chk-row-select">`; + } + }, + { + data: (aspecies) => { + return `${aspecies.FullName} (${aspecies.SpeciesName})`; + } + } + ], + { + select: "single", + paging: true, + scrollY: 700, + deferRender: true, + scroller: true, + scrollCollapse: true, + layout: { + topStart: "info", + topEnd: "search", + bottomStart: "pageLength", + bottomEnd: false + } + }); +}); diff --git a/uploader/static/js/upload_progress.js b/uploader/static/js/upload_progress.js new file mode 100644 index 0000000..9638b36 --- /dev/null +++ b/uploader/static/js/upload_progress.js @@ -0,0 +1,97 @@ +function make_processing_indicator(elt) { + var count = 0; + return function() { + var message = "Finalising upload and saving file: " + if(count > 5) { + count = 1; + } + for(i = 0; i < count; i++) { + message = message + "."; + } + elt.innerHTML = message + count = count + 1 + }; +} + +function make_progress_updater(file, indicator_elt) { + var progress_bar = indicator_elt.querySelector("#progress-bar"); + var progress_text = indicator_elt.querySelector("#progress-text"); + var extra_text = indicator_elt.querySelector("#progress-extra-text"); + return function(event) { + if(event.loaded <= file.size) { + var percent = Math.round((event.loaded / file.size) * 100); + progress_bar.value = percent + progress_text.innerHTML = "Uploading: " + percent + "%"; + extra_text.setAttribute("class", "hidden") + } + + if(event.loaded == event.total) { + progress_bar.value = 100; + progress_text.innerHTML = "Uploaded: 100%"; + extra_text.setAttribute("class", null); + intv = setInterval(make_processing_indicator(extra_text), 400); + setTimeout(function() {clearTimeout(intv);}, 20000); + } + }; +} + +function setup_cancel_upload(request, indicator_elt) { + document.getElementById("btn-cancel-upload").addEventListener( + "click", function(event) { + event.preventDefault(); + request.abort(); + }); +} + +function setup_request(file, progress_indicator_elt) { + var request = new XMLHttpRequest(); + var updater = make_progress_updater(file, progress_indicator_elt); + request.upload.addEventListener("progress", updater); + request.onload = function(event) { + document.location.assign(request.responseURL); + }; + setup_cancel_upload(request, progress_indicator_elt) + return request; +} + +function selected_filetype(radios) { + for(idx = 0; idx < radios.length; idx++) { + if(radios[idx].checked) { + return radios[idx].value; + } + } +} + +function make_data_uploader(setup_formdata) { + return function(event) { + event.preventDefault(); + + var pindicator = document.getElementById("upload-progress-indicator"); + + var form = event.target; + var the_file = form.querySelector("input[type='file']").files[0]; + if(the_file === undefined) { + form.querySelector("input[type='file']").parentElement.setAttribute( + "class", "invalid-input"); + error_elt = form.querySelector("#no-file-error"); + if(error_elt !== undefined) { + error_elt.setAttribute("style", "display: block;"); + } + return false; + } + var formdata = setup_formdata(form); + + document.getElementById("progress-filename").innerHTML = the_file.name; + var request = setup_request(the_file, pindicator); + request.open(form.getAttribute("method"), form.getAttribute("action")); + request.send(formdata); + return false; + } +} + + +function setup_upload_handlers(formid, datauploader) { + console.info("Setting up the upload handlers.") + upload_form = document.getElementById(formid); + upload_form.addEventListener("submit", datauploader); +} diff --git a/uploader/static/js/upload_samples.js b/uploader/static/js/upload_samples.js new file mode 100644 index 0000000..aed536f --- /dev/null +++ b/uploader/static/js/upload_samples.js @@ -0,0 +1,132 @@ +/* + * Read the file content and set the `data-preview-content` attribute on the + * file element + */ +function read_first_n_lines(event, + fileelement, + numlines, + firstlineheading = true) { + var thefile = fileelement.files[0]; + var reader = new FileReader(); + reader.addEventListener("load", (event) => { + var filecontent = event.target.result.split( + "\n").slice( + 0, (numlines + (firstlineheading ? 1 : 0))).map( + (line) => {return line.trim("\r");}); + fileelement.setAttribute( + "data-preview-content", JSON.stringify(filecontent)); + display_preview(event); + }) + reader.readAsText(thefile); +} + +function remove_rows(preview_table) { + var table_body = preview_table.getElementsByTagName("tbody")[0]; + while(table_body.children.length > 0) { + table_body.removeChild(table_body.children.item(0)); + } +} + +/* + * Display error row + */ +function display_error_row(preview_table, error_message) { + remove_rows(preview_table); + row = document.createElement("tr"); + cell = document.createElement("td"); + cell.setAttribute("colspan", 4); + cell.innerHTML = error_message; + row.appendChild(cell); + preview_table.getElementsByTagName("tbody")[0].appendChild(row); +} + +function strip(str, chars) { + var end = str.length; + var start = 0 + for(var j = str.length; j > 0; j--) { + if(!chars.includes(str[j - 1])) { + break; + } + end = end - 1; + } + for(var i = 0; i < end; i++) { + if(!chars.includes(str[i])) { + break; + } + start = start + 1; + } + return str.slice(start, end); +} + +function process_preview_data(preview_data, separator, delimiter) { + return preview_data.map((line) => { + return line.split(separator).map((field) => { + return strip(field, delimiter); + }); + }); +} + +function render_preview(preview_table, preview_data) { + remove_rows(preview_table); + var table_body = preview_table.getElementsByTagName("tbody")[0]; + preview_data.forEach((line) => { + var row = document.createElement("tr"); + line.forEach((field) => { + var cell = document.createElement("td"); + cell.innerHTML = field; + row.appendChild(cell); + }); + table_body.appendChild(row); + }); +} + +/* + * Display a preview of the data, relying on the user's selection. + */ +function display_preview(event) { + var data_preview_table = document.getElementById("tbl:samples-preview"); + remove_rows(data_preview_table); + + var separator = document.getElementById("select:separator").value; + if(separator === "other") { + separator = document.getElementById("txt:separator").value; + } + if(separator == "") { + display_error_row(data_preview_table, "Please provide a separator."); + return false; + } + + var delimiter = document.getElementById("txt:delimiter").value; + + var firstlineheading = document.getElementById("chk:heading").checked; + + var fileelement = document.getElementById("file:samples"); + var preview_data = JSON.parse( + fileelement.getAttribute("data-preview-content") || "[]"); + if(preview_data.length == 0) { + display_error_row( + data_preview_table, + "No file data to preview. Check that file is provided."); + } + + render_preview(data_preview_table, process_preview_data( + preview_data.slice(0 + (firstlineheading ? 1 : 0)), + separator, + delimiter)); +} + +document.getElementById("chk:heading").addEventListener( + "change", display_preview); +document.getElementById("select:separator").addEventListener( + "change", display_preview); +document.getElementById("txt:separator").addEventListener( + "keyup", display_preview); +document.getElementById("txt:delimiter").addEventListener( + "keyup", display_preview); +document.getElementById("file:samples").addEventListener( + "change", (event) => { + read_first_n_lines(event, + document.getElementById("file:samples"), + 30, + document.getElementById("chk:heading").checked); + }); diff --git a/uploader/static/js/utils.js b/uploader/static/js/utils.js new file mode 100644 index 0000000..1b31661 --- /dev/null +++ b/uploader/static/js/utils.js @@ -0,0 +1,37 @@ +function remove_children(element) { + Array.from(element.children).forEach(child => { + element.removeChild(child); + }); +} + +function trigger_change_event(element) { + evt = new Event("change"); + element.dispatchEvent(evt); +} + + +var remove_class = (element, classvalue) => { + new_classes = (element.attr("class") || "").split(" ").map((val) => { + return val.trim(); + }).filter((val) => { + return ((val !== classvalue) && + (val !== "")) + }).join(" "); + + if(new_classes === "") { + element.removeAttr("class"); + } else { + element.attr("class", new_classes); + } +}; + + +var add_class = (element, classvalue) => { + remove_class(element, classvalue); + element.attr("class", (element.attr("class") || "") + " " + classvalue); +}; + +$(".not-implemented").click((event) => { + event.preventDefault(); + alert("This feature is not implemented yet. Please bear with us."); +}); |