aboutsummaryrefslogtreecommitdiff
path: root/uploader/templates/phenotypes/add-phenotypes-raw-files.html
diff options
context:
space:
mode:
Diffstat (limited to 'uploader/templates/phenotypes/add-phenotypes-raw-files.html')
-rw-r--r--uploader/templates/phenotypes/add-phenotypes-raw-files.html352
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%}