fix run_model call so that it works with non-default options as well.
[sgn.git] / mason / tools / sequence_metadata / query_sequence_metadata.mas
blobad63ff3da31463f62dca4348b74e4236ea5a7824
2 <%args>
3 </%args>
5 <& /util/import_javascript.mas, classes => [ 'jquery', 'jquery.dataTables', 'jquery.dataTables-buttons-min', 'jszip-min', 'buttons.bootstrap-min', 'buttons.html5-min' ],  &>
7 <p>Filter the sequence metadata by position, sequence metadata type and/or protocol, and/or by protocol attribute value(s).</p>
9 <br />
10 <a id="sequence_metadata_anchor"></a>
12 <!-- START QUERY SECTION -->
13 <div id="sequence_metadata_section_query">
15     <form class="form-horizontal" id="sequence_metadata_filter_form" name="sequence_metadata_filter_form">
17         <!-- QUERY RANGE -->
18         <&| /page/info_section.mas, title => 'Query Range', collapsible=>1, collapsed=>0 &>
19             <div class="well">
20             
21                 <!-- Refence Genome -->
22                 <div class="form-group">
23                     <label class="col-sm-2 control-label">Reference Genome: </label>
24                     <div class="col-sm-10">
25                         <select class="form-control" id="sequence_metadata_filter_reference_genome" disabled>
26                             <option value="">Loading...</option>
27                         </select>
28                     </div>
29                 </div>
31                 <!-- Feature -->
32                 <div class="form-group">
33                     <label class="col-sm-2 control-label">Feature: </label>
34                     <div class="col-sm-10">
35                         <select class="form-control" id="sequence_metadata_filter_feature" disabled>
36                             <option value="">Loading...</option>
37                         </select>
38                     </div>
39                 </div>
41                 <!-- Start / End -->
42                 <div class="form-group">
43                     <label class="col-sm-2 control-label">Start: </label>
44                     <div class="col-sm-4">
45                         <input class="form-control" id="sequence_metadata_filter_start" type="text" value="">
46                     </div>
47                     <label class="col-sm-2 control-label">End: </label>
48                     <div class="col-sm-4">
49                         <input class="form-control" id="sequence_metadata_filter_end" type="text" value="">
50                     </div>
51                 </div>
53             </div>
54         </&>
55         <!-- END QUERY RANGE -->
58         <!-- DATA TYPE / PROTOCOL -->
59         <&| /page/info_section.mas, title => 'Protocol', collapsible=>1, collapsed=>0 &>
60             <div class="well">
62                 <!-- User Selected Type / Protocol -->
63                 <div id="sequence_metadata_filter_type_protocol_user_selection">
65                     <!-- Data Type -->
66                     <div class="form-group">
67                         <div class="col-sm-2" style="text-align: right">
68                             <label class="control-label">Protocol: </label><br /><br /><br />
69                             <a id="sequence_metadata_filter_type_info_btn" href="#"><span class="glyphicon glyphicon-question-sign"></span>&nbsp;Data Type Info</a>
70                             <br /><br />
71                             <a id="sequence_metadata_filter_protocol_info_btn" href="#"><span class="glyphicon glyphicon-question-sign"></span>&nbsp;Protocol Info</a>
72                         </div>
73                         <div class="col-sm-10">
74                             <select class="form-control" id="sequence_metadata_filter_protocol" size="10" multiple disabled>
75                                 <option value="">Loading...</option>
76                             </select>
77                         </div>
78                     </div>
79                     <br />
80                 
81                 </div>
83                 <!-- URL Selected Protocol -->
84                 <div id="sequence_metadata_filter_type_protocol_url_selection">
85                     <div class="form-group">
86                         <label class="col-sm-2 control-label">Selected Protocols: </label>
87                         <div class="col-sm-9">
88                             <p id="sequence_metadata_filter_type_protocol_url_selection_names">Loading...</p>
89                         </div>
90                         <div class="col-sm-1" style="margin-top: 10px;">
91                             <button id='sequence_metadata_filter_type_protocol_url_selection_remove' class='btn btn-danger btn-xs'><span class='glyphicon glyphicon-remove'></span></button>
92                         </div>
93                     </div>
94                 </div>
96             </div>
97         </&>
98         <!-- END DATA TYPE / PROTOCOL -->
101         <!-- ADVANCED SEARCH -->
102         <&| /page/info_section.mas, title=>'Advanced Search', subtitle=>'Filter by attribute values', collapsible=>1, collapsed=>1 &>
103             <div class="well">
105                 <p>Return only sequence metadata features that have attribute values that match the added comparisons.  If more than one attribute 
106                 filter is added, the sequence metadata feature must match all of the filters.</p>
108                 <br />
110                 <!-- Score Min / Max -->
111                 <div class="form-group">
112                     <label class="col-sm-2 control-label">Score: </label>
113                     <div class="col-sm-3">
114                         <label class="control-label">Protocol</label><br />
115                         <select class="form-control" id="sequence_metadata_filter_score_protocol" disabled>
116                             <option value="">Loading...</option>
117                         </select>
118                     </div>
119                     <div class="col-sm-3">
120                         <label class="control-label">Comparison</label><br />
121                         <select class="form-control" id="sequence_metadata_filter_score_comparison">
122                             <option value="eq">Equal</option>
123                             <option value="lte">Less Than or Equal</option>
124                             <option value="lt">Less Than</option>
125                             <option value="gte">Greater Than or Equal</option>
126                             <option value="gt">Greater Than</option>
127                         </select>
128                     </div>
129                     <div class="col-sm-3">
130                         <label class="control-label">Value</label><br />
131                         <input class="form-control" id="sequence_metadata_filter_score_value" type="text" value="">
132                     </div>
133                     <div class="col-sm-1">
134                         <br />
135                         <button id="sequence_metadata_filter_score_add" class="btn btn-info">Add</button>
136                     </div>
137                 </div>
138                 <br /><br />
140                 <!-- Attribute Value -->
141                 <div class="form-group">
142                     <label class="col-sm-2 control-label">Attribute: </label>
143                     <div class="col-sm-3">
144                         <label class="control-label">Protocol</label><br />
145                         <select class="form-control" id="sequence_metadata_filter_attribute_protocol" disabled>
146                             <option value="">Loading...</option>
147                         </select>
148                     </div>
149                     <div class="col-sm-2">
150                         <label class="control-label">Key</label><br />
151                         <select class="form-control" id="sequence_metadata_filter_attribute_key" disabled>
152                             <option value="">Loading...</option>
153                         </select>
154                     </div>
155                     <div class="col-sm-2">
156                         <label class="control-label">Comparison</label><br />
157                         <select class="form-control" id="sequence_metadata_filter_attribute_comparison">
158                             <option value="con">Contains</option>
159                             <option value="eq">Equal</option>
160                             <option value="lte">Less Than or Equal</option>
161                             <option value="lt">Less Than</option>
162                             <option value="gte">Greater Than or Equal</option>
163                             <option value="gt">Greater Than</option>
164                         </select>
165                     </div>
166                     <div class="col-sm-2">
167                         <label class="control-label">Value</label><br />
168                         <input class="form-control" id="sequence_metadata_filter_attribute_value" type="text" value="">
169                     </div>
170                     <div class="col-sm-1">
171                         <br />
172                         <button id="sequence_metadata_filter_attribute_add" class="btn btn-info">Add</button>
173                     </div>
174                 </div>
175                 <br />
177                 <!-- Attribute Table -->
178                 <div>
179                     <p><strong>Attribute Filters:</strong></p>
180                     <table id="sequence_metadata_filter_attributes_table" class="table table-striped table-hover">
181                         <tr>
182                             <th>Protocol</th>
183                             <th>Attribute</th>
184                             <th>Comparison</th>
185                             <th>Value</th>
186                             <th></th>
187                         </tr>
188                     </table>
189                 </div>
191             </div>
192         </&>
193         <!-- END ADVANCED SEARCH -->
194         
195         <br /><br /><br />
197         <!-- QUERY -->
198         <div class="center">
199             <button id="sequence_metadata_filter_query" class="btn btn-primary btn-block" style="max-width: 400px; margin: auto" disabled>Search</button>
200         </div>
202         <br /><br /><br />
205     </form>
207 </div>
208 <!-- END QUERY SECTION -->
211 <!-- START AUTO SEARCH SECTION -->
212 <div id="sequence_metadata_section_auto_search" style="display: none">
213     <div class="well" style="max-width: 500px; margin: 50px auto">
214         <h1>Searching...</h1>
215         <br /><br />
216         <p>Querying the database for the requested sequence metadata...</p>
217         <br /><br />
218     </div>
219 </div>
220 <!-- END AUTO SEARCH SECTION -->
223 <!-- Error Message -->
224 <br />
225 <div id="sequence_metadata_filter_error" class="alert alert-danger" role="alert" style="display:none"></div>
226 <br />
229 <!-- START RESULTS SECTION -->
230 <div id="sequence_metadata_section_results" style="display: none">
232     <!-- Query Results -->
233     <table id="sequence_metadata_filter_results" class="display"></table>
235     <br /><br />
237     <!-- Return -->
238     <div class="center">
239         <button id="sequence_metadata_filter_return" class="btn btn-primary btn-block" style="max-width: 400px; margin: auto">
240             <span class="glyphicon glyphicon-chevron-left">&nbsp;</span>Back to Search
241         </button>
242     </div>
244     <br /><br />
246 </div>
249 <!-- DATA TYPE INFO MODAL -->
250 <div id="sequence_metadata_filter_type_info" class="modal fade" tabindex="-1" role="dialog">
251     <div class="modal-dialog modal-lg" role="document">
252         <div class="modal-content">
253             <div class="modal-header">
254                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
255                 <h4 class="modal-title">Data Type Information</h4>
256             </div>
257             <div class="modal-body"></div>
258         </div>
259     </div>
260 </div>
262 <!-- PROTOCOL INFO MODAL -->
263 <div id="sequence_metadata_filter_protocol_info" class="modal fade" tabindex="-1" role="dialog">
264     <div class="modal-dialog modal-lg" role="document">
265         <div class="modal-content">
266             <div class="modal-header">
267                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
268                 <h4 class="modal-title">Protocol Information</h4>
269             </div>
270             <div class="modal-body"></div>
271         </div>
272     </div>
273 </div>
275 <!-- JBROWSE CONFIGURATION -->
276 <!-- OVERWRITE THIS FILE IN THE INSTANCE-SPECIFIC MASON REPO TO CONFIGURE -->
277 <& /tools/sequence_metadata/jbrowse_config_sequence_metadata.mas &>
280 <script type="text/javascript">
282 var selected_reference_genome = false;      // Selected reference genome via URL param
283 var selected_feature = false;               // Selected feature name via URL param
284 var selected_protocols = [];                // List of protocol ids selected via URL params
285 var auto_search = false;                    // Flag for automatically performing the search via URL param
286 var reference_genomes = [];                 // List of reference genomes / species fetched from the database
287 var features = {};                          // Object of features (key = species) fetched from the database
288 var protocols = {};                         // Object of sequence metadata protocols (key = reference) fetched from the database
289 var attribute_filter_count = 0;             // Current count of attribute filters added by user
290 var markers;                                // Object of queried markers from the database for specific ranges
292 jQuery(document).ready(function() {
294     // Parse URL Arguments
295     parse_args();
297     // Get and setup the initial query data types
298     setup();
300     //
301     // CLICK AND CHANGE LISTENERS
302     //
304     // Reference Genome Change Listener
305     jQuery('#sequence_metadata_filter_reference_genome').change(set_features).change(set_protocols).change(set_attribute_protocols);
307     // Toggle info boxes
308     jQuery('#sequence_metadata_filter_type_info_btn').click(function() {
309         jQuery('#sequence_metadata_filter_type_info').modal();
310         return false;
311     });
312     jQuery('#sequence_metadata_filter_protocol_info_btn').click(function() {
313         jQuery('#sequence_metadata_filter_protocol_info').modal();
314         return false;
315     });
317     // Protocol Change Listeners
318     jQuery('#sequence_metadata_filter_protocol').change(set_attribute_protocols);
320     // Attribute Protocol Listener
321     jQuery('#sequence_metadata_filter_attribute_protocol').change(set_attribute_keys);
323     // Add Score / Attribute Buttons
324     jQuery('#sequence_metadata_filter_score_add').click(function() { add_attribute_filter('score'); return false });
325     jQuery('#sequence_metadata_filter_attribute_add').click(function() { add_attribute_filter('attribute'); return false });
327     // Remove URL-selected Protocol Button
328     jQuery('#sequence_metadata_filter_type_protocol_url_selection_remove').click(reset_selected_protocols)
330     // Query Data
331     jQuery('#sequence_metadata_filter_query').click(query);
333     // Return to Query
334     jQuery('#sequence_metadata_filter_return').click(toggle_sections);
336     // Toggle Sections on history/state change
337     if (window.history && window.history.pushState) {
338         jQuery(window).on('popstate', toggle_sections);
339     }
342     //
343     // DATATABLES SETUP
344     //
346     // Set DataTables Buttons
347     let DT_BUTTONS = [
348         {
349             extend: 'excelHtml5',
350             title: 'sequence_metadata_results',
351             exportOptions: {
352                 orthogonal: 'export'
353             }
354         },
355         {
356             extend: 'csvHtml5',
357             title: 'sequence_metadata_results',
358             exportOptions: {
359                 orthogonal: 'export'
360             }
361         },
362         {
363             text: 'JSON',
364             action: function() {
365                 download(getQueryURL('JSON'), "sequence_metadata.json");
366             }
367         },
368         {
369             text: 'GA4GH',
370             action: function() {
371                 download(getQueryURL('GA4GH'), "sequence_metadata_ga4gh.json");
372             }
373         },
374         {
375             text: 'GFF',
376             action: function ( e, dt, button, config ) {
377                 download(getQueryURL("gff"), "sequence_metadata.gff");
378             }
379         }
380     ];
381     if ( JBROWSE_CONFIG_SEQUENCE_METADATA && JBROWSE_CONFIG_SEQUENCE_METADATA.enabled ) {
382         DT_BUTTONS.push({
383             text: "<span class='glyphicon glyphicon-new-window'></span>&nbsp;JBrowse",
384             action: function() {
385                 window.open(getJBrowseURL(JBROWSE_CONFIG_SEQUENCE_METADATA), '_blank');
386             }
387         });
388     }
390     // Init DataTable
391     jQuery('#sequence_metadata_filter_results').DataTable({
392         dom: 'Bfrtip',
393         autoWidth: false,
394         data: [],
395         columns: [
396             { title: "Protocol", data: "nd_protocol_name" },
397             { title: "Feature", data: "feature_name" },
398             { title: "Start", data: "start" },
399             { title: "End", data: "end" },
400             { title: "Score", data: "score" },
401             { title: "Attributes", data: "attributes", render: renderAttributesColumn },
402             { title: "External&nbsp;Links", data: "links", render: renderLinksColumn },
403             { title: "Markers", data: renderMarkersColumn }
404         ],
405         order: [[ 2, "asc" ]],
406         buttons: DT_BUTTONS
407     });
409     // Update the Marker Search display when the table is redrawn (on page change, etc...)
410     jQuery('#sequence_metadata_filter_results').DataTable().on('draw.dt', function() {
411         updateMarkers();
412     });
418 // SETUP FUNCTIONS
422  * Parse the URL query parameters for pre-defined filter attributes
423  */
424 function parse_args() {
425     const urlParams = new URLSearchParams(window.location.search);
426     if ( urlParams.has('nd_protocol_id') ) {
427         selected_protocols = urlParams.getAll('nd_protocol_id');
428         jQuery('#sequence_metadata_filter_type_protocol_user_selection').css('display', 'none');
429         jQuery('#sequence_metadata_filter_type_protocol_url_selection').css('display', 'block');
430     }
431     if ( urlParams.has('reference_genome') ) {
432         selected_reference_genome = urlParams.get('reference_genome');
433     }
434     if ( urlParams.has('feature') ) {
435         selected_feature = urlParams.get('feature');
436     }
437     if ( urlParams.has('start') ) {
438         jQuery('#sequence_metadata_filter_start').val(urlParams.get('start'));
439     }
440     if ( urlParams.has('end') ) {
441         jQuery('#sequence_metadata_filter_end').val(urlParams.get('end'));
442     }
443     if ( urlParams.has('results') && selected_feature ) {
444         auto_search = true;
445     }
450  * Setup the initial query data types
451  * - Call get_reference_genomes()
452  * - Then, call get_features(), get_types(), and get_protocols()
453  * - Enable the search once all are complete
454  * - If auto_search is enabled, start the query and display feedback
455  */
456 function setup() {
457     let completed = [];
459     // Show the auto search section, if enabled
460     if ( auto_search ) {
461         display_auto_search_loading();
462     }
464     // Setup the reference genomes...
465     get_reference_genomes(function() {
466         _complete("reference_genomes")
467     });
469     function _complete(type) {
470         completed.push(type);
472         // Setup the remaining data types...
473         if ( type === "reference_genomes" ) {
474             get_features(function() {
475                 _complete("features")
476             });
477             get_types(function() {
478                 _complete("types")
479             });
480             get_protocols(function() {
481                 _complete("protocols")
482             });
483         }
485         // Finish the setup
486         if ( completed.length === 4 ) {
487             jQuery('#sequence_metadata_filter_query').attr('disabled', false);
488             if ( auto_search ) {
489                 query();
490             }
491         }
492     }
497  * Remove the pre-defined selected protocols and display the 
498  * user-selectable type and protocol options
499  */
500 function reset_selected_protocols() {
501     selected_protocols = [];
502     set_protocols();
503     jQuery('#sequence_metadata_filter_type_protocol_user_selection').css('display', 'block');
504     jQuery('#sequence_metadata_filter_type_protocol_url_selection').css('display', 'none');
505     return false;
511 // DATABASE QUERIES AND RESPONSE HANDLERS
515  * Get the reference genomes / species associated with the stored genotype protocols
516  * - populate the options for the reference genome select box
517  * @param {Function} [callback] Callback function() (only called if successful)
518  */
519 function get_reference_genomes(callback) {
520     jQuery.ajax({
521         type: 'GET',
522         dataType: 'json',
523         url: '/ajax/markers/genotyped/reference_genomes',
524         success: function(data) {
525             if ( data && data.reference_genomes ) {
526                 reference_genomes = data.reference_genomes;
527                 let options = "";
528                 for ( let i = 0; i < reference_genomes.length; i++ ) {
529                     let reference = reference_genomes[i].reference_genome_name;
530                     let species = reference_genomes[i].species_name;
531                     let label = reference + " (" + species + ")";
532                     let selected = selected_reference_genome && reference_genomes[i].reference_genome_name === selected_reference_genome ? 'selected' : '';
533                     options += "<option value='" + reference + "' data-species='" + species + "' " + selected + ">" + label + "</option>";
534                 }
535                 jQuery('#sequence_metadata_filter_reference_genome').html(options);
536                 jQuery('#sequence_metadata_filter_reference_genome').prop('disabled', false);
537                 if ( callback ) return callback();
538             }
539             else {
540                 alert("ERROR: Could not load reference genomes!");
541             }
542         },
543         error: function() {
544             alert("ERROR: Could not load reference genomes!");
545         }
546     });
550  * Get the features associated with stored sequence metadata
551  * - call set_features() to populate the options for the feature select box
552  * @param {Function} [callback] Callback function() (only called if successful)
553  */
554 function get_features(callback) {
555     jQuery.ajax({
556         type: 'GET',
557         dataType: 'json',
558         url: '/ajax/sequence_metadata/features',
559         success: function(data) {
560             if ( data && data.features ) {
562                 // Save features by species
563                 features = {};
564                 for ( let i = 0; i < data.features.length; i++ ) {
565                     let s = data.features[i].organism_name;
566                     if ( !features.hasOwnProperty(s) ) {
567                         features[s] = [];
568                     }
569                     features[s].push(data.features[i]);
570                 }
572                 // Set feature options
573                 set_features();
575                 // Return to callback, if provided
576                 if ( callback ) return callback();
578             }
579             else {
580                 alert("ERROR: Could not load features!");
581             }
582         },
583         error: function() {
584          alert("ERROR: Could not load features");
585         }
586     });
590  * Set the options for the features select box based on the currently selected reference genome
591  */
592 function set_features() {
593     let s = jQuery("#sequence_metadata_filter_reference_genome option:selected").data("species");
594     let f = features[s];
595     let options = "";
596     if ( f ) {
597         for ( let i = 0; i < f.length; i++ ) {
598             let f_id = f[i].feature_id;
599             let f_name = f[i].feature_name;
600             let selected = selected_feature && selected_feature === f_name ? 'selected' : '';
601             options += "<option value='" + f_id + "' " + selected + ">" + f_name + "</option>";
602         }
603     }
604     jQuery('#sequence_metadata_filter_feature').html(options);
605     jQuery('#sequence_metadata_filter_feature').prop('disabled', false);
609  * Get the types associated with stored sequence metadata
610  * - build the data type info table
611  * @param {Function} [callback] Callback function() (only called if successful)
612  */
613 function get_types(callback) {
614     jQuery.ajax({
615         type: 'GET',
616         dataType: 'json',
617         url: '/ajax/sequence_metadata/types',
618         success: function(data) {
619             if ( data && data.types ) {
621                 // Build Info Table
622                 let info = "<table class='table table-striped'><thead><tr><th>Data&nbsp;Type</th><th>Definition</th></tr></thead>";
623                 info += "<tbody>";
624                 for ( let i = 0; i < data.types.length; i++ ) {
625                     info += "<tr><td>" + data.types[i].type_name + "</td><td>" + data.types[i].type_definition + "</td></tr>";
626                 }
627                 info += "</tbody>";
628                 info += "</table>";
629                 jQuery('#sequence_metadata_filter_type_info .modal-body').html(info);
631                 // Return to callback, if provided
632                 if ( callback ) return callback();
634             }
635             else {
636                 alert("ERROR: Could not load types!");
637             }
638         },
639         error: function() {
640          alert("ERROR: Could not load types");
641         }
642     });
646  * Get the protocols associated with stored sequence metadata
647  * - call set_protocols() to populate the options for the protocol select box'
648  * - call set_attribute_protocols() to set the options for the protocol lists for the attributes
649  * - build the protocol info table
650  */
651 function get_protocols(callback) {
652     jQuery.ajax({
653         type: 'GET',
654         dataType: 'json',
655         url: '/ajax/sequence_metadata/protocols',
656         success: function(data) {
657             if ( data && data.protocols ) {
658                 
659                 // Save protocols by reference genome
660                 protocols = {};
661                 for ( let i = 0; i < data.protocols.length; i++ ) {
662                     let g = data.protocols[i].nd_protocol_properties.reference_genome;
663                     if ( !protocols.hasOwnProperty(g) ) {
664                         protocols[g] = [];
665                     }
666                     protocols[g].push(data.protocols[i]);
667                 }
669                 // Set Select Options
670                 set_protocols();
671                 set_attribute_protocols();
673                 // Build Info Table
674                 let info = "<table class='table table-striped'><thead><tr><th>Protocol</th><th>Description</th><th>Properties</th></tr></thead>";
675                 info += "<tbody>";
676                 for ( let i = 0; i < data.protocols.length; i++ ) {
677                     let props = "<strong>Data Type:</strong>&nbsp;" + data.protocols[i].nd_protocol_properties.sequence_metadata_type + "<br />";
678                     props += "<strong>Reference Genome:</strong>&nbsp;" + data.protocols[i].nd_protocol_properties.reference_genome + "<br />";
679                     props += "<strong>Score:</strong>&nbsp;" + data.protocols[i].nd_protocol_properties.score_description + "<br />";
680                     props += "<strong>Attributes:</strong><br />";
681                     
682                     var keys = Object.keys(data.protocols[i].nd_protocol_properties.attribute_descriptions);
683                     keys.sort();
684                     let t = "<table class='table' style='background: transparent !important'>";
685                     t += "<thead><tr><th>Key</th><th>Definition</th></tr></thead>";
686                     t += "<tbody>";
687                     for ( let j = 0; j < keys.length; j++ ) {
688                         let key = keys[j];
689                         let description = data.protocols[i].nd_protocol_properties.attribute_descriptions[key];
690                         t += "<tr><td class='col-md-4'><strong>" + key + "</strong></td><td class='col-md-8'>" + description + "</td></tr>";
691                     }
692                     t += "</tbody>";
693                     t += "</table>";
694                     props += t;
696                     var links = data.protocols[i].nd_protocol_properties.links;
697                     if ( links ) {
698                         props += "<strong>Links:</strong><br />";
699                         var titles = Object.keys(links);
700                         titles.sort();
701                         let t = "<table class='table' style='background: transparent !important'>";
702                         t += "<thead><tr><th>Title</th><th>URL&nbsp;Template</th></tr></thead>";
703                         t += "<tbody>";
704                         for ( let j = 0; j < titles.length; j++ ) {
705                             let title = titles[j];
706                             let url = data.protocols[i].nd_protocol_properties.links[title];
707                             t += "<tr><td class='col-md-4'><strong>" + title + "</strong></td><td class='col-md-8'>" + url + "</td></tr>";
708                         }
709                         t += "</tbody>";
710                         t += "</table>";
711                         props += t;
712                     }
714                     info += "<tr>";
715                     info += "<td class='col-md-2'>" + data.protocols[i].nd_protocol_name + "</td>";
716                     info += "<td class='col-md-5'>" + data.protocols[i].nd_protocol_description + "</td>";
717                     info += "<td class='col-md-5'>" + props + "</td>";
718                     info += "</tr>";
719                 }
720                 info += "</tbody>";
721                 info += "</table>";
722                 jQuery('#sequence_metadata_filter_protocol_info .modal-body').html(info);
724                 if ( callback ) return callback();
726             }
727             else {
728                 alert("ERROR: Could not load protocols!");
729             }
730         },
731         error: function() {
732          alert("ERROR: Could not load protocols");
733         }
734     });
738  * Set the options for the Protocol select box
739  */
740 function set_protocols() {
741     let sel_reference_genome = jQuery('#sequence_metadata_filter_reference_genome').val();
742     let sel_protocols = jQuery('#sequence_metadata_filter_protocol').val();
744     // Add URL selected protocols
745     if ( !sel_protocols ) sel_protocols = [];
746     sel_protocols = sel_protocols.concat(selected_protocols);
747     let sel_protocol_names = [];
749     // Get protocols for the selected reference genome
750     let p = protocols[sel_reference_genome];
752     // Group protocols by type
753     let grouped = {};
754     if ( p ) {
755         for ( let i = 0; i < p.length; i++ ) {
756             let p_id = p[i].nd_protocol_id;
757             let p_name = p[i].nd_protocol_name;
758             let p_type = p[i].nd_protocol_properties.sequence_metadata_type;
759             
760             // Reselect previosuly selected items
761             let selected = false;
762             if ( sel_protocols ) {
763                 for ( let j = 0; j < sel_protocols.length; j++ ) {
764                     if ( parseInt(sel_protocols[j]) === parseInt(p_id) ) {
765                         selected = true;
766                         sel_protocol_names.push(p_name);
767                     }
768                 }
769             }
771             // Add protocol info to group of type
772             if ( !grouped[p_type] ) grouped[p_type] = [];
773             grouped[p_type].push({
774                 id: p_id,
775                 name: p_name,
776                 selected: selected ? 'selected' : ''
777             });
778         }
779     }
781     // Build options, grouped by type
782     let options = "";
783     let sorted_types = Object.keys(grouped).sort(function(x, y) {
784         if (x.toUpperCase() < y.toUpperCase()) return -1;
785         if (x.toUpperCase() > y.toUpperCase()) return 1;
786         return 0;
787     });
788     for ( let i = 0; i < sorted_types.length; i++ ) {
789         options += "<optgroup label='" + sorted_types[i] + "'>";
790         for ( let j = 0; j < grouped[sorted_types[i]].length; j++ ) {
791             let prot = grouped[sorted_types[i]][j];
792             options += "<option value='" + prot.id + "' " + prot.selected + ">" + prot.name + "</option>";
793         }
794         options += "</optgroup>";
795     }
797     // Set protocol selection options
798     jQuery('#sequence_metadata_filter_protocol').html(options);
799     jQuery('#sequence_metadata_filter_protocol').prop('disabled', false);
801     // Set selected protocol names
802     jQuery('#sequence_metadata_filter_type_protocol_url_selection_names').html(sel_protocol_names.join(', '));
806  * Set the protocol lists for the score and attribute select menus
807  */
808 function set_attribute_protocols() {
809     let sel_reference_genome = jQuery('#sequence_metadata_filter_reference_genome').val();
810     let sel_protocols = jQuery('#sequence_metadata_filter_protocol').val();
812     let options = "";
813     let p = protocols[sel_reference_genome];
814     if ( p ) {
815         for ( let i = 0; i < p.length; i++ ) {
816             let p_id = p[i].nd_protocol_id;
817             let p_name = p[i].nd_protocol_name;
818             let p_type_id = p[i].nd_protocol_properties.sequence_metadata_type_id;
820             // Only enable selected protocols or selected data types
821             let enabled = true;
822             if ( sel_protocols ) {
823                 enabled = false;
824                 for ( let j = 0; j < sel_protocols.length; j++ ) {
825                     if ( parseInt(sel_protocols[j]) === parseInt(p_id) ) {
826                         enabled = true;
827                     }
828                 }
829             }
831             let d = !enabled ? 'disabled' : '';
832             options += "<option value='" + p_id + "' " + d + ">" + p_name + "</option>";
833         }
834     }
836     // Set protocol selection options
837     jQuery('#sequence_metadata_filter_score_protocol').html(options);
838     jQuery('#sequence_metadata_filter_score_protocol').prop('disabled', false);
839     jQuery('#sequence_metadata_filter_attribute_protocol').html(options);
840     jQuery('#sequence_metadata_filter_attribute_protocol').prop('disabled', false);
842     // Update the attribute keys
843     set_attribute_keys();
848  * Set the attribute keys for the currently selected attribute protocol
849  */
850 function set_attribute_keys() {
851     let sel_reference_genome = jQuery('#sequence_metadata_filter_reference_genome').val();
852     let sel_attribute_protocol = jQuery('#sequence_metadata_filter_attribute_protocol').val();
853     
854     let options = "";
855     let p = protocols[sel_reference_genome];
856     if ( p ) {
857         for ( let i = 0; i < p.length; i++ ) {
858             if ( parseInt(sel_attribute_protocol) === parseInt(p[i].nd_protocol_id) ) {
859                 let attrs = Object.keys(p[i].nd_protocol_properties.attribute_descriptions);
860                 for ( let j = 0; j < attrs.length; j++ ) {
861                     options += "<option val='" + attrs[j] + "'>" + attrs[j] + "</option>";
862                 }
863             }
864         }
865     }
867     jQuery('#sequence_metadata_filter_attribute_key').html(options);
868     jQuery('#sequence_metadata_filter_attribute_key').prop('disabled', false);
873  * Add the specified attribute to the list of included attribute filters
874  * @param {String} type Attribute type ('score' or 'attribute')
875  */
876 function add_attribute_filter(type) {
877     let protocol = jQuery('#sequence_metadata_filter_' + type + '_protocol').val();
878     let protocol_name = jQuery('#sequence_metadata_filter_' + type + '_protocol option:selected').text();
879     let attribute = type === 'score' ? 'score' : jQuery('#sequence_metadata_filter_attribute_key').val();
880     let comp = jQuery('#sequence_metadata_filter_' + type + '_comparison').val();
881     let comp_name = jQuery('#sequence_metadata_filter_' + type + '_comparison option:selected').text();
882     let value = jQuery('#sequence_metadata_filter_' + type + '_value').val();
883     let attribute_param = [attribute, protocol, comp, value].join('|');
885     if ( value !== '' ) {
886         attribute_filter_count++;
887         let html = "<tr id='sequence_metadata_filter_attributes_table_row_" + attribute_filter_count + "'>";
888         html += "<td>" + protocol_name + "</td>";
889         html += "<td>" + attribute + "</td>";
890         html += "<td>" + comp_name + "</td>";
891         html += "<td>" + value + "</td>";
892         html += "<td style='text-align: right'>";
893         html += "<button id='sequence_metadata_filter_attributes_table_remove_" + attribute_filter_count + "' data-row='" + attribute_filter_count + "' class='btn btn-danger btn-xs'><span class='glyphicon glyphicon-remove'></span></button>";
894         html += "<input type='hidden' name='sequence_metadata_filter_attribute_" + attribute_filter_count + "' id='sequence_metadata_filter_attribute_" + attribute_filter_count + "' value='" + attribute_param + "'>";
895         html += "</td>";
896         html += "</tr>";
898         jQuery('#sequence_metadata_filter_' + type + '_value').val("");
899         jQuery('#sequence_metadata_filter_attributes_table').append(html);
900         jQuery('#sequence_metadata_filter_attributes_table_remove_' + attribute_filter_count).click(function() {
901             let row = jQuery(this).attr('data-row');
902             jQuery('#sequence_metadata_filter_attributes_table_row_' + row).remove();
903         });
904     }
910 // QUERY FUNCTIONS
915  * Build the Query URL using the current filter properties
916  * @param {string} [format] Output format (default: JSON)
917  * @param {int[]} [nd_protocol_id] Protocol ID to use (instead of selected protocols)
918  * @returns {string} relative URL to query endpoint
919  */
920 function getQueryURL(format, nd_protocol_id) {
921     let reference_genome = jQuery('#sequence_metadata_filter_reference_genome').val();
922     let feature_id = jQuery('#sequence_metadata_filter_feature option:selected').val();
923     let start = jQuery('#sequence_metadata_filter_start').val();
924     let end = jQuery('#sequence_metadata_filter_end').val();
925     let sel_nd_protocol_ids = jQuery('#sequence_metadata_filter_protocol').val();
927     let params = {
928         feature_id: feature_id,
929         format: format ? format : 'JSON'
930     }
931     if ( start && start !== '' ) params.start = start;
932     if ( end && end !== '' ) params.end = end;
933     if ( reference_genome && reference_genome !== '' ) params.reference_genome = reference_genome;
934     if ( nd_protocol_id ) {
935         params.nd_protocol_id = nd_protocol_id;
936     }
937     else {
938         if ( sel_nd_protocol_ids && sel_nd_protocol_ids.length > 0 ) params.nd_protocol_id = sel_nd_protocol_ids;
939     }
940     let attributes = [];
941     for ( let i = 1; i <= attribute_filter_count; i++ ) {
942         let attr = jQuery('#sequence_metadata_filter_attribute_' + i).val();
943         if ( attr && attr !== '' ) {
944             attributes.push(attr);
945         }
946     }
947     if ( attributes.length > 0 ) params.attribute = attributes.join(',');
949     let q = new URLSearchParams(params).toString();
950     let url = '/ajax/sequence_metadata/query?' + q;
952     return url;
957  * Perform a sequence metadata query
958  * - Required filter params: feature_id, start, end
959  * - Optional filter params: type_id, nd_protocol_id
960  * - Get the query results and send to handle_query_results to parse
961  */
962 function query() {
963     hide_error();
964     jQuery('#sequence_metadata_filter_query').html('Searching...');
965     jQuery('#sequence_metadata_filter_query').attr('disabled', true);
967     jQuery.ajax({
968         type: 'GET',
969         url: getQueryURL('JSON'),
970         dataType: 'json',
971         success: handle_query_results,
972         error: function() {
973             alert("ERROR: Could query database!");
974             jQuery('#sequence_metadata_filter_query').html('Search');
975             jQuery('#sequence_metadata_filter_query').attr('disabled', false);
976         }
977     });
979     return false;
984  * Parse the query results (in JSON format)
985  * - Update the rows in the DataTable
986  * @param {Object} response          JSON response from the query endpoint
987  *                 response.error    message of error encountered by server
988  *                 response.results array of sequence metadata objects
989  */
990 function handle_query_results(response) {
992     // Update DataTable
993     let dt = jQuery('#sequence_metadata_filter_results').DataTable();
994     dt.clear();
995     if ( response && response.error ) {
996         display_error(response.error);
997     }
998     else if ( response && response.results && response.results.length > 0) {
999         dt.rows.add(response.results);
1000         if ( auto_search ) {
1001             display_auto_search_results();
1002         }
1003         else {
1004             toggle_sections();
1005         }
1006         if (window.history && window.history.pushState) {
1007             window.history.pushState('results', null, null);
1008         }
1009     }
1010     else {
1011         display_error("No results found - try modifying your filter criteria");
1012     }
1013     dt.draw();
1015     // Reset Markers
1016     markers = undefined;
1018     // Reset Query Button
1019     jQuery('#sequence_metadata_filter_query').html('Search');
1020     jQuery('#sequence_metadata_filter_query').attr('disabled', false);
1026  * Build a URL to view the seleceted data in jBrowse
1027  * @params {Object} config JBrowse configuration properties
1028  * @returns {string} url to jbrowse with remote tracks added
1029  */
1030 function getJBrowseURL(config) {
1031     // Get Selected Types
1032     let sel_reference_genome = jQuery('#sequence_metadata_filter_reference_genome').val();
1033     let p = protocols[sel_reference_genome];
1034     
1035     // Get Selected Protocols (or all enabled protocols, if none selected)
1036     let sel_protocols = jQuery('#sequence_metadata_filter_protocol').val();
1037     if ( !sel_protocols ) {
1038         sel_protocols = [];
1039         if ( p ) {
1040             for ( let i = 0; i < p.length; i++ ) {
1041                 sel_protocols.push(p[i].nd_protocol_id);
1042             }
1043         }
1044     }
1046     // Get Selected Protocol names (for track labels)
1047     let sel_protocol_names = {};
1048     if ( p ) {
1049         for ( let i = 0; i < sel_protocols.length; i++ ) {
1050             for ( let j = 0; j < p.length; j++ ) {
1051                 if ( parseInt(sel_protocols[i]) === parseInt(p[j].nd_protocol_id) ) {
1052                     sel_protocol_names[sel_protocols[i].toString()] = p[j].nd_protocol_name.replaceAll(" ", "&nbsp;");
1053                 }
1054             }
1055         }
1056     }
1057     
1058     // Get Selected Feature (for jBrowse chromosome name)
1059     let sel_species = jQuery("#sequence_metadata_filter_reference_genome option:selected").data("species");
1060     let sel_feature = jQuery('#sequence_metadata_filter_feature').val();
1061     let sel_feature_name = '';
1062     let f = features[sel_species];
1063     for ( let i = 0; i < f.length; i++ ) {
1064         if ( parseInt(f[i].feature_id) === parseInt(sel_feature) ) {
1065             sel_feature_name = f[i].feature_name;
1066         }
1067     }
1069     // Get Selected start and end for range
1070     let start = jQuery('#sequence_metadata_filter_start').val();
1071     let end = jQuery('#sequence_metadata_filter_end').val();
1072     let range = '';
1073     if ( start && start !== '' && end && end !== '' ) {
1074         range = ":" + start + ".." + end
1075     }
1077     // Build JBrowse Stores & Tracks
1078     let stores = {};
1079     let tracks = [];
1080     for ( let i = 0; i < sel_protocols.length; i++ ) {
1081         let query_url = window.location.protocol + "//" + window.location.host + getQueryURL("gff", sel_protocols[i]);
1082         if ( config.query_params ) {
1083             let q = new URLSearchParams(config.query_params).toString();
1084             query_url += "&" + q;
1085         }
1086         stores["url" + i] = {
1087             type: "JBrowse/Store/SeqFeature/GFF3",
1088             urlTemplate: query_url
1089         }
1090         tracks.push({
1091             label: sel_protocol_names[sel_protocols[i].toString()],
1092             type: "JBrowse/View/Track/CanvasFeatures",
1093             store: "url" + i
1094         });            
1095     }
1097     // Build URL
1098     let base_url = config.base_url;
1099     let params = {
1100         data: config.data_dir,
1101         loc: config.location_name(sel_feature_name) + range,
1102         tracks: config.tracks.concat(Object.values(sel_protocol_names)).join(','),
1103         addStores: JSON.stringify(stores),
1104         addTracks: JSON.stringify(tracks)
1105     }
1106     let q = new URLSearchParams(params).toString();
1107     let url = base_url + '?' + q;
1109     return url;
1115 // MARKER SEARCH FUNCTIONS
1120  * Update the contents of the marker column in the results table
1121  * for the currently displayed rows of results
1122  */
1123 function updateMarkers() {
1124     let containers = jQuery('.sequence_metadata_marker_search_markers');
1125     for ( let i = 0; i < containers.length; i++ ) {
1126         let container = jQuery(containers[i]);
1127         let species = container.data("species");
1128         let reference_genome = container.data("reference-genome");
1129         let feature_name = container.data("feature-name");
1130         let start = container.data("start");
1131         let end = container.data("end");
1132         let key = [species, reference_genome, feature_name, start, end].join("-").replaceAll(' ', '');
1133         if ( markers && markers[key] ) {
1134             displayMarkers(key, species, reference_genome, feature_name, start, end);
1135         }
1136         else {
1137             getMarkers(key, species, reference_genome, feature_name, start, end);
1138         }
1139     }
1144  * Get markers matching the specified sequence metadata
1145  * - Query the DB for matching markers
1146  * - Update the display of the markers
1147  * @param {string} key Sequence Metadata results key (species-reference_genome-feature_name-start-end)
1148  * @param {string} species Name of the species
1149  * @param {string} reference_genome Name of the reference genome
1150  * @param {string} feature_name Name of the sequence metadata's associated feature
1151  * @param {int} start Start position of the sequence metadata
1152  * @param {int} end End position of the sequence metadata
1153  */
1154 function getMarkers(key, species, reference_genome, feature_name, start, end) {
1156     // Display searching
1157     if ( !markers ) markers = {};
1158     markers[key] = "Searching...";
1159     displayMarkers(key);
1161     // Query the Database for Markers
1162     jQuery.ajax({
1163         type: 'GET',
1164         url: '/ajax/markers/genotyped/query',
1165         data: {
1166             species: species,
1167             reference_genome: reference_genome,
1168             chrom: feature_name,
1169             start: start,
1170             end: end,
1171             limit: 50
1172         },
1173         dataType: 'json',
1174         success: function(data) {
1176             // Save the markers and update the DataTables
1177             if ( data && data.results ) {
1178                 if ( !markers ) markers = {};
1179                 markers[key] = data.results;
1180                 displayMarkers(key, species, reference_genome, feature_name, start, end);
1181             }
1183         },
1184         error: function() {
1185             alert("ERROR: Could not perform marker search");
1186         }
1187     });
1192  * Display the marker info for the specified sequence metadata row
1193  * @param {string} key Sequence Metadata results key (species-reference_genome-feature_name-start-end)
1194  * @param {string} species Species name
1195  * @param {string} reference_genome Reference genome name
1196  * @param {string} feature_name Feature / chromosome name
1197  * @param {int} start Start position
1198  * @param {int} end End position
1199  */
1200 function displayMarkers(key, species, reference_genome, feature_name, start, end) {
1201     let marker_info = markers[key];
1202     let container = jQuery('.sequence_metadata_marker_search_markers_' + key);
1203     if ( typeof marker_info === 'string' ) {
1204         container.html(marker_info);
1205     }
1206     else if ( typeof marker_info === 'object' ) {
1207         let html = "";
1208         let marker_count = marker_info.counts.markers;
1209         let variants = marker_info.variants;
1210         let markers_displayed = 0;
1211         if ( marker_count > 0 ) {
1212             let m = marker_count === 1 ? "marker" : "markers";
1213             html += "<strong>" + marker_count + " " + m + " found:</strong>";
1214             html += "<div class='sequence_metadata_marker_search_markers_list'>";
1215             let sorted_variants = Object.keys(variants).sort();
1216             for ( let i = 0; i < sorted_variants.length; i++ ) {
1217                 let variant_name = sorted_variants[i];
1218                 if ( variants.hasOwnProperty(variant_name) ) {
1219                     let m = variants[variant_name][0];
1220                     let url = "/variant/" + variant_name + "/details";
1221                     let label = m.chrom + " @ " + m.pos + " (" + m.ref + "/" + m.alt + ")";
1222                     html += "<a href='" + url + "' target='_blank'>" + label + "</a>";
1223                     html += "<ul style='padding-left: 25px'>";
1224                     for ( let i = 0; i < variants[variant_name].length; i++ ) {
1225                         let m = variants[variant_name][i];
1226                         markers_displayed++;
1227                         html += "<li>" + m.marker_name + " (" + m.nd_protocol_name + ")</li>";
1228                     }
1229                     html += "</ul>";
1230                 }        
1231             }
1232             if ( markers_displayed < marker_count ) {
1233                 let diff = marker_count - markers_displayed;
1234                 html += "<em>" + diff + " more not displayed...</em>";
1235                 if ( species && reference_genome && feature_name ) {
1236                     let url = "/search/variants/results?type=genotyped&refmap=" + reference_genome + "&species=" + species + "&chrom=" + feature_name + "&start=" + start + "&end=" + end;
1237                     html += "<br /><a href='" + url + "' target='_blank'>View All Markers</a>";
1238                 }
1239             }
1240             html += "</div>";
1241         }
1242         else {
1243             html += "<em>No markers found</em>";
1244         }
1245         container.html(html);
1246     }
1251 // DATA TABLE RENDER FUNCTIONS
1255  * Render the Markers column
1256  * @param {Object} row The current row's data
1257  * @param {String} type The display type
1258  * @returns {String} The text/html to display in the table
1259  */
1260 function renderMarkersColumn(row, type) {
1261     
1262     // Get reference genome by protocol
1263     let reference_genome = "";
1264     for ( let ref in protocols ) {
1265         if ( protocols.hasOwnProperty(ref) ) {
1266             let ps = protocols[ref];
1267             for ( let i = 0; i < ps.length; i++ ) {
1268                 if ( ps[i].nd_protocol_id === row.nd_protocol_id ) {
1269                     reference_genome = ref;
1270                 }
1271             }
1272         }
1273     }
1275     // Get species by feature
1276     let species = "";
1277     for ( let sp in features ) {
1278         if ( features.hasOwnProperty(sp) ) {
1279             let fs = features[sp];
1280             for ( let i = 0; i < fs.length; i++ ) {
1281                 if ( fs[i].feature_id === row.feature_id ) {
1282                     species = sp;
1283                 }
1284             }
1285         }
1286     }
1288     // Set data properties for marker search
1289     let data = "data-reference-genome='" + reference_genome + "' data-species='" + species + "' data-feature-name='" + row.feature_name + "' data-start='" + row.start + "' data-end='" + row.end + "'";
1290     let key = [species, reference_genome, row.feature_name, row.start, row.end].join("-").replaceAll(' ', '');
1291     
1292     let html = "<div class='sequence_metadata_marker_search'>";
1293     html += "<div class='sequence_metadata_marker_search_markers sequence_metadata_marker_search_markers_" + key + "' " + data + "></div>";
1294     html += "</div>";
1296     return html;
1300  * Render the Attributes column
1301  * @param data The column's data for the current row
1302  * @param {String} type The display type
1303  * @param {Object} row The current row's data
1304  * @returns {String} The text/html to display in the table
1305  */
1306 function renderAttributesColumn(data, type, row) {
1307     let rtn = [];
1308     let sep = type === 'export' ? ';' : '<br />';
1309     if ( data ) {
1310         var keys = Object.keys(data);
1311         keys.sort();
1312         for ( var i=0; i<keys.length; ++i ) {
1313             let key = keys[i];
1314             let value = data[key];
1315             if ( type === 'export' ) {
1316                 rtn.push(key + '=' + value);
1317             }
1318             else {
1319                 rtn.push("<strong>" + key + ":</strong>&nbsp;" + value);
1320             }
1321         }
1322     }
1323     return rtn.join(sep);
1327  * Render the Links column
1328  * @param data The column's data for the current row
1329  * @param {String} type The display type
1330  * @param {Object} row The current row's data
1331  * @returns {String} The text/html to display in the table
1332  */
1333 function renderLinksColumn(data, type, row) {
1334     let rtn = [];
1335     let sep = type === 'export' ? ';' : '<br /><br />';
1336     if ( data ) {
1337         var titles = Object.keys(data);
1338         titles.sort();
1339         for ( var i=0; i<titles.length; i++ ) {
1340             let title = titles[i];
1341             let url = data[title];
1342             if ( type === 'export' ) {
1343                 rtn.push(title + '=' + url);
1344             }
1345             else {
1346                 rtn.push("<a href='" + url + "' target='_blank'>" + title + "</a>");
1347             }
1348         }
1349     }
1350     return rtn.join(sep);
1355 // HELPER FUNCTIONS
1360  * Toggle the display of the query and results sections
1361  */
1362 function toggle_sections() {
1363     jQuery("#sequence_metadata_section_query").toggle();
1364     jQuery("#sequence_metadata_section_results").toggle();
1365     let anchor = jQuery('#sequence_metadata_anchor');
1366     jQuery('html,body').animate({scrollTop: anchor.offset().top-150}, 'fast');
1370  * Display the auto search loading section
1371  */
1372 function display_auto_search_loading() {
1373     jQuery("#sequence_metadata_section_query").hide();
1374     jQuery("#sequence_metadata_section_results").hide();
1375     jQuery("#sequence_metadata_section_auto_search").show();
1379  * Display the auto search results
1380  */
1381 function display_auto_search_results() {
1382     jQuery("#sequence_metadata_section_query").hide();
1383     jQuery("#sequence_metadata_section_auto_search").hide();
1384     jQuery("#sequence_metadata_section_results").show();
1388  * Display an error message
1389  * @param {string} message The message to display (undefined to clear the message)
1390  */
1391 function display_error(message) {
1392     jQuery('#sequence_metadata_filter_error').html(message ? message : "");
1393     jQuery('#sequence_metadata_filter_error').css('display', message ? 'block' : 'none');
1397  * Clear and hide the error message alert box
1398  */
1399 function hide_error() {
1400     display_error();
1404  * Prompt a download of the specified url with the given file name
1405  * @param {String} url URL to download
1406  * @param {String} name Name to give the downloaded file
1407  */
1408 function download(url, name) {
1409     var a = document.createElement('a');
1410     a.href = url;
1411     a.download = name;
1412     document.body.appendChild(a);
1413     a.click();
1414     document.body.removeChild(a);
1417 </script>
1420 <style>
1421     select option:disabled {
1422         font-weight: 200;
1423         font-style: italic;
1424         color: #DCDCDC;
1425     }
1426     #sequence_metadata_filter_type_protocol_url_selection {
1427         display: none;
1428     }
1429     #sequence_metadata_filter_type_protocol_url_selection_names {
1430         margin: 0;
1431         padding-top: 7px;
1432     }
1433     .sequence_metadata_marker_search_markers_list {
1434         max-height: 250px;
1435         overflow-y: auto;
1436     }
1437     #sequence_metadata_filter_results {
1438         border: 1px solid #ddd;
1439         border-radius: 5px;
1440         margin: 5px 0;
1441     }
1442 </style>