diff options
Diffstat (limited to 'uploader/templates/phenotypes/add-phenotypes-raw-files.html')
-rw-r--r-- | uploader/templates/phenotypes/add-phenotypes-raw-files.html | 352 |
1 files changed, 344 insertions, 8 deletions
diff --git a/uploader/templates/phenotypes/add-phenotypes-raw-files.html b/uploader/templates/phenotypes/add-phenotypes-raw-files.html index ef0895d..568cf0b 100644 --- a/uploader/templates/phenotypes/add-phenotypes-raw-files.html +++ b/uploader/templates/phenotypes/add-phenotypes-raw-files.html @@ -2,6 +2,8 @@ {%from "flash_messages.html" import flash_all_messages%} {%from "macro-table-pagination.html" import table_pagination%} {%from "phenotypes/macro-display-pheno-dataset-card.html" import display_pheno_dataset_card%} +{%from "phenotypes/macro-display-preview-table.html" import display_preview_table%} +{%from "phenotypes/macro-display-resumable-elements.html" import display_resumable_elements%} {%block title%}Phenotypes{%endblock%} @@ -106,13 +108,14 @@ <fieldset id="fldset-data-files"> <legend>Data File(s)</legend> - <div class="form-group"> + <div class="form-group non-resumable-elements"> <label for="finput-phenotype-descriptions" class="form-label"> Phenotype Descriptions</label> <input id="finput-phenotype-descriptions" name="phenotype-descriptions" class="form-control" type="file" + data-preview-table="tbl-preview-pheno-desc" required="required" /> <span class="form-text text-muted"> Provide a file that contains only the phenotype descriptions, @@ -121,44 +124,92 @@ the documentation for the expected format of the file</a>.</span> </div> - <div class="form-group"> + {{display_resumable_elements( + "resumable-phenotype-descriptions", + "phenotype descriptions", + '<p>You can drop a CSV file that contains the phenotype descriptions here, + or you can click the "Browse" button (below and to the right) to select it + from your computer.</p> + <p>The CSV file must conform to some standards, as documented in the + <a href="#docs-file-phenotype-description" + title="Documentation of the phenotype data file format."> + "Phenotypes Descriptions" documentation</a> section below.</p>')}} + + + <div class="form-group non-resumable-elements"> <label for="finput-phenotype-data" class="form-label">Phenotype Data</label> <input id="finput-phenotype-data" name="phenotype-data" class="form-control" type="file" + data-preview-table="tbl-preview-pheno-data" required="required" /> <span class="form-text text-muted"> Provide a file that contains only the phenotype data. See - <a href="#docs-phenotype-data" + <a href="#docs-file-phenotype-data" title="Documentation of the phenotype data file format."> the documentation for the expected format of the file</a>.</span> </div> + {{display_resumable_elements( + "resumable-phenotype-data", + "phenotype data", + '<p>You can drop a CSV file that contains the phenotype data here, + or you can click the "Browse" button (below and to the right) to select it + from your computer.</p> + <p>The CSV file must conform to some standards, as documented in the + <a href="#docs-file-phenotype-data" + title="Documentation of the phenotype data file format."> + "Phenotypes Data" documentation</a> section below.</p>')}} + {%if population.Family in families_with_se_and_n%} - <div class="form-group"> + <div class="form-group non-resumable-elements"> <label for="finput-phenotype-se" class="form-label">Phenotype: Standard Errors</label> <input id="finput-phenotype-se" name="phenotype-se" class="form-control" type="file" + data-preview-table="tbl-preview-pheno-se" required="required" /> <span class="form-text text-muted"> Provide a file that contains only the standard errors for the phenotypes, computed from the data above.</span> </div> + {{display_resumable_elements( + "resumable-phenotype-se", + "standard errors", + '<p>You can drop a CSV file that contains the computed standard-errors data + here, or you can click the "Browse" button (below and to the right) to + select it from your computer.</p> + <p>The CSV file must conform to some standards, as documented in the + <a href="#docs-file-phenotype-se" + title="Documentation of the phenotype data file format."> + "Phenotypes Data" documentation</a> section below.</p>')}} - <div class="form-group"> + + <div class="form-group non-resumable-elements"> <label for="finput-phenotype-n" class="form-label">Phenotype: Number of Samples/Individuals</label> <input id="finput-phenotype-n" name="phenotype-n" class="form-control" type="file" + data-preview-table="tbl-preview-pheno-n" required="required" /> <span class="form-text text-muted"> Provide a file that contains only the number of samples/individuals used in the computation of the standard errors above.</span> </div> + {{display_resumable_elements( + "resumable-phenotype-n", + "number of samples/individuals", + '<p>You can drop a CSV file that contains the number of samples/individuals + used in computation of the standard-errors here, or you can click the + "Browse" button (below and to the right) to select it from your computer. + </p> + <p>The CSV file must conform to some standards, as documented in the + <a href="#docs-file-phenotype-n" + title="Documentation of the phenotype data file format."> + "Phenotypes Data" documentation</a> section below.</p>')}} </fieldset> {%endif%} {%endblock%} @@ -268,9 +319,9 @@ comma as a way to separate the "general category and ontology terms".</p> <h3 class="subheading">file: Phenotype Data, Standard Errors and/or Sample Counts</h3> - <span id="docs-phenotype-data"></span> - <span id="docs-phenotype-se"></span> - <span id="docs-phenotype-n"></span> + <span id="docs-file-phenotype-data"></span> + <span id="docs-file-phenotype-se"></span> + <span id="docs-file-phenotype-n"></span> <p>The data is a matrix of <em>phenotypes × individuals</em>, e.g.</p> <code> # num-cases: 2549 @@ -294,20 +345,305 @@ {%endblock%} +{%block sidebarcontents%} +{{display_preview_table("tbl-preview-pheno-desc", "descriptions")}} +{{display_preview_table("tbl-preview-pheno-data", "data")}} +{%if population.Family in families_with_se_and_n%} +{{display_preview_table("tbl-preview-pheno-se", "standard errors")}} +{{display_preview_table("tbl-preview-pheno-n", "number of samples")}} +{%endif%} +{{display_pheno_dataset_card(species, population, dataset)}} +{%endblock%} + {%block more_javascript%} +<script src="{{url_for('base.node_modules', + filename='resumablejs/resumable.js')}}"></script> +<script type="text/javascript" src="/static/js/files.js"></script> + <script type="text/javascript"> $("#btn-reset-file-separator").on("click", (event) => { event.preventDefault(); $("#txt-file-separator").val("\t"); + $("#txt-file-separator").trigger("change"); }); $("#btn-reset-file-comment-character").on("click", (event) => { event.preventDefault(); $("#txt-file-comment-character").val("#"); + $("#txt-file-comment-character").trigger("change"); }); $("#btn-reset-file-na").on("click", (event) => { event.preventDefault(); $("#txt-file-na").val("- NA N/A"); + $("#txt-file-na").trigger("change"); + }); + + var update_preview = (table, filedata, formdata, numrows) => { + table.find("thead tr").remove() + table.find(".data-row").remove(); + var linenum = 0; + var tableheader = table.find("thead"); + var tablebody = table.find("tbody"); + var numheadings = 0; + var navalues = formdata + .na_strings + .split(" ") + .map((v) => {return v.trim();}) + .filter((v) => {return Boolean(v);}); + filedata.forEach((line) => { + if(line.startsWith(formdata.comment_char) || linenum >= numrows) { + return false; + } + var row = $("<tr></tr>"); + line.split(formdata.separator) + .map((field) => { + var value = field.trim(); + if(navalues.includes(value)) { + return "⋘NUL⋙"; + } + return value; + }) + .filter((field) => { + return (field !== "" && field != undefined && field != null); + }) + .forEach((field) => { + if(linenum == 0) { + numheadings += 1; + var tablefield = $("<th></th>"); + tablefield.text(field); + row.append(tablefield); + } else { + add_class(row, "data-row"); + var tablefield = $("<td></td>"); + tablefield.text(field); + row.append(tablefield); + } + }); + + if(linenum == 0) { + tableheader.append(row); + } else { + tablebody.append(row); + } + linenum += 1; + }); + + if(table.find("tbody tr.data-row").length > 0) { + add_class(table.find(".data-row-template"), "hidden"); + } else { + remove_class(table.find(".data-row-template"), "hidden"); + } + }; + + var makePreviewUpdater = (preview_table) => { + return (data) => { + update_preview( + preview_table, + data, + filesMetadata(), + PREVIEW_ROWS); + }; + }; + + var preview_tables_to_elements_map = { + "#tbl-preview-pheno-desc": "#finput-phenotype-descriptions", + "#tbl-preview-pheno-data": "#finput-phenotype-data", + "#tbl-preview-pheno-se": "#finput-phenotype-se", + "#tbl-preview-pheno-n": "#finput-phenotype-n" + }; + + var filesMetadata = () => { + return { + "separator": $("#txt-file-separator").val(), + "comment_char": $( + "#txt-file-comment-character").val(), + "na_strings": $("#txt-file-na").val() + } + }; + + var PREVIEW_ROWS = 5; + + var handler_update_previews = (event) => { + Object.entries(preview_tables_to_elements_map).forEach((mapentry) => { + var preview_table = $(mapentry[0]); + var file_input = $(mapentry[1]); + if(file_input.length === 1) { + readFirstNLines( + file_input[0].files[0], + 10, + [makePreviewUpdater(preview_table)]); + } + }); + }; + + [ + "#txt-file-separator", + "#txt-file-comment-character", + "#txt-file-na" + ].forEach((elementid) => { + $(elementid).on("change", handler_update_previews); + }); + + [ + "#finput-phenotype-descriptions", + "#finput-phenotype-data", + "#finput-phenotype-se", + "#finput-phenotype-n" + ].forEach((elementid) => { + $(elementid).on("change", (event) => { + readFirstNLines( + event.target.files[0], + 10, + [makePreviewUpdater( + $("#" + event.target.getAttribute("data-preview-table")))]); + }); + }); + + + var resumableDisplayFiles = (display_area, files) => { + files.forEach((file) => { + var display_element = display_area + .find(".file-display-template") + .clone(); + remove_class(display_element, "hidden"); + remove_class(display_element, "file-display-template"); + add_class(display_element, "file-display"); + display_element.find(".filename").text(file.name + || file.fileName + || file.relativePath + || file.webkitRelativePath); + display_element.find(".filesize").text( + (file.size / (1024*1024)).toFixed(2) + "MB"); + display_element.find(".fileuniqueid").text(file.uniqueIdentifier); + display_element.find(".filemimetype").text(file.file.type); + display_area.append(display_element); + }); + }; + + + var indicateProgress = (resumable, progress_bar) => { + return () => {/*Has no event!*/ + var progress = (resumable.progress() * 100).toFixed(2); + var pbar = progress_bar.find(".progress-bar"); + remove_class(progress_bar, "hidden"); + pbar.css("width", progress+"%"); + pbar.attr("aria-valuenow", progress); + pbar.text("Uploading: " + progress + "%"); + }; + }; + + var retryUpload = (retry_button, cancel_button) => { + retry_button.on("click", (event) => { + resumable.files.forEach((file) => {file.retry();}); + add_class(retry_button, "hidden"); + remove_class(cancel_button, "hidden"); + add_class(browse_button, "hidden"); + }); + }; + + var cancelUpload = (cancel_button, retry_button) => { + cancel_button.on("click", (event) => { + resumable.files.forEach((file) => { + if(file.isUploading()) { + file.abort(); + } + }); + add_class(cancel_button, "hidden"); + remove_class(retry_button, "hidden"); + remove_class(browse_button, "hidden"); + }); + }; + + + var startUpload = (browse_button, retry_button, cancel_button) => { + return (event) => { + remove_class(cancel_button, "hidden"); + add_class(retry_button, "hidden"); + add_class(browse_button, "hidden"); + }; + }; + + + var uploadSuccess = () => { + return (file, message) => { + console.log("THE FILE:", file); + console.log("THE SUCCESS MESSAGE:", message); + // TODOS: + // * Save filename/filepath somewhere + // * Trigger some function that will run when all files have succeeded + }; + }; + + + var uploadError = () => { + return (message, file) => { + console.log("THE FILE:", file); + console.log("THE ERROR MESSAGE:", message); + }; + }; + + + + var makeResumableObject = (form_id, file_input_id, resumable_element_id, preview_table_id) => { + var the_form = $("#" + form_id); + var file_input = $("#" + file_input_id); + var submit_button = the_form.find("input[type=submit]"); + var r = errorHandler( + fileSuccessHandler( + uploadStartHandler( + filesAddedHandler( + markResumableDragAndDropElement( + makeResumableElement( + the_form.attr("data-resumable-target"), + file_input.parent(), + $("#" + resumable_element_id), + submit_button, + ["csv", "tsv"]), + file_input.parent(), + $("#" + resumable_element_id), + $("#" + resumable_element_id + "-browse-button")), + (files) => { + // TODO: Also trigger preview! + resumableDisplayFiles( + $("#" + resumable_element_id + "-selected-files"), files); + files.forEach((file) => { + readFirstNLines( + file.file, + 10, + [makePreviewUpdater( + $("#" + preview_table_id))]) + }); + }), + startUpload($("#" + resumable_element_id + "-browse-button"), + $("#" + resumable_element_id + "-retry-button"), + $("#" + resumable_element_id + "-cancel-button"))), + uploadSuccess()), + uploadError()); + + /** Setup progress indicator **/ + progressHandler( + r, + indicateProgress(r, $("#" + resumable_element_id + "-progress-bar"))); + + return r; + }; + + resumables = [ + ["frm-add-phenotypes", "finput-phenotype-descriptions", "resumable-phenotype-descriptions", "tbl-preview-pheno-desc"], + ["frm-add-phenotypes", "finput-phenotype-data", "resumable-phenotype-data", "tbl-preview-pheno-data"], + ["frm-add-phenotypes", "finput-phenotype-se", "resumable-phenotype-se", "tbl-preview-pheno-se"], + ["frm-add-phenotypes", "finput-phenotype-n", "resumable-phenotype-n", "tbl-preview-pheno-n"], + ].map((row) => { + return makeResumableObject(row[0], row[1], row[2], row[3]); + }); + + $("#frm-add-phenotypes input[type=submit]").on("click", (event) => { + event.preventDefault(); + // TODO: Check all the relevant files exist + // TODO: Verify that files are not duplicated + // TODO: Check all fields + // Start the uploads. + resumables.forEach((r) => {r.upload();}); }); </script> {%endblock%} |