about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2023-12-07 12:40:37 +0300
committerFrederick Muriuki Muriithi2023-12-07 12:40:37 +0300
commit8760d188faebe552028f3142a821d0851df4e1e9 (patch)
treedc56fe37d79685c1107765d80ffe78085c7df7f1
parent493f8fbe747650a4fbac2e0b153ad0074b4f91e4 (diff)
downloadgn-uploader-8760d188faebe552028f3142a821d0851df4e1e9.tar.gz
Samples: Provide preview feature.
-rw-r--r--qc_app/static/js/upload_samples.js132
-rw-r--r--qc_app/templates/samples/upload-samples.html56
2 files changed, 178 insertions, 10 deletions
diff --git a/qc_app/static/js/upload_samples.js b/qc_app/static/js/upload_samples.js
new file mode 100644
index 0000000..aed536f
--- /dev/null
+++ b/qc_app/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/qc_app/templates/samples/upload-samples.html b/qc_app/templates/samples/upload-samples.html
index b19e38c..23dc8a8 100644
--- a/qc_app/templates/samples/upload-samples.html
+++ b/qc_app/templates/samples/upload-samples.html
@@ -46,13 +46,21 @@
   <fieldset>
     <input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
     <label class="form-col-1">species:</label>
-    <label class="form-col-2">{{species.SpeciesName}} [{{species.MenuName}}]</label>
+    <span class="form-col-2">{{species.SpeciesName}} [{{species.MenuName}}]</span>
   </fieldset>
 
   <fieldset>
     <input type="hidden" name="inbredset_id" value="{{population.InbredSetId}}" />
     <label class="form-col-1">grouping/population:</label>
-    <label class="form-col-2">{{population.Name}} [{{population.FullName}}]</label>
+    <span class="form-col-2">{{population.Name}} [{{population.FullName}}]</span>
+  </fieldset>
+
+  <fieldset>
+    <input id="chk:heading"
+	   type="checkbox"
+	   name="first_line_heading"
+	   class="form-col-1" />
+    <label for="chk:heading" class="form-col-2">first line is a heading?</label>
   </fieldset>
 
   <fieldset>
@@ -68,27 +76,34 @@
       <option value=";">Semicolon</option>
       <option value="other">Other</option>
     </select>
-    <input type="text" name="other_separator" class="form-col-2" />
-    <label class="form-col-2">
+    <input id="txt:separator"
+	   type="text"
+	   name="other_separator"
+	   class="form-col-2" />
+    <span class="form-col-2">
       This is the character that separates the fields in your CSV file. If you
       select "<strong>Other</strong>", then you must provide the separator in
       the text field provided.
-    </label>
+    </span>
   </fieldset>
 
   <fieldset>
     <label for="txt:delimiter" class="form-col-1">field delimiter</label>
-    <input type="text" name="field_delimiter" class="form-col-2" />
-    <label class="form-col-2">
+    <input id="txt:delimiter"
+	   type="text"
+	   name="field_delimiter"
+	   maxlength="1"
+	   class="form-col-2" />
+    <span class="form-col-2">
       If there is a character delimiting the string texts within particular
       fields in your CSV, provide the character here. This can be left blank if
       no such delimiters exist in your file.
-    </label>
+    </span>
   </fieldset>
 
   <fieldset>
-    <label for="file_upload" class="form-col-1">select file</label>
-    <input type="file" name="samples_file" id="file_upload"
+    <label for="file:samples" class="form-col-1">select file</label>
+    <input type="file" name="samples_file" id="file:samples"
 	   accept="text/csv, text/tab-separated-values"
 	   class="form-col-2" />
   </fieldset>
@@ -100,8 +115,29 @@
   </fieldset>
 </form>
 
+<table id="tbl:samples-preview">
+  <caption class="heading">preview content</caption>
+
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Name2</th>
+      <th>Symbol</th>
+      <th>Alias</th>
+    </tr>
+  </thead>
+
+  <tbody>
+    <tr id="default-row">
+      <td colspan="4">
+	Please make some selections to preview the data.</td>
+    </tr>
+  </tbody>
+</table>
+
 {%endblock%}
 
 
 {%block javascript%}
+<script src="/static/js/upload_samples.js" type="text/javascript"></script>
 {%endblock%}