diff options
author | BonfaceKilz | 2021-01-06 01:18:20 +0300 |
---|---|---|
committer | GitHub | 2021-01-06 01:18:20 +0300 |
commit | 509bfdfe7eae156714229a6c964270006788ac4e (patch) | |
tree | e719a96ece0cbb096cf9c21778f6fc551e1af343 /wqflask | |
parent | 7474529d86fbd5e21a373a2f96bb2fffe4dea478 (diff) | |
parent | 7032f7b05deac0bbd2e868ffd3fe42dd1c97fb7d (diff) | |
download | genenetwork2-509bfdfe7eae156714229a6c964270006788ac4e.tar.gz |
Merge pull request #404 from Uditgulati/snp_browser_changes
WIP: Snp browser changes
Diffstat (limited to 'wqflask')
-rw-r--r-- | wqflask/tests/unit/wqflask/test_server_side.py | 31 | ||||
-rw-r--r-- | wqflask/wqflask/search_results.py | 16 | ||||
-rw-r--r-- | wqflask/wqflask/server_side.py | 93 | ||||
-rw-r--r-- | wqflask/wqflask/snp_browser/snp_browser.py | 54 | ||||
-rw-r--r-- | wqflask/wqflask/static/new/css/snp_browser.css | 6 | ||||
-rw-r--r-- | wqflask/wqflask/templates/search_result_page.html | 38 | ||||
-rw-r--r-- | wqflask/wqflask/templates/snp_browser.html | 142 | ||||
-rw-r--r-- | wqflask/wqflask/views.py | 42 |
8 files changed, 349 insertions, 73 deletions
diff --git a/wqflask/tests/unit/wqflask/test_server_side.py b/wqflask/tests/unit/wqflask/test_server_side.py new file mode 100644 index 00000000..4f91d8ca --- /dev/null +++ b/wqflask/tests/unit/wqflask/test_server_side.py @@ -0,0 +1,31 @@ +import unittest + +from wqflask.server_side import ServerSideTable + + +class TestServerSideTableTests(unittest.TestCase): + """ + Test the ServerSideTable class + + test table: + first, second, third + 'd', 4, 'zz' + 'b', 2, 'aa' + 'c', 1, 'ss' + """ + + def test_get_page(self): + rows_count = 3 + table_rows = [ + {'first': 'd', 'second': 4, 'third': 'zz'}, + {'first': 'b', 'second': 2, 'third': 'aa'}, + {'first': 'c', 'second': 1, 'third': 'ss'}, + ] + headers = ['first', 'second', 'third'] + request_args = {'sEcho': '1', 'iSortCol_0': '1', 'iSortingCols': '1', 'sSortDir_0': 'asc', 'iDisplayStart': '0', 'iDisplayLength': '3'} + + test_page = ServerSideTable(rows_count, table_rows, headers, request_args).get_page() + self.assertEqual(test_page['sEcho'], '1') + self.assertEqual(test_page['iTotalRecords'], 'nan') + self.assertEqual(test_page['iTotalDisplayRecords'], '3') + self.assertEqual(test_page['data'], [{'first': 'b', 'second': 2, 'third': 'aa'}, {'first': 'c', 'second': 1, 'third': 'ss'}, {'first': 'd', 'second': 4, 'third': 'zz'}]) diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index 0d0894a4..c4ea2921 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -28,9 +28,9 @@ class SearchResultPage(object): #maxReturn = 3000 def __init__(self, kw): - """This class gets invoked after hitting submit on the main menu (in -views.py). - + """ + This class gets invoked after hitting submit on the main menu (in + views.py). """ ########################################### @@ -86,7 +86,6 @@ views.py). else: self.gen_search_result() - def gen_search_result(self): """ Get the info displayed in the search result table from the set of results computed in @@ -161,7 +160,14 @@ views.py). trait_dict[key] = trait_dict[key].decode('utf-8') trait_list.append(trait_dict) - self.trait_list = json.dumps(trait_list) + self.trait_list = trait_list + + if this_trait.dataset.type == "ProbeSet": + self.header_data_names = ['index', 'display_name', 'symbol', 'description', 'location', 'mean', 'lrs_score', 'lrs_location', 'additive'] + elif this_trait.dataset.type == "Publish": + self.header_data_names = ['index', 'display_name', 'description', 'mean', 'authors', 'pubmed_text', 'lrs_score', 'lrs_location', 'additive'] + elif this_trait.dataset.type == "Geno": + self.header_data_names = ['index', 'display_name', 'location'] def search(self): """ diff --git a/wqflask/wqflask/server_side.py b/wqflask/wqflask/server_side.py new file mode 100644 index 00000000..5f764767 --- /dev/null +++ b/wqflask/wqflask/server_side.py @@ -0,0 +1,93 @@ +# handles server side table processing + + + +class ServerSideTable(object): + """ + This class is used to do server-side processing + on the DataTables table such as paginating, sorting, + filtering(not implemented) etc. This takes the load off + the client-side and reduces the size of data interchanged. + + Usage: + ServerSideTable(table_data, request_values) + where, + `table_data` must have data members + `rows_count` as number of rows in the table, + `table_rows` as data rows of the table, + `header_data_names` as headers names of the table. + + `request_values` must have request arguments values + including the DataTables server-side processing arguments. + + Have a look at snp_browser_table() function in + wqflask/wqflask/views.py for reference use. + """ + + def __init__(self, rows_count, table_rows, header_data_names, request_values): + self.request_values = request_values + self.sEcho = self.request_values['sEcho'] + + self.rows_count = rows_count + self.table_rows = table_rows + self.header_data_names = header_data_names + + self.sort_rows() + self.paginate_rows() + + def sort_rows(self): + """ + Sorts the rows taking in to account the column (or columns) that the + user has selected. + """ + def is_reverse(str_direction): + """ Maps the 'desc' and 'asc' words to True or False. """ + return True if str_direction == 'desc' else False + + if (self.request_values['iSortCol_0'] != "") and (int(self.request_values['iSortingCols']) > 0): + for i in range(0, int(self.request_values['iSortingCols'])): + column_number = int(self.request_values['iSortCol_' + str(i)]) + column_name = self.header_data_names[column_number - 1] + sort_direction = self.request_values['sSortDir_' + str(i)] + self.table_rows = sorted(self.table_rows, + key=lambda x: x[column_name], + reverse=is_reverse(sort_direction)) + + def paginate_rows(self): + """ + Selects a subset of the filtered and sorted data based on if the table + has pagination, the current page and the size of each page. + """ + def requires_pagination(): + """ Check if the table is going to be paginated """ + if self.request_values['iDisplayStart'] != "": + if int(self.request_values['iDisplayLength']) != -1: + return True + return False + + if not requires_pagination(): + return + + start = int(self.request_values['iDisplayStart']) + length = int(self.request_values['iDisplayLength']) + + # if search returns only one page + if len(self.table_rows) <= length: + # display only one page + self.table_rows = self.table_rows[start:] + else: + limit = -len(self.table_rows) + start + length + if limit < 0: + # display pagination + self.table_rows = self.table_rows[start:limit] + else: + # display last page of pagination + self.table_rows = self.table_rows[start:] + + def get_page(self): + output = {} + output['sEcho'] = str(self.sEcho) + output['iTotalRecords'] = str(float('Nan')) + output['iTotalDisplayRecords'] = str(self.rows_count) + output['data'] = self.table_rows + return output diff --git a/wqflask/wqflask/snp_browser/snp_browser.py b/wqflask/wqflask/snp_browser/snp_browser.py index 6c3fcf53..a52399a2 100644 --- a/wqflask/wqflask/snp_browser/snp_browser.py +++ b/wqflask/wqflask/snp_browser/snp_browser.py @@ -4,7 +4,7 @@ import string from PIL import (Image) from utility.logger import getLogger -logger = getLogger(__name__ ) +logger = getLogger(__name__) from base import species from base import webqtlConfig @@ -14,21 +14,21 @@ class SnpBrowser(object): def __init__(self, start_vars): self.strain_lists = get_browser_sample_lists() self.initialize_parameters(start_vars) - self.limit_number = 10000 if self.first_run == "false": self.filtered_results = self.get_browser_results() + self.table_rows = self.get_table_rows() + self.rows_count = len(self.table_rows) - if len(self.filtered_results) <= self.limit_number: - self.table_rows = self.get_table_rows() - else: - self.empty_columns = None + del self.filtered_results + + if 'sEcho' not in start_vars: self.table_rows = [] if self.limit_strains == "true": - self.header_fields, self.empty_field_count = get_header_list(variant_type = self.variant_type, strains = self.chosen_strains, empty_columns = self.empty_columns) + self.header_fields, self.empty_field_count, self.header_data_names = get_header_list(variant_type = self.variant_type, strains = self.chosen_strains, empty_columns = self.empty_columns) else: - self.header_fields, self.empty_field_count = get_header_list(variant_type = self.variant_type, strains = self.strain_lists, species = self.species_name, empty_columns = self.empty_columns) + self.header_fields, self.empty_field_count, self.header_data_names = get_header_list(variant_type = self.variant_type, strains = self.strain_lists, species = self.species_name, empty_columns = self.empty_columns) def initialize_parameters(self, start_vars): if 'first_run' in start_vars: @@ -368,19 +368,19 @@ class SnpBrowser(object): #ZS: list of booleans representing which columns are entirely empty, so they aren't displayed on the page; only including ones that are sometimes empty (since there's always a location, etc) self.empty_columns = { - "snp_source": "false", - "conservation_score": "false", - "gene_name": "false", - "transcript": "false", - "exon": "false", - "domain_2": "false", - "function": "false", - "function_details": "false" - } + "snp_source": "false", + "conservation_score": "false", + "gene_name": "false", + "transcript": "false", + "exon": "false", + "domain_2": "false", + "function": "false", + "function_details": "false" + } the_rows = [] for i, result in enumerate(self.filtered_results): - this_row = [] + this_row = {} if self.variant_type == "SNP": snp_name, rs, chr, mb, alleles, gene, transcript, exon, domain, function, function_details, snp_source, conservation_score, snp_id = result[:14] allele_value_list = result[14:] @@ -520,13 +520,10 @@ class SnpBrowser(object): "source_name": str(source_name) } #this_row = [indel_name, indel_chr, indel_mb_s, indel_mb_e, indel_strand, indel_type, indel_size, indel_sequence, source_name] - else: - this_row = {} the_rows.append(this_row) return the_rows - def include_record(self, domain, function, snp_source, conservation_score): """ Decide whether to add this record """ @@ -674,9 +671,13 @@ def get_header_list(variant_type, strains, species = None, empty_columns = None) empty_field_count = 0 #ZS: This is an awkward way of letting the javascript know the index where the allele value columns start; there's probably a better way of doing this header_fields = [] + header_data_names = [] if variant_type == "SNP": header_fields.append(['Index', 'SNP ID', 'Chr', 'Mb', 'Alleles', 'Source', 'ConScore', 'Gene', 'Transcript', 'Exon', 'Domain 1', 'Domain 2', 'Function', 'Details']) + header_data_names = ['index', 'snp_name', 'chr', 'mb_formatted', 'alleles', 'snp_source', 'conservation_score', 'gene_name', 'transcript', 'exon', 'domain_1', 'domain_2', 'function', 'function_details'] + header_fields.append(strain_list) + header_data_names += strain_list if empty_columns != None: if empty_columns['snp_source'] == "false": @@ -703,11 +704,16 @@ def get_header_list(variant_type, strains, species = None, empty_columns = None) if empty_columns['function_details'] == "false": empty_field_count += 1 header_fields[0].remove('Details') + + for col in empty_columns.keys(): + if empty_columns[col] == "false": + header_data_names.remove(col) elif variant_type == "InDel": header_fields = ['Index', 'ID', 'Type', 'InDel Chr', 'Mb Start', 'Mb End', 'Strand', 'Size', 'Sequence', 'Source'] + header_data_names = ['index', 'indel_name', 'indel_type', 'indel_chr', 'indel_mb_s', 'indel_mb_e', 'indel_strand', 'indel_size', 'indel_sequence', 'source_name'] - return header_fields, empty_field_count + return header_fields, empty_field_count, header_data_names def get_effect_details_by_category(effect_name = None, effect_value = None): gene_list = [] @@ -868,8 +874,6 @@ def get_gene_id_name_dict(species_id, gene_name_list): if len(results) > 0: for item in results: gene_id_name_dict[item[1]] = item[0] - else: - pass return gene_id_name_dict @@ -883,7 +887,7 @@ def check_if_in_gene(species_id, chr, mb): query = """SELECT geneId,geneSymbol FROM GeneList WHERE chromosome = '{0}' AND - (txStart < {1} AND txEnd > {1}); """.format(species_id, chr, mb) + (txStart < {1} AND txEnd > {1}); """.format(chr, mb) result = g.db.execute(query).fetchone() diff --git a/wqflask/wqflask/static/new/css/snp_browser.css b/wqflask/wqflask/static/new/css/snp_browser.css index 30fe9a59..a7942d2a 100644 --- a/wqflask/wqflask/static/new/css/snp_browser.css +++ b/wqflask/wqflask/static/new/css/snp_browser.css @@ -6,6 +6,10 @@ table.dataTable thead th { vertical-align: bottom; } +table.dataTable tbody tr.selected { + background-color: #ffee99; +} + table.dataTable thead .sorting, table.dataTable thead .sorting_asc, table.dataTable thead .sorting_desc, @@ -18,7 +22,7 @@ table.dataTable thead .sorting_desc_disabled { table.dataTable thead th{ border-right: 1px solid white; color: white; - background-color: royalblue; + background-color: #369; } table.dataTable tbody td { diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index f2334512..765c4ab8 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -160,6 +160,16 @@ <script type="text/javascript" charset="utf-8"> $(document).ready( function () { + + var getParams = function(url) { + let parser = document.createElement('a'); + parser.href = url; + let params = parser.search.substring(1); + if(params.length > 0) { + return ('?'+params); + } + return params; + }; $('#trait_table tr').click(function(event) { if (event.target.type !== 'checkbox') { @@ -225,7 +235,7 @@ 'data': null, 'width': "25px", 'orderDataType': "dom-checkbox", - 'orderSequence': [ "desc", "asc"], + 'orderable': false, 'render': function(data, type, row, meta) { return '<input type="checkbox" name="searchResult" class="checkbox trait_checkbox" value="' + data.hmac + '">' } @@ -373,15 +383,33 @@ }{% endif %} ], "order": [[1, "asc" ]], - 'sDom': "itirp", + {% if dataset.type != 'Geno' %} + buttons: [ + { + extend: 'columnsToggle', + columns: function( idx, data, node ) { + if (idx != 0) { + return true; + } else { + return false; + } + }, + postfixButtons: [ 'colvisRestore' ] + } + ], + 'sDom': "Bpitirp", + {% else %} + 'sDom': "pitirp", + {% endif %} 'iDisplayLength': 500, 'deferRender': true, 'paging': true, 'orderClasses': true, 'processing': true, - 'language': { - 'loadingRecords': ' ', - 'processing': 'Loading...' + 'bServerSide': true, + 'sAjaxSource': '/search_table'+getParams(window.location.href), + 'infoCallback': function(settings, start, end, max, total, pre) { + return "Showing " + start + " to " + (start + this.api().data().length - 1) + " of " + total + " entries"; } } ); diff --git a/wqflask/wqflask/templates/snp_browser.html b/wqflask/wqflask/templates/snp_browser.html index a96b8e3b..b9aea570 100644 --- a/wqflask/wqflask/templates/snp_browser.html +++ b/wqflask/wqflask/templates/snp_browser.html @@ -15,7 +15,7 @@ <input type="hidden" name="first_run" value="{{ first_run }}"> <input type="hidden" name="chosen_strains_mouse" value="{{ chosen_strains_mouse|join(",") }}"> <input type="hidden" name="chosen_strains_rat" value="{{ chosen_strains_rat|join(",") }}"> - <div class="col-xs-4" style="padding-left: 0px;"> + <div class="col-xs-4" style="width: 260px; padding-left: 30px; padding-right: 0px;"> <div class="form-group row" style="margin-bottom: 5px;"> <label for="snp_or_indel" style="text-align: right;" class="col-xs-4 col-form-label"><b>Type:</b></label> <div class="col-xs-8"> @@ -74,7 +74,7 @@ </div> </div> </div> - <div class="col-xs-4" style="padding-left: 0px;"> + <div class="col-xs-4" style="width: 310px; padding-left: 0px; padding-right: 20px;"> <div class="form-group row" style="margin-bottom: 10px;"> <label for="strains" style="text-align: right;" class="col-xs-4 col-form-label"><b>Strains:</b></label> <div class="col-xs-8"> @@ -107,8 +107,14 @@ </div> </div> </div> + <div class="form-group row"> + <label class="col-xs-4 col-form-label"></label> + <div class="col-xs-8" style="margin-top: 65px;"> + <input class="btn btn-primary" type="button" name="export_csv" value="Export to CSV"> + </div> + </div> </div> - <div class="col-xs-4" style="padding-left: 20px;"> + <div class="col-xs-4" style="width: 310px; padding-left: 20px;"> <div class="form-group row" style="margin-bottom: 5px;"> <label for="domain" style="text-align: right;" class="col-xs-4 col-form-label"><b>Domain:</b></label> <div class="col-xs-8"> @@ -182,24 +188,21 @@ </div> <div style="margin-top: 20px;"> - {% if filtered_results is defined %} - {% if filtered_results|length > limit_number %} - There are more than 10000 results. Consider limiting your search to a smaller range. - {% else %} + {% if table_rows is defined %} <table class="dataTable cell-border nowrap" id="results_table" style="float: left;"> <thead> <tr> <th></th> {% if header_fields|length == 2 %} {% for header in header_fields[0] %} - <th data-export="{{ header }}">{{ header }}</th> + <th data-export="{{ header }}" name="{{ header }}">{{ header }}</th> {% endfor %} {% for strain in header_fields[1] %} - <th data-export="{{ strain }}" style="align: center; text-align: center; line-height: 15px;">{% for letter in strain %}<div style="transform: rotate(90deg);">{{ letter }}</div>{% endfor %}</th> + <th data-export="{{ strain }}" name="{{ strain }}" style="align: center; text-align: center; line-height: 12px;">{% for letter in strain|reverse %}<div style="transform: rotate(270deg);">{{ letter }}</div>{% endfor %}</th> {% endfor %} {% else %} {% for header in header_fields %} - <th data-export="{{ header }}">{{ header }}</th> + <th data-export="{{ header }}" name="{{ header }}">{{ header }}</th> {% endfor %} {% endif %} </tr> @@ -208,7 +211,6 @@ <td colspan="100%" align="center"><br><b><font size="15">Loading...</font></b><br></td> </tbody> </table> - {% endif %} {% endif %} </div> </div> @@ -222,12 +224,24 @@ <script language="javascript" type="text/javascript" src="/static/new/javascript/typeahead_rn6.json"></script> <script type='text/javascript'> - var json_rows = {{ table_rows|safe }}; var empty_columns = {{ empty_columns|safe }}; + + var remain_field_count = 15 - {{ empty_field_count|safe }}; + var total_field_count = 15 - {{ empty_field_count|safe }} + {{ allele_list|safe|length }}; </script> <script language="javascript"> + var getParams = function(url) { + let parser = document.createElement('a'); + parser.href = url; + let params = parser.search.substring(1); + if(params.length > 0) { + return ('?'+params); + } + return params; + }; + var substringMatcher = function(strs) { return function findMatches(q, cb) { var matches, substringRegex; @@ -260,19 +274,20 @@ source: substringMatcher(rat_genes) }); - {% if filtered_results is defined %} + {% if table_rows is defined %} $("#results_table").DataTable( { - 'data': json_rows, {% if variant_type == "SNP" %} 'columns': [ { 'data': null, + 'className': 'dt-body-center', 'orderable': false, 'render': function(data, type, row, meta) { - return '<input type="checkbox" name="trait_check">' + return '<input type="checkbox" class="checkbox" id="variant_checkbox" onchange="onVarinatCheck(this)" name="trait_check">' } }, { - 'data': 'index' + 'data': 'index', + 'className': 'dt-body-right' }, { 'data': null, 'render': function(data, type, row, meta) { @@ -283,27 +298,30 @@ } } }, { - 'data': 'chr' + 'data': 'chr', + 'className': 'dt-body-center' }, { - 'data': 'mb_formatted' + 'data': 'mb_formatted', + 'className': 'dt-body-right' }, { 'data': 'alleles' }, {% if empty_columns['snp_source'] == "true" %}{ 'data': null, 'render': function(data, type, row, meta) { if (data.snp_source == "Sanger/UCLA") { - return '<a href="' + data.source_urls[0] + '">Sanger</a><a href="' + data.source_urls[1] + '">UCLA</a>' + return '<a href="' + data.source_urls[0] + '">Sanger</a>, <a href="' + data.source_urls[1] + '">UCLA</a>' } else { return data.snp_source } } }, {% endif %} {% if empty_columns['conservation_score'] == "true" %}{ - 'data': 'conservation_score' + 'data': 'conservation_score', + 'className': 'dt-body-right' }, {% endif %} {% if empty_columns['gene_name'] == "true" %}{ 'data': null, 'render': function(data, type, row, meta) { if (data.gene_name != "") { - return '<i>' + data.gene_name + '</i><br><a href="' + data.gene_link + '">NCBI</a>' + return '<i>' + data.gene_name + '</i>, <a href="' + data.gene_link + '">NCBI</a>' } else { return data.gene_name } @@ -330,6 +348,7 @@ }, {% endif %} {% for item in allele_list %} { 'data': null, 'orderable': false, + 'className': 'dt-body-center', 'render': function(data, type, row, meta) { if (typeof data.allele_value_list[{{ loop.index - 1 }}][0] !== "undefined") { return data.allele_value_list[{{ loop.index - 1 }}][0] @@ -339,12 +358,9 @@ } }{% if loop.index < allele_list|length %},{% endif %}{% endfor %} ], - 'createdRow': function( row, data, dataIndex) { - $('td', row).eq(0).attr("style", "text-align: center; padding: 4px 10px 2px 10px;"); - $('td', row).eq(1).attr("align", "right"); - for (i = {{ 15 - empty_field_count }}; i < ({{ 15 - empty_field_count }} + {{ allele_list|length }}); i++) { + 'createdRow': function(row, data, dataIndex) { + for (i = remain_field_count; i < total_field_count; i++) { var this_allele = $('td', row).eq(i).text(); - $('td', row).eq(i).attr("style", "text-align: center; padding: 4px 10px 2px 10px;"); switch (this_allele) { case "A": $('td', row).eq(i).addClass('A_allele_color'); @@ -380,24 +396,29 @@ { 'data': null, 'render': function(data, type, row, meta) { - return '<input type="checkbox" name="trait_check">' + return '<input type="checkbox" class="checkbox" id="variant_checkbox" onchange="onVarinatCheck(this)" name="trait_check">' } }, { - 'data': 'index' + 'data': 'index', + 'className': 'dt-body-right' }, { 'data': 'indel_name' }, { 'data': 'indel_type' }, { - 'data': 'indel_chr' + 'data': 'indel_chr', + 'className': 'dt-body-center' }, { - 'data': 'indel_mb_s' + 'data': 'indel_mb_s', + 'className': 'dt-body-right' }, { - 'data': 'indel_mb_e' + 'data': 'indel_mb_e', + 'className': 'dt-body-right' }, { 'data': 'indel_strand' }, { - 'data': 'indel_size' + 'data': 'indel_size', + 'className': 'dt-body-right' }, { 'data': 'indel_sequence' }, { @@ -407,15 +428,28 @@ {% endif %} 'order': [[1, "asc" ]], 'sDom': "rtip", - 'iDisplayLength': 500, - 'processing': true, - 'language': { - 'loadingRecords': ' ', - 'processing': 'Loading...' + 'iDisplayLength': 100, + 'bServerSide': true, + 'sAjaxSource': '/snp_browser_table'+getParams(window.location.href), + 'infoCallback': function(settings, start, end, max, total, pre) { + return "Showing " + start + " to " + (start + this.api().data().length - 1) + " of " + total + " entries"; } }); {% endif %} + function onVarinatCheck(checkboxElem) { + if (checkboxElem.checked) { + if (!checkboxElem.parentElement.parentElement.classList.contains('selected')) { + checkboxElem.parentElement.parentElement.classList.add('selected') + } + } + else { + if (checkboxElem.parentElement.parentElement.classList.contains('selected')) { + checkboxElem.parentElement.parentElement.classList.remove('selected') + } + } + } + $("#species_select").change(function() { this_species = $(this).val(); $("#strain_select").empty() @@ -509,6 +543,40 @@ }); $("input[name=chosen_strains]").val(strain_list.join(",")); }); + + + + $("input[name=export_csv]").click(function() { + var csv = []; + var rows = document.querySelectorAll("table tr"); + + var headers = []; + var col_header = rows[0].querySelectorAll("th"); + for(let i = 1; i < col_header.length; i++) { + headers.push(col_header[i].getAttribute("name")); + } + csv.push(headers.join(",")); + + for (let i = 1; i < rows.length; i++) { + var row = [], cols = rows[i].querySelectorAll("td"); + var checkBox = rows[i].querySelector("input"); + + if(checkBox.checked == true) { + for (let j = 1; j < cols.length; j++) + row.push(cols[j].innerText); + + csv.push(row.join(",")); + } + } + + var csvFile = new Blob([csv.join("\n")], {type: "text/csv"}); + var downloadLink = document.createElement("a"); + downloadLink.download = "variant_data.csv"; + downloadLink.href = window.URL.createObjectURL(csvFile); + downloadLink.style.display = "none"; + document.body.appendChild(downloadLink); + downloadLink.click(); + }); </script> {% endblock %} diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index 25563e86..632e0d2d 100644 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -26,6 +26,15 @@ import sqlalchemy from wqflask import app from flask import g, Response, request, make_response, render_template, send_from_directory, jsonify, redirect, url_for, send_file +from wqflask import group_manager +from wqflask import resource_manager +from wqflask import search_results +from wqflask import export_traits +from wqflask import gsearch +from wqflask import update_search_results +from wqflask import docs +from wqflask import news +from wqflask import server_side from wqflask.submit_bnw import get_bnw_input from base.data_set import create_dataset, DataSet # Used by YAML in marker_regression from wqflask.show_trait import show_trait @@ -220,6 +229,26 @@ def search_page(): else: return render_template("search_error.html") +@app.route("/search_table", methods=('GET',)) +def search_page_table(): + logger.info("in search_page table") + logger.info(request.url) + + logger.info("request.args is", request.args) + the_search = search_results.SearchResultPage(request.args) + + logger.info(type(the_search.trait_list)) + logger.info(the_search.trait_list) + + current_page = server_side.ServerSideTable( + len(the_search.trait_list), + the_search.trait_list, + the_search.header_data_names, + request.args, + ).get_page() + + return flask.jsonify(current_page) + @app.route("/gsearch", methods=('GET',)) def gsearchact(): logger.info(request.url) @@ -896,6 +925,19 @@ def db_info_page(): return render_template("info_page.html", **template_vars.__dict__) +@app.route("/snp_browser_table", methods=('GET',)) +def snp_browser_table(): + logger.info(request.url) + snp_table_data = snp_browser.SnpBrowser(request.args) + current_page = server_side.ServerSideTable( + snp_table_data.rows_count, + snp_table_data.table_rows, + snp_table_data.header_data_names, + request.args, + ).get_page() + + return flask.jsonify(current_page) + @app.route("/tutorial/WebQTLTour", methods=('GET',)) def tutorial_page(): #ZS: Currently just links to GN1 |